UNPKG

4.26 kBJavaScriptView Raw
1import path from 'path';
2import util from 'util';
3import fs from 'fs-extra';
4import isObject from 'is-plain-object';
5import globby from 'globby';
6import { green, bold, yellow } from 'colorette';
7
8function stringify(value) {
9 return util.inspect(value, {
10 breakLength: Infinity
11 });
12}
13
14async function isFile(filePath) {
15 const fileStats = await fs.stat(filePath);
16 return fileStats.isFile();
17}
18
19function renameTarget(target, rename, src) {
20 const parsedPath = path.parse(target);
21 return typeof rename === 'string' ? rename : rename(parsedPath.name, parsedPath.ext.replace('.', ''), src);
22}
23
24async function generateCopyTarget(src, dest, {
25 flatten,
26 rename,
27 transform
28}) {
29 if (transform && !(await isFile(src))) {
30 throw new Error(`"transform" option works only on files: '${src}' must be a file`);
31 }
32
33 const {
34 base,
35 dir
36 } = path.parse(src);
37 const destinationFolder = flatten || !flatten && !dir ? dest : dir.replace(dir.split('/')[0], dest);
38 return {
39 src,
40 dest: path.join(destinationFolder, rename ? renameTarget(base, rename, src) : base),
41 ...(transform && {
42 contents: await transform(await fs.readFile(src), base)
43 }),
44 renamed: rename,
45 transformed: transform
46 };
47}
48
49function copy(options = {}) {
50 const {
51 copyOnce = false,
52 copySync = false,
53 flatten = true,
54 hook = 'buildEnd',
55 targets = [],
56 verbose = false,
57 ...restPluginOptions
58 } = options;
59 let copied = false;
60 return {
61 name: 'copy',
62 [hook]: async () => {
63 if (copyOnce && copied) {
64 return;
65 }
66
67 const copyTargets = [];
68
69 if (Array.isArray(targets) && targets.length) {
70 for (const target of targets) {
71 if (!isObject(target)) {
72 throw new Error(`${stringify(target)} target must be an object`);
73 }
74
75 const {
76 dest,
77 rename,
78 src,
79 transform,
80 ...restTargetOptions
81 } = target;
82
83 if (!src || !dest) {
84 throw new Error(`${stringify(target)} target must have "src" and "dest" properties`);
85 }
86
87 if (rename && typeof rename !== 'string' && typeof rename !== 'function') {
88 throw new Error(`${stringify(target)} target's "rename" property must be a string or a function`);
89 }
90
91 const matchedPaths = await globby(src, {
92 expandDirectories: false,
93 onlyFiles: false,
94 ...restPluginOptions,
95 ...restTargetOptions
96 });
97
98 if (matchedPaths.length) {
99 for (const matchedPath of matchedPaths) {
100 const generatedCopyTargets = Array.isArray(dest) ? await Promise.all(dest.map(destination => generateCopyTarget(matchedPath, destination, {
101 flatten,
102 rename,
103 transform
104 }))) : [await generateCopyTarget(matchedPath, dest, {
105 flatten,
106 rename,
107 transform
108 })];
109 copyTargets.push(...generatedCopyTargets);
110 }
111 }
112 }
113 }
114
115 if (copyTargets.length) {
116 if (verbose) {
117 console.log(green('copied:'));
118 }
119
120 for (const copyTarget of copyTargets) {
121 const {
122 contents,
123 dest,
124 src,
125 transformed
126 } = copyTarget;
127
128 if (transformed) {
129 await fs.outputFile(dest, contents, restPluginOptions);
130 } else if (!copySync) {
131 await fs.copy(src, dest, restPluginOptions);
132 } else {
133 fs.copySync(src, dest, restPluginOptions);
134 }
135
136 if (verbose) {
137 let message = green(` ${bold(src)}${bold(dest)}`);
138 const flags = Object.entries(copyTarget).filter(([key, value]) => ['renamed', 'transformed'].includes(key) && value).map(([key]) => key.charAt(0).toUpperCase());
139
140 if (flags.length) {
141 message = `${message} ${yellow(`[${flags.join(', ')}]`)}`;
142 }
143
144 console.log(message);
145 }
146 }
147 } else if (verbose) {
148 console.log(yellow('no items to copy'));
149 }
150
151 copied = true;
152 }
153 };
154}
155
156export { copy as default };