1 | 'use strict';
|
2 |
|
3 |
|
4 | var errorcount, lastInfo;
|
5 |
|
6 | var grunt = require('../grunt');
|
7 |
|
8 |
|
9 | var path = require('path');
|
10 |
|
11 |
|
12 | var parent = grunt.util.task.create();
|
13 |
|
14 |
|
15 | var task = module.exports = Object.create(parent);
|
16 |
|
17 |
|
18 | var registry = {tasks: [], untasks: [], meta: {}};
|
19 |
|
20 |
|
21 | var loadTaskDepth = 0;
|
22 |
|
23 |
|
24 | task.registerTask = function(name) {
|
25 |
|
26 | registry.tasks.push(name);
|
27 |
|
28 | parent.registerTask.apply(task, arguments);
|
29 |
|
30 | var thisTask = task._tasks[name];
|
31 |
|
32 | thisTask.meta = grunt.util._.clone(registry.meta);
|
33 |
|
34 | var _fn = thisTask.fn;
|
35 | thisTask.fn = function(arg) {
|
36 |
|
37 | var name = thisTask.name;
|
38 |
|
39 | errorcount = grunt.fail.errorcount;
|
40 |
|
41 | Object.defineProperty(this, 'errorCount', {
|
42 | enumerable: true,
|
43 | get: function() {
|
44 | return grunt.fail.errorcount - errorcount;
|
45 | }
|
46 | });
|
47 |
|
48 | this.requires = task.requires.bind(task);
|
49 |
|
50 | this.requiresConfig = grunt.config.requires;
|
51 |
|
52 |
|
53 | this.options = function() {
|
54 | var args = [{}].concat(grunt.util.toArray(arguments)).concat([
|
55 | grunt.config([name, 'options'])
|
56 | ]);
|
57 | var options = grunt.util._.extend.apply(null, args);
|
58 | grunt.verbose.writeflags(options, 'Options');
|
59 | return options;
|
60 | };
|
61 |
|
62 |
|
63 | var logger = _fn.alias || (thisTask.multi && (!arg || arg === '*')) ? 'verbose' : 'log';
|
64 |
|
65 | grunt[logger].header('Running "' + this.nameArgs + '"' +
|
66 | (this.name !== this.nameArgs ? ' (' + this.name + ')' : '') + ' task');
|
67 |
|
68 | grunt[logger].debug('Task source: ' + thisTask.meta.filepath);
|
69 |
|
70 | return _fn.apply(this, arguments);
|
71 | };
|
72 | return task;
|
73 | };
|
74 |
|
75 |
|
76 | function isValidMultiTaskTarget(target) {
|
77 | return !/^_|^options$/.test(target);
|
78 | }
|
79 |
|
80 |
|
81 | task.normalizeMultiTaskFiles = function(data, target) {
|
82 | var prop, obj;
|
83 | var files = [];
|
84 | if (grunt.util.kindOf(data) === 'object') {
|
85 | if ('src' in data || 'dest' in data) {
|
86 | obj = {};
|
87 | for (prop in data) {
|
88 | if (prop !== 'options') {
|
89 | obj[prop] = data[prop];
|
90 | }
|
91 | }
|
92 | files.push(obj);
|
93 | } else if (grunt.util.kindOf(data.files) === 'object') {
|
94 | for (prop in data.files) {
|
95 | files.push({src: data.files[prop], dest: grunt.config.process(prop)});
|
96 | }
|
97 | } else if (Array.isArray(data.files)) {
|
98 | grunt.util._.flattenDeep(data.files).forEach(function(obj) {
|
99 | var prop;
|
100 | if ('src' in obj || 'dest' in obj) {
|
101 | files.push(obj);
|
102 | } else {
|
103 | for (prop in obj) {
|
104 | files.push({src: obj[prop], dest: grunt.config.process(prop)});
|
105 | }
|
106 | }
|
107 | });
|
108 | }
|
109 | } else {
|
110 | files.push({src: data, dest: grunt.config.process(target)});
|
111 | }
|
112 |
|
113 |
|
114 | if (files.length === 0) {
|
115 | grunt.verbose.writeln('File: ' + '[no files]'.yellow);
|
116 | return [];
|
117 | }
|
118 |
|
119 |
|
120 | files = grunt.util._(files).chain().forEach(function(obj) {
|
121 | if (!('src' in obj) || !obj.src) { return; }
|
122 |
|
123 | if (Array.isArray(obj.src)) {
|
124 | obj.src = grunt.util._.flatten(obj.src);
|
125 | } else {
|
126 | obj.src = [obj.src];
|
127 | }
|
128 | }).map(function(obj) {
|
129 |
|
130 | var expandOptions = grunt.util._.extend({}, obj);
|
131 | delete expandOptions.src;
|
132 | delete expandOptions.dest;
|
133 |
|
134 |
|
135 | if (obj.expand) {
|
136 | return grunt.file.expandMapping(obj.src, obj.dest, expandOptions).map(function(mapObj) {
|
137 |
|
138 | var result = grunt.util._.extend({}, obj);
|
139 |
|
140 | result.orig = grunt.util._.extend({}, obj);
|
141 |
|
142 | result.src = grunt.config.process(mapObj.src);
|
143 | result.dest = grunt.config.process(mapObj.dest);
|
144 |
|
145 | ['expand', 'cwd', 'flatten', 'rename', 'ext'].forEach(function(prop) {
|
146 | delete result[prop];
|
147 | });
|
148 | return result;
|
149 | });
|
150 | }
|
151 |
|
152 |
|
153 | var result = grunt.util._.extend({}, obj);
|
154 |
|
155 | result.orig = grunt.util._.extend({}, obj);
|
156 |
|
157 | if ('src' in result) {
|
158 |
|
159 | Object.defineProperty(result, 'src', {
|
160 | enumerable: true,
|
161 | get: function fn() {
|
162 | var src;
|
163 | if (!('result' in fn)) {
|
164 | src = obj.src;
|
165 |
|
166 | src = Array.isArray(src) ? grunt.util._.flatten(src) : [src];
|
167 |
|
168 | fn.result = grunt.file.expand(expandOptions, src);
|
169 | }
|
170 | return fn.result;
|
171 | }
|
172 | });
|
173 | }
|
174 |
|
175 | if ('dest' in result) {
|
176 | result.dest = obj.dest;
|
177 | }
|
178 |
|
179 | return result;
|
180 | }).flatten().value();
|
181 |
|
182 |
|
183 | if (grunt.option('verbose')) {
|
184 | files.forEach(function(obj) {
|
185 | var output = [];
|
186 | if ('src' in obj) {
|
187 | output.push(obj.src.length > 0 ? grunt.log.wordlist(obj.src) : '[no src]'.yellow);
|
188 | }
|
189 | if ('dest' in obj) {
|
190 | output.push('-> ' + (obj.dest ? String(obj.dest).cyan : '[no dest]'.yellow));
|
191 | }
|
192 | if (output.length > 0) {
|
193 | grunt.verbose.writeln('Files: ' + output.join(' '));
|
194 | }
|
195 | });
|
196 | }
|
197 |
|
198 | return files;
|
199 | };
|
200 |
|
201 |
|
202 | task.registerMultiTask = function(name, info, fn) {
|
203 |
|
204 | if (fn == null) {
|
205 | fn = info;
|
206 | info = 'Custom multi task.';
|
207 | }
|
208 |
|
209 | var thisTask;
|
210 | task.registerTask(name, info, function(target) {
|
211 |
|
212 | var name = thisTask.name;
|
213 |
|
214 | this.args = grunt.util.toArray(arguments).slice(1);
|
215 |
|
216 | if (!target || target === '*') {
|
217 | return task.runAllTargets(name, this.args);
|
218 | } else if (!isValidMultiTaskTarget(target)) {
|
219 | throw new Error('Invalid target "' + target + '" specified.');
|
220 | }
|
221 |
|
222 | this.requiresConfig([name, target]);
|
223 |
|
224 |
|
225 | this.options = function() {
|
226 | var targetObj = grunt.config([name, target]);
|
227 | var args = [{}].concat(grunt.util.toArray(arguments)).concat([
|
228 | grunt.config([name, 'options']),
|
229 | grunt.util.kindOf(targetObj) === 'object' ? targetObj.options : {}
|
230 | ]);
|
231 | var options = grunt.util._.extend.apply(null, args);
|
232 | grunt.verbose.writeflags(options, 'Options');
|
233 | return options;
|
234 | };
|
235 |
|
236 | this.target = target;
|
237 |
|
238 | this.flags = {};
|
239 | this.args.forEach(function(arg) { this.flags[arg] = true; }, this);
|
240 |
|
241 | this.data = grunt.config([name, target]);
|
242 |
|
243 | this.files = task.normalizeMultiTaskFiles(this.data, target);
|
244 |
|
245 | Object.defineProperty(this, 'filesSrc', {
|
246 | enumerable: true,
|
247 | get: function() {
|
248 | return grunt.util._(this.files).chain().map('src').flatten().uniq().value();
|
249 | }.bind(this)
|
250 | });
|
251 |
|
252 | return fn.apply(this, this.args);
|
253 | });
|
254 |
|
255 | thisTask = task._tasks[name];
|
256 | thisTask.multi = true;
|
257 | };
|
258 |
|
259 |
|
260 |
|
261 | task.registerInitTask = function(name, info, fn) {
|
262 | task.registerTask(name, info, fn);
|
263 | task._tasks[name].init = true;
|
264 | };
|
265 |
|
266 |
|
267 | task.renameTask = function(oldname, newname) {
|
268 | var result;
|
269 | try {
|
270 |
|
271 | result = parent.renameTask.apply(task, arguments);
|
272 |
|
273 | registry.untasks.push(oldname);
|
274 | registry.tasks.push(newname);
|
275 |
|
276 | return result;
|
277 | } catch (e) {
|
278 | grunt.log.error(e.message);
|
279 | }
|
280 | };
|
281 |
|
282 |
|
283 | task.runAllTargets = function(taskname, args) {
|
284 |
|
285 | var targets = Object.keys(grunt.config.getRaw(taskname) || {});
|
286 |
|
287 | targets = targets.filter(isValidMultiTaskTarget);
|
288 |
|
289 | if (targets.length === 0) {
|
290 | grunt.log.error('No "' + taskname + '" targets found.');
|
291 | return false;
|
292 | }
|
293 |
|
294 | targets.forEach(function(target) {
|
295 |
|
296 | task.run([taskname, target].concat(args || []).join(':'));
|
297 | });
|
298 | };
|
299 |
|
300 |
|
301 | var loadTaskStack = [];
|
302 | function loadTask(filepath) {
|
303 |
|
304 | loadTaskStack.push(registry);
|
305 |
|
306 | registry = {tasks: [], untasks: [], meta: {info: lastInfo, filepath: filepath}};
|
307 | var filename = path.basename(filepath);
|
308 | var msg = 'Loading "' + filename + '" tasks...';
|
309 | var regCount = 0;
|
310 | var fn;
|
311 | try {
|
312 |
|
313 | fn = require(path.resolve(filepath));
|
314 | if (typeof fn === 'function') {
|
315 | fn.call(grunt, grunt);
|
316 | }
|
317 | grunt.verbose.write(msg).ok();
|
318 |
|
319 | ['un', ''].forEach(function(prefix) {
|
320 | var list = grunt.util._.chain(registry[prefix + 'tasks']).uniq().sort().value();
|
321 | if (list.length > 0) {
|
322 | regCount++;
|
323 | grunt.verbose.writeln((prefix ? '- ' : '+ ') + grunt.log.wordlist(list));
|
324 | }
|
325 | });
|
326 | if (regCount === 0) {
|
327 | grunt.verbose.warn('No tasks were registered or unregistered.');
|
328 | }
|
329 | } catch (e) {
|
330 |
|
331 | grunt.log.write(msg).error().verbose.error(e.stack).or.error(e);
|
332 | }
|
333 |
|
334 | registry = loadTaskStack.pop() || {};
|
335 | }
|
336 |
|
337 |
|
338 | function loadTasksMessage(info) {
|
339 |
|
340 |
|
341 | if (loadTaskDepth === 0) { lastInfo = info; }
|
342 | grunt.verbose.subhead('Registering ' + info + ' tasks.');
|
343 | }
|
344 |
|
345 |
|
346 | function loadTasks(tasksdir) {
|
347 | try {
|
348 | var files = grunt.file.glob.sync('*.{js,cjs,coffee}', {cwd: tasksdir, maxDepth: 1});
|
349 |
|
350 | files.forEach(function(filename) {
|
351 | loadTask(path.join(tasksdir, filename));
|
352 | });
|
353 | } catch (e) {
|
354 | grunt.log.verbose.error(e.stack).or.error(e);
|
355 | }
|
356 | }
|
357 |
|
358 |
|
359 | task.loadTasks = function(tasksdir) {
|
360 | loadTasksMessage('"' + tasksdir + '"');
|
361 | if (grunt.file.exists(tasksdir)) {
|
362 | loadTasks(tasksdir);
|
363 | } else {
|
364 | grunt.log.error('Tasks directory "' + tasksdir + '" not found.');
|
365 | }
|
366 | };
|
367 |
|
368 |
|
369 |
|
370 | task.loadNpmTasks = function(name) {
|
371 | loadTasksMessage('"' + name + '" local Npm module');
|
372 | var root = path.resolve('node_modules');
|
373 | var pkgpath = path.join(root, name);
|
374 | var pkgfile = path.join(pkgpath, 'package.json');
|
375 |
|
376 |
|
377 | if (!grunt.file.exists(pkgpath)) {
|
378 | var nameParts = name.split('/');
|
379 |
|
380 |
|
381 | var normailzedName = (name[0] === '@' ? nameParts.slice(0,2).join('/') : nameParts[0]);
|
382 | try {
|
383 | pkgfile = require.resolve(normailzedName + '/package.json');
|
384 | root = pkgfile.substr(0, pkgfile.length - normailzedName.length - '/package.json'.length);
|
385 | } catch (err) {
|
386 | grunt.log.error('Local Npm module "' + normailzedName + '" not found. Is it installed?');
|
387 | return;
|
388 | }
|
389 | }
|
390 | var pkg = grunt.file.exists(pkgfile) ? grunt.file.readJSON(pkgfile) : {keywords: []};
|
391 |
|
392 |
|
393 | if (pkg.keywords && pkg.keywords.indexOf('gruntcollection') !== -1) {
|
394 | loadTaskDepth++;
|
395 | Object.keys(pkg.dependencies).forEach(function(depName) {
|
396 |
|
397 |
|
398 | var filepath = grunt.file.findup('node_modules/' + depName, {
|
399 | cwd: path.resolve('node_modules', name),
|
400 | nocase: true
|
401 | });
|
402 | if (filepath) {
|
403 |
|
404 | task.loadNpmTasks(path.relative(root, filepath));
|
405 | }
|
406 | });
|
407 | loadTaskDepth--;
|
408 | return;
|
409 | }
|
410 |
|
411 |
|
412 | var tasksdir = path.join(root, name, 'tasks');
|
413 | if (grunt.file.exists(tasksdir)) {
|
414 | loadTasks(tasksdir);
|
415 | } else {
|
416 | grunt.log.error('Local Npm module "' + name + '" not found. Is it installed?');
|
417 | }
|
418 | };
|
419 |
|
420 |
|
421 | task.init = function(tasks, options) {
|
422 | if (!options) { options = {}; }
|
423 |
|
424 |
|
425 | var allInit = tasks.length > 0 && tasks.every(function(name) {
|
426 | var obj = task._taskPlusArgs(name).task;
|
427 | return obj && obj.init;
|
428 | });
|
429 |
|
430 |
|
431 |
|
432 | var gruntfile, msg;
|
433 | if (allInit || options.gruntfile === false) {
|
434 | gruntfile = null;
|
435 | } else {
|
436 | gruntfile = grunt.option('gruntfile') ||
|
437 | grunt.file.findup('Gruntfile.{js,cjs,coffee}', {nocase: true});
|
438 | msg = 'Reading "' + (gruntfile ? path.basename(gruntfile) : '???') + '" Gruntfile...';
|
439 | }
|
440 |
|
441 | if (options.gruntfile === false) {
|
442 |
|
443 | } else if (gruntfile && grunt.file.exists(gruntfile)) {
|
444 | grunt.verbose.writeln().write(msg).ok();
|
445 |
|
446 |
|
447 | process.chdir(grunt.option('base') || path.dirname(gruntfile));
|
448 |
|
449 | loadTasksMessage('Gruntfile');
|
450 | loadTask(gruntfile);
|
451 | } else if (options.help || allInit) {
|
452 |
|
453 | } else if (grunt.option('gruntfile')) {
|
454 |
|
455 | grunt.log.writeln().write(msg).error();
|
456 | grunt.fatal('Unable to find "' + gruntfile + '" Gruntfile.', grunt.fail.code.MISSING_GRUNTFILE);
|
457 | } else if (!grunt.option('help')) {
|
458 | grunt.verbose.writeln().write(msg).error();
|
459 | grunt.log.writelns(
|
460 | 'A valid Gruntfile could not be found. Please see the getting ' +
|
461 | 'started guide for more information on how to configure grunt: ' +
|
462 | 'http://gruntjs.com/getting-started'
|
463 | );
|
464 | grunt.fatal('Unable to find Gruntfile.', grunt.fail.code.MISSING_GRUNTFILE);
|
465 | }
|
466 |
|
467 |
|
468 | (grunt.option('npm') || []).map(String).forEach(task.loadNpmTasks);
|
469 |
|
470 | (grunt.option('tasks') || []).map(String).forEach(task.loadTasks);
|
471 | };
|