├── .gitignore
├── .jshintrc
├── .npmignore
├── .travis.yml
├── LICENSE-MIT
├── README.md
├── changelog.md
├── gruntfile.js
├── lib
└── util.js
├── package.json
├── tasks
└── newer.js
└── test
├── .jshintrc
├── helper.js
├── integration
├── fixtures
│ ├── newer-clean-dest
│ │ ├── gruntfile.js
│ │ └── src
│ │ │ ├── one.coffee
│ │ │ └── two.coffee
│ ├── newer-dest
│ │ ├── gruntfile.js
│ │ └── src
│ │ │ ├── one.coffee
│ │ │ └── two.coffee
│ ├── newer-modify-none
│ │ ├── gruntfile.js
│ │ └── src
│ │ │ ├── one.js
│ │ │ └── two.js
│ ├── newer-modify-one
│ │ ├── gruntfile.js
│ │ └── src
│ │ │ ├── one.js
│ │ │ └── two.js
│ ├── newer-override
│ │ ├── gruntfile.js
│ │ └── src
│ │ │ ├── one.js
│ │ │ ├── three.js
│ │ │ └── two.js
│ ├── newer-reconfigure
│ │ ├── gruntfile.js
│ │ └── src
│ │ │ ├── one.coffee
│ │ │ └── two.coffee
│ └── newer-tolerance
│ │ ├── gruntfile.js
│ │ └── src
│ │ ├── one.coffee
│ │ └── two.coffee
├── newer-clean-dest.spec.js
├── newer-dest.spec.js
├── newer-modify-none.spec.js
├── newer-modify-one.spec.js
├── newer-override.spec.js
├── newer-reconfigure.spec.js
├── newer-tolerance.spec.js
└── tasks
│ └── index.js
└── lib
└── util.spec.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | npm-debug.log
3 | /.cache/
4 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "curly": true,
3 | "eqeqeq": true,
4 | "indent": 2,
5 | "latedef": true,
6 | "newcap": true,
7 | "nonew": true,
8 | "quotmark": "single",
9 | "undef": true,
10 | "trailing": true,
11 | "maxlen": 80,
12 | "globals": {
13 | "exports": true,
14 | "module": false,
15 | "process": false,
16 | "require": false,
17 | "__dirname": false
18 | }
19 | }
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .travis.yml
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 | - "0.8"
5 |
6 | before_install:
7 | - '[ "${TRAVIS_NODE_VERSION}" != "0.8" ] || npm install -g npm@1.4.28'
8 | - npm install -g npm@latest
9 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Tim Schaub
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # grunt-newer
2 |
3 | Configure [Grunt](http://gruntjs.com/) tasks to run with newer files only.
4 |
5 | **Synopsis:** The [`newer`](#newer) task will configure another task to run with `src` files that are *a)* newer than the `dest` files or *b)* newer than the last successful run (if there are no `dest` files). See below for examples and more detail.
6 |
7 | ## Getting Started
8 | This plugin requires Grunt `~0.4.1`
9 |
10 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [`gruntfile.js`](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
11 |
12 | ```shell
13 | npm install grunt-newer --save-dev
14 | ```
15 |
16 | Once the plugin has been installed, it may be enabled inside your `gruntfile.js` with this line:
17 |
18 | ```js
19 | grunt.loadNpmTasks('grunt-newer');
20 | ```
21 |
22 |
23 | ## The `newer` task
24 |
25 | The `newer` task doesn't require any special configuration. To use it, just add `newer` as the first argument when running other tasks.
26 |
27 | For example, if you want to use [Uglify](https://npmjs.org/package/grunt-contrib-uglify) to minify your source files only when one or more of them is newer than the previously minified destination file, configure the `uglify` task as you would otherwise, and then register a task with `newer` at the front.
28 |
29 | ```js
30 | grunt.initConfig({
31 | uglify: {
32 | all: {
33 | files: {
34 | 'dest/app.min.js': ['src/**/*.js']
35 | }
36 | }
37 | }
38 | });
39 |
40 | grunt.loadNpmTasks('grunt-contrib-uglify');
41 | grunt.loadNpmTasks('grunt-newer');
42 |
43 | grunt.registerTask('minify', ['newer:uglify:all']);
44 | ```
45 |
46 | With the above configuration the `minify` task will only run `uglify` if one or more of the `src/**/*.js` files is newer than the `dest/app.min.js` file.
47 |
48 | The above example shows how the `newer` task works with other tasks that specify both `src` and `dest` files. In this case, the modification time of `src` files are compared to modification times of corresponding `dest` files to determine which `src` files to include.
49 |
50 | The `newer` task can also be used with tasks that don't generate any `dest` files. In this case, `newer` will only use files that are newer than the last successful run of the same task.
51 |
52 | For example, if you want to run [JSHint](https://npmjs.org/package/grunt-contrib-jshint) on only those files that have been modified since the last successful run, configure the `jshint` task as you would otherwise, and then register a task with `newer` at the front.
53 |
54 | ```js
55 | grunt.initConfig({
56 | jshint: {
57 | all: {
58 | src: 'src/**/*.js'
59 | }
60 | }
61 | });
62 |
63 | grunt.loadNpmTasks('grunt-contrib-jshint');
64 | grunt.loadNpmTasks('grunt-newer');
65 |
66 | grunt.registerTask('lint', ['newer:jshint:all']);
67 | ```
68 |
69 | With the above configuration, running `grunt lint` will configure your `jshint:all` task to use only files in the `jshint.all.src` config that have been modified since the last successful run of the same task. The first time the `jshint:newer:all` task runs, all source files will be used. After that, only the files you modify will be run through the linter.
70 |
71 | Another example is to use the `newer` task in conjunction with `watch`. For example, you might want to set up a watch to run a linter on all your `.js` files whenever one changes. With the `newer` task, instead of re-running the linter on all files, you only need to run it on the files that changed.
72 |
73 | ```js
74 | var srcFiles = 'src/**/*.js';
75 |
76 | grunt.initConfig({
77 | jshint: {
78 | all: {
79 | src: srcFiles
80 | }
81 | },
82 | watch: {
83 | all: {
84 | files: srcFiles,
85 | tasks: ['newer:jshint:all']
86 | }
87 | }
88 | });
89 |
90 | grunt.loadNpmTasks('grunt-contrib-jshint');
91 | grunt.loadNpmTasks('grunt-contrib-watch');
92 | grunt.loadNpmTasks('grunt-newer');
93 |
94 | ```
95 |
96 | With the above configuration, running `grunt jshint watch` will first lint all your files with `jshint` and then set up a watch. Whenever one of your source files changes, the `jshint` task will be run on just the modified file.
97 |
98 | *Note:* If your task is configured with `dest` files, `newer` will run your task with only those files that are newer than the corresponding `dest` files.
99 |
100 | ## Options for the `newer` task
101 |
102 | In most cases, you shouldn't need to add any special configuration for the `newer` task. Just `grunt.loadNpmTasks('grunt-newer')` and you can use `newer` as a prefix to your other tasks. The options below are available for advanced usage.
103 |
104 | #### options.cache
105 | * type: `string`
106 | * default: `node_modules/grunt-newer/.cache`
107 |
108 | To keep track of timestamps for successful runs, the `newer` task writes to a cache directory. The default is to use a `.cache` directory within the `grunt-newer` installation directory. If you need timestamp info to be written to a different location, configure the task with a `cache` option.
109 |
110 | Example use of the `cache` option:
111 |
112 | ```js
113 | grunt.initConfig({
114 | newer: {
115 | options: {
116 | cache: 'path/to/custom/cache/directory'
117 | }
118 | }
119 | });
120 | ```
121 |
122 | #### options.override
123 | * type: `function(Object, function(boolean))`
124 | * default: `null`
125 |
126 | The `newer` task determines which files to include for a specific task based on file modification time. There are occassions where you may want to include a file even if it has not been modified. For example, if a LESS file imports some other files, you will want to include it if any of the imports have been modified. To support this, you can provide an `override` function that takes two arguments:
127 |
128 | * **details** - `Object`
129 | * **task** - `string` The currently running task name.
130 | * **target** - `string` The currently running target name.
131 | * **path** - `string` The path to a `src` file that appears to be "older" (not modified since the time below).
132 | * **time** - `Date` The comparison time. For tasks with `dest` files, this is the modification time of the `dest` file. For tasks without `dest` files, this is the last successful run time of the same task.
133 | * **include** - `function(boolean)` A callback that determines whether this `src` file should be included. Call with `true` to include or `false` to exclude the file.
134 |
135 | Example use of the `override` option:
136 |
137 | ```js
138 | grunt.initConfig({
139 | newer: {
140 | options: {
141 | override: function(detail, include) {
142 | if (detail.task === 'less') {
143 | checkForModifiedImports(detail.path, detail.time, include);
144 | } else {
145 | include(false);
146 | }
147 | }
148 | }
149 | }
150 | });
151 | ```
152 |
153 | #### options.tolerance
154 | * type: `number` (milliseconds)
155 | * default: `0`
156 |
157 | The `newer` tasks compares the file modification times of the source and destination files with millisecond precision.
158 | On some file systems this causes destination files to always be considered older because of imprecision in the registration of modification times.
159 |
160 | If your tasks are always run even though the source files are not modified use the `tolerance` option to compensate for this imprecision.
161 |
162 | In most cases setting the option to `1000` milliseconds should be enough. If the file system is very imprecise use a higher value.
163 |
164 | Example use of the `tolerance` option:
165 |
166 | ```js
167 | grunt.initConfig({
168 | newer: {
169 | options: {
170 | tolerance: 1000
171 | }
172 | }
173 | });
174 | ```
175 |
176 | ## That's it
177 |
178 | Please [submit an issue](https://github.com/tschaub/grunt-newer/issues) if you encounter any trouble. Contributions or suggestions for improvements welcome!
179 |
180 | [](https://travis-ci.org/tschaub/grunt-newer)
181 |
182 | ## Known limitations
183 |
184 | The `newer` task relies on Grunt's convention for specifying [`src`/`dest` mappings](http://gruntjs.com/configuring-tasks#files). So it should be expected to work with two types of tasks:
185 |
186 | 1) Tasks that specify both `src` and `dest` files. In this case, the task prefixed by `newer` will be configured to run with `src` files that are newer than the corresponding `dest` file (based on the `mtime` of files).
187 |
188 | 2) Tasks that specify only `src` files. In this case, the task prefixed by `newer` will be configured to run with `src` files that are newer than the previous successful run of the same task.
189 |
190 | The `newer` task will *not* work as a prefix for the following tasks:
191 |
192 | * [`grunt-rsync`](http://npmjs.org/package/grunt-rsync) - Though this task specifies `src` and `dest` files, the `dest` file is not generated based on `src` files (instead it is a directory).
193 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## 1.3.0
4 |
5 | * Updated dev dependencies
6 |
7 | ## 1.2.0
8 |
9 | * Add `tolerance` option to account for filesystem time precision (thanks @jorrit, see [#94][#94])
10 | * Updated dependencies (thanks @jorrit, see [#93][#93])
11 |
12 | ## 1.1.2
13 |
14 | * Update peer dependency for Grunt (thanks @steveoh, see [#91][91])
15 |
16 | ## 1.1.1
17 |
18 | * Update license identifier (MIT)
19 |
20 | ## 1.1.0
21 |
22 | * Write current time to timestamp file (thanks @malys, see [#69][69])
23 |
24 | ## 1.0.0
25 |
26 | * Document that grunt-newer works with grunt-spritesmith >= 3.1.0 (thanks @danez, see [#66][66])
27 | * Support for an empty list of source files (thanks @ruslansagitov, see [#62][62])
28 |
29 | ## 0.8.0
30 |
31 | * Support for a single source file that matches the dest file (thanks @btholt, see [#42][42] and [#62][62])
32 | * Avoid unhandled error when task is aliased (see [#61][61])
33 |
34 | ## 0.7.0
35 |
36 | * Support for `override` option. In cases where a `src` file should be included even if it has not been modified (e.g. a LESS file whose imports have been modified), the `override` option can be used (see [#35][35])
37 |
38 | ## 0.6.1
39 |
40 | * When `src` and `dest` files are the same, the previous run time is considered (see [#24][24])
41 |
42 | ## 0.6.0
43 |
44 | * Deprecated `any-newer` task (`newer` task now handles this automatically, see [#17][17])
45 | * Deprecated `timestamps` option (use `cache` instead)
46 | * Consolidated `newer-reconfigure` and `newer-timestamp` into single `newer-postrun` task
47 | * Refactor task for easier unit testing (see [#16][16])
48 |
49 | ## 0.5.4
50 |
51 | * Correctly handle cases where `dest` file is not present (thanks @royriojas, see [#11][11])
52 |
53 | ## 0.5.3
54 |
55 | * Add `newer-reconfigure` to properly reset task configuration (see [#8][8])
56 |
57 | ## 0.5.2
58 |
59 | * Fix use of `any-newer` on task with multiple targets (thanks @royriojas, see [#7][7])
60 |
61 | ## 0.5.1
62 |
63 | * Filter out file objects with no remaining `src` files (see [#6][6])
64 |
65 | ## 0.5.0
66 |
67 | * Compare `src` file modification times to `dest` files if present (see [#2][2])
68 |
69 | [2]: https://github.com/tschaub/grunt-newer/pull/2
70 | [6]: https://github.com/tschaub/grunt-newer/pull/6
71 | [7]: https://github.com/tschaub/grunt-newer/pull/7
72 | [8]: https://github.com/tschaub/grunt-newer/pull/8
73 | [11]: https://github.com/tschaub/grunt-newer/pull/11
74 | [16]: https://github.com/tschaub/grunt-newer/pull/16
75 | [17]: https://github.com/tschaub/grunt-newer/pull/17
76 | [24]: https://github.com/tschaub/grunt-newer/pull/24
77 | [35]: https://github.com/tschaub/grunt-newer/pull/35
78 | [42]: https://github.com/tschaub/grunt-newer/pull/42
79 | [61]: https://github.com/tschaub/grunt-newer/pull/61
80 | [62]: https://github.com/tschaub/grunt-newer/pull/62
81 | [66]: https://github.com/tschaub/grunt-newer/pull/66
82 | [69]: https://github.com/tschaub/grunt-newer/pull/69
83 | [91]: https://github.com/tschaub/grunt-newer/pull/91
84 | [93]: https://github.com/tschaub/grunt-newer/pull/93
85 | [94]: https://github.com/tschaub/grunt-newer/pull/94
86 |
--------------------------------------------------------------------------------
/gruntfile.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | var path = require('path');
3 | var fs = require('fs');
4 |
5 |
6 | /**
7 | * @param {Object} grunt Grunt.
8 | */
9 | module.exports = function(grunt) {
10 |
11 | var gruntfileSrc = 'gruntfile.js';
12 | var tasksSrc = ['tasks/**/*.js', 'lib/**/*.js'];
13 | var testSrc = 'test/**/*.spec.js';
14 | var fixturesJs = 'test/integration/fixtures/**/*.js';
15 | var fixturesAll = 'test/integration/fixtures/**/*';
16 |
17 | grunt.initConfig({
18 |
19 | cafemocha: {
20 | options: {
21 | reporter: 'spec'
22 | },
23 | all: {
24 | src: testSrc
25 | }
26 | },
27 |
28 | jshint: {
29 | options: {
30 | jshintrc: true
31 | },
32 | gruntfile: {
33 | src: gruntfileSrc
34 | },
35 | tasks: {
36 | src: tasksSrc
37 | },
38 | tests: {
39 | src: testSrc
40 | },
41 | fixturesJs: {
42 | src: fixturesJs
43 | }
44 | },
45 |
46 | watch: {
47 | tasks: {
48 | files: tasksSrc,
49 | tasks: ['cafemocha']
50 | },
51 | tests: {
52 | files: testSrc,
53 | tasks: ['newer:cafemocha']
54 | },
55 | fixturesAll: {
56 | files: fixturesAll,
57 | tasks: ['cafemocha']
58 | },
59 | allJs: {
60 | files: [gruntfileSrc, tasksSrc, testSrc, fixturesJs],
61 | tasks: ['newer:jshint']
62 | }
63 | }
64 |
65 | });
66 |
67 | grunt.loadTasks('tasks');
68 | grunt.loadNpmTasks('grunt-contrib-jshint');
69 | grunt.loadNpmTasks('grunt-contrib-watch');
70 | grunt.loadNpmTasks('grunt-cafe-mocha');
71 |
72 | grunt.registerTask('test', ['newer:jshint', 'cafemocha']);
73 |
74 | grunt.registerTask('default', 'test');
75 |
76 | };
77 |
--------------------------------------------------------------------------------
/lib/util.js:
--------------------------------------------------------------------------------
1 | var crypto = require('crypto');
2 | var fs = require('fs');
3 | var path = require('path');
4 |
5 | var async = require('async');
6 |
7 | /**
8 | * Filter a list of files by mtime.
9 | * @param {Array.} paths List of file paths.
10 | * @param {Date} time The comparison time.
11 | * @param {number} tolerance Maximum time in milliseconds that the destination
12 | * file is allowed to be newer than the source file to compensate for
13 | * imprecisions in modification times in file systems.
14 | * @param {function(string, Date, function(boolean))} override Override.
15 | * @param {function(Err, Array.)} callback Callback called with any
16 | * error and a list of files that have mtimes newer than the provided time.
17 | */
18 | var filterPathsByTime = exports.filterPathsByTime = function(paths, time,
19 | tolerance, override, callback) {
20 | async.map(paths, fs.stat, function(err, stats) {
21 | if (err) {
22 | return callback(err);
23 | }
24 |
25 | var olderPaths = [];
26 | var newerPaths = paths.filter(function(filePath, index) {
27 | var newer = stats[index].mtime - time > tolerance;
28 | if (!newer) {
29 | olderPaths.push(filePath);
30 | }
31 | return newer;
32 | });
33 |
34 | async.filter(olderPaths, function(filePath, include) {
35 | override(filePath, time, include);
36 | }, function(overrides) {
37 | callback(null, newerPaths.concat(overrides));
38 | });
39 | });
40 | };
41 |
42 |
43 | /**
44 | * Determine if any of the given files are newer than the provided time.
45 | * @param {Array.} paths List of file paths.
46 | * @param {Date} time The comparison time.
47 | * @param {number} tolerance Maximum time in milliseconds that the destination
48 | * file is allowed to be newer than the source file to compensate for
49 | * imprecisions in modification times in file systems.
50 | * @param {function(string, Date, function(boolean))} override Override.
51 | * @param {function(Err, boolean)} callback Callback called with any error and
52 | * a boolean indicating whether any one of the supplied files is newer than
53 | * the comparison time.
54 | */
55 | var anyNewer = exports.anyNewer = function(paths, time, tolerance, override,
56 | callback) {
57 | if (paths.length === 0) {
58 | process.nextTick(function() {
59 | callback(null, false);
60 | });
61 | return;
62 | }
63 | var complete = 0;
64 | function iterate() {
65 | fs.stat(paths[complete], function(err, stats) {
66 | if (err) {
67 | return callback(err);
68 | }
69 |
70 | var pathTime = stats.mtime.getTime();
71 | var comparisonTime = time.getTime();
72 | var difference = pathTime - comparisonTime;
73 |
74 | if (difference > tolerance) {
75 | return callback(null, true);
76 | } else {
77 | override(paths[complete], time, function(include) {
78 | if (include) {
79 | callback(null, true);
80 | } else {
81 | ++complete;
82 | if (complete >= paths.length) {
83 | return callback(null, false);
84 | }
85 | iterate();
86 | }
87 | });
88 | }
89 | });
90 | }
91 | iterate();
92 | };
93 |
94 |
95 | /**
96 | * Filter a list of file config objects by time. Source files on the provided
97 | * objects are removed if they have not been modified since the provided time
98 | * or any dest file mtime for a dest file on the same object.
99 | * @param {Array.