1 | 'use strict';
|
2 |
|
3 | var path = require('node:path');
|
4 | var schemaUtils = require('schema-utils');
|
5 | var fs = require('node:fs');
|
6 | var fsExtra = require('fs-extra');
|
7 | var cpy = require('cpy');
|
8 | var isGlob = require('is-glob');
|
9 | var del = require('del');
|
10 | var archiver = require('archiver');
|
11 |
|
12 | function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
13 |
|
14 | var path__default = _interopDefaultLegacy(path);
|
15 | var fs__default = _interopDefaultLegacy(fs);
|
16 | var fsExtra__default = _interopDefaultLegacy(fsExtra);
|
17 | var cpy__default = _interopDefaultLegacy(cpy);
|
18 | var isGlob__default = _interopDefaultLegacy(isGlob);
|
19 | var del__default = _interopDefaultLegacy(del);
|
20 | var archiver__default = _interopDefaultLegacy(archiver);
|
21 |
|
22 | var optionsSchema = {
|
23 | title: 'FileManagerPluginOptions',
|
24 | type: 'object',
|
25 | additionalProperties: false,
|
26 | definitions: {
|
27 | Copy: {
|
28 | description: 'Copy individual files or entire directories from a source folder to a destination folder',
|
29 | type: 'array',
|
30 | minItems: 1,
|
31 | additionalItems: true,
|
32 | itmes: [
|
33 | {
|
34 | type: 'object',
|
35 | additionalProperties: false,
|
36 | properties: {
|
37 | source: {
|
38 | description: 'Copy source. A file or directory or a glob',
|
39 | type: 'string',
|
40 | minLength: 1,
|
41 | },
|
42 | destination: {
|
43 | description: 'Copy destination',
|
44 | type: 'string',
|
45 | minLength: 1,
|
46 | },
|
47 | },
|
48 | },
|
49 | ],
|
50 | },
|
51 | Delete: {
|
52 | description: 'Delete individual files or entire directories',
|
53 | type: 'array',
|
54 | minItems: 1,
|
55 | additionalItems: true,
|
56 | items: {
|
57 | anyOf: [
|
58 | {
|
59 | type: 'object',
|
60 | additionalProperties: false,
|
61 | properties: {
|
62 | source: {
|
63 | type: 'string',
|
64 | minLength: 1,
|
65 | },
|
66 | options: {
|
67 | additionalProperties: true,
|
68 | type: 'object',
|
69 | description: 'Options to forward to del',
|
70 | },
|
71 | },
|
72 | },
|
73 | {
|
74 | type: 'string',
|
75 | minLength: 1,
|
76 | },
|
77 | ],
|
78 | },
|
79 | },
|
80 | Move: {
|
81 | description: 'Move individual files or entire directories from a source folder to a destination folder',
|
82 | type: 'array',
|
83 | additionalItems: true,
|
84 | items: [
|
85 | {
|
86 | type: 'object',
|
87 | additionalProperties: false,
|
88 | properties: {
|
89 | source: {
|
90 | description: 'Move source. A file or directory or a glob',
|
91 | type: 'string',
|
92 | minLength: 1,
|
93 | },
|
94 | destination: {
|
95 | description: 'Move destination',
|
96 | type: 'string',
|
97 | minLength: 1,
|
98 | },
|
99 | },
|
100 | },
|
101 | ],
|
102 | },
|
103 | Mkdir: {
|
104 | description: 'Create Directories',
|
105 | type: 'array',
|
106 | minItems: 1,
|
107 | additionalItems: true,
|
108 | items: {
|
109 | type: 'string',
|
110 | },
|
111 | },
|
112 | Archive: {
|
113 | description: 'Archive individual files or entire directories.',
|
114 | type: 'array',
|
115 | additionalItems: true,
|
116 | items: [
|
117 | {
|
118 | type: 'object',
|
119 | additionalProperties: false,
|
120 | properties: {
|
121 | source: {
|
122 | description: 'Source. A file or directory or a glob',
|
123 | type: 'string',
|
124 | minLength: 1,
|
125 | },
|
126 | destination: {
|
127 | description: 'Archive destination',
|
128 | type: 'string',
|
129 | minLength: 1,
|
130 | },
|
131 | format: {
|
132 | type: 'string',
|
133 | enum: ['zip', 'tar'],
|
134 | },
|
135 | options: {
|
136 | additionalProperties: true,
|
137 | type: 'object',
|
138 | description: 'Options to forward to archiver',
|
139 | },
|
140 | },
|
141 | },
|
142 | ],
|
143 | },
|
144 | Actions: {
|
145 | type: 'object',
|
146 | additionalProperties: false,
|
147 | properties: {
|
148 | copy: {
|
149 | $ref: '#/definitions/Copy',
|
150 | },
|
151 | delete: {
|
152 | $ref: '#/definitions/Delete',
|
153 | },
|
154 | move: {
|
155 | $ref: '#/definitions/Move',
|
156 | },
|
157 | mkdir: {
|
158 | $ref: '#/definitions/Mkdir',
|
159 | },
|
160 | archive: {
|
161 | $ref: '#/definitions/Archive',
|
162 | },
|
163 | },
|
164 | },
|
165 | },
|
166 | properties: {
|
167 | events: {
|
168 | type: 'object',
|
169 | additionalProperties: false,
|
170 | properties: {
|
171 | onStart: {
|
172 | oneOf: [
|
173 | {
|
174 | $ref: '#/definitions/Actions',
|
175 | },
|
176 | {
|
177 | type: 'array',
|
178 | items: {
|
179 | $ref: '#/definitions/Actions',
|
180 | },
|
181 | },
|
182 | ],
|
183 | },
|
184 | onEnd: {
|
185 | oneOf: [
|
186 | {
|
187 | $ref: '#/definitions/Actions',
|
188 | },
|
189 | {
|
190 | type: 'array',
|
191 | items: {
|
192 | $ref: '#/definitions/Actions',
|
193 | },
|
194 | },
|
195 | ],
|
196 | },
|
197 | },
|
198 | },
|
199 | runTasksInSeries: {
|
200 | type: 'boolean',
|
201 | default: false,
|
202 | description: 'Run tasks in an action in series',
|
203 | },
|
204 | context: {
|
205 | type: 'string',
|
206 | description: 'The directory, an absolute path, for resolving files. Defaults to webpack context',
|
207 | },
|
208 | },
|
209 | };
|
210 |
|
211 | const defaultTask = async () => {};
|
212 |
|
213 | const pExec = async (series = false, arr = [], task = defaultTask) => {
|
214 | if (series) {
|
215 | await arr.reduce(async (p, spec) => {
|
216 | await p;
|
217 | return task(spec);
|
218 | }, Promise.resolve(null));
|
219 | return;
|
220 | }
|
221 |
|
222 | const pMap = arr.map(async (spec) => await task(spec));
|
223 | await Promise.all(pMap);
|
224 | };
|
225 |
|
226 | const fsExtraDefaultOptions = {
|
227 | preserveTimestamps: true,
|
228 | };
|
229 |
|
230 | const copy = async (task, { logger }) => {
|
231 | const { source, absoluteSource, destination, absoluteDestination, context, toType } = task;
|
232 |
|
233 | logger.log(`copying from ${source} to ${destination}`);
|
234 |
|
235 | if (isGlob__default['default'](source)) {
|
236 | const src = path__default['default'].isAbsolute(source) ? source : path__default['default'].posix.join(context, source);
|
237 | await cpy__default['default'](src, absoluteDestination);
|
238 | } else {
|
239 | const isSourceFile = fs__default['default'].lstatSync(absoluteSource).isFile();
|
240 |
|
241 |
|
242 |
|
243 | if (isSourceFile && toType === 'dir') {
|
244 | await fsExtra__default['default'].ensureDir(absoluteDestination);
|
245 |
|
246 | const sourceFileName = path__default['default'].basename(absoluteSource);
|
247 | const filePath = path__default['default'].resolve(absoluteDestination, sourceFileName);
|
248 |
|
249 | await fsExtra__default['default'].copy(absoluteSource, filePath, fsExtraDefaultOptions);
|
250 | return;
|
251 | }
|
252 |
|
253 | await fsExtra__default['default'].copy(absoluteSource, absoluteDestination, fsExtraDefaultOptions);
|
254 | }
|
255 |
|
256 | logger.info(`copied "${source}" to "${destination}`);
|
257 | };
|
258 |
|
259 | const copyAction = async (tasks, options) => {
|
260 | const { runTasksInSeries, logger } = options;
|
261 |
|
262 | const taskOptions = {
|
263 | logger,
|
264 | };
|
265 |
|
266 | logger.debug(`processing copy tasks. tasks: ${tasks}`);
|
267 |
|
268 | await pExec(runTasksInSeries, tasks, async (task) => {
|
269 | try {
|
270 | await copy(task, taskOptions);
|
271 | } catch (err) {
|
272 | logger.error(`error while copying. task ${err}`);
|
273 | }
|
274 | });
|
275 | logger.debug(`copy tasks complete. tasks: ${tasks}`);
|
276 | };
|
277 |
|
278 | const moveAction = async (tasks, options) => {
|
279 | const { runTasksInSeries, logger } = options;
|
280 |
|
281 | logger.debug(`processing move tasks. tasks: ${tasks}`);
|
282 |
|
283 | await pExec(runTasksInSeries, tasks, async (task) => {
|
284 | try {
|
285 | await fsExtra__default['default'].move(task.absoluteSource, task.absoluteDestination);
|
286 | logger.info(`moved ${task.source} to ${task.destination}`);
|
287 | } catch (err) {
|
288 | logger.error(`unable to move ${task.source}, ${err}`);
|
289 | }
|
290 | });
|
291 |
|
292 | logger.debug(`move tasks complete. tasks: ${tasks}`);
|
293 | };
|
294 |
|
295 | const deleteAction = async (tasks, taskOptions) => {
|
296 | const { runTasksInSeries, logger } = taskOptions;
|
297 |
|
298 | logger.debug(`processing delete tasks. tasks: ${tasks}`);
|
299 |
|
300 | await pExec(runTasksInSeries, tasks, async (task) => {
|
301 | const { options = {} } = task;
|
302 |
|
303 | try {
|
304 | await del__default['default'](task.absoluteSource, options);
|
305 | logger.info(`deleted ${task.source}`);
|
306 | } catch (err) {
|
307 | logger.error(`unable to delete ${task.source}. ${err}`);
|
308 | }
|
309 | });
|
310 |
|
311 | logger.debug(`delete tasks complete. tasks: ${tasks}`);
|
312 | };
|
313 |
|
314 | const mkdirAction = async (tasks, options) => {
|
315 | const { runTasksInSeries, logger } = options;
|
316 |
|
317 | logger.debug(`processing mkdir tasks. tasks: ${tasks}`);
|
318 |
|
319 | await pExec(runTasksInSeries, tasks, async (task) => {
|
320 | try {
|
321 | await fs__default['default'].promises.mkdir(task.absoluteSource, { recursive: true });
|
322 | logger.info(`created directory. ${task.source}`);
|
323 | } catch (err) {
|
324 | logger.error(`unable to create direcotry: ${task.source}. ${err}`);
|
325 | }
|
326 | });
|
327 |
|
328 | logger.debug(`mkdir tasks complete. tasks: ${tasks}`);
|
329 | };
|
330 |
|
331 | const archive = async (task, { logger }) => {
|
332 | const { source, absoluteSource, absoluteDestination, options = {}, context } = task;
|
333 | const format = task.format || path__default['default'].extname(absoluteDestination).replace('.', '');
|
334 |
|
335 |
|
336 | const destFile = path__default['default'].basename(absoluteDestination);
|
337 | const destDir = path__default['default'].dirname(absoluteDestination);
|
338 |
|
339 | const ignore = ((Array.isArray(options.ignore) && options.ignore) || []).concat(destFile);
|
340 | const archiverOptions = { ignore, ...(options.globOptions || {}) };
|
341 |
|
342 | await fsExtra__default['default'].ensureDir(destDir);
|
343 |
|
344 | const output = fs__default['default'].createWriteStream(absoluteDestination);
|
345 | const archive = archiver__default['default'](format, options);
|
346 | archive.pipe(output);
|
347 |
|
348 | logger.log(`archiving src ${source}`);
|
349 |
|
350 | if (isGlob__default['default'](source)) {
|
351 | const opts = {
|
352 | ...archiverOptions,
|
353 | cwd: context,
|
354 | };
|
355 |
|
356 | await archive.glob(source, opts).finalize();
|
357 | } else {
|
358 | const sStat = fs__default['default'].lstatSync(absoluteSource);
|
359 |
|
360 | if (sStat.isDirectory()) {
|
361 | const opts = {
|
362 | ...archiverOptions,
|
363 | cwd: absoluteSource,
|
364 | };
|
365 |
|
366 | await archive.glob('**/*', opts).finalize();
|
367 | }
|
368 |
|
369 | if (sStat.isFile()) {
|
370 | const opts = {
|
371 | name: path__default['default'].basename(source),
|
372 | };
|
373 |
|
374 | await archive.file(absoluteSource, opts).finalize();
|
375 | }
|
376 | }
|
377 |
|
378 | logger.info(`archive created at "${absoluteDestination}"`);
|
379 | };
|
380 |
|
381 | const archiveAction = async (tasks, options) => {
|
382 | const { runTasksInSeries, logger } = options;
|
383 |
|
384 | const taskOptions = {
|
385 | logger,
|
386 | };
|
387 |
|
388 | logger.debug(`processing archive tasks. tasks: ${tasks}`);
|
389 | await pExec(runTasksInSeries, tasks, async (task) => {
|
390 | try {
|
391 | await archive(task, taskOptions);
|
392 | } catch (err) {
|
393 | logger.error(`error while creating archive. task ${task}`);
|
394 | }
|
395 | });
|
396 | logger.debug(`archive tasks complete. tasks: ${tasks}`);
|
397 | };
|
398 |
|
399 | const PLUGIN_NAME = 'FileManagerPlugin';
|
400 |
|
401 | const defaultOptions = {
|
402 | events: {
|
403 | onStart: [],
|
404 | onEnd: [],
|
405 | },
|
406 | runTasksInSeries: false,
|
407 | context: null,
|
408 | runOnceInWatchMode: false,
|
409 | };
|
410 |
|
411 | const resolvePaths = (action, context) => {
|
412 | return action.map((task) => {
|
413 | if (typeof task === 'string') {
|
414 | const source = task;
|
415 | return {
|
416 | source,
|
417 | absoluteSource: path__default['default'].isAbsolute(source) ? source : path__default['default'].join(context, source),
|
418 | };
|
419 | }
|
420 |
|
421 | const { source, destination } = task;
|
422 |
|
423 | if (!destination) {
|
424 | return {
|
425 | ...task,
|
426 | source,
|
427 | absoluteSource: path__default['default'].isAbsolute(source) ? source : path__default['default'].join(context, source),
|
428 | };
|
429 | }
|
430 |
|
431 | const toType = /(?:\\|\/)$/.test(destination) ? 'dir' : 'file';
|
432 |
|
433 | return {
|
434 | ...task,
|
435 | source,
|
436 | absoluteSource: path__default['default'].isAbsolute(source) ? source : path__default['default'].join(context, source),
|
437 | destination,
|
438 | absoluteDestination: path__default['default'].isAbsolute(destination) ? destination : path__default['default'].join(context, destination),
|
439 | toType,
|
440 | context,
|
441 | };
|
442 | });
|
443 | };
|
444 |
|
445 | class FileManagerPlugin {
|
446 | constructor(options) {
|
447 | schemaUtils.validate(optionsSchema, options, {
|
448 | name: PLUGIN_NAME,
|
449 | baseDataPath: 'actions',
|
450 | });
|
451 |
|
452 | this.options = { ...defaultOptions, ...options };
|
453 | }
|
454 |
|
455 | async applyAction(action, actionParams) {
|
456 | const opts = {
|
457 | runTasksInSeries: this.options.runTasksInSeries,
|
458 | logger: this.logger,
|
459 | };
|
460 |
|
461 | await action(resolvePaths(actionParams, this.context), opts);
|
462 | }
|
463 |
|
464 | async run(event) {
|
465 | for (const actionType in event) {
|
466 | const action = event[actionType];
|
467 |
|
468 | switch (actionType) {
|
469 | case 'delete':
|
470 | await this.applyAction(deleteAction, action);
|
471 | break;
|
472 |
|
473 | case 'mkdir':
|
474 | await this.applyAction(mkdirAction, action);
|
475 | break;
|
476 |
|
477 | case 'copy':
|
478 | await this.applyAction(copyAction, action);
|
479 | break;
|
480 |
|
481 | case 'move':
|
482 | await this.applyAction(moveAction, action);
|
483 | break;
|
484 |
|
485 | case 'archive':
|
486 | await this.applyAction(archiveAction, action);
|
487 | break;
|
488 |
|
489 | default:
|
490 | throw Error('Unknown action');
|
491 | }
|
492 | }
|
493 | }
|
494 |
|
495 | async execute(eventName) {
|
496 | const { events } = this.options;
|
497 |
|
498 | if (Array.isArray(events[eventName])) {
|
499 | const eventsArr = events[eventName];
|
500 |
|
501 | await pExec(true, eventsArr, async (event) => await this.run(event));
|
502 | return;
|
503 | }
|
504 |
|
505 | const event = events[eventName];
|
506 | await this.run(event);
|
507 | }
|
508 |
|
509 | apply(compiler) {
|
510 | this.context = this.options.context || compiler.options.context;
|
511 | this.logger = compiler.getInfrastructureLogger(PLUGIN_NAME);
|
512 |
|
513 | const onStart = async () => {
|
514 | await this.execute('onStart');
|
515 | };
|
516 |
|
517 | const onEnd = async () => {
|
518 | await this.execute('onEnd');
|
519 | };
|
520 |
|
521 | compiler.hooks.beforeRun.tapPromise(PLUGIN_NAME, onStart);
|
522 | compiler.hooks.afterEmit.tapPromise(PLUGIN_NAME, onEnd);
|
523 |
|
524 | let watchRunCount = 0;
|
525 | compiler.hooks.watchRun.tapPromise(PLUGIN_NAME, async () => {
|
526 | if (this.options.runOnceInWatchMode && watchRunCount > 0) {
|
527 | return;
|
528 | }
|
529 |
|
530 | ++watchRunCount;
|
531 | await onStart();
|
532 | });
|
533 | }
|
534 | }
|
535 |
|
536 | module.exports = FileManagerPlugin;
|
537 |
|