UNPKG

14.6 kBJavaScriptView Raw
1'use strict';
2
3var path = require('node:path');
4var schemaUtils = require('schema-utils');
5var fs = require('node:fs');
6var fsExtra = require('fs-extra');
7var cpy = require('cpy');
8var isGlob = require('is-glob');
9var del = require('del');
10var archiver = require('archiver');
11
12function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
13
14var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
15var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
16var fsExtra__default = /*#__PURE__*/_interopDefaultLegacy(fsExtra);
17var cpy__default = /*#__PURE__*/_interopDefaultLegacy(cpy);
18var isGlob__default = /*#__PURE__*/_interopDefaultLegacy(isGlob);
19var del__default = /*#__PURE__*/_interopDefaultLegacy(del);
20var archiver__default = /*#__PURE__*/_interopDefaultLegacy(archiver);
21
22var 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
211const defaultTask = async () => {};
212
213const 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
226const fsExtraDefaultOptions = {
227 preserveTimestamps: true,
228};
229
230const 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 // if source is a file and target is a directory
242 // create the directory and copy the file into that directory
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
259const 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
278const 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
295const 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
314const 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
331const 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 // Exclude destination file from archive
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
381const 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
399const PLUGIN_NAME = 'FileManagerPlugin';
400
401const defaultOptions = {
402 events: {
403 onStart: [],
404 onEnd: [],
405 },
406 runTasksInSeries: false,
407 context: null,
408 runOnceInWatchMode: false,
409};
410
411const 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
445class 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
536module.exports = FileManagerPlugin;
537//# sourceMappingURL=index.js.map