1 | import path from 'path';
|
2 | import util from 'util';
|
3 | import fs from 'fs-extra';
|
4 | import isObject from 'is-plain-object';
|
5 | import globby from 'globby';
|
6 | import { green, bold, yellow } from 'colorette';
|
7 |
|
8 | function stringify(value) {
|
9 | return util.inspect(value, {
|
10 | breakLength: Infinity
|
11 | });
|
12 | }
|
13 |
|
14 | async function isFile(filePath) {
|
15 | const fileStats = await fs.stat(filePath);
|
16 | return fileStats.isFile();
|
17 | }
|
18 |
|
19 | function 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 |
|
24 | async 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 |
|
49 | function 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 |
|
156 | export { copy as default };
|