diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 000000000..8c18257f8
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,4 @@
+{
+ "extends": "grunt",
+ "root": true
+}
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..222ba621b
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+* text=auto
+/test/fixtures/*.txt text eol=lf
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 000000000..38725bf86
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,39 @@
+name: Tests
+
+on: [push, pull_request]
+
+env:
+ FORCE_COLOR: 2
+
+jobs:
+ run:
+ name: Node ${{ matrix.node }} on ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ node: [16, 18, 19]
+ os: [ubuntu-latest, windows-latest]
+
+ steps:
+ - name: Clone repository
+ uses: actions/checkout@v2
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v2
+ with:
+ node-version: ${{ matrix.node }}
+
+ - name: Install npm dependencies
+ run: npm i
+
+ - name: Run tests
+ run: npm test
+
+ # We test multiple Windows shells because of prior stdout buffering issues
+ # filed against Grunt. https://github.com/joyent/node/issues/3584
+ - name: Run PowerShell tests
+ run: "npm test # PowerShell" # Pass comment to PS for easier debugging
+ shell: powershell
+ if: startsWith(matrix.os, 'windows')
diff --git a/.gitignore b/.gitignore
index 032c58e0c..6024b19ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
node_modules
.npm-debug.log
+package-lock.json
tmp
diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index 5032ab276..000000000
--- a/.npmignore
+++ /dev/null
@@ -1,7 +0,0 @@
-docs
-test
-.travis.yml
-AUTHORS
-CHANGELOG
-custom-gruntfile.js
-Gruntfile.js
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index ba38dc02b..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-language: node_js
-node_js:
- - "0.8"
- - "0.10"
- - "0.11"
-before_script:
- - npm install -g grunt-cli
- - npm uninstall grunt # https://github.com/npm/npm/issues/3958
-matrix:
- fast_finish: true
- allow_failures:
- - node_js: "0.11"
diff --git a/CHANGELOG b/CHANGELOG
index bf6896bab..78f9d6bb9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,139 @@
+v1.6.1
+ date: 2023-01-31
+ changes:
+ - Downgrades to glob 7 for Windows compatability
+ - Removes mkdirp and rimraf in favour of node.js APIs.
+v1.6.0
+ date: 2023-01-28
+ changes:
+ - Requires node.js 16+.
+ - template.date now uses dateformat ~4.6.2.
+ - other dependency updates such as glob, rimraf, etc.
+v1.5.3
+ date: 2022-04-23
+ changes:
+ - Patch up race condition in symlink copying.
+v1.5.2
+ date: 2022-04-12
+ changes:
+ - Unlink symlinks when copy destination is a symlink.
+v1.5.1
+ date: 2022-04-11
+ changes:
+ - Fixed symlink destination handling.
+v1.5.0
+ date: 2022-04-10
+ changes:
+ - Updated dependencies.
+ - Add symlink handling for copying files.
+v1.4.1
+ date: 2021-05-24
+ changes:
+ - Fix --preload option to be a known option
+ - Switch to GitHub Actions
+v1.4.0
+ date: 2021-04-21
+ changes:
+ - Security fixes in production and dev dependencies
+ - Liftup/Liftoff upgrade breaking change. Update your scripts to use --preload instead of --require. Ref: https://github.com/js-cli/js-liftoff/commit/e7a969d6706e730d90abb4e24d3cb4d3bce06ddb.
+v1.3.0
+ date: 2020-08-18
+ changes:
+ - Switch to use `safeLoad` for loading YML files via `file.readYAML`.
+ - Upgrade legacy-log to ~3.0.0.
+ - Upgrade legacy-util to ~2.0.0.
+v1.2.1
+ date: 2020-07-07
+ changes:
+ - Remove path-is-absolute dependency.
+ (PR: https://github.com/gruntjs/grunt/pull/1715)
+v1.2.0
+ date: 2020-07-03
+ changes:
+ - Allow usage of grunt plugins that are located in any location that
+ is visible to Node.js and NPM, instead of node_modules directly
+ inside package that have a dev dependency to these plugins.
+ (PR: https://github.com/gruntjs/grunt/pull/1677)
+ - Removed coffeescript from dependencies. To ease transition, if
+ coffeescript is still around, Grunt will attempt to load it.
+ If it is not, and the user loads a CoffeeScript file,
+ Grunt will print a useful error indicating that the
+ coffeescript package should be installed as a dev dependency.
+ This is considerably more user-friendly than dropping the require entirely,
+ but doing so is feasible with the latest grunt-cli as users
+ may simply use grunt --require coffeescript/register.
+ (PR: https://github.com/gruntjs/grunt/pull/1675)
+ - Exposes Grunt Option keys for ease of use.
+ (PR: https://github.com/gruntjs/grunt/pull/1570)
+ - Avoiding infinite loop on very long command names.
+ (PR: https://github.com/gruntjs/grunt/pull/1697)
+v1.1.0
+ date: 2020-03-16
+ changes:
+ - Update to mkdirp ~1.0.3
+ - Only support versions of Node >= 8
+v1.0.4
+ date: 2019-04-22
+ changes:
+ - Update js-yaml to address https://npmjs.com/advisories/788
+ - Use SOURCE_DATE_EPOCH to render dates in template.
+v1.0.3
+ date: 2018-06-03
+ changes:
+ - Drop support for Node 0.10 and 0.12.
+ - Dependency updates: rimraf, grunt-legacy-log, grunt-legacy-util.
+ - Fix race condition with file.mkdir.
+v1.0.2
+ date: 2018-02-07
+ changes:
+ - Fix for readYAML error messages.
+ - Remove deprecation warning for coffeescript. Pull #1621.
+v1.0.1
+ date: 2016-04-05
+ changes:
+ - minor fix for npm issues when installing grunt and grunt-cli at the same time. Pull #1500.
+v1.0.0
+ date: 2016-04-04
+ changes:
+ - full list of changes is on http://gruntjs.com, please also see changes from 1.0.0-rc1.
+ - if you have a Grunt plugin that includes `grunt` in the `peerDependencies`,
+ we recommend tagging with `"grunt": "">= 0.4.0"` and publishing a new version on npm.
+ - Prevent async callback from being called multiple times. Pull #1464.
+ - Update copyright to jQuery Foundation and remove redundant headers. Fixes #1478.
+ - Update glob to 7.0.x. Fixes #1467.
+ - Removing duplicate BOM strip code. Pull #1482.
+ - Update legacy log and util to 1.0.0.
+ - Update to latest cli ~1.2.0.
+ - Use grunt-known-options for shared options between Grunt and grunt-cli.
+v1.0.0-rc1
+ date: 2016-02-11
+ changes:
+ - full list of changes is on http://gruntjs.com
+ - if you have a Grunt plugin that includes `grunt` in the `peerDependencies`,
+ we recommend tagging with `"grunt": "">= 0.4.0"`
+ - `coffee-script` is upgraded to `~1.10.0` which could incur breaking changes
+ when using the language with plugins and Gruntfiles.
+ - `nopt` is upgraded to `~3.0.6` which has fixed many issues, including passing
+ multiple arguments and dealing with numbers as options. Be aware previously
+ `--foo bar` used to pass the value `'bar'` to the option `foo`. It will now
+ set the option `foo` to `true` and run the task `bar`.
+ -`glob` is upgraded to `~6.0.4` and `minimatch` is upgraded to `~3.0.0`. Results
+ are now sorted by default with `grunt.file.expandMapping()`. Pass the
+ `nosort: true` option if you don't want the results to be sorted.
+ - `lodash` was upgraded to `~4.3.0`. Many changes have occurred. Some of which
+ that directly effect Grunt are `grunt.util._.template()` returns a compile
+ function and `grunt.util._.flatten` no longer flattens deeply.
+ `grunt.util._` is deprecated and we highly encourage you to
+ `npm install lodash` and `var _ = require('lodash')` to use `lodash`.
+ Please see the lodash changelog for a full list of changes: https://github.com/lodash/lodash/wiki/Changelog
+ - `iconv-lite` is upgraded to `~0.4.13` and strips the BOM by default.
+ - `js-yaml` is upgraded to `~3.5.2` and may affect `grunt.file.readYAML`.
+ We encourage you to please `npm install js-yaml` and use
+ `var YAML = require('js-yaml')` directly in case of future deprecations.
+ - A file `mode` option can be passed into
+ [grunt.file.write()](http://gruntjs.com/api/grunt.file#grunt.file.write).
+ - `Done, without errors.` was changed to `Done.` to avoid failing by mistake
+ on the word `errors`.
v0.4.5:
date: 2014-05-12
changes:
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..919d3d703
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,114 @@
+# Code of Conduct
+
+Grunt, The OpenJS Foundation and its member projects use [Contributor Covenant v2.0](https://contributor-covenant.org/version/2/0/code_of_conduct) as their code of conduct. The full text is included [below](#contributor-covenant-code-of-conduct) in English, and translations are available from the Contributor Covenant organisation:
+
+- [contributor-covenant.org/translations](https://www.contributor-covenant.org/translations)
+- [github.com/ContributorCovenant](https://github.com/ContributorCovenant/contributor_covenant/tree/release/content/version/2/0)
+
+Refer to the sections on reporting and escalation in this document for the specific emails that can be used to report and escalate issues.
+
+## Reporting
+
+### Project Spaces
+
+For reporting issues in spaces related to a member project please use the email provided by the project for reporting. Projects handle CoC issues related to the spaces that they maintain. Projects maintainers commit to:
+
+- maintain the confidentiality with regard to the reporter of an incident
+- to participate in the path for escalation as outlined in
+ the section on Escalation when required.
+
+### Foundation Spaces
+
+For reporting issues in spaces managed by the OpenJS Foundation, for example, repositories within the OpenJS organization, use the email `report@lists.openjsf.org`. The Cross Project Council (CPC) is responsible for managing these reports and commits to:
+
+- maintain the confidentiality with regard to the reporter of an incident
+- to participate in the path for escalation as outlined in
+ the section on Escalation when required.
+
+## Escalation
+
+The OpenJS Foundation maintains a Code of Conduct Panel (CoCP). This is a foundation-wide team established to manage escalation when a reporter believes that a report to a member project or the CPC has not been properly handled. In order to escalate to the CoCP send an email to `coc-escalation@lists.openjsf.org`.
+
+For more information, refer to the full
+[Code of Conduct governance document](https://github.com/openjs-foundation/bootstrap/blob/master/proposals/stage-2/CODE_OF_CONDUCT/FOUNDATION_CODE_OF_CONDUCT_REQUIREMENTS.md).
+
+---
+
+## Contributor Covenant Code of Conduct v2.0
+
+### Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
+
+### Our Standards
+
+Examples of behavior that contributes to a positive environment for our community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+### Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
+
+### Scope
+
+This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
+
+### Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at the email addresses listed above in the [Reporting](#reporting) and [Escalation](#escalation) sections. All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the reporter of any incident.
+
+### Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
+
+#### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
+
+#### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
+
+#### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
+
+#### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the project community.
+
+### Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.0, available at [contributor-covenant.org/version/2/0/code_of_conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct).
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
+
+For answers to common questions about this code of conduct, see the FAQ at
+[contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at [contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).
diff --git a/Gruntfile.js b/Gruntfile.js
index 7b011838f..8cfc6470e 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
'use strict';
module.exports = function(grunt) {
@@ -14,39 +5,32 @@ module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
nodeunit: {
- all: ['test/{grunt,tasks,util}/**/*.js']
- },
- jshint: {
- gruntfile_tasks: ['Gruntfile.js', 'internal-tasks/*.js'],
- libs_n_tests: ['lib/**/*.js', '<%= nodeunit.all %>'],
- subgrunt: ['<%= subgrunt.all %>'],
- options: {
- curly: true,
- eqeqeq: true,
- immed: true,
- latedef: 'nofunc',
- newcap: true,
- noarg: true,
- sub: true,
- undef: true,
- unused: true,
- boss: true,
- eqnull: true,
- node: true,
+ all: ['test/{grunt,tasks,util}/**/*.js'],
+ tap: {
+ src: '<%= nodeunit.all %>',
+ options: {
+ reporter: 'tap',
+ reporterOutput: 'tests.tap'
+ }
}
},
+ eslint: {
+ gruntfileTasks: ['Gruntfile.js', 'internal-tasks/*.js'],
+ libsAndTests: ['lib/**/*.js', '<%= nodeunit.all %>'],
+ subgrunt: ['<%= subgrunt.all %>']
+ },
watch: {
- gruntfile_tasks: {
- files: ['<%= jshint.gruntfile_tasks %>'],
- tasks: ['jshint:gruntfile_tasks']
+ gruntfileTasks: {
+ files: ['<%= eslint.gruntfileTasks %>'],
+ tasks: ['eslint:gruntfileTasks']
},
- libs_n_tests: {
- files: ['<%= jshint.libs_n_tests %>'],
- tasks: ['jshint:libs_n_tests', 'nodeunit']
+ libsAndTests: {
+ files: ['<%= eslint.libsAndTests %>'],
+ tasks: ['eslint:libsAndTests', 'nodeunit']
},
subgrunt: {
files: ['<%= subgrunt.all %>'],
- tasks: ['jshint:subgrunt', 'subgrunt']
+ tasks: ['eslint:subgrunt', 'subgrunt']
}
},
subgrunt: {
@@ -55,7 +39,7 @@ module.exports = function(grunt) {
});
// These plugins provide necessary tasks.
- grunt.loadNpmTasks('grunt-contrib-jshint');
+ grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-contrib-nodeunit');
grunt.loadNpmTasks('grunt-contrib-watch');
@@ -63,7 +47,9 @@ module.exports = function(grunt) {
grunt.loadTasks('internal-tasks');
// "npm test" runs these tasks
- grunt.registerTask('test', ['jshint', 'nodeunit', 'subgrunt']);
+ grunt.registerTask('test', '', function(reporter) {
+ grunt.task.run(['eslint', 'nodeunit:' + (reporter || 'all'), 'subgrunt']);
+ });
// Default task.
grunt.registerTask('default', ['test']);
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..dcf8a0c01
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,35 @@
+Copyright jQuery Foundation and other contributors, https://jquery.org/
+
+This software consists of voluntary contributions made by many
+individuals. For exact contribution history, see the revision history
+available at https://github.com/gruntjs/grunt .
+
+The following license applies to all parts of this software except as
+documented below:
+
+====
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+====
+
+All files located in the node_modules directory are externally maintained
+libraries used by this software which have their own licenses; we recommend
+you read them, as their terms may differ from the terms above.
\ No newline at end of file
diff --git a/LICENSE-MIT b/LICENSE-MIT
deleted file mode 100644
index 1056fb5a0..000000000
--- a/LICENSE-MIT
+++ /dev/null
@@ -1,22 +0,0 @@
-Copyright (c) 2014 "Cowboy" Ben Alman
-
-Permission is hereby granted, free of charge, to any person
-obtaining a copy of this software and associated documentation
-files (the "Software"), to deal in the Software without
-restriction, including without limitation the rights to use,
-copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following
-conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index df3d3441b..e03b5f137 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,24 @@
# Grunt: The JavaScript Task Runner
-[](http://travis-ci.org/gruntjs/grunt)
-
-[](http://gruntjs.com/)
+[](https://travis-ci.org/gruntjs/grunt)
+[](https://ci.appveyor.com/project/gruntjs/grunt/branch/master)
+[](http://gruntjs.com/)
+[](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fgruntjs%2Fgrunt?ref=badge_shield)
-
+
### Documentation
-Visit the [gruntjs.com](http://gruntjs.com/) website for all the things.
+Visit the [gruntjs.com](https://gruntjs.com/) website for all the things.
### Support / Contributing
-Before you make an issue, please read our [Contributing](http://gruntjs.com/contributing) guide.
-You can find the grunt team in [#grunt on irc.freenode.net](http://webchat.freenode.net/?channels=grunt).
+Before you make an issue, please read our [Contributing](https://gruntjs.com/contributing) guide.
### Release History
See the [CHANGELOG](CHANGELOG).
+
+### License
+
+[MIT](LICENSE)
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..d2ad44c7a
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,5 @@
+## Reporting a Vulnerability
+
+If you discover a security vulnerability within grunt, please submit a report via [huntr.dev](https://huntr.dev/bounties/?target=https%3A%2F%2Fgithub.com%2Fgruntjs%2Fgrunt). Bounties and CVEs are automatically managed and allocated via the platform.
+
+All security vulnerabilities will be promptly addressed.
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 4cbe6d965..000000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,43 +0,0 @@
-# http://www.appveyor.com/docs/appveyor-yml
-
-# Fix line endings in Windows. (runs before repo cloning)
-init:
- - git config --global core.autocrlf input
-
-# Test against these versions of Node.js.
-environment:
- matrix:
- - nodejs_version: "0.10"
- - nodejs_version: "0.8"
- - nodejs_version: "0.11"
-
-# Allow failing jobs for bleeding-edge Node.js versions.
-matrix:
- allow_failures:
- - nodejs_version: "0.11"
-
-# Install scripts. (runs after repo cloning)
-install:
- # Get the latest stable version of Node 0.STABLE.latest
- - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
- # Typical npm stuff.
- - npm install
- # Grunt-specific stuff.
- - npm install -g grunt-cli
- - npm uninstall grunt # https://github.com/npm/npm/issues/3958
-
-# Post-install test scripts.
-test_script:
- # Output useful info for debugging.
- - node --version
- - npm --version
- # We test multiple Windows shells because of prior stdout buffering issues
- # filed against Grunt. https://github.com/joyent/node/issues/3584
- - ps: "npm test # PowerShell" # Pass comment to PS for easier debugging
- - cmd: npm test
-
-# Don't actually build.
-build: off
-
-# Set build version format here instead of in the admin panel.
-version: "{build}"
diff --git a/bin/grunt b/bin/grunt
new file mode 100755
index 000000000..9ffa44405
--- /dev/null
+++ b/bin/grunt
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+require('grunt-cli/bin/grunt');
diff --git a/docs/README.md b/docs/README.md
index bf4851501..3870b3f47 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1 +1 @@
-Visit the [gruntjs.com](http://gruntjs.com/) website for all the things.
\ No newline at end of file
+Visit the [gruntjs.com](https://gruntjs.com/) website for all the things.
diff --git a/internal-tasks/bump.js b/internal-tasks/bump.js
deleted file mode 100644
index d690bc4a7..000000000
--- a/internal-tasks/bump.js
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * grunt-contrib-bump
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman, contributors
- * Licensed under the MIT license.
- */
-
-'use strict';
-
-var semver = require('semver');
-var shell = require('shelljs');
-
-module.exports = function(grunt) {
-
- grunt.registerTask('bump', 'Bump the version property of a JSON file.', function() {
- // Validate specified semver increment modes.
- var valids = ['major', 'minor', 'patch', 'prerelease'];
- var modes = [];
- this.args.forEach(function(mode) {
- var matches = [];
- valids.forEach(function(valid) {
- if (valid.indexOf(mode) === 0) { matches.push(valid); }
- });
- if (matches.length === 0) {
- grunt.log.error('Error: mode "' + mode + '" does not match any known modes.');
- } else if (matches.length > 1) {
- grunt.log.error('Error: mode "' + mode + '" is ambiguous (possibly: ' + matches.join(', ') + ').');
- } else {
- modes.push(matches[0]);
- }
- });
- if (this.errorCount === 0 && modes.length === 0) {
- grunt.log.error('Error: no modes specified.');
- }
- if (this.errorCount > 0) {
- grunt.log.error('Valid modes are: ' + valids.join(', ') + '.');
- throw new Error('Use valid modes (or unambiguous mode abbreviations).');
- }
- // Options.
- var options = this.options({
- filepaths: ['package.json'],
- syncVersions: false,
- commit: true,
- commitMessage: 'Bumping version to {%= version %}.',
- tag: true,
- tagName: 'v{%= version %}',
- tagMessage: 'Version {%= version %}',
- tagPrerelease: false,
- });
- // Normalize filepaths to array.
- var filepaths = Array.isArray(options.filepaths) ? options.filepaths : [options.filepaths];
- // Process JSON files, in-order.
- var versions = {};
- filepaths.forEach(function(filepath) {
- var o = grunt.file.readJSON(filepath);
- var origVersion = o.version;
- // If syncVersions is enabled, only grab version from the first file,
- // guaranteeing new versions will always be in sync.
- var firstVersion = Object.keys(versions)[0];
- if (options.syncVersions && firstVersion) {
- o.version = firstVersion;
- }
- modes.forEach(function(mode) {
- var orig = o.version;
- var s = semver.parse(o.version);
- s.inc(mode);
- o.version = String(s);
- // Workaround for https://github.com/isaacs/node-semver/issues/50
- if (/-/.test(orig) && mode === 'patch') {
- o.version = o.version.replace(/\d+$/, function(n) { return n - 1; });
- }
- // If prerelease on an un-prerelease version, bump patch version first
- if (!/-/.test(orig) && mode === 'prerelease') {
- s.inc('patch');
- s.inc('prerelease');
- o.version = String(s);
- }
- });
- if (versions[origVersion]) {
- versions[origVersion].filepaths.push(filepath);
- } else {
- versions[origVersion] = {version: o.version, filepaths: [filepath]};
- }
- // Actually *do* something.
- grunt.log.write('Bumping version in ' + filepath + ' from ' + origVersion + ' to ' + o.version + '...');
- grunt.file.write(filepath, JSON.stringify(o, null, 2));
- grunt.log.ok();
- });
- // Commit changed files?
- if (options.commit) {
- Object.keys(versions).forEach(function(origVersion) {
- var o = versions[origVersion];
- commit(o.filepaths, processTemplate(options.commitMessage, {
- version: o.version,
- origVersion: origVersion
- }));
- });
- }
- // We're only going to create one tag. And it's going to be the new
- // version of the first bumped file. Because, sanity.
- var newVersion = versions[Object.keys(versions)[0]].version;
- if (options.tag) {
- if (options.tagPrerelease || modes.indexOf('prerelease') === -1) {
- tag(
- processTemplate(options.tagName, {version: newVersion}),
- processTemplate(options.tagMessage, {version: newVersion})
- );
- } else {
- grunt.log.writeln('Not tagging (prerelease version).');
- }
- }
- if (this.errorCount > 0) {
- grunt.warn('There were errors.');
- }
- });
-
- // Using custom delimiters keeps templates from being auto-processed.
- grunt.template.addDelimiters('bump', '{%', '%}');
-
- function processTemplate(message, data) {
- return grunt.template.process(message, {
- delimiters: 'bump',
- data: data,
- });
- }
-
- // Kinda borrowed from https://github.com/geddski/grunt-release
- function commit(filepaths, message) {
- grunt.log.writeln('Committing ' + filepaths.join(', ') + ' with message: ' + message);
- run("git commit -m '" + message + "' '" + filepaths.join("' '") + "'");
- }
-
- function tag(name, message) {
- grunt.log.writeln('Tagging ' + name + ' with message: ' + message);
- run("git tag '" + name + "' -m '" + message + "'");
- }
-
- function run(cmd) {
- if (grunt.option('no-write')) {
- grunt.verbose.writeln('Not actually running: ' + cmd);
- } else {
- grunt.verbose.writeln('Running: ' + cmd);
- var result = shell.exec(cmd, {silent:true});
- if (result.code !== 0) {
- grunt.log.error('Error (' + result.code + ') ' + result.output);
- }
- }
- }
-
-};
\ No newline at end of file
diff --git a/internal-tasks/subgrunt.js b/internal-tasks/subgrunt.js
index 95b5c6770..dba8d833e 100644
--- a/internal-tasks/subgrunt.js
+++ b/internal-tasks/subgrunt.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
'use strict';
module.exports = function(grunt) {
diff --git a/lib/grunt.js b/lib/grunt.js
index 611799ae9..025333307 100644
--- a/lib/grunt.js
+++ b/lib/grunt.js
@@ -1,19 +1,28 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
'use strict';
// Nodejs libs.
var path = require('path');
// This allows grunt to require() .coffee files.
-require('coffee-script');
+try {
+ // Note: grunt no longer depends on CoffeeScript, it will only use it if it is intentionally
+ // installed in the project.
+ require('coffeescript/register');
+} catch (e) {
+ // This is fine, and will cause no problems so long as the user doesn't load .coffee files.
+ // Print a useful error if we attempt to load a .coffee file.
+ if (require.extensions) {
+ var FILE_EXTENSIONS = ['.coffee', '.litcoffee', '.coffee.md'];
+ for (var i = 0; i < FILE_EXTENSIONS.length; i++) {
+ require.extensions[FILE_EXTENSIONS[i]] = function() {
+ throw new Error(
+ 'Grunt attempted to load a .coffee file but CoffeeScript was not installed.\n' +
+ 'Please run `npm install --dev coffeescript` to enable loading CoffeeScript.'
+ );
+ };
+ }
+ }
+}
// The module to be exported.
var grunt = module.exports = {};
@@ -117,7 +126,7 @@ grunt.tasks = function(tasks, options, done) {
tasks = task.parseArgs([tasksSpecified ? tasks : 'default']);
// Initialize tasks.
- task.init(tasks);
+ task.init(tasks, options);
verbose.writeln();
if (!tasksSpecified) {
@@ -161,5 +170,5 @@ grunt.tasks = function(tasks, options, done) {
tasks.forEach(function(name) { task.run(name); });
// Run tasks async internally to reduce call-stack, per:
// https://github.com/gruntjs/grunt/pull/1026
- task.start({asyncDone:true});
+ task.start({asyncDone: true});
};
diff --git a/lib/grunt/cli.js b/lib/grunt/cli.js
index 7938cf1c2..1252f54a9 100644
--- a/lib/grunt/cli.js
+++ b/lib/grunt/cli.js
@@ -1,21 +1,10 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
'use strict';
var grunt = require('../grunt');
-// Nodejs libs.
-var path = require('path');
-
// External libs.
var nopt = require('nopt');
+var gruntOptions = require('grunt-known-options');
// This is only executed when run via command line.
var cli = module.exports = function(options, done) {
@@ -39,69 +28,7 @@ var cli = module.exports = function(options, done) {
};
// Default options.
-var optlist = cli.optlist = {
- help: {
- short: 'h',
- info: 'Display this help text.',
- type: Boolean
- },
- base: {
- info: 'Specify an alternate base path. By default, all file paths are relative to the Gruntfile. (grunt.file.setBase) *',
- type: path
- },
- color: {
- info: 'Disable colored output.',
- type: Boolean,
- negate: true
- },
- gruntfile: {
- info: 'Specify an alternate Gruntfile. By default, grunt looks in the current or parent directories for the nearest Gruntfile.js or Gruntfile.coffee file.',
- type: path
- },
- debug: {
- short: 'd',
- info: 'Enable debugging mode for tasks that support it.',
- type: [Number, Boolean]
- },
- stack: {
- info: 'Print a stack trace when exiting with a warning or fatal error.',
- type: Boolean
- },
- force: {
- short: 'f',
- info: 'A way to force your way past warnings. Want a suggestion? Don\'t use this option, fix your code.',
- type: Boolean
- },
- tasks: {
- info: 'Additional directory paths to scan for task and "extra" files. (grunt.loadTasks) *',
- type: Array
- },
- npm: {
- info: 'Npm-installed grunt plugins to scan for task and "extra" files. (grunt.loadNpmTasks) *',
- type: Array
- },
- write: {
- info: 'Disable writing files (dry run).',
- type: Boolean,
- negate: true
- },
- verbose: {
- short: 'v',
- info: 'Verbose mode. A lot more information output.',
- type: Boolean
- },
- version: {
- short: 'V',
- info: 'Print the grunt version. Combine with --verbose for more info.',
- type: Boolean
- },
- // Even though shell auto-completion is now handled by grunt-cli, leave this
- // option here for display in the --help screen.
- completion: {
- info: 'Output shell auto-completion rules. See the grunt-cli documentation for more information.',
- type: String
- },
-};
+var optlist = cli.optlist = gruntOptions;
// Parse `optlist` into a form that nopt can handle.
var aliases = {};
diff --git a/lib/grunt/config.js b/lib/grunt/config.js
index 59b8242ac..ef2bf8094 100644
--- a/lib/grunt/config.js
+++ b/lib/grunt/config.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
'use strict';
var grunt = require('../grunt');
diff --git a/lib/grunt/event.js b/lib/grunt/event.js
index f9ed33625..7ec1027d4 100644
--- a/lib/grunt/event.js
+++ b/lib/grunt/event.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
'use strict';
// External lib.
diff --git a/lib/grunt/fail.js b/lib/grunt/fail.js
index f53c65cab..631e249ac 100644
--- a/lib/grunt/fail.js
+++ b/lib/grunt/fail.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
'use strict';
var grunt = require('../grunt');
@@ -79,6 +70,6 @@ fail.report = function() {
if (fail.warncount > 0) {
grunt.log.writeln().fail('Done, but with warnings.');
} else {
- grunt.log.writeln().success('Done, without errors.');
+ grunt.log.writeln().success('Done.');
}
};
diff --git a/lib/grunt/file.js b/lib/grunt/file.js
index 100561d2b..9619e8083 100644
--- a/lib/grunt/file.js
+++ b/lib/grunt/file.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
'use strict';
var grunt = require('../grunt');
@@ -23,7 +14,6 @@ file.glob = require('glob');
file.minimatch = require('minimatch');
file.findup = require('findup-sync');
var YAML = require('js-yaml');
-var rimraf = require('rimraf');
var iconv = require('iconv-lite');
// Windows?
@@ -50,7 +40,7 @@ var processPatterns = function(patterns, fn) {
// Filepaths to return.
var result = [];
// Iterate over flattened patterns array.
- grunt.util._.flatten(patterns).forEach(function(pattern) {
+ grunt.util._.flattenDeep(patterns).forEach(function(pattern) {
// If the first character is ! it should be omitted
var exclusion = pattern.indexOf('!') === 0;
// If the pattern is an exclusion, remove the !
@@ -122,7 +112,7 @@ file.expand = function() {
// If the file is of the right type and exists, this should work.
return fs.statSync(filepath)[options.filter]();
}
- } catch(e) {
+ } catch (e) {
// Otherwise, it's probably not the right type.
return false;
}
@@ -188,22 +178,11 @@ file.expandMapping = function(patterns, destBase, options) {
// Like mkdir -p. Create a directory and any intermediary directories.
file.mkdir = function(dirpath, mode) {
if (grunt.option('no-write')) { return; }
- // Set directory mode in a strict-mode-friendly way.
- if (mode == null) {
- mode = parseInt('0777', 8) & (~process.umask());
+ try {
+ fs.mkdirSync(dirpath, { recursive: true, mode: mode });
+ } catch (e) {
+ throw grunt.util.error('Unable to create directory "' + dirpath + '" (Error code: ' + e.code + ').', e);
}
- dirpath.split(pathSeparatorRe).reduce(function(parts, part) {
- parts += part + '/';
- var subpath = path.resolve(parts);
- if (!file.exists(subpath)) {
- try {
- fs.mkdirSync(subpath, mode);
- } catch(e) {
- throw grunt.util.error('Unable to create directory "' + subpath + '" (Error code: ' + e.code + ').', e);
- }
- }
- return parts;
- }, '');
};
// Recurse into a directory, executing callback for each file.
@@ -234,15 +213,11 @@ file.read = function(filepath, options) {
// If encoding is not explicitly null, convert from encoded buffer to a
// string. If no encoding was specified, use the default.
if (options.encoding !== null) {
- contents = iconv.decode(contents, options.encoding || file.defaultEncoding);
- // Strip any BOM that might exist.
- if (!file.preserveBOM && contents.charCodeAt(0) === 0xFEFF) {
- contents = contents.substring(1);
- }
+ contents = iconv.decode(contents, options.encoding || file.defaultEncoding, {stripBOM: !file.preserveBOM});
}
grunt.verbose.ok();
return contents;
- } catch(e) {
+ } catch (e) {
grunt.verbose.error();
throw grunt.util.error('Unable to read "' + filepath + '" file (Error code: ' + e.code + ').', e);
}
@@ -257,24 +232,33 @@ file.readJSON = function(filepath, options) {
result = JSON.parse(src);
grunt.verbose.ok();
return result;
- } catch(e) {
+ } catch (e) {
grunt.verbose.error();
throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.message + ').', e);
}
};
// Read a YAML file, parse its contents, return an object.
-file.readYAML = function(filepath, options) {
+file.readYAML = function(filepath, options, yamlOptions) {
+ if (!options) { options = {}; }
+ if (!yamlOptions) { yamlOptions = {}; }
+
var src = file.read(filepath, options);
var result;
grunt.verbose.write('Parsing ' + filepath + '...');
try {
- result = YAML.load(src);
+ // use the recommended way of reading YAML files
+ // https://github.com/nodeca/js-yaml#safeload-string---options-
+ if (yamlOptions.unsafeLoad) {
+ result = YAML.load(src);
+ } else {
+ result = YAML.safeLoad(src);
+ }
grunt.verbose.ok();
return result;
- } catch(e) {
+ } catch (e) {
grunt.verbose.error();
- throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.problem + ').', e);
+ throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.message + ').', e);
}
};
@@ -293,18 +277,39 @@ file.write = function(filepath, contents, options) {
}
// Actually write file.
if (!nowrite) {
- fs.writeFileSync(filepath, contents);
+ fs.writeFileSync(filepath, contents, 'mode' in options ? {mode: options.mode} : {});
}
grunt.verbose.ok();
return true;
- } catch(e) {
+ } catch (e) {
grunt.verbose.error();
throw grunt.util.error('Unable to write "' + filepath + '" file (Error code: ' + e.code + ').', e);
}
};
// Read a file, optionally processing its content, then write the output.
-file.copy = function(srcpath, destpath, options) {
+// Or read a directory, recursively creating directories, reading files,
+// processing content, writing output.
+// Handles symlinks by coping them as files or directories.
+file.copy = function copy(srcpath, destpath, options) {
+ if (file.isLink(srcpath)) {
+ file._copySymbolicLink(srcpath, destpath);
+ } else if (file.isDir(srcpath)) {
+ // Copy a directory, recursively.
+ // Explicitly create new dest directory.
+ file.mkdir(destpath);
+ // Iterate over all sub-files/dirs, recursing.
+ fs.readdirSync(srcpath).forEach(function(filepath) {
+ copy(path.join(srcpath, filepath), path.join(destpath, filepath), options);
+ });
+ } else {
+ // Copy a single file.
+ file._copy(srcpath, destpath, options);
+ }
+};
+
+// Read a file, optionally processing its content, then write the output.
+file._copy = function(srcpath, destpath, options) {
if (!options) { options = {}; }
// If a process function was specified, and noProcess isn't true or doesn't
// match the srcpath, process the file's source.
@@ -318,16 +323,16 @@ file.copy = function(srcpath, destpath, options) {
if (process) {
grunt.verbose.write('Processing source...');
try {
- contents = options.process(contents, srcpath);
+ contents = options.process(contents, srcpath, destpath);
grunt.verbose.ok();
- } catch(e) {
+ } catch (e) {
grunt.verbose.error();
throw grunt.util.error('Error while processing "' + srcpath + '" file.', e);
}
}
// Abort copy if the process function returns false.
- if (contents === false) {
- grunt.verbose.writeln('Write aborted.');
+ if (contents === false || file.isLink(destpath)) {
+ grunt.verbose.writeln('Write aborted. Either the process function returned false or the destination is a symlink');
} else {
file.write(destpath, contents, readWriteOptions);
}
@@ -366,11 +371,11 @@ file.delete = function(filepath, options) {
try {
// Actually delete. Or not.
if (!nowrite) {
- rimraf.sync(filepath);
+ fs.rmSync(filepath, { recursive: true, force: true });
}
grunt.verbose.ok();
return true;
- } catch(e) {
+ } catch (e) {
grunt.verbose.error();
throw grunt.util.error('Unable to delete "' + filepath + '" file (' + e.message + ').', e);
}
@@ -385,7 +390,15 @@ file.exists = function() {
// True if the file is a symbolic link.
file.isLink = function() {
var filepath = path.join.apply(path, arguments);
- return file.exists(filepath) && fs.lstatSync(filepath).isSymbolicLink();
+ try {
+ return fs.lstatSync(filepath).isSymbolicLink();
+ } catch (e) {
+ if (e.code === 'ENOENT') {
+ // The file doesn't exist, so it's not a symbolic link.
+ return false;
+ }
+ throw grunt.util.error('Unable to read "' + filepath + '" file (Error code: ' + e.code + ').', e);
+ }
};
// True if the path is a directory.
@@ -403,7 +416,7 @@ file.isFile = function() {
// Is a given file path absolute?
file.isPathAbsolute = function() {
var filepath = path.join.apply(path, arguments);
- return path.resolve(filepath) === filepath.replace(/[\/\\]+$/, '');
+ return path.isAbsolute(filepath);
};
// Do all the specified paths refer to the same path?
@@ -432,17 +445,32 @@ file.isPathCwd = function() {
var filepath = path.join.apply(path, arguments);
try {
return file.arePathsEquivalent(fs.realpathSync(process.cwd()), fs.realpathSync(filepath));
- } catch(e) {
+ } catch (e) {
return false;
}
};
+file._copySymbolicLink = function(srcpath, destpath) {
+ var destdir = path.join(destpath, '..');
+ // Use the correct relative path for the symlink
+ if (!grunt.file.isPathAbsolute(srcpath)) {
+ srcpath = path.relative(destdir, srcpath) || '.';
+ }
+ file.mkdir(destdir);
+ var mode = grunt.file.isDir(srcpath) ? 'dir' : 'file';
+ if (fs.existsSync(destpath)) {
+ // skip symlink if file already exists
+ return;
+ }
+ return fs.symlinkSync(srcpath, destpath, mode);
+};
+
// Test to see if a filepath is contained within the CWD.
file.isPathInCwd = function() {
var filepath = path.join.apply(path, arguments);
try {
return file.doesPathContain(fs.realpathSync(process.cwd()), fs.realpathSync(filepath));
- } catch(e) {
+ } catch (e) {
return false;
}
};
diff --git a/lib/grunt/help.js b/lib/grunt/help.js
index 37b1ad2f4..d761ef565 100644
--- a/lib/grunt/help.js
+++ b/lib/grunt/help.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
'use strict';
var grunt = require('../grunt');
@@ -21,7 +12,8 @@ exports.initCol1 = function(str) {
};
exports.initWidths = function() {
// Widths for options/tasks table output.
- exports.widths = [1, col1len, 2, 76 - col1len];
+ var commandWidth = Math.max(col1len + 20, 76);
+ exports.widths = [1, col1len, 2, commandWidth - col1len];
};
// Render an array in table form.
@@ -49,7 +41,6 @@ exports.display = function() {
exports.queue.forEach(function(name) { exports[name](); });
};
-
// Header.
exports.header = function() {
grunt.log.writeln('Grunt: The JavaScript Task Runner (v' + grunt.version + ')');
diff --git a/lib/grunt/option.js b/lib/grunt/option.js
index 10eb123ad..bfb4074f1 100644
--- a/lib/grunt/option.js
+++ b/lib/grunt/option.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
'use strict';
// The actual option data.
@@ -40,3 +31,8 @@ option.flags = function() {
(typeof val === 'boolean' ? '' : '=' + val);
});
};
+
+// Get all option keys
+option.keys = function() {
+ return Object.keys(data);
+};
diff --git a/lib/grunt/task.js b/lib/grunt/task.js
index 5d1ac8bf5..8eb853f3c 100644
--- a/lib/grunt/task.js
+++ b/lib/grunt/task.js
@@ -1,14 +1,8 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
'use strict';
+// Keep track of the number of log.error() calls and the last specified tasks message.
+var errorcount, lastInfo;
+
var grunt = require('../grunt');
// Nodejs libs.
@@ -23,15 +17,9 @@ var task = module.exports = Object.create(parent);
// A temporary registry of tasks and metadata.
var registry = {tasks: [], untasks: [], meta: {}};
-// The last specified tasks message.
-var lastInfo;
-
// Number of levels of recursion when loading tasks in collections.
var loadTaskDepth = 0;
-// Keep track of the number of log.error() calls.
-var errorcount;
-
// Override built-in registerTask.
task.registerTask = function(name) {
// Add task to registry.
@@ -107,7 +95,7 @@ task.normalizeMultiTaskFiles = function(data, target) {
files.push({src: data.files[prop], dest: grunt.config.process(prop)});
}
} else if (Array.isArray(data.files)) {
- grunt.util._.flatten(data.files).forEach(function(obj) {
+ grunt.util._.flattenDeep(data.files).forEach(function(obj) {
var prop;
if ('src' in obj || 'dest' in obj) {
files.push(obj);
@@ -257,7 +245,7 @@ task.registerMultiTask = function(name, info, fn) {
Object.defineProperty(this, 'filesSrc', {
enumerable: true,
get: function() {
- return grunt.util._(this.files).chain().pluck('src').flatten().uniq().value();
+ return grunt.util._(this.files).chain().map('src').flatten().uniq().value();
}.bind(this)
});
// Call original task function, passing in the target and any other args.
@@ -286,7 +274,7 @@ task.renameTask = function(oldname, newname) {
registry.tasks.push(newname);
// Return result.
return result;
- } catch(e) {
+ } catch (e) {
grunt.log.error(e.message);
}
};
@@ -295,13 +283,15 @@ task.renameTask = function(oldname, newname) {
task.runAllTargets = function(taskname, args) {
// Get an array of sub-property keys under the given config object.
var targets = Object.keys(grunt.config.getRaw(taskname) || {});
+ // Remove invalid target properties.
+ targets = targets.filter(isValidMultiTaskTarget);
// Fail if there are no actual properties to iterate over.
if (targets.length === 0) {
grunt.log.error('No "' + taskname + '" targets found.');
return false;
}
- // Iterate over all valid target properties, running a task for each.
- targets.filter(isValidMultiTaskTarget).forEach(function(target) {
+ // Iterate over all targets, running a task for each.
+ targets.forEach(function(target) {
// Be sure to pass in any additionally specified args.
task.run([taskname, target].concat(args || []).join(':'));
});
@@ -336,7 +326,7 @@ function loadTask(filepath) {
if (regCount === 0) {
grunt.verbose.warn('No tasks were registered or unregistered.');
}
- } catch(e) {
+ } catch (e) {
// Something went wrong.
grunt.log.write(msg).error().verbose.error(e.stack).or.error(e);
}
@@ -355,12 +345,12 @@ function loadTasksMessage(info) {
// Load tasks and handlers from a given directory.
function loadTasks(tasksdir) {
try {
- var files = grunt.file.glob.sync('*.{js,coffee}', {cwd: tasksdir, maxDepth: 1});
+ var files = grunt.file.glob.sync('*.{js,cjs,coffee}', {cwd: tasksdir, maxDepth: 1});
// Load tasks from files.
files.forEach(function(filename) {
loadTask(path.join(tasksdir, filename));
});
- } catch(e) {
+ } catch (e) {
grunt.log.verbose.error(e.stack).or.error(e);
}
}
@@ -380,7 +370,23 @@ task.loadTasks = function(tasksdir) {
task.loadNpmTasks = function(name) {
loadTasksMessage('"' + name + '" local Npm module');
var root = path.resolve('node_modules');
- var pkgfile = path.join(root, name, 'package.json');
+ var pkgpath = path.join(root, name);
+ var pkgfile = path.join(pkgpath, 'package.json');
+ // If package does not exist where grunt expects it to be,
+ // try to find it using Node's package path resolution mechanism
+ if (!grunt.file.exists(pkgpath)) {
+ var nameParts = name.split('/');
+ // In case name points to directory inside module,
+ // get real name of the module with respect to scope (if any)
+ var normailzedName = (name[0] === '@' ? nameParts.slice(0,2).join('/') : nameParts[0]);
+ try {
+ pkgfile = require.resolve(normailzedName + '/package.json');
+ root = pkgfile.substr(0, pkgfile.length - normailzedName.length - '/package.json'.length);
+ } catch (err) {
+ grunt.log.error('Local Npm module "' + normailzedName + '" not found. Is it installed?');
+ return;
+ }
+ }
var pkg = grunt.file.exists(pkgfile) ? grunt.file.readJSON(pkgfile) : {keywords: []};
// Process collection plugins.
@@ -423,11 +429,18 @@ task.init = function(tasks, options) {
// Get any local Gruntfile or tasks that might exist. Use --gruntfile override
// if specified, otherwise search the current directory or any parent.
- var gruntfile = allInit ? null : grunt.option('gruntfile') ||
- grunt.file.findup('Gruntfile.{js,coffee}', {nocase: true});
+ var gruntfile, msg;
+ if (allInit || options.gruntfile === false) {
+ gruntfile = null;
+ } else {
+ gruntfile = grunt.option('gruntfile') ||
+ grunt.file.findup('Gruntfile.{js,cjs,coffee}', {nocase: true});
+ msg = 'Reading "' + (gruntfile ? path.basename(gruntfile) : '???') + '" Gruntfile...';
+ }
- var msg = 'Reading "' + (gruntfile ? path.basename(gruntfile) : '???') + '" Gruntfile...';
- if (gruntfile && grunt.file.exists(gruntfile)) {
+ if (options.gruntfile === false) {
+ // Grunt was run as a lib with {gruntfile: false}.
+ } else if (gruntfile && grunt.file.exists(gruntfile)) {
grunt.verbose.writeln().write(msg).ok();
// Change working directory so that all paths are relative to the
// Gruntfile's location (or the --base option, if specified).
@@ -452,7 +465,7 @@ task.init = function(tasks, options) {
}
// Load all user-specified --npm tasks.
- (grunt.option('npm') || []).forEach(task.loadNpmTasks);
+ (grunt.option('npm') || []).map(String).forEach(task.loadNpmTasks);
// Load all user-specified --tasks.
- (grunt.option('tasks') || []).forEach(task.loadTasks);
+ (grunt.option('tasks') || []).map(String).forEach(task.loadTasks);
};
diff --git a/lib/grunt/template.js b/lib/grunt/template.js
index 5cdc98141..15e1fe10b 100644
--- a/lib/grunt/template.js
+++ b/lib/grunt/template.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
'use strict';
var grunt = require('../grunt');
@@ -19,7 +10,11 @@ template.date = require('dateformat');
// Format today's date.
template.today = function(format) {
- return template.date(new Date(), format);
+ var now = new Date();
+ if (process.env.SOURCE_DATE_EPOCH) {
+ now = new Date((process.env.SOURCE_DATE_EPOCH * 1000) + (now.getTimezoneOffset() * 60000));
+ }
+ return template.date(now, format);
};
// Template delimiters.
@@ -51,7 +46,7 @@ template.setDelimiters = function(name) {
// Get the appropriate delimiters.
var delimiters = allDelimiters[name in allDelimiters ? name : 'config'];
// Tell Lo-Dash which delimiters to use.
- grunt.util._.templateSettings = delimiters.lodash;
+ grunt.util._.extend(grunt.util._.templateSettings, delimiters.lodash);
// Return the delimiters.
return delimiters;
};
@@ -72,7 +67,7 @@ template.process = function(tmpl, options) {
// As long as tmpl contains template tags, render it and get the result,
// otherwise just use the template string.
while (tmpl.indexOf(delimiters.opener) >= 0) {
- tmpl = grunt.util._.template(tmpl, data);
+ tmpl = grunt.util._.template(tmpl, options)(data);
// Abort if template didn't change - nothing left to process!
if (tmpl === last) { break; }
last = tmpl;
diff --git a/lib/util/task.js b/lib/util/task.js
index 61a004d7a..d39ce1b02 100644
--- a/lib/util/task.js
+++ b/lib/util/task.js
@@ -1,16 +1,9 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
(function(exports) {
'use strict';
+ var grunt = require('../grunt');
+
// Construct-o-rama.
function Task() {
// Information about the currently-running task.
@@ -223,7 +216,7 @@
// only call done async if explicitly requested to
// see: https://github.com/gruntjs/grunt/pull/1026
if (asyncDone) {
- process.nextTick(function () {
+ process.nextTick(function() {
done(err, success);
});
} else {
@@ -237,9 +230,9 @@
async = true;
// The returned function should execute asynchronously in case
// someone tries to do this.async()(); inside a task (WTF).
- return function(success) {
+ return grunt.util._.once(function(success) {
setTimeout(function() { complete(success); }, 1);
- };
+ });
};
// Expose some information about the currently-running task.
diff --git a/package.json b/package.json
index 7286cd3eb..e99ca6d37 100644
--- a/package.json
+++ b/package.json
@@ -1,28 +1,21 @@
{
"name": "grunt",
"description": "The JavaScript Task Runner",
- "version": "0.4.5",
- "author": "\"Cowboy\" Ben Alman (http://benalman.com/)",
- "homepage": "http://gruntjs.com/",
- "repository": {
- "type": "git",
- "url": "git://github.com/gruntjs/grunt.git"
- },
- "bugs": {
- "url": "http://github.com/gruntjs/grunt/issues"
+ "version": "1.6.1",
+ "author": "Grunt Development Team (https://gruntjs.com/development-team)",
+ "homepage": "https://gruntjs.com/",
+ "repository": "https://github.com/gruntjs/grunt.git",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
},
- "licenses": [
- {
- "type": "MIT",
- "url": "http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT"
- }
- ],
- "main": "lib/grunt",
"scripts": {
- "test": "grunt test"
+ "test": "node bin/grunt test",
+ "test-tap": "node bin/grunt test:tap"
},
- "engines": {
- "node": ">= 0.8.0"
+ "main": "lib/grunt",
+ "bin": {
+ "grunt": "bin/grunt"
},
"keywords": [
"task",
@@ -44,34 +37,31 @@
"tool"
],
"dependencies": {
- "async": "~0.1.22",
- "coffee-script": "~1.3.3",
- "colors": "~0.6.2",
- "dateformat": "1.0.2-1.2.3",
+ "dateformat": "~4.6.2",
"eventemitter2": "~0.4.13",
- "findup-sync": "~0.1.2",
- "glob": "~3.1.21",
- "hooker": "~0.2.3",
- "iconv-lite": "~0.2.11",
- "minimatch": "~0.2.12",
- "nopt": "~1.0.10",
- "rimraf": "~2.2.8",
- "lodash": "~0.9.2",
- "underscore.string": "~2.2.1",
- "which": "~1.0.5",
- "js-yaml": "~2.0.5",
- "exit": "~0.1.1",
- "getobject": "~0.1.0",
- "grunt-legacy-util": "~0.2.0",
- "grunt-legacy-log": "~0.1.0"
+ "exit": "~0.1.2",
+ "findup-sync": "~5.0.0",
+ "glob": "~7.1.6",
+ "grunt-cli": "~1.4.3",
+ "grunt-known-options": "~2.0.0",
+ "grunt-legacy-log": "~3.0.0",
+ "grunt-legacy-util": "~2.0.1",
+ "iconv-lite": "~0.6.3",
+ "js-yaml": "~3.14.0",
+ "minimatch": "~3.0.4",
+ "nopt": "~3.0.6"
},
"devDependencies": {
- "temporary": "~0.0.4",
- "grunt-contrib-jshint": "~0.6.4",
- "grunt-contrib-nodeunit": "~0.2.0",
- "grunt-contrib-watch": "~0.5.3",
- "difflet": "~0.2.3",
- "semver": "2.1.0",
- "shelljs": "~0.2.5"
- }
-}
\ No newline at end of file
+ "difflet": "~1.0.1",
+ "eslint-config-grunt": "~2.0.1",
+ "grunt-contrib-nodeunit": "~4.0.0",
+ "grunt-contrib-watch": "~1.1.0",
+ "grunt-eslint": "~24.0.1",
+ "temporary": "~1.1.0",
+ "through2": "~4.0.2"
+ },
+ "files": [
+ "lib",
+ "bin"
+ ]
+}
diff --git a/test/.eslintrc b/test/.eslintrc
new file mode 100644
index 000000000..d9d1a3b26
--- /dev/null
+++ b/test/.eslintrc
@@ -0,0 +1,6 @@
+{
+ "rules": {
+ // Nullified until the files in test/ can be cleaned
+ "max-len": "off"
+ }
+}
diff --git a/test/fixtures/error.yaml b/test/fixtures/error.yaml
new file mode 100644
index 000000000..855f17f87
--- /dev/null
+++ b/test/fixtures/error.yaml
@@ -0,0 +1,4 @@
+v1.0.0:
+ - Duplicate key
+v1.0.0:
+ - Duplicate key
diff --git a/test/fixtures/expand/css/baz.css b/test/fixtures/expand/css/baz.css
index e69de29bb..3f9538666 100644
--- a/test/fixtures/expand/css/baz.css
+++ b/test/fixtures/expand/css/baz.css
@@ -0,0 +1 @@
+baz
\ No newline at end of file
diff --git a/test/fixtures/expand/css/qux.css b/test/fixtures/expand/css/qux.css
index e69de29bb..78df5b06b 100644
--- a/test/fixtures/expand/css/qux.css
+++ b/test/fixtures/expand/css/qux.css
@@ -0,0 +1 @@
+qux
\ No newline at end of file
diff --git a/test/fixtures/expand/deep/deep.txt b/test/fixtures/expand/deep/deep.txt
index e69de29bb..d1f857b3c 100644
--- a/test/fixtures/expand/deep/deep.txt
+++ b/test/fixtures/expand/deep/deep.txt
@@ -0,0 +1 @@
+deep
\ No newline at end of file
diff --git a/test/fixtures/expand/deep/deeper/deeper.txt b/test/fixtures/expand/deep/deeper/deeper.txt
index e69de29bb..2e4e0c168 100644
--- a/test/fixtures/expand/deep/deeper/deeper.txt
+++ b/test/fixtures/expand/deep/deeper/deeper.txt
@@ -0,0 +1 @@
+deeper
\ No newline at end of file
diff --git a/test/fixtures/expand/deep/deeper/deepest/deepest.txt b/test/fixtures/expand/deep/deeper/deepest/deepest.txt
index e69de29bb..d087677af 100644
--- a/test/fixtures/expand/deep/deeper/deepest/deepest.txt
+++ b/test/fixtures/expand/deep/deeper/deepest/deepest.txt
@@ -0,0 +1 @@
+deepest
\ No newline at end of file
diff --git a/test/fixtures/expand/js/bar.js b/test/fixtures/expand/js/bar.js
index e69de29bb..ba0e162e1 100644
--- a/test/fixtures/expand/js/bar.js
+++ b/test/fixtures/expand/js/bar.js
@@ -0,0 +1 @@
+bar
\ No newline at end of file
diff --git a/test/fixtures/expand/js/foo.js b/test/fixtures/expand/js/foo.js
index e69de29bb..191028156 100644
--- a/test/fixtures/expand/js/foo.js
+++ b/test/fixtures/expand/js/foo.js
@@ -0,0 +1 @@
+foo
\ No newline at end of file
diff --git a/test/fixtures/load-npm-tasks/node_modules/grunt-foo-plugin/package.json b/test/fixtures/load-npm-tasks/node_modules/grunt-foo-plugin/package.json
new file mode 100644
index 000000000..decedf6f4
--- /dev/null
+++ b/test/fixtures/load-npm-tasks/node_modules/grunt-foo-plugin/package.json
@@ -0,0 +1,6 @@
+{
+ "private": true,
+ "name": "grunt-foo-plugin",
+ "description": "",
+ "version": "1.0.0"
+}
diff --git a/test/fixtures/load-npm-tasks/node_modules/grunt-foo-plugin/tasks/foo.js b/test/fixtures/load-npm-tasks/node_modules/grunt-foo-plugin/tasks/foo.js
new file mode 100644
index 000000000..8dd9048f8
--- /dev/null
+++ b/test/fixtures/load-npm-tasks/node_modules/grunt-foo-plugin/tasks/foo.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = function(grunt) {
+ grunt.registerTask('foo', function() {
+ grunt.log.writeln(this.name + ' has ran.');
+ });
+};
diff --git a/test/fixtures/load-npm-tasks/package.json b/test/fixtures/load-npm-tasks/package.json
new file mode 100644
index 000000000..de5fb74e5
--- /dev/null
+++ b/test/fixtures/load-npm-tasks/package.json
@@ -0,0 +1,7 @@
+{
+ "private": true,
+ "name": "load-npm-tasks",
+ "devDependencies": {
+ "grunt-foo-plugin": "1.0.0"
+ }
+}
diff --git a/test/fixtures/load-npm-tasks/test-package/package.json b/test/fixtures/load-npm-tasks/test-package/package.json
new file mode 100644
index 000000000..8de3ba72d
--- /dev/null
+++ b/test/fixtures/load-npm-tasks/test-package/package.json
@@ -0,0 +1,7 @@
+{
+ "private": true,
+ "name": "test-package",
+ "devDependencies": {
+ "grunt-foo-plugin": "1.0.0"
+ }
+}
\ No newline at end of file
diff --git a/test/fixtures/spawn-multibyte.js b/test/fixtures/spawn-multibyte.js
index 8277711f6..b592341c6 100644
--- a/test/fixtures/spawn-multibyte.js
+++ b/test/fixtures/spawn-multibyte.js
@@ -4,7 +4,7 @@
// A multibyte buffer containing all our output. We will slice it later.
// In this case we are using a Japanese word for hello / good day, where each
// character takes three bytes.
-var fullOutput = new Buffer('こんにちは');
+var fullOutput = Buffer.from('こんにちは');
// Output one full character and one third of a character
process.stdout.write(fullOutput.slice(0, 4));
diff --git a/test/grunt/cli_test.js b/test/grunt/cli_test.js
index 4c1e9a55f..beb5036d7 100644
--- a/test/grunt/cli_test.js
+++ b/test/grunt/cli_test.js
@@ -9,7 +9,7 @@ function getOptionValues(str) {
return matches ? JSON.parse(matches[1]) : {};
}
-exports['cli'] = {
+exports.cli = {
'--debug taskname': function(test) {
test.expect(1);
grunt.util.spawn({
diff --git a/test/grunt/config_test.js b/test/grunt/config_test.js
index ac6fa1785..e2db6cf12 100644
--- a/test/grunt/config_test.js
+++ b/test/grunt/config_test.js
@@ -2,7 +2,7 @@
var grunt = require('../../lib/grunt');
-exports['config'] = {
+exports.config = {
setUp: function(done) {
this.origData = grunt.config.data;
grunt.config.init({
@@ -18,7 +18,7 @@ exports['config'] = {
bar: 'bar',
arr: ['foo', '<%= obj.foo2 %>'],
arr2: ['<%= arr %>', '<%= obj.Arr %>'],
- buffer: new Buffer('test'),
+ buffer: Buffer.from('test'),
});
done();
},
@@ -57,7 +57,7 @@ exports['config'] = {
test.deepEqual(grunt.config.process(['<%= arr %>', '<%= obj.Arr %>']), [['foo', 'bar'], ['foo', 'bar']], 'Should expand <%= arr %> and <%= obj.Arr %> values as objects if possible.');
var buf = grunt.config.process('<%= buffer %>');
test.ok(Buffer.isBuffer(buf), 'Should retrieve Buffer instances as Buffer.');
- test.deepEqual(buf, new Buffer('test'), 'Should return buffers as-is.');
+ test.deepEqual(buf, Buffer.from('test'), 'Should return buffers as-is.');
test.done();
},
'config.get': function(test) {
@@ -72,7 +72,7 @@ exports['config'] = {
test.deepEqual(grunt.config.get(['obj', 'arr2']), [['foo', 'bar'], ['foo', 'bar']], 'Should expand <%= arr %> and <%= obj.Arr %> values as objects if possible.');
var buf = grunt.config.get('buffer');
test.ok(Buffer.isBuffer(buf), 'Should retrieve Buffer instances as Buffer.');
- test.deepEqual(buf, new Buffer('test'), 'Should return buffers as-is.');
+ test.deepEqual(buf, Buffer.from('test'), 'Should return buffers as-is.');
test.done();
},
'config.set': function(test) {
diff --git a/test/grunt/event_test.js b/test/grunt/event_test.js
index 0d7235316..c8359dc85 100644
--- a/test/grunt/event_test.js
+++ b/test/grunt/event_test.js
@@ -2,7 +2,7 @@
var grunt = require('../../lib/grunt');
-exports['event'] = function(test) {
+exports.event = function(test) {
test.expect(3);
grunt.event.on('test.foo', function(a, b, c) {
// This should get executed once (emit test.foo).
diff --git a/test/grunt/file_test.js b/test/grunt/file_test.js
index 939bdfc13..eafc57745 100644
--- a/test/grunt/file_test.js
+++ b/test/grunt/file_test.js
@@ -8,9 +8,18 @@ var path = require('path');
var Tempfile = require('temporary/lib/file');
var Tempdir = require('temporary/lib/dir');
+var win32 = process.platform === 'win32';
+
var tmpdir = new Tempdir();
-fs.symlinkSync(path.resolve('test/fixtures/octocat.png'), path.join(tmpdir.path, 'octocat.png'), 'file');
-fs.symlinkSync(path.resolve('test/fixtures/expand'), path.join(tmpdir.path, 'expand'), 'dir');
+try {
+ fs.symlinkSync(path.resolve('test/fixtures/octocat.png'), path.join(tmpdir.path, 'octocat.png'), 'file');
+ fs.symlinkSync(path.resolve('test/fixtures/expand'), path.join(tmpdir.path, 'expand'), 'dir');
+} catch (err) {
+ console.error('** ERROR: Cannot create symbolic links; link-related tests will fail.');
+ if (win32) {
+ console.error('** Tests must be run with Administrator privileges on Windows.');
+ }
+}
exports['file.match'] = {
'empty set': function(test) {
@@ -198,10 +207,10 @@ exports['file.expand*'] = {
'exclusion': function(test) {
test.expect(8);
test.deepEqual(grunt.file.expand(['!js/*.js']), [], 'solitary exclusion should match nothing');
- test.deepEqual(grunt.file.expand(['js/bar.js','!js/bar.js']), [], 'exclusion should cancel match');
+ test.deepEqual(grunt.file.expand(['js/bar.js', '!js/bar.js']), [], 'exclusion should cancel match');
test.deepEqual(grunt.file.expand(['**/*.js', '!js/foo.js']), ['js/bar.js'], 'should omit single file from matched set');
test.deepEqual(grunt.file.expand(['!js/foo.js', '**/*.js']), ['js/bar.js', 'js/foo.js'], 'inclusion / exclusion order matters');
- test.deepEqual(grunt.file.expand(['**/*.js', '**/*.css', '!js/bar.js', '!css/baz.css']), ['js/foo.js','css/qux.css'], 'multiple exclusions should be removed from the set');
+ test.deepEqual(grunt.file.expand(['**/*.js', '**/*.css', '!js/bar.js', '!css/baz.css']), ['js/foo.js', 'css/qux.css'], 'multiple exclusions should be removed from the set');
test.deepEqual(grunt.file.expand(['**/*.js', '**/*.css', '!**/*.css']), ['js/bar.js', 'js/foo.js'], 'excluded wildcards should be removed from the matched set');
test.deepEqual(grunt.file.expand(['js/bar.js', 'js/foo.js', 'css/baz.css', 'css/qux.css', '!**/b*.*']), ['js/foo.js', 'css/qux.css'], 'different pattern for exclusion should still work');
test.deepEqual(grunt.file.expand(['js/bar.js', '!**/b*.*', 'js/foo.js', 'css/baz.css', 'css/qux.css']), ['js/foo.js', 'css/baz.css', 'css/qux.css'], 'inclusion / exclusion order matters');
@@ -353,6 +362,7 @@ exports['file.expandMapping'] = {
filter: 'isFile',
cwd: 'expand',
flatten: true,
+ nosort: true,
rename: function(destBase, destPath) {
return path.join(destBase, 'all' + path.extname(destPath));
}
@@ -368,7 +378,6 @@ exports['file.expandMapping'] = {
},
};
-
// Compare two buffers. Returns true if they are equivalent.
var compareBuffers = function(buf1, buf2) {
if (!Buffer.isBuffer(buf1) || !Buffer.isBuffer(buf2)) { return false; }
@@ -384,7 +393,7 @@ var compareFiles = function(filepath1, filepath2) {
return compareBuffers(fs.readFileSync(filepath1), fs.readFileSync(filepath2));
};
-exports['file'] = {
+exports.file = {
setUp: function(done) {
this.defaultEncoding = grunt.file.defaultEncoding;
grunt.file.defaultEncoding = 'utf8';
@@ -443,21 +452,30 @@ exports['file'] = {
test.done();
},
'readYAML': function(test) {
- test.expect(3);
+ test.expect(5);
var obj;
obj = grunt.file.readYAML('test/fixtures/utf8.yaml');
- test.deepEqual(obj, this.object, 'file should be read as utf8 by default and parsed correctly.');
+ test.deepEqual(obj, this.object, 'file should be safely read as utf8 by default and parsed correctly.');
+
+ obj = grunt.file.readYAML('test/fixtures/utf8.yaml', null, {unsafeLoad: true});
+ test.deepEqual(obj, this.object, 'file should be unsafely read as utf8 by default and parsed correctly.');
obj = grunt.file.readYAML('test/fixtures/iso-8859-1.yaml', {encoding: 'iso-8859-1'});
test.deepEqual(obj, this.object, 'file should be read using the specified encoding.');
+ test.throws(function() {
+ obj = grunt.file.readYAML('test/fixtures/error.yaml');
+ }, function(err) {
+ return err.message.indexOf('undefined') === -1;
+ }, 'error thrown should not contain undefined.');
+
grunt.file.defaultEncoding = 'iso-8859-1';
obj = grunt.file.readYAML('test/fixtures/iso-8859-1.yaml');
test.deepEqual(obj, this.object, 'changing the default encoding should work.');
test.done();
},
'write': function(test) {
- test.expect(5);
+ test.expect(6);
var tmpfile;
tmpfile = new Tempfile();
grunt.file.write(tmpfile.path, this.string);
@@ -469,6 +487,13 @@ exports['file'] = {
test.strictEqual(grunt.file.read(tmpfile.path, {encoding: 'iso-8859-1'}), this.string, 'file should be written using the specified encoding.');
tmpfile.unlinkSync();
+ tmpfile = new Tempfile();
+ tmpfile.unlinkSync();
+ grunt.file.write(tmpfile.path, this.string, {mode: parseInt('0444', 8)});
+ test.strictEqual(fs.statSync(tmpfile.path).mode & parseInt('0222', 8), 0, 'file should be read only.');
+ fs.chmodSync(tmpfile.path, parseInt('0666', 8));
+ tmpfile.unlinkSync();
+
grunt.file.defaultEncoding = 'iso-8859-1';
tmpfile = new Tempfile();
grunt.file.write(tmpfile.path, this.string);
@@ -513,12 +538,13 @@ exports['file'] = {
test.done();
},
'copy and process': function(test) {
- test.expect(13);
+ test.expect(14);
var tmpfile;
tmpfile = new Tempfile();
grunt.file.copy('test/fixtures/utf8.txt', tmpfile.path, {
- process: function(src, filepath) {
- test.equal(filepath, 'test/fixtures/utf8.txt', 'filepath should be passed in, as-specified.');
+ process: function(src, srcpath, destpath) {
+ test.equal(srcpath, 'test/fixtures/utf8.txt', 'srcpath should be passed in, as-specified.');
+ test.equal(destpath, tmpfile.path, 'destpath should be passed in, as-specified.');
test.equal(Buffer.isBuffer(src), false, 'when no encoding is specified, use default encoding and process src as a string');
test.equal(typeof src, 'string', 'when no encoding is specified, use default encoding and process src as a string');
return 'føø' + src + 'bår';
@@ -544,7 +570,7 @@ exports['file'] = {
encoding: null,
process: function(src) {
test.ok(Buffer.isBuffer(src), 'when encoding is specified as null, process src as a buffer');
- return new Buffer('føø' + src.toString() + 'bår');
+ return Buffer.from('føø' + src.toString() + 'bår');
}
});
test.equal(grunt.file.read(tmpfile.path), 'føø' + this.string + 'bår', 'file should be saved as the buffer returned by process.');
@@ -604,6 +630,32 @@ exports['file'] = {
test.done();
},
+ 'copy directory recursively': function(test) {
+ test.expect(34);
+ var copyroot1 = path.join(tmpdir.path, 'copy-dir-1');
+ var copyroot2 = path.join(tmpdir.path, 'copy-dir-2');
+ grunt.file.copy('test/fixtures/expand/', copyroot1);
+ grunt.file.recurse('test/fixtures/expand/', function(srcpath, rootdir, subdir, filename) {
+ var destpath = path.join(copyroot1, subdir || '', filename);
+ test.ok(grunt.file.isFile(srcpath), 'file should have been copied.');
+ test.equal(grunt.file.read(srcpath), grunt.file.read(destpath), 'file contents should be the same.');
+ });
+ grunt.file.mkdir(path.join(copyroot1, 'empty'));
+ grunt.file.mkdir(path.join(copyroot1, 'deep/deeper/empty'));
+ grunt.file.copy(copyroot1, copyroot2, {
+ process: function(contents) {
+ return '<' + contents + '>';
+ },
+ });
+ test.ok(grunt.file.isDir(path.join(copyroot2, 'empty')), 'empty directory should have been created.');
+ test.ok(grunt.file.isDir(path.join(copyroot2, 'deep/deeper/empty')), 'empty directory should have been created.');
+ grunt.file.recurse('test/fixtures/expand/', function(srcpath, rootdir, subdir, filename) {
+ var destpath = path.join(copyroot2, subdir || '', filename);
+ test.ok(grunt.file.isFile(srcpath), 'file should have been copied.');
+ test.equal('<' + grunt.file.read(srcpath) + '>', grunt.file.read(destpath), 'file contents should be processed correctly.');
+ });
+ test.done();
+ },
'delete': function(test) {
test.expect(2);
var oldBase = process.cwd();
@@ -639,7 +691,7 @@ exports['file'] = {
test.equal(grunt.file.delete(path.join(outsidecwd, 'test.js')), false, 'should not delete anything outside the cwd.');
test.ok(this.warnCount, 'should issue a warning when deleting outside working directory');
- test.ok(grunt.file.delete(path.join(outsidecwd), {force:true}), 'should delete outside cwd when using the --force.');
+ test.ok(grunt.file.delete(path.join(outsidecwd), {force: true}), 'should delete outside cwd when using the --force.');
test.equal(grunt.file.exists(outsidecwd), false, 'file outside cwd should have been deleted when using the --force.');
grunt.file.setBase(oldBase);
@@ -728,11 +780,16 @@ exports['file'] = {
test.done();
},
'isLink': function(test) {
- test.expect(6);
+ test.expect(8);
test.equals(grunt.file.isLink('test/fixtures/octocat.png'), false, 'files are not links.');
test.equals(grunt.file.isLink('test/fixtures'), false, 'directories are not links.');
test.ok(grunt.file.isLink(path.join(tmpdir.path, 'octocat.png')), 'file links are links.');
test.ok(grunt.file.isLink(path.join(tmpdir.path, 'expand')), 'directory links are links.');
+ grunt.file.mkdir(path.join(tmpdir.path, 'relative-links'));
+ fs.symlinkSync('test/fixtures/octocat.png', path.join(tmpdir.path, 'relative-links/octocat.png'), 'file');
+ fs.symlinkSync('test/fixtures/expand', path.join(tmpdir.path, 'relative-links/expand'), 'file');
+ test.ok(grunt.file.isLink(path.join(tmpdir.path, 'relative-links/octocat.png')), 'relative file links are links.');
+ test.ok(grunt.file.isLink(path.join(tmpdir.path, 'relative-links/expand')), 'relative directory links are links.');
test.ok(grunt.file.isLink(tmpdir.path, 'octocat.png'), 'should work for paths in parts.');
test.equals(grunt.file.isLink('test/fixtures/does/not/exist'), false, 'nonexistent files are not links.');
test.done();
@@ -758,12 +815,17 @@ exports['file'] = {
test.done();
},
'isPathAbsolute': function(test) {
- test.expect(5);
+ test.expect(6);
test.ok(grunt.file.isPathAbsolute(path.resolve('/foo')), 'should return true');
test.ok(grunt.file.isPathAbsolute(path.resolve('/foo') + path.sep), 'should return true');
test.equal(grunt.file.isPathAbsolute('foo'), false, 'should return false');
test.ok(grunt.file.isPathAbsolute(path.resolve('test/fixtures/a.js')), 'should return true');
test.equal(grunt.file.isPathAbsolute('test/fixtures/a.js'), false, 'should return false');
+ if (win32) {
+ test.equal(grunt.file.isPathAbsolute('C:/Users/'), true, 'should return true');
+ } else {
+ test.equal(grunt.file.isPathAbsolute('/'), true, 'should return true');
+ }
test.done();
},
'arePathsEquivalent': function(test) {
@@ -831,5 +893,36 @@ exports['file'] = {
test.ok(grunt.file.isPathInCwd(path.resolve('deep')), 'subdirectory is in cwd');
test.done();
},
- }
+ 'symbolicLinkCopy': function(test) {
+ test.expect(4);
+ var srcfile = new Tempdir();
+ fs.symlinkSync(path.resolve('test/fixtures/octocat.png'), path.join(srcfile.path, 'octocat.png'), 'file');
+ // test symlink copy for files
+ var destdir = new Tempdir();
+ grunt.file.copy(path.join(srcfile.path, 'octocat.png'), path.join(destdir.path, 'octocat.png'));
+ test.ok(fs.lstatSync(path.join(srcfile.path, 'octocat.png')).isSymbolicLink());
+ test.ok(fs.lstatSync(path.join(destdir.path, 'octocat.png')).isSymbolicLink());
+
+ // test symlink copy for directories
+ var srcdir = new Tempdir();
+ var destdir = new Tempdir();
+ var fixtures = path.resolve('test/fixtures');
+ var symlinkSource = path.join(srcdir.path, path.basename(fixtures));
+ var destSource = path.join(destdir.path, path.basename(fixtures));
+ fs.symlinkSync(fixtures, symlinkSource, 'dir');
+
+ grunt.file.copy(symlinkSource, destSource);
+ test.ok(fs.lstatSync(symlinkSource).isSymbolicLink());
+ test.ok(fs.lstatSync(path.join(destdir.path, path.basename(fixtures))).isSymbolicLink());
+ test.done();
+ },
+ },
+ 'symbolicLinkDestError': function(test) {
+ test.expect(1);
+ var tmpfile = new Tempdir();
+ fs.symlinkSync(path.resolve('test/fixtures/octocat.png'), path.join(tmpfile.path, 'octocat.png'), 'file');
+ grunt.file.copy(path.resolve('test/fixtures/octocat.png'), path.join(tmpfile.path, 'octocat.png'));
+ test.ok(fs.lstatSync(path.join(tmpfile.path, 'octocat.png')).isSymbolicLink());
+ test.done();
+ },
};
diff --git a/test/grunt/option_test.js b/test/grunt/option_test.js
index 8917a57fe..382fba125 100644
--- a/test/grunt/option_test.js
+++ b/test/grunt/option_test.js
@@ -2,7 +2,7 @@
var grunt = require('../../lib/grunt');
-exports['option'] = {
+exports.option = {
setUp: function(done) {
grunt.option.init();
done();
@@ -13,15 +13,15 @@ exports['option'] = {
},
'option.init': function(test) {
test.expect(1);
- var expected = {foo:'bar',bool:true,'bar':{foo:'bar'}};
+ var expected = {foo: 'bar', bool: true, 'bar': {foo: 'bar'}};
test.deepEqual(grunt.option.init(expected), expected);
test.done();
},
'option': function(test) {
test.expect(4);
test.equal(grunt.option('foo', 'bar'), grunt.option('foo'));
- grunt.option('foo', {foo:'bar'});
- test.deepEqual(grunt.option('foo'), {foo:'bar'});
+ grunt.option('foo', {foo: 'bar'});
+ test.deepEqual(grunt.option('foo'), {foo: 'bar'});
test.equal(grunt.option('no-there'), false);
grunt.option('there', false);
test.equal(grunt.option('no-there'), true);
@@ -38,4 +38,15 @@ exports['option'] = {
test.deepEqual(grunt.option.flags(), ['--foo=bar', '--there', '--obj=[object Object]']);
test.done();
},
+ 'option.keys': function(test) {
+ test.expect(1);
+ grunt.option.init({
+ foo: 'bar',
+ there: true,
+ obj: {foo: 'bar'},
+ arr: []
+ });
+ test.deepEqual(grunt.option.keys(), ['foo', 'there', 'obj', 'arr']);
+ test.done();
+ }
};
diff --git a/test/grunt/template_test.js b/test/grunt/template_test.js
index b7903ac3d..e188b4738 100644
--- a/test/grunt/template_test.js
+++ b/test/grunt/template_test.js
@@ -2,7 +2,7 @@
var grunt = require('../../lib/grunt');
-exports['template'] = {
+exports.template = {
'process': function(test) {
test.expect(4);
var obj = {
diff --git a/test/gruntfile/load-npm-tasks.js b/test/gruntfile/load-npm-tasks.js
new file mode 100644
index 000000000..c8e3c04d4
--- /dev/null
+++ b/test/gruntfile/load-npm-tasks.js
@@ -0,0 +1,49 @@
+'use strict';
+
+var Log = require('grunt-legacy-log').Log;
+var assert = require('assert');
+var through = require('through2');
+
+function test(grunt, fixture) {
+ grunt.file.setBase('../fixtures/' + fixture);
+
+ // Create a custom log to assert output
+ var stdout = [];
+ var oldlog = grunt.log;
+ var stream = through(function(data, enc, next) {
+ stdout.push(data.toString());
+ next(null, data);
+ });
+ stream.pipe(process.stdout);
+ var log = new Log({
+ grunt: grunt,
+ outStream: stream,
+ });
+ grunt.log = log;
+
+ // Load a npm task
+ grunt.loadNpmTasks('grunt-foo-plugin');
+
+ // Run them
+ grunt.registerTask('default', ['foo', 'done']);
+
+ // Assert they loaded and ran correctly
+ grunt.registerTask('done', function() {
+ grunt.log = oldlog;
+ stdout = stdout.join('\n');
+ try {
+ assert.ok(stdout.indexOf('foo has ran.') !== -1, 'oh-four task should have ran.');
+ } catch (err) {
+ grunt.log.subhead(err.message);
+ grunt.log.error('Expected ' + err.expected + ' but actually: ' + err.actual);
+ throw err;
+ }
+ });
+}
+
+module.exports = function(grunt) {
+ // NPM task package is inside $CWD/node_modules
+ test(grunt, 'load-npm-tasks');
+ // NPM task package hoisted to $CWD/../node_modules
+ test(grunt, 'load-npm-tasks/test-package');
+};
diff --git a/test/gruntfile/multi-task-files.js b/test/gruntfile/multi-task-files.js
index 151173dc6..3a519f649 100644
--- a/test/gruntfile/multi-task-files.js
+++ b/test/gruntfile/multi-task-files.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2014 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
// For now, run this "test suite" with:
// grunt --gruntfile ./test/gruntfile/multi-task-files.js
@@ -34,7 +25,7 @@ module.exports = function(grunt) {
'dist/built1.js': ['src/*1.js', 'src/*2.js'],
// This is the "medium" format. The target name is arbitrary and can be
// used like "grunt run:built". Supports per-target options, templated
- // dest, and arbitrary "extra" paramters. Doesn't support >1 srcs-dest
+ // dest, and arbitrary "extra" parameters. Doesn't support >1 srcs-dest
// grouping.
built: {
options: {a: 2, c: 22},
@@ -59,7 +50,7 @@ module.exports = function(grunt) {
{'dist/built-<%= build %>-b.js': ['src/*1.js', 'src/*2.js']},
]
},
- // This "full" variant supports per srcs-dest arbitrary "extra" paramters.
+ // This "full" variant supports per srcs-dest arbitrary "extra" parameters.
long3: {
options: {a: 5, c: 55},
files: [
@@ -68,7 +59,7 @@ module.exports = function(grunt) {
]
},
// File mapping options can be specified in these 2 formats.
- built_mapping: {
+ builtMapping: {
options: {a: 6, c: 66},
expand: true,
cwd: '<%= mappings.cwd %>',
@@ -77,7 +68,7 @@ module.exports = function(grunt) {
rename: '<%= mappings.rename %>',
extra: 123
},
- long3_mapping: {
+ long3Mapping: {
options: {a: 7, c: 77},
files: [
{
@@ -90,22 +81,22 @@ module.exports = function(grunt) {
}
]
},
- long4_mapping: {
+ long4Mapping: {
options: {a: 8, c: 88},
files: [
- '<%= run.long3_mapping.files %>'
+ '<%= run.long3Mapping.files %>'
]
},
- long5_mapping: {
+ long5Mapping: {
options: {a: 9, c: 99},
files: [
- '<%= run.long3_mapping.files %>',
- '<%= run.long4_mapping.files %>'
+ '<%= run.long3Mapping.files %>',
+ '<%= run.long4Mapping.files %>'
]
},
// Need to ensure the task function is run if no files or options were
// specified!
- no_files_or_options: {},
+ noFilesOrOptions: {},
},
});
@@ -133,7 +124,7 @@ module.exports = function(grunt) {
});
var expecteds = {
- 'run:no_files_or_options': {
+ 'run:noFilesOrOptions': {
options: {a: 1, b: 11, d: 9},
files: [],
},
@@ -245,7 +236,7 @@ module.exports = function(grunt) {
},
],
},
- 'run:built_mapping': {
+ 'run:builtMapping': {
options: {a: 6, b: 11, c: 66, d: 9},
files: [
{
@@ -257,7 +248,7 @@ module.exports = function(grunt) {
cwd: grunt.config.get('mappings.cwd'),
src: ['*1.js', '*2.js'],
dest: grunt.config.get('mappings.dest'),
- rename: grunt.config.get('run.built_mapping.rename'),
+ rename: grunt.config.get('run.builtMapping.rename'),
extra: 123,
},
},
@@ -267,16 +258,16 @@ module.exports = function(grunt) {
extra: 123,
orig: {
expand: true,
- cwd: grunt.config.get('run.built_mapping.cwd'),
+ cwd: grunt.config.get('run.builtMapping.cwd'),
src: ['*1.js', '*2.js'],
- dest: grunt.config.get('run.built_mapping.dest'),
- rename: grunt.config.get('run.built_mapping.rename'),
+ dest: grunt.config.get('run.builtMapping.dest'),
+ rename: grunt.config.get('run.builtMapping.rename'),
extra: 123,
},
},
],
},
- 'run:long3_mapping': {
+ 'run:long3Mapping': {
options: {a: 7, b: 11, c: 77, d: 9},
files: [
{
@@ -301,13 +292,13 @@ module.exports = function(grunt) {
cwd: grunt.config.get('mappings.cwd'),
src: ['*1.js', '*2.js'],
dest: grunt.config.get('mappings.dest'),
- rename: grunt.config.get('run.built_mapping.rename'),
+ rename: grunt.config.get('run.builtMapping.rename'),
extra: 123,
},
},
],
},
- 'run:long4_mapping': {
+ 'run:long4Mapping': {
options: {a: 8, b: 11, c: 88, d: 9},
files: [
{
@@ -332,13 +323,13 @@ module.exports = function(grunt) {
cwd: grunt.config.get('mappings.cwd'),
src: ['*1.js', '*2.js'],
dest: grunt.config.get('mappings.dest'),
- rename: grunt.config.get('run.built_mapping.rename'),
+ rename: grunt.config.get('run.builtMapping.rename'),
extra: 123,
},
},
],
},
- 'run:long5_mapping': {
+ 'run:long5Mapping': {
options: {a: 9, b: 11, c: 99, d: 9},
files: [
{
@@ -363,7 +354,7 @@ module.exports = function(grunt) {
cwd: grunt.config.get('mappings.cwd'),
src: ['*1.js', '*2.js'],
dest: grunt.config.get('mappings.dest'),
- rename: grunt.config.get('run.built_mapping.rename'),
+ rename: grunt.config.get('run.builtMapping.rename'),
extra: 123,
},
},
@@ -389,7 +380,7 @@ module.exports = function(grunt) {
cwd: grunt.config.get('mappings.cwd'),
src: ['*1.js', '*2.js'],
dest: grunt.config.get('mappings.dest'),
- rename: grunt.config.get('run.built_mapping.rename'),
+ rename: grunt.config.get('run.builtMapping.rename'),
extra: 123,
},
},
@@ -436,8 +427,8 @@ module.exports = function(grunt) {
});
grunt.registerTask('default', [
- 'run:no_files_or_options',
- 'test:no_files_or_options',
+ 'run:noFilesOrOptions',
+ 'test:noFilesOrOptions',
'run:dist/built.js',
'test:dist/built.js',
'run:dist/built1.js',
@@ -450,14 +441,14 @@ module.exports = function(grunt) {
'test:long2',
'run:long3',
'test:long3',
- 'run:built_mapping',
- 'test:built_mapping',
- 'run:long3_mapping',
- 'test:long3_mapping',
- 'run:long4_mapping',
- 'test:long4_mapping',
- 'run:long5_mapping',
- 'test:long5_mapping',
+ 'run:builtMapping',
+ 'test:builtMapping',
+ 'run:long3Mapping',
+ 'test:long3Mapping',
+ 'run:long4Mapping',
+ 'test:long4Mapping',
+ 'run:long5Mapping',
+ 'test:long5Mapping',
'run',
'test:all',
'test:counters',
diff --git a/test/util/task_test.js b/test/util/task_test.js
index 9c96cc308..379162011 100644
--- a/test/util/task_test.js
+++ b/test/util/task_test.js
@@ -26,7 +26,7 @@ exports['new Task'] = {
}
};
-exports['Tasks'] = {
+exports.Tasks = {
setUp: function(done) {
result.reset();
this.task = requireTask().create();
@@ -278,6 +278,17 @@ exports['Tasks'] = {
});
task.run('a', 'h').start();
},
+ 'Task#run (async task with multiple callbacks)': function(test) {
+ test.expect(1);
+ var task = this.task;
+ task.registerTask('a', 'Call async callback twice.', function() { var done = this.async(); done(); done(); });
+ task.registerTask('b', 'Never call async callback.', function() { this.async(); });
+ task.run('a', 'b').start();
+ delay(function() {
+ test.deepEqual(task.current.name, 'b', 'Should be stuck on task with no async callback');
+ test.done();
+ });
+ },
'Task#current': function(test) {
test.expect(8);
var task = this.task;