UNPKG

34.6 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3var path = require('path'),
4 fs = require('fs');
5
6var async = require('async'),
7 request = require('request'),
8 mkdirp = require('mkdirp'),
9 cjson = require('cjson'),
10 _ = require('lodash'),
11 fastGlob = require('fast-glob'),
12 isGlob = require('is-glob'),
13 UglifyJS = require('uglify-js'),
14 md5 = require('md5'),
15 isUtf8 = require('is-utf8');
16
17var logger = require('note-down');
18logger.removeOption('showLogLine');
19
20var chalk = logger.chalk;
21
22var argv = require('yargs')
23 .help(false)
24 .version(false)
25 .argv;
26
27var packageJson = require('./package.json');
28
29var nodeVersion = process.versions.node;
30var paramHelp = argv.h || argv.help,
31 paramVersion = argv.v || argv.version,
32 paramVerbose = argv.verbose,
33 paramOutdated = argv.outdated,
34 paramWhenFileExists = argv.whenFileExists;
35
36var cwd = process.cwd();
37
38var utils = {
39 // https://github.com/sindresorhus/strip-bom/blob/f01a9435b8e7d31bb2bd757e67436d0a1864db0e/index.js
40 // Catches EFBBBF (UTF-8 BOM) because the buffer-to-string
41 // conversion translates it to FEFF (UTF-16 BOM)
42 stripBom: function (string) {
43 if (string.charCodeAt(0) === 0xFEFF) {
44 return string.slice(1);
45 }
46 return string;
47 },
48
49 getEncoding: function(contents) {
50 return isUtf8(contents) ? 'utf8' : 'binary';
51 },
52 getColoredTypeString: function(encoding) {
53 switch (encoding) {
54 case 'remote': return chalk.cyan('remote');
55 case 'binary': return chalk.yellow('binary');
56 case 'utf8': return ' utf8 ';
57 default: return chalk.red(encoding);
58 }
59 },
60 isRemoteResource: function (resourcePath) {
61 if (
62 resourcePath.indexOf('https://') === 0 ||
63 resourcePath.indexOf('http://') === 0 ||
64 resourcePath.indexOf('ftp://') === 0
65 ) {
66 return true;
67 }
68 return false;
69 },
70 getRelativePath: function (wrt, fullPath) {
71 if (utils.isRemoteResource(fullPath)) {
72 return fullPath;
73 }
74 return path.relative(wrt, fullPath);
75 },
76
77 exitWithError: function (e, errMsg) {
78 if (errMsg) {
79 logger.log(chalk.magenta(errMsg));
80 }
81 if (e) {
82 logger.error(e);
83 }
84 process.exit(1);
85 },
86
87 // Returns true/false/<defaultValue>
88 booleanIntention: function (val, defaultValue) {
89 if (val === undefined) {
90 return defaultValue;
91 } else {
92 return !!val;
93 }
94 },
95
96 ensureDirectoryExistence: function(dirPath) {
97 var dirname = dirPath[dirPath.length-1] === '/' ? path.normalize(dirPath) : path.dirname(dirPath);
98 if (!fs.existsSync(dirPath)) {
99 try {
100 mkdirp.sync(dirname);
101 } catch (e) {
102 logger.error('\n' + chalk.bold.underline('Error:'));
103 logger.error('Unable to create directory ' + dirname);
104
105 logger.error('\n' + chalk.bold.underline('Error details:'));
106 logger.error(e);
107
108 process.exit(1);
109 }
110 }
111 },
112
113 doUglify: function (needsUglify, code, cb) {
114 if (needsUglify) {
115 var result = UglifyJS.minify(
116 code,
117 // Equivalent to: uglifyjs <source> --compress sequences=false --beautify beautify=false,semicolons=false,comments=some --output <destination>
118 {
119 compress: {
120 sequences: false
121 },
122 mangle: false,
123 output: {
124 semicolons: false,
125 comments: 'some'
126 }
127 }
128 );
129 var consoleCommand = 'uglifyjs <source> --compress sequences=false --beautify beautify=false,semicolons=false,comments=some --output <destination>';
130 cb(result.code, consoleCommand);
131 } else {
132 cb(code, null);
133 }
134 },
135
136 readContents: function (sourceFullPath, cb) {
137 if (utils.isRemoteResource(sourceFullPath)) {
138 request(
139 {
140 uri: sourceFullPath,
141 gzip: true,
142 timeout: 30000
143 },
144 function (err, response, body) {
145 if (err) {
146 cb(err);
147 } else {
148 if (response.statusCode === 200) {
149 cb(null, body, 'remote');
150 } else {
151 cb('Unexpected statusCode (' + response.statusCode + ') for response of: ' + sourceFullPath);
152 }
153 }
154 }
155 );
156 } else {
157 try {
158 var rawContents = fs.readFileSync(sourceFullPath);
159 var encoding = utils.getEncoding(rawContents);
160 var contents = encoding === 'binary'
161 ? rawContents
162 : rawContents.toString('utf8');
163 cb(null, contents, encoding);
164 } catch (e) {
165 cb(e);
166 }
167 }
168 }
169};
170
171if (module.parent) {
172 // Just show a warning and do not exit the process since a basic mocha test checks for the sanity of the code of this file
173 logger.warn('\nWarning: Please run this module (' + packageJson.name + ') from its binary file.' + '\n');
174} else {
175 var showHelp = function () {
176 logger.log([
177 '',
178 chalk.bold('Usage:'),
179 ' copy-files-from-to [--config <config-file>] [--mode <mode-name>] [...]',
180 '',
181 chalk.bold('Examples:'),
182 ' copy-files-from-to',
183 ' copy-files-from-to --config copy-files-from-to.json',
184 ' copy-files-from-to --mode production',
185 ' copy-files-from-to -h',
186 ' copy-files-from-to --version',
187 '',
188 chalk.bold('Options:'),
189 ' --config <config-file-path> Path to configuration file',
190 ' When unspecified, it looks for copy-files-from-to.cjson / copy-files-from-to.json',
191 ' --mode <mode-name> Mode to use for copying the files',
192 ' When unspecified, it uses "default" mode',
193 ' --when-file-exists <operation> Override "whenFileExists" setting specified in configuration file',
194 ' <operation> can be "notify-about-available-change" or "overwrite" or "do-nothing"',
195 ' --outdated Notify about outdated parts of the configuration file',
196 ' (takes cue from "latest" property, wherever specified)',
197 ' --verbose Verbose logging',
198 ' -v --version Output the version number',
199 ' -h --help Show help',
200 ''
201 ].join('\n'));
202 };
203
204 if (paramHelp) {
205 showHelp();
206 process.exit(0);
207 }
208
209 if (paramVersion || paramVerbose) {
210 logger.log(packageJson.name + ' version: ' + packageJson.version);
211 logger.log('Node JS version: ' + nodeVersion);
212 if (paramVersion) {
213 process.exit(0);
214 }
215 }
216
217 var configFile = null;
218
219 configFile = argv.config;
220 if (!configFile) {
221 if (fs.existsSync(path.resolve(cwd, 'copy-files-from-to.cjson'))) {
222 configFile = 'copy-files-from-to.cjson';
223 } else if (fs.existsSync(path.resolve(cwd, 'copy-files-from-to.json'))) {
224 configFile = 'copy-files-from-to.json';
225 } else {
226 logger.error(
227 '\n' +
228 chalk.bold('Error:') + ' Please ensure that you have passed correct arguments. Exiting with error (code 1).'
229 );
230 showHelp();
231 process.exit(1);
232 }
233 }
234
235 var configFileSource,
236 configFileSourceDirectory;
237
238 if (configFile.indexOf('/') === 0 || configFile.indexOf('\\') === 0) { // readListFromFile has an absolute path
239 configFileSource = configFile;
240 } else { // readListFromFile has a relative path
241 configFileSource = path.resolve(cwd, configFile);
242 }
243 configFileSourceDirectory = path.dirname(configFileSource);
244
245 var cjsonText;
246 try {
247 logger.info('Reading copy instructions from file ' + utils.getRelativePath(cwd, configFileSource));
248 cjsonText = fs.readFileSync(configFileSource, 'utf8');
249 cjsonText = utils.stripBom(cjsonText);
250 } catch (e) {
251 utils.exitWithError(e, 'Error in reading file: ' + configFileSource);
252 }
253
254 var copyFiles = [],
255 settings = {};
256 try {
257 var cjsonData = cjson.parse(cjsonText);
258 if (cjsonData instanceof Object) {
259 if (Array.isArray(cjsonData.copyFiles)) {
260 copyFiles = cjsonData.copyFiles;
261 }
262 if (cjsonData.settings instanceof Object) {
263 settings = cjsonData.settings;
264 }
265 }
266 } catch (e) {
267 utils.exitWithError(e, 'Invalid (C)JSON data:\n ' + cjsonText.replace(/\n/g, '\n '));
268 }
269
270 // ".only" is useful for debugging (This feature is not mentioned in documentation)
271 copyFiles = (function (copyFiles) {
272 var copyFilesWithOnly = [];
273
274 copyFiles.forEach(function (copyFile) {
275 if (copyFile.only) {
276 copyFilesWithOnly.push(copyFile);
277 }
278 });
279
280 if (copyFilesWithOnly.length) {
281 return copyFilesWithOnly;
282 } else {
283 return copyFiles;
284 }
285 }(copyFiles));
286
287 if (paramOutdated) {
288 var arrFromAndLatest = [];
289
290 for (var i = 0; i < copyFiles.length; i++) {
291 let copyFile = copyFiles[i];
292 let from = copyFile.from;
293 if (from && typeof from === 'object') {
294 let modes = Object.keys(from);
295 for (let i = 0; i < modes.length; i++) {
296 let mode = modes[i],
297 fromMode = from[mode];
298 if (fromMode.src && fromMode.latest) {
299 var ob = {
300 src: fromMode.src,
301 latest: fromMode.latest
302 };
303 arrFromAndLatest.push(ob);
304 }
305 }
306 }
307 }
308
309 if (paramVerbose) {
310 logger.verbose('Need to check for updates for the following entries:');
311 logger.verbose(JSON.stringify(arrFromAndLatest, null, ' '));
312 }
313
314 var compareSrcAndLatest = function (arrSrcAndLatest) {
315 async.eachLimit(
316 arrSrcAndLatest,
317 8,
318 function (srcAndLatest, callback) {
319 var resourceSrc = srcAndLatest.src,
320 resourceSrcContents = null;
321 var resourceLatest = srcAndLatest.latest,
322 resourceLatestContents = null;
323
324 async.parallel(
325 [
326 function (cb) {
327 utils.readContents(resourceSrc, function (err, contents, encoding) {
328 if (err) {
329 logger.error(' ✗ Could not read: ' + resourceSrc);
330 } else {
331 if (encoding === 'binary')
332 resourceSrcContents = md5(contents);
333 else
334 resourceSrcContents = contents;
335 }
336 cb();
337 });
338 },
339 function (cb) {
340 utils.readContents(resourceLatest, function (err, contents, encoding) {
341 if (err) {
342 logger.error(' ✗ (Could not read) ' + chalk.gray(resourceLatest));
343 } else {
344 if (encoding === 'binary')
345 resourceLatestContents = md5(contents);
346 else
347 resourceLatestContents = contents;
348 }
349 cb();
350 });
351 }
352 ],
353 function () {
354 if (resourceSrcContents !== null && resourceLatestContents !== null) {
355 if (resourceSrcContents === resourceLatestContents) {
356 logger.success(' ✓' + chalk.gray(' (Up to date) ' + resourceSrc));
357 } else {
358 logger.warn(' 🔃 ("src" is outdated w.r.t. "latest") ' + chalk.gray(resourceSrc));
359 }
360 }
361 callback();
362 }
363 );
364 }
365 );
366 };
367
368 if (arrFromAndLatest.length) {
369 compareSrcAndLatest(arrFromAndLatest);
370 } else {
371 logger.warn('There are no "from" entries for which "from.<mode>.src" file needs to be compared with "from.<mode>.latest" file.');
372 }
373 } else {
374 var WHEN_FILE_EXISTS_NOTIFY_ABOUT_AVAILABLE_CHANGE = 'notify-about-available-change',
375 WHEN_FILE_EXISTS_OVERWRITE = 'overwrite',
376 WHEN_FILE_EXISTS_DO_NOTHING = 'do-nothing',
377 ARR_WHEN_FILE_EXISTS = [
378 WHEN_FILE_EXISTS_NOTIFY_ABOUT_AVAILABLE_CHANGE,
379 WHEN_FILE_EXISTS_OVERWRITE,
380 WHEN_FILE_EXISTS_DO_NOTHING
381 ];
382 var whenFileExists = paramWhenFileExists;
383 if (ARR_WHEN_FILE_EXISTS.indexOf(whenFileExists) === -1) {
384 whenFileExists = settings.whenFileExists;
385 if (ARR_WHEN_FILE_EXISTS.indexOf(whenFileExists) === -1) {
386 whenFileExists = WHEN_FILE_EXISTS_DO_NOTHING;
387 }
388 }
389 var overwriteIfFileAlreadyExists = (whenFileExists === WHEN_FILE_EXISTS_OVERWRITE),
390 notifyAboutAvailableChange = (whenFileExists === WHEN_FILE_EXISTS_NOTIFY_ABOUT_AVAILABLE_CHANGE);
391
392 var mode = argv.mode || 'default';
393
394 var warningsEncountered = 0;
395
396 copyFiles = copyFiles.map(function normalizeData(copyFile) {
397 var latest = null;
398 var from = null,
399 skipFrom = null;
400 if (typeof copyFile.from === 'string' || Array.isArray(copyFile.from)) {
401 from = copyFile.from;
402 } else {
403 var fromMode = copyFile.from[mode] || copyFile.from['default'] || {};
404 if (typeof fromMode === 'string') {
405 from = fromMode;
406 } else {
407 from = fromMode.src;
408 skipFrom = !!fromMode.skip;
409 latest = fromMode.latest;
410 }
411 }
412
413 var to = null,
414 skipTo = null,
415 uglify = null;
416 if (typeof copyFile.to === 'string') {
417 to = copyFile.to;
418 } else {
419 var toMode = copyFile.to[mode] || copyFile.to['default'] || {};
420 if (typeof toMode === 'string') {
421 to = toMode;
422 } else {
423 to = toMode.dest;
424 skipTo = !!toMode.skip;
425 }
426
427 if (typeof toMode === 'object' && toMode.uglifyJs !== undefined) {
428 uglify = utils.booleanIntention(toMode.uglifyJs, false);
429 } else {
430 uglify = utils.booleanIntention(settings.uglifyJs, false);
431 }
432 }
433
434 if (isGlob(to)) {
435 warningsEncountered++;
436 logger.log('');
437 logger.warn('The "to" entries should not be a "glob" pattern. ' + chalk.blue('(Reference: https://github.com/isaacs/node-glob#glob-primer)'));
438
439 to = null;
440 }
441
442 if ((typeof from === 'string' || Array.isArray(from)) && typeof to === 'string') {
443 if (!Array.isArray(from) && (from.match(/\.js$/) || to.match(/\.js$/))) {
444 // If "from" or "to" path ends with ".js", that indicates that it is a JS file
445 // So, retain the uglify setting.
446 // It is a "do nothing" block
447 } else {
448 // It does not seem to be a JS file. So, don't uglify it.
449 uglify = false;
450 }
451
452 return {
453 intendedFrom: from,
454 intendedTo: to,
455 latest: latest,
456 from: (function () {
457 if (utils.isRemoteResource(from)) {
458 return from;
459 }
460 if (Array.isArray(from)) {
461 // If array, it's a glob instruction. Any objects are
462 let globPatterns = [];
463 let globSettings = {};
464 from.forEach( globPart => {
465 if (typeof globPart === 'string') {
466 if (globPart.charAt(0) === '!')
467 globPatterns.push('!' + path.join(configFileSourceDirectory, globPart.substring(1)));
468 else
469 globPatterns.push(path.join(configFileSourceDirectory, globPart));
470 } else {
471 Object.assign(globSettings, globPart);
472 }
473 });
474 return {
475 globPatterns: globPatterns,
476 globSettings: globSettings,
477 };
478 }
479 return path.join(configFileSourceDirectory, from);
480 }()),
481 to: path.join(configFileSourceDirectory, to),
482 uglify: uglify
483 };
484 } else {
485 if (
486 (typeof from !== 'string' && !skipFrom) ||
487 (typeof to !== 'string' && !skipTo)
488 ) {
489 warningsEncountered++;
490 if (warningsEncountered === 1) { // Show this only once
491 logger.log('');
492 logger.warn('Some entries will not be considered in the current mode (' + mode + ').');
493 }
494
495 logger.log('');
496
497 var applicableModesForSkip = _.uniq(['"default"', '"' + mode + '"']).join(' / ');
498 if (typeof from !== 'string' && !skipFrom) {
499 var fromValuesToCheck = _.uniq([
500 '"from"',
501 '"from.default"',
502 '"from.default.src"',
503 '"from.' + mode + '"',
504 '"from.' + mode + '.src"'
505 ]).join(' / ');
506 logger.warn(' Please ensure that the value for ' + fromValuesToCheck + ' is a string.');
507 logger.warn(' Otherwise, add ' + chalk.blue('"skip": true') + ' under "from" for mode: ' + applicableModesForSkip);
508 }
509 if (typeof to !== 'string' && !skipTo) {
510 var toValuesToCheck = _.uniq([
511 '"to"',
512 '"to.default"',
513 '"to.default.dest"',
514 '"to.' + mode + '"',
515 '"to.' + mode + '.dest"'
516 ]).join(' / ');
517 logger.warn(' Please ensure that the value for ' + toValuesToCheck + ' is a string.');
518 logger.warn(' Otherwise, add ' + chalk.blue('"skip": true') + ' under "to" for mode: ' + applicableModesForSkip);
519 }
520
521 logger.warn(' ' + JSON.stringify(copyFile, null, ' ').replace(/\n/g, '\n '));
522 }
523 }
524 });
525
526 copyFiles = (function () {
527 var arr = [];
528 copyFiles.forEach(function (copyFile) {
529 if (copyFile && copyFile.from) {
530 const entries = function() {
531 if (typeof copyFile.from === 'string' && isGlob(copyFile.from)) {
532 return fastGlob.sync([copyFile.from], { dot: !settings.ignoreDotFilesAndFolders });
533 } else if (copyFile.from.globPatterns) {
534 return fastGlob.sync(
535 copyFile.from.globPatterns,
536 Object.assign({ dot: !settings.ignoreDotFilesAndFolders }, copyFile.from.globSettings)
537 );
538 } else {
539 return null;
540 }
541 }();
542 if (entries && entries.length) {
543 entries.forEach(function (entry) {
544 var ob = JSON.parse(JSON.stringify(copyFile));
545 ob.from = entry;
546 const entryPoint = entry.substring(configFileSourceDirectory.length+1);
547 const targetTo = entryPoint.substring(entryPoint.indexOf('/'));
548 ob.to = path.join(
549 ob.to,
550 targetTo
551 );
552 arr.push(ob);
553 });
554 } else {
555 arr.push(copyFile);
556 }
557 }
558 });
559 return arr;
560 }());
561
562 var writeContents = function (copyFile, options, cb) {
563 var to = copyFile.to,
564 intendedFrom = copyFile.intendedFrom;
565 var contents = options.contents,
566 uglified = options.uglified,
567 overwriteIfFileAlreadyExists = options.overwriteIfFileAlreadyExists;
568
569 utils.ensureDirectoryExistence(to);
570
571 var fileExists = fs.existsSync(to),
572 fileDoesNotExist = !fileExists;
573
574 var avoidedFileOverwrite;
575 let finalPath = '';
576 if (
577 fileDoesNotExist ||
578 (fileExists && overwriteIfFileAlreadyExists)
579 ) {
580 try {
581 if (to[to.length-1] === '/') {
582 const stats = fs.statSync(to);
583 if (stats.isDirectory()) {
584 if (typeof intendedFrom === 'string' && !isGlob(intendedFrom)) {
585 const fileName = path.basename(intendedFrom);
586 to = path.join(to, fileName);
587 }
588 }
589 }
590
591 fs.writeFileSync(to, contents, copyFile.encoding === 'binary' ? null : 'utf8');
592 finalPath = to;
593 } catch (e) {
594 cb(e);
595 return;
596 }
597 if (settings.addReferenceToSourceOfOrigin) {
598 var sourceDetails = intendedFrom;
599 if (uglified) {
600 sourceDetails += (uglified.uglifyCommand || '');
601 }
602
603 /*
604 TODO: Handle error scenario for this ".writeFileSync()" operation.
605 Not handling it yet because for all practical use-cases, if
606 the code has been able to write the "to" file, then it should
607 be able to write the "<to>.source.txt" file
608 */
609 fs.writeFileSync(to + '.source.txt', sourceDetails, 'utf8');
610 }
611 avoidedFileOverwrite = false;
612 } else {
613 avoidedFileOverwrite = true;
614 }
615 cb(null, avoidedFileOverwrite, finalPath);
616 };
617
618 var checkForAvailableChange = function (copyFile, contentsOfFrom, config, cb) {
619 var notifyAboutAvailableChange = config.notifyAboutAvailableChange;
620
621 if (notifyAboutAvailableChange) {
622 var to = copyFile.to;
623 utils.readContents(to, function (err, contentsOfTo, encoding) {
624 if (err) {
625 cb(chalk.red(' (unable to read "to.<mode>.dest" file at path ' + to + ')'));
626 warningsEncountered++;
627 } else {
628 copyFile.encoding = encoding;
629 var needsUglify = copyFile.uglify;
630
631 utils.doUglify(needsUglify, contentsOfFrom, function (processedCode) {
632 if (copyFile.encoding === 'binary') {
633 // Only run resource-intensive md5 on binary files
634 if (md5(processedCode) === md5(contentsOfTo)) {
635 cb(chalk.gray(' (up to date)'));
636 } else {
637 cb(chalk.yellow(' (md5 update is available)'));
638 }
639 } else {
640 if (processedCode === contentsOfTo) {
641 cb(chalk.gray(' (up to date)'));
642 } else {
643 cb(chalk.yellow(' (update is available)'));
644 }
645 }
646 });
647 }
648 });
649 } else {
650 cb();
651 }
652 };
653
654 var preWriteOperations = function (copyFile, contents, cb) {
655 var needsUglify = copyFile.uglify;
656 utils.doUglify(needsUglify, contents, function (processedCode, consoleCommand) {
657 if (needsUglify) {
658 cb({
659 contentsAfterPreWriteOperations: processedCode,
660 uglified: {
661 uglifyCommand:
662 '\n' +
663 '\n' +
664 '$ ' + consoleCommand +
665 '\n' +
666 '\nWhere:' +
667 '\n uglifyjs = npm install -g uglify-js@' + packageJson.dependencies['uglify-js'] +
668 '\n <source> = File ' + copyFile.intendedFrom +
669 '\n <destination> = File ./' + path.basename(copyFile.intendedTo)
670 }
671 });
672 } else {
673 cb({
674 contentsAfterPreWriteOperations: processedCode
675 });
676 }
677 });
678 };
679
680 var postWriteOperations = function (copyFile, originalContents, contentsAfterPreWriteOperations, config, cb) {
681 checkForAvailableChange(copyFile, originalContents, config, function (status) {
682 cb(status);
683 });
684 };
685
686 var doCopyFile = function (copyFile, cb) {
687 var from = copyFile.from,
688 to = copyFile.to;
689
690 var printFrom = ' ' + chalk.gray(utils.getRelativePath(cwd, from));
691 var printFromToOriginal = ' ' + chalk.gray(utils.getRelativePath(cwd, to));
692
693 var successMessage = ' ' + chalk.green('✓') + ` Copied `,
694 successMessageAvoidedFileOverwrite = ' ' + chalk.green('✓') + chalk.gray(' Already exists'),
695 errorMessageCouldNotReadFromSrc = ' ' + chalk.red('✗') + ' Could not read',
696 errorMessageFailedToCopy = ' ' + chalk.red('✗') + ' Failed to copy';
697
698 var destFileExists = fs.existsSync(to),
699 destFileDoesNotExist = !destFileExists;
700 if (
701 destFileDoesNotExist ||
702 (
703 destFileExists &&
704 (
705 overwriteIfFileAlreadyExists ||
706 notifyAboutAvailableChange
707 )
708 )
709 ) {
710 utils.readContents(copyFile.from, function (err, contentsOfFrom, encoding) {
711 if (err) {
712 warningsEncountered++;
713 if (destFileExists && notifyAboutAvailableChange) {
714 logger.log(errorMessageCouldNotReadFromSrc + printFrom);
715 } else {
716 logger.log(errorMessageFailedToCopy + printFromToOriginal);
717 }
718 cb();
719 return;
720 }
721 copyFile.encoding = encoding;
722 var typeString = `[${utils.getColoredTypeString(encoding)}]`;
723
724 preWriteOperations(copyFile, contentsOfFrom, function (options) {
725 var contentsAfterPreWriteOperations = options.contentsAfterPreWriteOperations,
726 uglified = options.uglified;
727 writeContents(
728 copyFile,
729 {
730 contents: contentsAfterPreWriteOperations,
731 uglified: uglified,
732 overwriteIfFileAlreadyExists: overwriteIfFileAlreadyExists
733 },
734 function (err, avoidedFileOverwrite, finalPath) {
735 if (err) {
736 warningsEncountered++;
737 logger.log(errorMessageFailedToCopy + printFromTo);
738 cb();
739 return;
740 } else {
741 var printTo = ' ' + chalk.gray(utils.getRelativePath(cwd, finalPath));
742 var printFromTo = printFrom + ' to' + printTo;
743
744 postWriteOperations(
745 copyFile,
746 contentsOfFrom,
747 contentsAfterPreWriteOperations,
748 {
749 notifyAboutAvailableChange: notifyAboutAvailableChange
750 },
751 function (appendToSuccessMessage) {
752 if (avoidedFileOverwrite) {
753 logger.log(successMessageAvoidedFileOverwrite + (appendToSuccessMessage || '') + printTo);
754 } else {
755 // Copying value of "destFileDoesNotExist" to "destFileDidNotExist" since that has a better
756 // sematic name for the given context
757 let destFileDidNotExist = destFileDoesNotExist;
758 if (destFileDidNotExist) {
759 logger.log(successMessage + typeString + printFromTo);
760 } else {
761 logger.log(successMessage + typeString + (appendToSuccessMessage || '') + printFromTo);
762 }
763 }
764 cb();
765 }
766 );
767 }
768 }
769 );
770 });
771 });
772 } else {
773 logger.log(successMessageAvoidedFileOverwrite + printFromToOriginal);
774 cb();
775 }
776 };
777
778 var done = function (warningsEncountered) {
779 if (warningsEncountered) {
780 if (warningsEncountered === 1) {
781 logger.warn('\nEncountered ' + warningsEncountered + ' warning. Please check.');
782 } else {
783 logger.warn('\nEncountered ' + warningsEncountered + ' warnings. Please check.');
784 }
785 logger.error('Error: Please resolve the above mentioned warnings. Exiting with code 1.');
786 process.exit(1);
787 }
788 };
789
790 if (copyFiles.length) {
791 logger.log(
792 chalk.blue('\nStarting copy operation in "' + (mode || 'default') + '" mode:') +
793 (overwriteIfFileAlreadyExists ? chalk.yellow(' (overwrite option is on)') : '')
794 );
795
796 async.eachLimit(
797 copyFiles,
798 8,
799 function (copyFile, callback) {
800 // "copyFile" would be "undefined" when copy operation is not applicable
801 // in current "mode" for the given file
802 if (copyFile && typeof copyFile === 'object') {
803 doCopyFile(copyFile, function () {
804 callback();
805 });
806 } else {
807 callback();
808 }
809 },
810 function () {
811 done(warningsEncountered);
812 }
813 );
814 } else {
815 logger.warn('No instructions applicable for copy operation.');
816 }
817 }
818}