module.exports = function(grunt) {
var path = require('path'),
- fs = require('fs'),
tasks = {},
cwd = process.env.PWD || process.cwd(),
inAMD = path.basename(cwd) == 'amd';
files: ['**/yui/src/**/*.js'],
tasks: ['shifter']
},
+ },
+ shifter: {
+ options: {
+ recursive: true,
+ paths: [cwd]
+ }
}
});
+ /**
+ * Shifter task. Is configured with a path to a specific file or a directory,
+ * in the case of a specific file it will work out the right module to be built.
+ *
+ * Note that this task runs the invidiaul shifter jobs async (becase it spawns
+ * so be careful to to call done().
+ */
tasks.shifter = function() {
- var exec = require('child_process').spawn,
+ var async = require('async'),
done = this.async(),
- args = [],
- options = {
- recursive: true,
- watch: false,
- walk: false,
- module: false
- },
- shifter;
+ options = grunt.config('shifter.options');
- grunt.log.ok("Running shifter on " + cwd);
+ // Run the shifter processes one at a time to avoid confusing output.
+ async.eachSeries(options.paths, function (src, filedone) {
+ var args = [];
args.push( path.normalize(__dirname + '/node_modules/shifter/bin/shifter'));
+ // Always ignore the node_modules directory.
+ args.push('--excludes', 'node_modules');
+
// Determine the most appropriate options to run with based upon the current location.
- if (path.basename(cwd) === 'src') {
- // Detect whether we're in a src directory.
+ if (grunt.file.isMatch('**/yui/**/*.js', src)) {
+ // When passed a JS file, build our containing module (this happen with
+ // watch).
+ grunt.log.debug('Shifter passed a specific JS file');
+ src = path.dirname(path.dirname(src));
+ options.recursive = false;
+ } else if (grunt.file.isMatch('**/yui/src', src)) {
+ // When in a src directory --walk all modules.
grunt.log.debug('In a src directory');
args.push('--walk');
- options.walk = true;
- } else if (path.basename(path.dirname(cwd)) === 'src') {
- // Detect whether we're in a module directory.
+ options.recursive = false;
+ } else if (grunt.file.isMatch('**/yui/src/*', src)) {
+ // When in module, only build our module.
grunt.log.debug('In a module directory');
- options.module = true;
- }
-
- if (grunt.option('watch')) {
- if (!options.walk && !options.module) {
- grunt.fail.fatal('Unable to watch unless in a src or module directory');
- }
-
- // It is not advisable to run with recursivity and watch - this
- // leads to building the build directory in a race-like fashion.
- grunt.log.debug('Detected a watch - disabling recursivity');
options.recursive = false;
- args.push('--watch');
+ } else if (grunt.file.isMatch('**/yui/src/*/js', src)) {
+ // When in module src, only build our module.
+ grunt.log.debug('In a source directory');
+ src = path.dirname(src);
+ options.recursive = false;
}
- if (options.recursive) {
- args.push('--recursive');
+ if (grunt.option('watch')) {
+ grunt.fail.fatal('The --watch option has been removed, please use `grunt watch` instead');
}
- // Always ignore the node_modules directory.
- args.push('--excludes', 'node_modules');
-
// Add the stderr option if appropriate
if (grunt.option('verbose')) {
args.push('--lint-stderr');
var execShifter = function() {
- shifter = exec("node", args, {
- cwd: cwd,
- stdio: 'inherit',
- env: process.env
- });
-
- // Tidy up after exec.
- shifter.on('exit', function (code) {
+ grunt.log.ok("Running shifter on " + src);
+ grunt.util.spawn({
+ cmd: "node",
+ args: args,
+ opts: {cwd: src, stdio: 'inherit', env: process.env}
+ }, function (error, result, code) {
if (code) {
grunt.fail.fatal('Shifter failed with code: ' + code);
} else {
grunt.log.ok('Shifter build complete.');
- done();
+ filedone();
}
});
};
execShifter();
} else {
// Check that there are yui modules otherwise shifter ends with exit code 1.
- var found = false;
- var hasYuiModules = function(directory, callback) {
- fs.readdir(directory, function(err, files) {
- if (err) {
- return callback(err, null);
- }
-
- // If we already found a match there is no need to continue scanning.
- if (found === true) {
- return;
- }
-
- // We need to track the number of files to know when we return a result.
- var pending = files.length;
-
- // We first check files, so if there is a match we don't need further
- // async calls and we just return a true.
- for (var i = 0; i < files.length; i++) {
- if (files[i] === 'yui') {
- return callback(null, true);
- }
- }
-
- // Iterate through subdirs if there were no matches.
- files.forEach(function (file) {
-
- var p = path.join(directory, file);
- var stat = fs.statSync(p);
- if (!stat.isDirectory()) {
- pending--;
- } else {
-
- // We defer the pending-1 until we scan the whole dir and subdirs.
- hasYuiModules(p, function(err, result) {
- if (err) {
- return callback(err);
- }
-
- if (result === true) {
- // Once we get a true we notify the caller.
- found = true;
- return callback(null, true);
- }
-
- pending--;
- if (pending === 0) {
- // Notify the caller that the whole dir has been scaned and there are no matches.
- return callback(null, false);
- }
- });
- }
-
- // No subdirs here, otherwise the return would be deferred until all subdirs are scanned.
- if (pending === 0) {
- return callback(null, false);
- }
- });
- });
- };
-
- hasYuiModules(cwd, function(err, result) {
- if (err) {
- grunt.fail.fatal(err.message);
- }
-
- if (result === true) {
- execShifter();
- } else {
- grunt.log.ok('No YUI modules to build.');
- done();
- }
- });
+ if (grunt.file.expand({cwd: src}, '**/yui/src/**/*.js').length > 0) {
+ args.push('--recursive');
+ execShifter();
+ } else {
+ grunt.log.ok('No YUI modules to build.');
+ filedone();
+ }
}
+ }, done);
};
tasks.startup = function() {
}
};
+ // On watch, we dynamically modify config to build only affected files. This
+ // method is slightly complicated to deal with multiple changed files at once (copied
+ // from the grunt-contrib-watch readme).
+ var changedFiles = Object.create(null);
+ var onChange = grunt.util._.debounce(function() {
+ var files = Object.keys(changedFiles);
+ grunt.config('jshint.amd.src', files);
+ grunt.config('uglify.amd.files', [{ expand: true, src: files, rename: uglify_rename }]);
+ grunt.config('shifter.options.paths', files);
+ changedFiles = Object.create(null);
+ }, 200);
- // On watch, we dynamically modify config to build only affected files.
grunt.event.on('watch', function(action, filepath) {
- grunt.config('jshint.amd.src', filepath);
- grunt.config('uglify.amd.files', [{ expand: true, src: filepath, rename: uglify_rename }]);
- if (filepath.match('yui')) {
- // Set the cwd to the base directory for yui modules which have changed.
- cwd = filepath.split(path.sep + 'yui' + path.sep + 'src').shift();
- } else {
- cwd = process.env.PWD || process.cwd();
- }
+ changedFiles[filepath] = action;
+ onChange();
});
// Register NPM tasks.