UNPKG

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