UNPKG

5.6 kBJavaScriptView Raw
1var fs = require('fs');
2var path = require('path');
3
4var async = require('async');
5var rimraf = require('rimraf');
6
7var util = require('../lib/util');
8
9var counter = 0;
10var configCache = {};
11
12function cacheConfig(config) {
13 ++counter;
14 configCache[counter] = config;
15 return counter;
16}
17
18function pluckConfig(id) {
19 if (!configCache.hasOwnProperty(id)) {
20 throw new Error('Failed to find id in cache');
21 }
22 var config = configCache[id];
23 delete configCache[id];
24 return config;
25}
26
27function nullOverride(details, include) {
28 include(false);
29}
30
31function createTask(grunt) {
32 return function(taskName, targetName) {
33 var tasks = [];
34 var prefix = this.name;
35 if (!targetName) {
36 if (!grunt.config(taskName)) {
37 grunt.fatal('The "' + prefix + '" prefix is not supported for aliases');
38 return;
39 }
40 Object.keys(grunt.config(taskName)).forEach(function(targetName) {
41 if (!/^_|^options$/.test(targetName)) {
42 tasks.push(prefix + ':' + taskName + ':' + targetName);
43 }
44 });
45 return grunt.task.run(tasks);
46 }
47 var args = Array.prototype.slice.call(arguments, 2).join(':');
48 var options = this.options({
49 cache: path.join(__dirname, '..', '.cache'),
50 override: nullOverride
51 });
52
53 // support deprecated timestamps option
54 if (options.timestamps) {
55 grunt.log.warn('DEPRECATED OPTION. Use the "cache" option instead');
56 options.cache = options.timestamps;
57 }
58
59 var done = this.async();
60
61 var originalConfig = grunt.config.get([taskName, targetName]);
62 var config = grunt.util._.clone(originalConfig);
63
64 /**
65 * Special handling for tasks that expect the `files` config to be a string
66 * or array of string source paths.
67 */
68 var srcFiles = true;
69 if (typeof config.files === 'string') {
70 config.src = [config.files];
71 delete config.files;
72 srcFiles = false;
73 } else if (Array.isArray(config.files) &&
74 typeof config.files[0] === 'string') {
75 config.src = config.files;
76 delete config.files;
77 srcFiles = false;
78 }
79
80 var stamp = util.getStampPath(options.cache, taskName, targetName);
81 var previous;
82 try {
83 previous = fs.statSync(stamp).mtime;
84 } catch (err) {
85 // task has never succeeded before
86 previous = new Date(0);
87 }
88
89 function override(filePath, time, include) {
90 var details = {
91 task: taskName,
92 target: targetName,
93 path: filePath,
94 time: time
95 };
96 options.override(details, include);
97 }
98
99 var files = grunt.task.normalizeMultiTaskFiles(config, targetName);
100 util.filterFilesByTime(files, previous, override, function(e, newerFiles) {
101 if (e) {
102 return done(e);
103 } else if (newerFiles.length === 0) {
104 grunt.log.writeln('No newer files to process.');
105 return done();
106 }
107
108 /**
109 * If we started out with only src files in the files config,
110 * transform the newerFiles array into an array of source files.
111 */
112 if (!srcFiles) {
113 newerFiles = newerFiles.map(function(obj) {
114 return obj.src;
115 });
116 }
117
118 // configure target with only newer files
119 config.files = newerFiles;
120 delete config.src;
121 delete config.dest;
122 grunt.config.set([taskName, targetName], config);
123
124 // because we modified the task config, cache the original
125 var id = cacheConfig(originalConfig);
126
127 // run the task, and attend to postrun tasks
128 var qualified = taskName + ':' + targetName;
129 var tasks = [
130 qualified + (args ? ':' + args : ''),
131 'newer-postrun:' + qualified + ':' + id + ':' + options.cache
132 ];
133 grunt.task.run(tasks);
134
135 done();
136 });
137
138 };
139}
140
141
142/** @param {Object} grunt Grunt. */
143module.exports = function(grunt) {
144
145 grunt.registerTask(
146 'newer', 'Run a task with only those source files that have been ' +
147 'modified since the last successful run.', createTask(grunt));
148
149 var deprecated = 'DEPRECATED TASK. Use the "newer" task instead';
150 grunt.registerTask(
151 'any-newer', deprecated, function() {
152 grunt.log.warn(deprecated);
153 var args = Array.prototype.join.call(arguments, ':');
154 grunt.task.run(['newer:' + args]);
155 });
156
157 var internal = 'Internal task.';
158 grunt.registerTask(
159 'newer-postrun', internal, function(taskName, targetName, id, dir) {
160
161 // if dir includes a ':', grunt will split it among multiple args
162 dir = Array.prototype.slice.call(arguments, 3).join(':');
163 grunt.file.write(util.getStampPath(dir, taskName, targetName),
164 String(Date.now()));
165
166 // reconfigure task with original config
167 grunt.config.set([taskName, targetName], pluckConfig(id));
168
169 });
170
171 var clean = 'Remove cached timestamps.';
172 grunt.registerTask(
173 'newer-clean', clean, function(taskName, targetName) {
174 var done = this.async();
175
176 /**
177 * This intentionally only works with the default cache dir. If a
178 * custom cache dir is provided, it is up to the user to keep it clean.
179 */
180 var cacheDir = path.join(__dirname, '..', '.cache');
181 if (taskName && targetName) {
182 cacheDir = util.getStampPath(cacheDir, taskName, targetName);
183 } else if (taskName) {
184 cacheDir = path.join(cacheDir, taskName);
185 }
186 if (grunt.file.exists(cacheDir)) {
187 grunt.log.writeln('Cleaning ' + cacheDir);
188 rimraf(cacheDir, done);
189 } else {
190 done();
191 }
192 });
193
194};