Gruntfile.js 16.9 KB
Newer Older
Chris Rebert's avatar
Chris Rebert committed
1
2
3
/*!
 * Bootstrap's Gruntfile
 * http://getbootstrap.com
XhmikosR's avatar
XhmikosR committed
4
 * Copyright 2013-2015 Twitter, Inc.
Chris Rebert's avatar
Chris Rebert committed
5
6
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 */
7

Chris Rebert's avatar
Chris Rebert committed
8
module.exports = function (grunt) {
XhmikosR's avatar
XhmikosR committed
9
  'use strict';
10

11
12
13
  // Force use of Unix newlines
  grunt.util.linefeed = '\n';

Zlatan Vasović's avatar
Zlatan Vasović committed
14
  RegExp.quote = function (string) {
Chris Rebert's avatar
Chris Rebert committed
15
16
    return string.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
  };
17

Chris Rebert's avatar
Chris Rebert committed
18
  var fs = require('fs');
19
  var path = require('path');
20
  var glob = require('glob');
Chris Rebert's avatar
Chris Rebert committed
21
  var isTravis = require('is-travis');
22
  var npmShrinkwrap = require('npm-shrinkwrap');
23
  var mq4HoverShim = require('mq4-hover-shim');
24
25
  var autoprefixer = require('autoprefixer')({
    browsers: [
26
27
28
29
30
31
32
33
34
35
36
37
38
39
      //
      // Official browser support policy:
      // http://v4-alpha.getbootstrap.com/getting-started/browsers-devices/#supported-browsers
      //
      'Chrome >= 35', // Exact version number here is kinda arbitrary
      // Rather than using Autoprefixer's native "Firefox ESR" version specifier string,
      // we deliberately hardcode the number. This is to avoid unwittingly severely breaking the previous ESR in the event that:
      // (a) we happen to ship a new Bootstrap release soon after the release of a new ESR,
      //     such that folks haven't yet had a reasonable amount of time to upgrade; and
      // (b) the new ESR has unprefixed CSS properties/values whose absence would severely break webpages
      //     (e.g. `box-sizing`, as opposed to `background: linear-gradient(...)`).
      //     Since they've been unprefixed, Autoprefixer will stop prefixing them,
      //     thus causing them to not work in the previous ESR (where the prefixes were required).
      'Firefox >= 31', // Current Firefox Extended Support Release (ESR)
40
41
42
43
44
45
      // Note: Edge versions in Autoprefixer & Can I Use refer to the EdgeHTML rendering engine version,
      // NOT the Edge app version shown in Edge's "About" screen.
      // For example, at the time of writing, Edge 20 on an up-to-date system uses EdgeHTML 12.
      // See also https://github.com/Fyrd/caniuse/issues/1928
      'Edge >= 12',
      'Explorer >= 9',
46
      // Out of leniency, we prefix these 1 version further back than the official policy.
47
      'iOS >= 8',
48
      'Safari >= 8',
49
50
51
52
      // The following remain NOT officially supported, but we're lenient and include their prefixes to avoid severely breaking in them.
      'Android 2.3',
      'Android >= 4',
      'Opera >= 12'
53
54
    ]
  });
Mark Otto's avatar
Mark Otto committed
55

56
  var generateCommonJSModule = require('./grunt/bs-commonjs-generator.js');
57
58
59
60
61
62
63
  var configBridge = grunt.file.readJSON('./grunt/configBridge.json', { encoding: 'utf8' });

  Object.keys(configBridge.paths).forEach(function (key) {
    configBridge.paths[key].forEach(function (val, i, arr) {
      arr[i] = path.join('./docs/assets', val);
    });
  });
64

65
66
67
68
69
  // Project configuration.
  grunt.initConfig({

    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
70
    banner: '/*!\n' +
XhmikosR's avatar
XhmikosR committed
71
72
            ' * Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' +
            ' * Copyright 2011-<%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
73
            ' * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n' +
XhmikosR's avatar
XhmikosR committed
74
            ' */\n',
75
76
77
78
79
    jqueryCheck: 'if (typeof jQuery === \'undefined\') {\n' +
                 '  throw new Error(\'Bootstrap\\\'s JavaScript requires jQuery\')\n' +
                 '}\n',
    jqueryVersionCheck: '+function ($) {\n' +
                        '  var version = $.fn.jquery.split(\' \')[0].split(\'.\')\n' +
Chris Rebert's avatar
Chris Rebert committed
80
81
                        '  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] >= 3)) {\n' +
                        '    throw new Error(\'Bootstrap\\\'s JavaScript requires at least jQuery v1.9.1 but less than v3.0.0\')\n' +
82
83
                        '  }\n' +
                        '}(jQuery);\n\n',
84
85
86

    // Task configuration.
    clean: {
87
88
      dist: 'dist',
      docs: 'docs/dist'
89
90
    },

91
92
93
94
95
96
    // JS build configuration
    lineremover: {
      es6Import: {
        files: {
          '<%= concat.bootstrap.dest %>': '<%= concat.bootstrap.dest %>'
        },
97
        options: {
98
99
100
101
102
          exclusionPattern: /^(import|export)/g
        }
      }
    },

fat's avatar
fat committed
103
    babel: {
fat's avatar
fat committed
104
      dev: {
105
106
107
        options: {
          sourceMap: true,
          modules: 'ignore'
108
        },
fat's avatar
fat committed
109
        files: {
fat's avatar
fat committed
110
111
112
113
114
115
116
          'js/dist/util.js'      : 'js/src/util.js',
          'js/dist/alert.js'     : 'js/src/alert.js',
          'js/dist/button.js'    : 'js/src/button.js',
          'js/dist/carousel.js'  : 'js/src/carousel.js',
          'js/dist/collapse.js'  : 'js/src/collapse.js',
          'js/dist/dropdown.js'  : 'js/src/dropdown.js',
          'js/dist/modal.js'     : 'js/src/modal.js',
fat's avatar
tab es6    
fat committed
117
          'js/dist/scrollspy.js' : 'js/src/scrollspy.js',
118
          'js/dist/tab.js'       : 'js/src/tab.js',
fat's avatar
fat committed
119
120
          'js/dist/tooltip.js'   : 'js/src/tooltip.js',
          'js/dist/popover.js'   : 'js/src/popover.js'
fat's avatar
fat committed
121
        }
122
      },
123
      dist: {
XhmikosR's avatar
XhmikosR committed
124
        options: {
125
          modules: 'ignore'
XhmikosR's avatar
XhmikosR committed
126
        },
127
128
129
        files: {
          '<%= concat.bootstrap.dest %>' : '<%= concat.bootstrap.dest %>'
        }
130
      },
fat's avatar
fat committed
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
      umd: {
        options: {
          modules: 'umd'
        },
        files: {
          'dist/js/umd/util.js'      : 'js/src/util.js',
          'dist/js/umd/alert.js'     : 'js/src/alert.js',
          'dist/js/umd/button.js'    : 'js/src/button.js',
          'dist/js/umd/carousel.js'  : 'js/src/carousel.js',
          'dist/js/umd/collapse.js'  : 'js/src/collapse.js',
          'dist/js/umd/dropdown.js'  : 'js/src/dropdown.js',
          'dist/js/umd/modal.js'     : 'js/src/modal.js',
          'dist/js/umd/scrollspy.js' : 'js/src/scrollspy.js',
          'dist/js/umd/tab.js'       : 'js/src/tab.js',
          'dist/js/umd/tooltip.js'   : 'js/src/tooltip.js',
          'dist/js/umd/popover.js'   : 'js/src/popover.js'
        }
148
149
      }
    },
150

Jacob Thornton's avatar
Jacob Thornton committed
151
152
153
154
155
156
157
    eslint: {
      options: {
        configFile: 'js/.eslintrc'
      },
      target: 'js/src/*.js'
    },

Chris Rebert's avatar
Chris Rebert committed
158
159
    jscs: {
      options: {
XhmikosR's avatar
XhmikosR committed
160
        config: 'js/.jscsrc'
Chris Rebert's avatar
Chris Rebert committed
161
      },
Chris Rebert's avatar
Chris Rebert committed
162
      grunt: {
fat's avatar
fat committed
163
        src: ['Gruntfile.js', 'grunt/*.js']
Chris Rebert's avatar
Chris Rebert committed
164
      },
165
      core: {
fat's avatar
fat committed
166
        src: 'js/src/*.js'
Chris Rebert's avatar
Chris Rebert committed
167
168
      },
      test: {
fat's avatar
fat committed
169
        src: 'js/tests/unit/*.js'
170
171
      },
      assets: {
172
173
174
        options: {
          requireCamelCaseOrUpperCaseIdentifiers: null
        },
fat's avatar
fat committed
175
        src: ['docs/assets/js/src/*.js', 'docs/assets/js/*.js', '!docs/assets/js/*.min.js']
Chris Rebert's avatar
Chris Rebert committed
176
177
178
      }
    },

179
    stamp: {
180
      options: {
181
182
        banner: '<%= banner %>\n<%= jqueryCheck %>\n<%= jqueryVersionCheck %>\n+function ($) {\n',
        footer: '\n}(jQuery);'
183
184
      },
      bootstrap: {
185
186
187
        files: {
          src: '<%= concat.bootstrap.dest %>'
        }
188
189
      }
    },
190

191
    concat: {
192
      options: {
193
        stripBanners: false
194
      },
195
      bootstrap: {
fat's avatar
fat committed
196
        src: [
197
198
199
200
201
202
203
204
205
206
207
          'js/src/util.js',
          'js/src/alert.js',
          'js/src/button.js',
          'js/src/carousel.js',
          'js/src/collapse.js',
          'js/src/dropdown.js',
          'js/src/modal.js',
          'js/src/scrollspy.js',
          'js/src/tab.js',
          'js/src/tooltip.js',
          'js/src/popover.js'
fat's avatar
fat committed
208
        ],
209
        dest: 'dist/js/<%= pkg.name %>.js'
fat's avatar
fat committed
210
211
212
213
214
      }
    },

    uglify: {
      options: {
fat's avatar
fat committed
215
216
        compress: {
          warnings: false
fat's avatar
fat committed
217
        },
fat's avatar
fat committed
218
        mangle: true,
XhmikosR's avatar
XhmikosR committed
219
        preserveComments: /^!|@preserve|@license|@cc_on/i
XhmikosR's avatar
XhmikosR committed
220
      },
fat's avatar
fat committed
221
222
      core: {
        src: '<%= concat.bootstrap.dest %>',
223
        dest: 'dist/js/<%= pkg.name %>.min.js'
fat's avatar
fat committed
224
      },
225
      docsJs: {
226
        src: configBridge.paths.docsJs,
227
        dest: 'docs/assets/js/docs.min.js'
228
229
230
      }
    },

Mark Otto's avatar
Mark Otto committed
231
232
233
234
235
236
237
    qunit: {
      options: {
        inject: 'js/tests/unit/phantom.js'
      },
      files: 'js/tests/index.html'
    },

238
    // CSS build configuration
Chris Rebert's avatar
Chris Rebert committed
239
240
    scsslint: {
      options: {
241
        bundleExec: true,
nextgenthemes's avatar
nextgenthemes committed
242
        config: 'scss/.scss-lint.yml',
Mark Otto's avatar
Mark Otto committed
243
244
245
        reporterOutput: null
      },
      src: ['scss/*.scss', '!scss/_normalize.scss']
Chris Rebert's avatar
Chris Rebert committed
246
247
    },

Chris Rebert's avatar
Chris Rebert committed
248
    postcss: {
Bas Bosman's avatar
Bas Bosman committed
249
250
      core: {
        options: {
251
252
253
254
255
          map: true,
          processors: [
            mq4HoverShim.postprocessorFor({ hoverSelectorPrefix: '.bs-true-hover ' }),
            autoprefixer
          ]
Bas Bosman's avatar
Bas Bosman committed
256
        },
257
        src: 'dist/css/*.css'
Bas Bosman's avatar
Bas Bosman committed
258
259
      },
      docs: {
260
261
262
263
264
        options: {
          processors: [
            autoprefixer
          ]
        },
Mark Otto's avatar
Mark Otto committed
265
        src: 'docs/assets/css/docs.min.css'
Bas Bosman's avatar
Bas Bosman committed
266
267
      },
      examples: {
268
269
270
271
272
        options: {
          processors: [
            autoprefixer
          ]
        },
Bas Bosman's avatar
Bas Bosman committed
273
274
275
276
277
278
279
        expand: true,
        cwd: 'docs/examples/',
        src: ['**/*.css'],
        dest: 'docs/examples/'
      }
    },

XhmikosR's avatar
XhmikosR committed
280
    cssmin: {
XhmikosR's avatar
XhmikosR committed
281
      options: {
282
283
        // TODO: disable `zeroUnits` optimization once clean-css 3.2 is released
        //    and then simplify the fix for https://github.com/twbs/bootstrap/issues/14837 accordingly
284
        compatibility: 'ie9',
285
        keepSpecialComments: '*',
286
        sourceMap: true,
287
        noAdvanced: true
XhmikosR's avatar
XhmikosR committed
288
      },
Mark Otto's avatar
Mark Otto committed
289
      core: {
290
291
292
293
294
295
296
297
298
        files: [
          {
            expand: true,
            cwd: 'dist/css',
            src: ['*.css', '!*.min.css'],
            dest: 'dist/css',
            ext: '.min.css'
          }
        ]
299
      },
XhmikosR's avatar
XhmikosR committed
300
      docs: {
Mark Otto's avatar
Mark Otto committed
301
        src: 'docs/assets/css/docs.min.css',
302
        dest: 'docs/assets/css/docs.min.css'
XhmikosR's avatar
XhmikosR committed
303
304
305
      }
    },

306
    csscomb: {
307
      options: {
Mark Otto's avatar
Mark Otto committed
308
        config: 'scss/.csscomb.json'
309
310
      },
      dist: {
XhmikosR's avatar
XhmikosR committed
311
312
313
314
        expand: true,
        cwd: 'dist/css/',
        src: ['*.css', '!*.min.css'],
        dest: 'dist/css/'
315
316
      },
      examples: {
Chris Rebert's avatar
Chris Rebert committed
317
318
        expand: true,
        cwd: 'docs/examples/',
Zlatan Vasović's avatar
Zlatan Vasović committed
319
        src: '**/*.css',
Chris Rebert's avatar
Chris Rebert committed
320
        dest: 'docs/examples/'
XhmikosR's avatar
XhmikosR committed
321
322
      },
      docs: {
323
324
        src: 'docs/assets/css/src/docs.css',
        dest: 'docs/assets/css/src/docs.css'
325
326
327
      }
    },

Mark Otto's avatar
Mark Otto committed
328
    copy: {
Mark Otto's avatar
Mark Otto committed
329
      docs: {
XhmikosR's avatar
XhmikosR committed
330
331
332
333
334
335
        expand: true,
        cwd: 'dist/',
        src: [
          '**/*'
        ],
        dest: 'docs/dist/'
Mark Otto's avatar
Mark Otto committed
336
337
338
      }
    },

339
340
341
342
343
    connect: {
      server: {
        options: {
          port: 3000,
          base: '.'
344
        }
345
346
347
      }
    },

348
    jekyll: {
349
      options: {
XhmikosR's avatar
XhmikosR committed
350
        bundleExec: true,
XhmikosR's avatar
XhmikosR committed
351
352
        config: '_config.yml',
        incremental: false
353
354
355
356
357
358
359
      },
      docs: {},
      github: {
        options: {
          raw: 'github: true'
        }
      }
360
361
    },

362
    htmllint: {
363
      options: {
364
365
        ignore: [
          'Element “img” is missing required attribute “src”.',
366
          'Attribute “autocomplete” is only allowed when the input type is “color”, “date”, “datetime”, “datetime-local”, “email”, “month”, “number”, “password”, “range”, “search”, “tel”, “text”, “time”, “url”, or “week”.',
367
368
          'Attribute “autocomplete” not allowed on element “button” at this point.',
          'Element “div” not allowed as child of element “progress” in this context. (Suppressing further errors from this subtree.)',
369
370
          'Consider using the “h1” element as a top-level heading only (all “h1” elements are treated as top-level headings by many screen readers and other tools).',
          'The “datetime” input type is not supported in all browsers. Please be sure to test, and consider using a polyfill.'
371
        ]
372
      },
373
      src: ['_gh_pages/**/*.html', 'js/tests/visual/*.html']
374
375
    },

376
377
    watch: {
      src: {
fat's avatar
fat committed
378
        files: '<%= jscs.core.src %>',
fat's avatar
fat committed
379
        tasks: ['babel:dev']
380
      },
381
382
      sass: {
        files: 'scss/**/*.scss',
Mark Otto's avatar
Mark Otto committed
383
        tasks: ['dist-css', 'docs']
Mark Otto's avatar
Mark Otto committed
384
385
386
387
      },
      docs: {
        files: 'docs/assets/scss/**/*.scss',
        tasks: ['dist-css', 'docs']
388
      }
389
390
    },

391
392
393
394
    'saucelabs-qunit': {
      all: {
        options: {
          build: process.env.TRAVIS_JOB_ID,
Chris Rebert's avatar
Chris Rebert committed
395
          concurrency: 10,
396
          maxRetries: 3,
397
          maxPollRetries: 4,
398
          urls: ['http://127.0.0.1:3000/js/tests/index.html?hidepassed'],
399
          browsers: grunt.file.readYAML('grunt/sauce_browsers.yml')
400
401
        }
      }
Chris Rebert's avatar
Chris Rebert committed
402
403
404
405
406
    },

    exec: {
      npmUpdate: {
        command: 'npm update'
Mark Otto's avatar
Mark Otto committed
407
      }
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
    },

    buildcontrol: {
      options: {
        dir: '_gh_pages',
        commit: true,
        push: true,
        message: 'Built %sourceName% from commit %sourceCommit% on branch %sourceBranch%'
      },
      pages: {
        options: {
          remote: 'git@github.com:twbs/derpstrap.git',
          branch: 'gh-pages'
        }
      }
XhmikosR's avatar
XhmikosR committed
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
    },

    compress: {
      main: {
        options: {
          archive: 'bootstrap-<%= pkg.version %>-dist.zip',
          mode: 'zip',
          level: 9,
          pretty: true
        },
        files: [
          {
            expand: true,
            cwd: 'dist/',
            src: ['**'],
            dest: 'bootstrap-<%= pkg.version %>-dist'
          }
        ]
      }
442
    }
XhmikosR's avatar
XhmikosR committed
443

444
  });
445
446


447
  // These plugins provide necessary tasks.
448
449
450
  require('load-grunt-tasks')(grunt, { scope: 'devDependencies',
    // Exclude Sass compilers. We choose the one to load later on.
    pattern: ['grunt-*', '!grunt-sass', '!grunt-contrib-sass'] });
XhmikosR's avatar
XhmikosR committed
451
  require('time-grunt')(grunt);
452

453
  // Docs HTML validation task
XhmikosR's avatar
XhmikosR committed
454
  grunt.registerTask('validate-html', ['jekyll:docs', 'htmllint']);
455

456
457
458
  var runSubset = function (subset) {
    return !process.env.TWBS_TEST || process.env.TWBS_TEST === subset;
  };
459
460
461
  var isUndefOrNonZero = function (val) {
    return val === undefined || val !== '0';
  };
462

463
  // Test task.
464
465
  var testSubtasks = [];
  // Skip core tests if running a different subset of the test suite
466
  if (runSubset('core') &&
Mark Otto's avatar
Mark Otto committed
467
    // Skip core tests if this is a Savage build
468
469
    process.env.TRAVIS_REPO_SLUG !== 'twbs-savage/bootstrap') {
    testSubtasks = testSubtasks.concat(['dist-css', 'dist-js', 'test-scss', 'test-js', 'docs']);
470
471
  }
  // Skip HTML validation if running a different subset of the test suite
472
  if (runSubset('validate-html') &&
Chris Rebert's avatar
Chris Rebert committed
473
474
      isTravis &&
      // Skip HTML5 validator when [skip validator] is in the commit message
475
      isUndefOrNonZero(process.env.TWBS_DO_VALIDATOR)) {
476
477
    testSubtasks.push('validate-html');
  }
478
  // Only run Sauce Labs tests if there's a Sauce access key
Chris Rebert's avatar
Chris Rebert committed
479
  if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined' &&
480
      // Skip Sauce if running a different subset of the test suite
481
482
483
      runSubset('sauce-js-unit') &&
      // Skip Sauce on Travis when [skip sauce] is in the commit message
      isUndefOrNonZero(process.env.TWBS_DO_SAUCE)) {
484
    testSubtasks.push('babel:dev');
485
486
    testSubtasks.push('connect');
    testSubtasks.push('saucelabs-qunit');
487
488
  }
  grunt.registerTask('test', testSubtasks);
Jacob Thornton's avatar
Jacob Thornton committed
489
  grunt.registerTask('test-js', ['eslint', 'jscs:core', 'jscs:test', 'jscs:grunt', 'qunit']);
490

491
  // JS distribution task.
492
  grunt.registerTask('dist-js', ['babel:dev', 'concat', 'lineremover', 'babel:dist', 'stamp', 'uglify:core', 'commonjs']);
493

Mark Otto's avatar
Mark Otto committed
494
  grunt.registerTask('test-scss', ['scsslint']);
Chris Rebert's avatar
Chris Rebert committed
495

496
  // CSS distribution task.
497
498
499
500
  // Supported Compilers: sass (Ruby) and libsass.
  (function (sassCompilerName) {
    require('./grunt/bs-sass-compile/' + sassCompilerName + '.js')(grunt);
  })(process.env.TWBS_SASS || 'libsass');
501
502
  // grunt.registerTask('sass-compile', ['sass:core', 'sass:extras', 'sass:docs']);
  grunt.registerTask('sass-compile', ['sass:core', 'sass:docs']);
503

504
  grunt.registerTask('dist-css', ['sass-compile', 'postcss:core', 'csscomb:dist', 'cssmin:core', 'cssmin:docs']);
Mark Otto's avatar
Mark Otto committed
505

506
  // Full distribution task.
Mark Otto's avatar
Mark Otto committed
507
  grunt.registerTask('dist', ['clean:dist', 'dist-css', 'dist-js']);
508

509
  // Default task.
Mark Otto's avatar
Mark Otto committed
510
  grunt.registerTask('default', ['clean:dist', 'test']);
511

fat's avatar
fat committed
512
513
514
515
516
517
  grunt.registerTask('commonjs', ['babel:umd', 'npm-js']);

  grunt.registerTask('npm-js', 'Generate npm-js entrypoint module in dist dir.', function () {
    var srcFiles = Object.keys(grunt.config.get('babel.umd.files')).map(function (filename) {
      return './' + path.join('umd', path.basename(filename))
    })
518
519
    var destFilepath = 'dist/js/npm.js';
    generateCommonJSModule(grunt, srcFiles, destFilepath);
520
521
  });

522
  // Docs task.
523
  grunt.registerTask('docs-css', ['postcss:docs', 'postcss:examples', 'csscomb:docs', 'csscomb:examples', 'cssmin:docs']);
Mark Otto's avatar
Mark Otto committed
524
  grunt.registerTask('docs-js', ['uglify:docsJs']);
fat's avatar
fat committed
525
  grunt.registerTask('lint-docs-js', ['jscs:assets']);
Mark Otto's avatar
Mark Otto committed
526
  grunt.registerTask('docs', ['docs-css', 'docs-js', 'lint-docs-js', 'clean:docs', 'copy:docs']);
527
  grunt.registerTask('docs-github', ['jekyll:github', 'htmlmin']);
528

529
  grunt.registerTask('prep-release', ['dist', 'docs', 'docs-github', 'compress']);
530

Mark Otto's avatar
Mark Otto committed
531
532
533
  // Publish to GitHub
  grunt.registerTask('publish', ['buildcontrol:pages']);

534
535
536
537
538
539
540
  // Task for updating the cached npm packages used by the Travis build (which are controlled by test-infra/npm-shrinkwrap.json).
  // This task should be run and the updated file should be committed whenever Bootstrap's dependencies change.
  grunt.registerTask('update-shrinkwrap', ['exec:npmUpdate', '_update-shrinkwrap']);
  grunt.registerTask('_update-shrinkwrap', function () {
    var done = this.async();
    npmShrinkwrap({ dev: true, dirname: __dirname }, function (err) {
      if (err) {
541
        grunt.fail.warn(err);
542
      }
543
      var dest = 'grunt/npm-shrinkwrap.json';
544
545
546
547
548
      fs.renameSync('npm-shrinkwrap.json', dest);
      grunt.log.writeln('File ' + dest.cyan + ' updated.');
      done();
    });
  });
549
};