diff --git a/Gruntfile.js b/Gruntfile.js index 08cff05..62517fd 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -48,11 +48,6 @@ module.exports = function(grunt) { 'tmp/custom_options': ['test/fixtures/file1', 'test/fixtures/file2'] } }, - handling_invalid_files: { - src: ['test/fixtures/file1', 'invalid_file/should_warn/but_not_fail', 'test/fixtures/file2'], - dest: 'tmp/handling_invalid_files', - nonull: true - }, process_function: { options: { process: function(src, filepath) { @@ -160,6 +155,31 @@ module.exports = function(grunt) { }); + // Encapsulates tasks which invoke `grunt.fail.warn` and should abort. + grunt.registerTask('concat-warn', function() { + grunt.config('concat', { + handling_invalid_files: { + src: ['test/fixtures/file1', 'invalid_file/should_warn/and_abort', 'test/fixtures/file2'], + dest: 'tmp/handling_invalid_files', + nonull: true + } + }); + grunt.task.run(['concat']); + }); + + // Encapsulates tasks which invoke `grunt.fail.warn` and should abort, but the + // test is checking behavior in the case of a user-specified `--force` flag. + grunt.registerTask('concat-force', function() { + grunt.config('concat', { + handling_invalid_files_force: { + src: ['test/fixtures/file1', 'invalid_file/should_warn/but_not_fail', 'test/fixtures/file2'], + dest: 'tmp/handling_invalid_files_force', + nonull: true + } + }); + grunt.task.run(['concat']); + }); + // Actually load this plugin's task(s). grunt.loadTasks('tasks'); diff --git a/README.md b/README.md index 7acf9c7..717c239 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,7 @@ grunt.initConfig({ ``` #### Invalid or Missing Files Warning -If you would like the `concat` task to warn if a given file is missing or invalid be sure to set `nonull` to `true`: +If you would like the `concat` task to warn and abort if a given file is missing or invalid be sure to set `nonull` to `true`: ```js grunt.initConfig({ @@ -249,6 +249,8 @@ grunt.initConfig({ }); ``` +Additionally invoke grunt with `--force` to skip over missing files. + See [configuring files for a task](http://gruntjs.com/configuring-tasks#files) for how to configure file globbing in Grunt. @@ -298,4 +300,4 @@ grunt.initConfig({ Task submitted by ["Cowboy" Ben Alman](http://benalman.com/) -*This file was generated on Wed Apr 20 2016 08:41:44.* +*This file was generated on Sun Jun 05 2016 08:36:20.* diff --git a/docs/concat-examples.md b/docs/concat-examples.md index f0c87f9..75fc448 100644 --- a/docs/concat-examples.md +++ b/docs/concat-examples.md @@ -136,7 +136,7 @@ grunt.initConfig({ ``` ## Invalid or Missing Files Warning -If you would like the `concat` task to warn if a given file is missing or invalid be sure to set `nonull` to `true`: +If you would like the `concat` task to warn and abort if a given file is missing or invalid be sure to set `nonull` to `true`: ```js grunt.initConfig({ @@ -150,6 +150,8 @@ grunt.initConfig({ }); ``` +Additionally invoke grunt with `--force` to skip over missing files. + See [configuring files for a task](http://gruntjs.com/configuring-tasks#files) for how to configure file globbing in Grunt. diff --git a/tasks/concat.js b/tasks/concat.js index 81fd862..af51d30 100644 --- a/tasks/concat.js +++ b/tasks/concat.js @@ -60,62 +60,80 @@ module.exports = function(grunt) { sourceMap = false; } - // Iterate over all src-dest file pairs. - this.files.forEach(function(f) { - // Initialize source map objects. - var sourceMapHelper; - if (sourceMap) { - sourceMapHelper = sourcemap.helper(f, options); - sourceMapHelper.add(banner); - } - - // Concat banner + specified files + footer. - var src = banner + f.src.filter(function(filepath) { - // Warn on and remove invalid source files (if nonull was set). - if (!grunt.file.exists(filepath)) { - grunt.log.warn('Source file "' + filepath + '" not found.'); - return false; - } - return true; - }).map(function(filepath, i) { - if (grunt.file.isDir(filepath)) { - return; - } - // Read file source. - var src = grunt.file.read(filepath); - // Process files as templates if requested. - if (typeof options.process === 'function') { - src = options.process(src, filepath); - } else if (options.process) { - src = grunt.template.process(src, options.process); - } - // Strip banners if requested. - if (options.stripBanners) { - src = comment.stripBanner(src, options.stripBanners); + var exitEarly = {}; + try { + // Iterate over all src-dest file pairs. + this.files.forEach(function(f) { + // Initialize source map objects. + var sourceMapHelper; + if (sourceMap) { + sourceMapHelper = sourcemap.helper(f, options); + sourceMapHelper.add(banner); } - // Add the lines of this file to our map. - if (sourceMapHelper) { - src = sourceMapHelper.addlines(src, filepath); - if (i < f.src.length - 1) { - sourceMapHelper.add(options.separator); + + // Concat banner + specified files + footer. + var src = banner + f.src.filter(function(filepath) { + // Warn on invalid source files (if nonull was set). They will be + // removed if --force is specified. + if (!grunt.file.exists(filepath)) { + grunt.fail.warn('Source file "' + filepath + '" not found.'); + if (!grunt.option('force')) { + // See the catch clause below for why we do this. + throw exitEarly; + } + return false; } - } - return src; - }).join(options.separator) + footer; + return true; + }).map(function(filepath, i) { + if (grunt.file.isDir(filepath)) { + return; + } + // Read file source. + var src = grunt.file.read(filepath); + // Process files as templates if requested. + if (typeof options.process === 'function') { + src = options.process(src, filepath); + } else if (options.process) { + src = grunt.template.process(src, options.process); + } + // Strip banners if requested. + if (options.stripBanners) { + src = comment.stripBanner(src, options.stripBanners); + } + // Add the lines of this file to our map. + if (sourceMapHelper) { + src = sourceMapHelper.addlines(src, filepath); + if (i < f.src.length - 1) { + sourceMapHelper.add(options.separator); + } + } + return src; + }).join(options.separator) + footer; - if (sourceMapHelper) { - sourceMapHelper.add(footer); - sourceMapHelper.write(); - // Add sourceMappingURL to the end. - src += sourceMapHelper.url(); - } + if (sourceMapHelper) { + sourceMapHelper.add(footer); + sourceMapHelper.write(); + // Add sourceMappingURL to the end. + src += sourceMapHelper.url(); + } - // Write the destination file. - grunt.file.write(f.dest, src); + // Write the destination file. + grunt.file.write(f.dest, src); - // Print a success message. - grunt.verbose.write('File ' + chalk.cyan(f.dest) + ' created.'); - }); + // Print a success message. + grunt.verbose.write('File ' + chalk.cyan(f.dest) + ' created.'); + }); + } catch (reason) { + if (reason === exitEarly) { + // grunt.fail.warn uses the "exit" library, which doesn't stop the + // process synchronously on Node.js 0.10 on Windows, which could result + // in files still being concatenated even when some are missing. See: + // https://github.com/nodejs/node-v0.x-archive/issues/3584#issuecomment-8034529 + // Therefore, we throw a unique reference to emulate exiting early. + return; + } + throw reason; + } }); }; diff --git a/test/concat_test.js b/test/concat_test.js index 3de58d6..c6cb32c 100644 --- a/test/concat_test.js +++ b/test/concat_test.js @@ -2,6 +2,11 @@ var grunt = require('grunt'); var comment = require('../tasks/lib/comment').init(grunt); +var exec = require('child_process').exec; +var path = require('path'); +var fs = require('fs'); + +var execOptions = {cwd: path.join(__dirname, '..')}; function getNormalizedFile(filepath) { return grunt.util.normalizelf(grunt.file.read(filepath)); @@ -27,13 +32,30 @@ exports.concat = { test.done(); }, handling_invalid_files: function(test) { - test.expect(1); + test.expect(3); - var actual = getNormalizedFile('tmp/handling_invalid_files'); - var expected = getNormalizedFile('test/expected/handling_invalid_files'); - test.equal(actual, expected, 'will have warned, but should not fail.'); + exec('grunt concat-warn', execOptions, function(error, stdout) { + test.ok(stdout.indexOf('Warning:') > -1, 'should print a warning.'); + test.ok(stdout.indexOf('Aborted due to warnings.') > -1, 'should abort.'); - test.done(); + fs.exists('tmp/handling_invalid_files', function(exists) { + test.ok(!exists, 'should not have created a file.'); + test.done(); + }); + }); + }, + handling_invalid_files_force: function(test) { + test.expect(2); + + exec('grunt concat-force --force', execOptions, function(error, stdout) { + test.ok(stdout.indexOf('Warning:') > -1, 'should print a warning.'); + + var actual = getNormalizedFile('tmp/handling_invalid_files_force'); + var expected = getNormalizedFile('test/expected/handling_invalid_files_force'); + test.equal(actual, expected, 'should not fail.'); + + test.done(); + }); }, strip_banner: function(test) { test.expect(10); diff --git a/test/expected/handling_invalid_files b/test/expected/handling_invalid_files_force similarity index 100% rename from test/expected/handling_invalid_files rename to test/expected/handling_invalid_files_force