├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github └── workflows │ ├── dev.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── lib ├── get-task.js ├── helpers │ ├── buildTree.js │ ├── createExtensions.js │ ├── metadata.js │ ├── normalizeArgs.js │ └── validateRegistry.js ├── last-run.js ├── parallel.js ├── registry.js ├── series.js ├── set-task.js ├── task.js └── tree.js ├── package.json └── test ├── fixtures ├── taskTree │ ├── aliasNested.js │ ├── aliasSimple.js │ ├── doubleLevel.js │ ├── simple.js │ ├── singleLevel.js │ └── tripleLevel.js ├── test.js └── testMore.js ├── integration.js ├── last-run.js ├── parallel.js ├── registry.js ├── series.js ├── task.js └── tree.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | end_of_line = lf 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | test/fixtures/out 3 | test/fixtures/tmp 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "gulp" 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | name: dev 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | env: 9 | CI: true 10 | 11 | jobs: 12 | prettier: 13 | name: Format code 14 | runs-on: ubuntu-latest 15 | if: ${{ github.event_name == 'push' }} 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: Prettier 22 | uses: gulpjs/prettier_action@v3.0 23 | with: 24 | commit_message: 'chore: Run prettier' 25 | prettier_options: '--write .' 26 | 27 | test: 28 | name: Tests for Node ${{ matrix.node }} on ${{ matrix.os }} 29 | runs-on: ${{ matrix.os }} 30 | 31 | strategy: 32 | fail-fast: false 33 | matrix: 34 | node: [10, 12, 14, 16] 35 | os: [ubuntu-latest, windows-latest, macos-latest] 36 | 37 | steps: 38 | - name: Clone repository 39 | uses: actions/checkout@v2 40 | 41 | - name: Set Node.js version 42 | uses: actions/setup-node@v2 43 | with: 44 | node-version: ${{ matrix.node }} 45 | 46 | - run: node --version 47 | - run: npm --version 48 | 49 | - name: Install npm dependencies 50 | run: npm install 51 | 52 | - name: Run lint 53 | run: npm run lint 54 | 55 | - name: Run tests 56 | run: npm test 57 | 58 | - name: Coveralls 59 | uses: coverallsapp/github-action@v1.1.2 60 | with: 61 | github-token: ${{ secrets.GITHUB_TOKEN }} 62 | flag-name: ${{matrix.os}}-node-${{ matrix.node }} 63 | parallel: true 64 | 65 | coveralls: 66 | needs: test 67 | name: Finish up 68 | 69 | runs-on: ubuntu-latest 70 | steps: 71 | - name: Coveralls Finished 72 | uses: coverallsapp/github-action@v1.1.2 73 | with: 74 | github-token: ${{ secrets.GITHUB_TOKEN }} 75 | parallel-finished: true 76 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | 8 | jobs: 9 | release-please: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: GoogleCloudPlatform/release-please-action@v2 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | release-type: node 16 | package-name: release-please-action 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # Garbage files 64 | .DS_Store 65 | 66 | # Test results 67 | test.xunit 68 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | .nyc_output/ 3 | CHANGELOG.md 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.0.0](https://www.github.com/gulpjs/undertaker/compare/v1.3.0...v2.0.0) (2024-03-22) 4 | 5 | 6 | ### ⚠ BREAKING CHANGES 7 | 8 | * Stop inspecting UNDERTAKER_TIME_RESOLUTION environment variable (#98) 9 | * Normalize repository, dropping node <10.13 support (#97) 10 | 11 | ### Features 12 | 13 | * Attach properties to missing task error ([#102](https://www.github.com/gulpjs/undertaker/issues/102)) ([989d9e9](https://www.github.com/gulpjs/undertaker/commit/989d9e90bd65f50b54c89ab2e62a628db6af1c47)) 14 | 15 | 16 | ### Miscellaneous Chores 17 | 18 | * Normalize repository, dropping node <10.13 support ([#97](https://www.github.com/gulpjs/undertaker/issues/97)) ([b270c28](https://www.github.com/gulpjs/undertaker/commit/b270c2852c8398c26f1ac5b1fb80cd6e2aebdf89)) 19 | * Stop inspecting UNDERTAKER_TIME_RESOLUTION environment variable ([#98](https://www.github.com/gulpjs/undertaker/issues/98)) ([e255fc7](https://www.github.com/gulpjs/undertaker/commit/e255fc7c9be27d11dc8a711a3f15e058040e712c)) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014, 2016-2020, 2022 Blaine Bublitz and Eric Schoffstall . 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | # undertaker 8 | 9 | [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][ci-image]][ci-url] [![Coveralls Status][coveralls-image]][coveralls-url] 10 | 11 | Task registry that allows composition through series/parallel methods. 12 | 13 | ## Usage 14 | 15 | ```js 16 | var fs = require('fs'); 17 | var Undertaker = require('undertaker'); 18 | 19 | var taker = new Undertaker(); 20 | 21 | taker.task('task1', function(cb){ 22 | // do things 23 | 24 | cb(); // when everything is done 25 | }); 26 | 27 | taker.task('task2', function(){ 28 | return fs.createReadStream('./myFile.js') 29 | .pipe(fs.createWriteStream('./myFile.copy.js')); 30 | }); 31 | 32 | taker.task('task3', function(){ 33 | return new Promise(function(resolve, reject){ 34 | // do things 35 | 36 | resolve(); // when everything is done 37 | }); 38 | }); 39 | 40 | taker.task('combined', taker.series('task1', 'task2')); 41 | 42 | taker.task('all', taker.parallel('combined', 'task3')); 43 | ``` 44 | 45 | ## API 46 | 47 | __Task functions can be completed in any of the ways supported by 48 | [`async-done`][async-resolution]__ 49 | 50 | ### `new Undertaker([registryInstance])` 51 | 52 | The constructor is used to create a new instance of `Undertaker`. Each instance of 53 | `Undertaker` gets its own instance of a registry. By default, the registry is an 54 | instance of [`undertaker-registry`][undertaker-registry] 55 | but it can be an instance of any other registry that follows the [Custom Registries API][custom-registries]. 56 | 57 | To use a custom registry, pass a custom registry instance (`new CustomRegistry([options])`) when 58 | instantiating a new `Undertaker` instance. This will use the custom registry instance for that `Undertaker` instance. 59 | 60 | ### `task([taskName,] fn)` 61 | 62 | Both a `getter` and `setter` for tasks. 63 | 64 | If a string (`taskName`) is given as the only argument, it behaves as a `getter` 65 | and returns the wrapped task (not the original function). The wrapped task has a `unwrap` 66 | method that will return the original function. 67 | 68 | If a function (`fn`) and optionally a string (`taskName`) is given, it behaves as 69 | a `setter` and will register the task by the `taskName`. If `taskName` is not 70 | specified, the `name` or `displayName` property of the function is used as the `taskName`. 71 | 72 | Will throw if: 73 | 74 | * As a `getter`: `taskName` is missing or not a string. 75 | * As a `setter`: `taskName` is missing and `fn` is anonymous. 76 | * As a `setter`: `fn` is missing or not a function. 77 | 78 | ### `series(taskName || fn...)` 79 | 80 | Takes a variable amount of strings (`taskName`) and/or functions (`fn`) and 81 | returns a function of the composed tasks or functions. Any `taskNames` are 82 | retrieved from the registry using the `get` method. 83 | 84 | When the returned function is executed, the tasks or functions will be executed 85 | in series, each waiting for the prior to finish. If an error occurs, execution 86 | will stop. 87 | 88 | ### `parallel(taskName || fn...)` 89 | 90 | Takes a variable amount of strings (`taskName`) and/or functions (`fn`) and 91 | returns a function of the composed tasks or functions. Any `taskNames` are 92 | retrieved from the registry using the `get` method. 93 | 94 | When the returned function is executed, the tasks or functions will be executed 95 | in parallel, all being executed at the same time. If an error occurs, all execution 96 | will complete. 97 | 98 | ### `registry([registryInstance])` 99 | 100 | Optionally takes an instantiated registry object. If no arguments are passed, returns 101 | the current registry object. If an instance of a registry (`customRegistry`) is passed 102 | the tasks from the current registry will be transferred to it and the current registry 103 | will be replaced with the new registry. 104 | 105 | The ability to assign new registries will allow you to pre-define/share tasks or add 106 | custom functionality to your registries. See [Custom Registries][custom-registries] 107 | for more information. 108 | 109 | ### `tree([options])` 110 | 111 | Optionally takes an `options` object and returns an object representing the 112 | tree of registered tasks. The object returned is [`archy`][archy] 113 | compatible. Also, each node has a `type` property that can be used to determine if the node is a `task` or `function`. 114 | 115 | #### `options` 116 | 117 | ##### `options.deep` 118 | 119 | Whether or not the whole tree should be returned. 120 | 121 | Type: `Boolean` 122 | 123 | Default: `false` 124 | 125 | ### `lastRun(task, [precision])` 126 | 127 | Takes a string or function (`task`) and returns a timestamp of the last time the task 128 | was run successfully. The time will be the time the task started. 129 | 130 | Returns `undefined` if the task has not been run. 131 | 132 | If a task errors, the result of `lastRun` will be undefined because the task 133 | should probably be re-run from scratch to get into a good state again. 134 | 135 | The timestamp is always given in millisecond but the time resolution can be 136 | rounded using the `precision` parameter. The use case is to be able to compare a build time 137 | to a file time attribute. 138 | 139 | Assuming `undertakerInst.lastRun('someTask')` returns `1426000001111`, 140 | `undertakerInst.lastRun('someTask', 1000)` returns `1426000001000`. 141 | 142 | The default time resolution is `1`. 143 | 144 | ## Custom Registries 145 | 146 | Custom registries are constructor functions allowing you to pre-define/share tasks 147 | or add custom functionality to your registries. 148 | 149 | A registry's prototype should define: 150 | 151 | - `init(taker)`: receives the undertaker instance to set pre-defined tasks using the `task(taskName, fn)` method. 152 | - `get(taskName)`: returns the task with that name 153 | or `undefined` if no task is registered with that name. 154 | - `set(taskName, fn)`: add task to the registry. If `set` modifies a task, it should return the new task. 155 | - `tasks()`: returns an object listing all tasks in the registry. 156 | 157 | You should not call these functions yourself; leave that to Undertaker, so it can 158 | keep its metadata consistent. 159 | 160 | The easiest way to create a custom registry is to inherit from [undertaker-registry]: 161 | 162 | ```js 163 | var util = require('util'); 164 | 165 | var DefaultRegistry = require('undertaker-registry'); 166 | 167 | function MyRegistry(){ 168 | DefaultRegistry.call(this); 169 | } 170 | 171 | util.inherits(MyRegistry, DefaultRegistry); 172 | 173 | module.exports = MyRegistry; 174 | ``` 175 | 176 | ### Sharing tasks 177 | 178 | To share common tasks with all your projects, you can expose an `init` method on the registry 179 | prototype and it will receive the `Undertaker` instance as the only argument. You can then use 180 | `undertaker.task(name, fn)` to register pre-defined tasks. 181 | 182 | For example you might want to share a `clean` task: 183 | 184 | ```js 185 | var fs = require('fs'); 186 | var util = require('util'); 187 | 188 | var DefaultRegistry = require('undertaker-registry'); 189 | var del = require('del'); 190 | 191 | function CommonRegistry(opts){ 192 | DefaultRegistry.call(this); 193 | 194 | opts = opts || {}; 195 | 196 | this.buildDir = opts.buildDir || './build'; 197 | } 198 | 199 | util.inherits(CommonRegistry, DefaultRegistry); 200 | 201 | CommonRegistry.prototype.init = function(takerInst){ 202 | var buildDir = this.buildDir; 203 | var exists = fs.existsSync(buildDir); 204 | 205 | if(exists){ 206 | throw new Error('Cannot initialize common tasks. ' + buildDir + ' directory exists.'); 207 | } 208 | 209 | takerInst.task('clean', function(){ 210 | return del([buildDir]); 211 | }); 212 | } 213 | 214 | module.exports = CommonRegistry; 215 | ``` 216 | 217 | Then to use it in a project: 218 | ```js 219 | var Undertaker = require('undertaker'); 220 | var CommonRegistry = require('myorg-common-tasks'); 221 | 222 | var taker = new Undertaker(CommonRegistry({ buildDir: '/dist' })); 223 | 224 | taker.task('build', taker.series('clean', function build(cb) { 225 | // do things 226 | cb(); 227 | })); 228 | ``` 229 | 230 | ### Sharing Functionalities 231 | 232 | By controlling how tasks are added to the registry, you can decorate them. 233 | 234 | For example if you wanted all tasks to share some data, you can use a custom registry 235 | to bind them to that data. Be sure to return the altered task, as per the description 236 | of registry methods above: 237 | 238 | ```js 239 | var util = require('util'); 240 | 241 | var Undertaker = require('undertaker'); 242 | var DefaultRegistry = require('undertaker-registry'); 243 | 244 | // Some task defined somewhere else 245 | var BuildRegistry = require('./build.js'); 246 | var ServeRegistry = require('./serve.js'); 247 | 248 | function ConfigRegistry(config){ 249 | DefaultRegistry.call(this); 250 | this.config = config; 251 | } 252 | 253 | util.inherits(ConfigRegistry, DefaultRegistry); 254 | 255 | ConfigRegistry.prototype.set = function set(name, fn) { 256 | // The `DefaultRegistry` uses `this._tasks` for storage. 257 | var task = this._tasks[name] = fn.bind(this.config); 258 | return task; 259 | }; 260 | 261 | var taker = new Undertaker(); 262 | 263 | taker.registry(new BuildRegistry()); 264 | taker.registry(new ServeRegistry()); 265 | 266 | // `taker.registry` will reset each task in the registry with 267 | // `ConfigRegistry.prototype.set` which will bind them to the config object. 268 | taker.registry(new ConfigRegistry({ 269 | src: './src', 270 | build: './build', 271 | bindTo: '0.0.0.0:8888' 272 | })); 273 | 274 | taker.task('default', taker.series('clean', 'build', 'serve', function(cb) { 275 | console.log('Server bind to ' + this.bindTo); 276 | console.log('Serving' + this.build); 277 | cb(); 278 | })); 279 | ``` 280 | 281 | ### In the wild 282 | 283 | * [undertaker-registry] - Custom registries probably want to inherit from this. 284 | * [undertaker-forward-reference] - Custom registry supporting forward referenced tasks (similar to gulp 3.x). 285 | * [undertaker-task-metadata] - Proof-of-concept custom registry that attaches metadata to each task. 286 | * [undertaker-common-tasks] - Proof-of-concept custom registry that pre-defines some tasks. 287 | * [alchemist-gulp] - A default set of tasks for building alchemist plugins. 288 | * [gulp-hub] - Custom registry to run tasks in multiple gulpfiles. (In a branch as of this writing) 289 | * [gulp-pipeline] - [RailsRegistry][rails-registry] is an ES2015 class that provides a gulp pipeline replacement for rails applications 290 | 291 | ## License 292 | 293 | MIT 294 | 295 | 296 | [downloads-image]: https://img.shields.io/npm/dm/undertaker.svg?style=flat-square 297 | [npm-url]: https://www.npmjs.com/package/undertaker 298 | [npm-image]: https://img.shields.io/npm/v/undertaker.svg?style=flat-square 299 | 300 | [ci-url]: https://github.com/gulpjs/undertaker/actions?query=workflow:dev 301 | [ci-image]: https://img.shields.io/github/workflow/status/gulpjs/undertaker/dev?style=flat-square 302 | 303 | [coveralls-url]: https://coveralls.io/r/gulpjs/undertaker 304 | [coveralls-image]: https://img.shields.io/coveralls/gulpjs/undertaker/master.svg?style=flat-square 305 | 306 | 307 | 308 | [async-resolution]: https://github.com/phated/async-done#completion-and-error-resolution 309 | [undertaker-registry]: https://github.com/gulpjs/undertaker-registry 310 | [custom-registries]: #custom-registries 311 | [archy]: https://www.npmjs.org/package/archy 312 | [undertaker-forward-reference]: https://github.com/gulpjs/undertaker-forward-reference 313 | [undertaker-task-metadata]: https://github.com/gulpjs/undertaker-task-metadata 314 | [undertaker-common-tasks]: https://github.com/gulpjs/undertaker-common-tasks 315 | [alchemist-gulp]: https://github.com/webdesserts/alchemist-gulp 316 | [gulp-hub]: https://github.com/frankwallis/gulp-hub/tree/registry-init 317 | [gulp-pipeline]: https://github.com/alienfast/gulp-pipeline 318 | [rails-registry]: https://github.com/alienfast/gulp-pipeline/blob/master/src/registry/railsRegistry.js 319 | 320 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var inherits = require('util').inherits; 4 | var EventEmitter = require('events').EventEmitter; 5 | 6 | var DefaultRegistry = require('undertaker-registry'); 7 | 8 | var tree = require('./lib/tree'); 9 | var task = require('./lib/task'); 10 | var series = require('./lib/series'); 11 | var lastRun = require('./lib/last-run'); 12 | var parallel = require('./lib/parallel'); 13 | var registry = require('./lib/registry'); 14 | var _getTask = require('./lib/get-task'); 15 | var _setTask = require('./lib/set-task'); 16 | 17 | function Undertaker(customRegistry) { 18 | EventEmitter.call(this); 19 | 20 | this._registry = new DefaultRegistry(); 21 | if (customRegistry) { 22 | this.registry(customRegistry); 23 | } 24 | 25 | this._settle = (process.env.UNDERTAKER_SETTLE === 'true'); 26 | } 27 | 28 | inherits(Undertaker, EventEmitter); 29 | 30 | 31 | Undertaker.prototype.tree = tree; 32 | 33 | Undertaker.prototype.task = task; 34 | 35 | Undertaker.prototype.series = series; 36 | 37 | Undertaker.prototype.lastRun = lastRun; 38 | 39 | Undertaker.prototype.parallel = parallel; 40 | 41 | Undertaker.prototype.registry = registry; 42 | 43 | Undertaker.prototype._getTask = _getTask; 44 | 45 | Undertaker.prototype._setTask = _setTask; 46 | 47 | module.exports = Undertaker; 48 | -------------------------------------------------------------------------------- /lib/get-task.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function get(name) { 4 | return this._registry.get(name); 5 | } 6 | 7 | module.exports = get; 8 | -------------------------------------------------------------------------------- /lib/helpers/buildTree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var metadata = require('./metadata'); 4 | 5 | function buildTree(tasks) { 6 | return Object.values(tasks).map(function(task) { 7 | var meta = metadata.get(task); 8 | if (meta) { 9 | return meta.tree; 10 | } 11 | 12 | var name = task.displayName || task.name || ''; 13 | meta = { 14 | name: name, 15 | tree: { 16 | label: name, 17 | type: 'function', 18 | nodes: [], 19 | }, 20 | }; 21 | 22 | metadata.set(task, meta); 23 | return meta.tree; 24 | }); 25 | } 26 | 27 | module.exports = buildTree; 28 | -------------------------------------------------------------------------------- /lib/helpers/createExtensions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var captureLastRun = require('last-run').capture; 4 | var releaseLastRun = require('last-run').release; 5 | 6 | var metadata = require('./metadata'); 7 | 8 | var uid = 0; 9 | 10 | function Storage(fn) { 11 | var meta = metadata.get(fn); 12 | 13 | this.fn = meta.orig || fn; 14 | this.uid = uid++; 15 | this.name = meta.name; 16 | this.branch = meta.branch || false; 17 | this.captureTime = Date.now(); 18 | this.startHr = []; 19 | } 20 | 21 | Storage.prototype.capture = function() { 22 | captureLastRun(this.fn, this.captureTime); 23 | }; 24 | 25 | Storage.prototype.release = function() { 26 | releaseLastRun(this.fn); 27 | }; 28 | 29 | function createExtensions(ee) { 30 | return { 31 | create: function(fn) { 32 | return new Storage(fn); 33 | }, 34 | before: function(storage) { 35 | storage.startHr = process.hrtime(); 36 | ee.emit('start', { 37 | uid: storage.uid, 38 | name: storage.name, 39 | branch: storage.branch, 40 | time: Date.now(), 41 | }); 42 | }, 43 | after: function(result, storage) { 44 | if (result && result.state === 'error') { 45 | return this.error(result.value, storage); 46 | } 47 | storage.capture(); 48 | ee.emit('stop', { 49 | uid: storage.uid, 50 | name: storage.name, 51 | branch: storage.branch, 52 | duration: process.hrtime(storage.startHr), 53 | time: Date.now(), 54 | }); 55 | }, 56 | error: function(error, storage) { 57 | if (Array.isArray(error)) { 58 | error = error[0]; 59 | } 60 | storage.release(); 61 | ee.emit('error', { 62 | uid: storage.uid, 63 | name: storage.name, 64 | branch: storage.branch, 65 | error: error, 66 | duration: process.hrtime(storage.startHr), 67 | time: Date.now(), 68 | }); 69 | }, 70 | }; 71 | } 72 | 73 | module.exports = createExtensions; 74 | -------------------------------------------------------------------------------- /lib/helpers/metadata.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var metadata = new WeakMap(); 4 | 5 | module.exports = metadata; 6 | -------------------------------------------------------------------------------- /lib/helpers/normalizeArgs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var levenshtein = require('fast-levenshtein'); 6 | 7 | function normalizeArgs(registry, args) { 8 | function getFunction(task) { 9 | if (typeof task === 'function') { 10 | return task; 11 | } 12 | 13 | var fn = registry.get(task); 14 | if (!fn) { 15 | var similar = similarTasks(registry, task); 16 | var err; 17 | if (similar.length > 0) { 18 | err = new Error('Task never defined: ' + task + ' - did you mean? ' + similar.join(', ')); 19 | err.task = task; 20 | err.similar = similar; 21 | } else { 22 | err = new Error('Task never defined: ' + task); 23 | err.task = task; 24 | } 25 | throw err; 26 | } 27 | return fn; 28 | } 29 | 30 | var flattenArgs = Array.prototype.concat.apply([], args); 31 | assert(flattenArgs.length, 'One or more tasks should be combined using series or parallel'); 32 | 33 | return flattenArgs.map(getFunction); 34 | } 35 | 36 | function similarTasks(registry, queryTask) { 37 | if (typeof queryTask !== 'string') { 38 | return []; 39 | } 40 | 41 | var tasks = registry.tasks(); 42 | var similarTasks = []; 43 | for (var task in tasks) { 44 | if (Object.prototype.hasOwnProperty.call(tasks, task)) { 45 | var distance = levenshtein.get(task, queryTask); 46 | var allowedDistance = Math.floor(0.4 * task.length) + 1; 47 | if (distance < allowedDistance) { 48 | similarTasks.push(task); 49 | } 50 | } 51 | } 52 | return similarTasks; 53 | } 54 | 55 | module.exports = normalizeArgs; 56 | -------------------------------------------------------------------------------- /lib/helpers/validateRegistry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | function isFunction(fn) { 6 | return typeof fn === 'function'; 7 | } 8 | 9 | function isConstructor(registry) { 10 | if (!(registry && registry.prototype)) { 11 | return false; 12 | } 13 | 14 | var hasProtoGet = isFunction(registry.prototype.get); 15 | var hasProtoSet = isFunction(registry.prototype.set); 16 | var hasProtoInit = isFunction(registry.prototype.init); 17 | var hasProtoTasks = isFunction(registry.prototype.tasks); 18 | 19 | if (hasProtoGet || hasProtoSet || hasProtoInit || hasProtoTasks) { 20 | return true; 21 | } 22 | 23 | return false; 24 | } 25 | 26 | function validateRegistry(registry) { 27 | try { 28 | assert(isFunction(registry.get), 'Custom registry must have `get` function'); 29 | assert(isFunction(registry.set), 'Custom registry must have `set` function'); 30 | assert(isFunction(registry.init), 'Custom registry must have `init` function'); 31 | assert(isFunction(registry.tasks), 'Custom registry must have `tasks` function'); 32 | } catch (err) { 33 | if (isConstructor(registry)) { 34 | assert(false, 'Custom registries must be instantiated, but it looks like you passed a constructor'); 35 | } else { 36 | throw err; 37 | } 38 | } 39 | } 40 | 41 | module.exports = validateRegistry; 42 | -------------------------------------------------------------------------------- /lib/last-run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var retrieveLastRun = require('last-run'); 4 | 5 | var metadata = require('./helpers/metadata'); 6 | 7 | function lastRun(task, timeResolution) { 8 | var fn = task; 9 | if (typeof task === 'string') { 10 | fn = this._getTask(task); 11 | } 12 | 13 | var meta = metadata.get(fn); 14 | 15 | if (meta) { 16 | fn = meta.orig || fn; 17 | } 18 | 19 | return retrieveLastRun(fn, timeResolution); 20 | } 21 | 22 | module.exports = lastRun; 23 | -------------------------------------------------------------------------------- /lib/parallel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var bach = require('bach'); 4 | 5 | var metadata = require('./helpers/metadata'); 6 | var buildTree = require('./helpers/buildTree'); 7 | var normalizeArgs = require('./helpers/normalizeArgs'); 8 | var createExtensions = require('./helpers/createExtensions'); 9 | 10 | function parallel() { 11 | var create = this._settle ? bach.settleParallel : bach.parallel; 12 | 13 | var args = normalizeArgs(this._registry, arguments); 14 | var extensions = createExtensions(this); 15 | var fn = create(args, extensions); 16 | var name = ''; 17 | 18 | metadata.set(fn, { 19 | name: name, 20 | branch: true, 21 | tree: { 22 | label: name, 23 | type: 'function', 24 | branch: true, 25 | nodes: buildTree(args), 26 | }, 27 | }); 28 | return fn; 29 | } 30 | 31 | module.exports = parallel; 32 | -------------------------------------------------------------------------------- /lib/registry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var validateRegistry = require('./helpers/validateRegistry'); 4 | 5 | function setTasks(registry, nameAndTask) { 6 | var name = nameAndTask[0]; 7 | var task = nameAndTask[1]; 8 | registry.set(name, task); 9 | return registry; 10 | } 11 | 12 | function registry(newRegistry) { 13 | if (!newRegistry) { 14 | return this._registry; 15 | } 16 | 17 | validateRegistry(newRegistry); 18 | 19 | var tasks = this._registry.tasks(); 20 | 21 | this._registry = Object.entries(tasks).reduce(setTasks, newRegistry); 22 | this._registry.init(this); 23 | } 24 | 25 | module.exports = registry; 26 | -------------------------------------------------------------------------------- /lib/series.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var bach = require('bach'); 4 | 5 | var metadata = require('./helpers/metadata'); 6 | var buildTree = require('./helpers/buildTree'); 7 | var normalizeArgs = require('./helpers/normalizeArgs'); 8 | var createExtensions = require('./helpers/createExtensions'); 9 | 10 | function series() { 11 | var create = this._settle ? bach.settleSeries : bach.series; 12 | 13 | var args = normalizeArgs(this._registry, arguments); 14 | var extensions = createExtensions(this); 15 | var fn = create(args, extensions); 16 | var name = ''; 17 | 18 | metadata.set(fn, { 19 | name: name, 20 | branch: true, 21 | tree: { 22 | label: name, 23 | type: 'function', 24 | branch: true, 25 | nodes: buildTree(args), 26 | }, 27 | }); 28 | return fn; 29 | } 30 | 31 | module.exports = series; 32 | -------------------------------------------------------------------------------- /lib/set-task.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | 5 | var metadata = require('./helpers/metadata'); 6 | 7 | function set(name, fn) { 8 | assert(name, 'Task name must be specified'); 9 | assert(typeof name === 'string', 'Task name must be a string'); 10 | assert(typeof fn === 'function', 'Task function must be specified'); 11 | 12 | function taskWrapper() { 13 | return fn.apply(this, arguments); 14 | } 15 | 16 | function unwrap() { 17 | return fn; 18 | } 19 | 20 | taskWrapper.unwrap = unwrap; 21 | taskWrapper.displayName = name; 22 | 23 | var meta = metadata.get(fn) || {}; 24 | var nodes = []; 25 | if (meta.branch) { 26 | nodes.push(meta.tree); 27 | } 28 | 29 | var task = this._registry.set(name, taskWrapper) || taskWrapper; 30 | 31 | metadata.set(task, { 32 | name: name, 33 | orig: fn, 34 | tree: { 35 | label: name, 36 | type: 'task', 37 | nodes: nodes, 38 | }, 39 | }); 40 | } 41 | 42 | module.exports = set; 43 | -------------------------------------------------------------------------------- /lib/task.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function task(name, fn) { 4 | if (typeof name === 'function') { 5 | fn = name; 6 | name = fn.displayName || fn.name; 7 | } 8 | 9 | if (!fn) { 10 | return this._getTask(name); 11 | } 12 | 13 | this._setTask(name, fn); 14 | } 15 | 16 | module.exports = task; 17 | -------------------------------------------------------------------------------- /lib/tree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var metadata = require('./helpers/metadata'); 4 | 5 | function tree(opts) { 6 | opts = Object.assign({ deep: false }, opts); 7 | 8 | var tasks = this._registry.tasks(); 9 | var nodes = Object.values(tasks).map(function(task) { 10 | var meta = metadata.get(task); 11 | 12 | if (opts.deep) { 13 | return meta.tree; 14 | } 15 | 16 | return meta.tree.label; 17 | }); 18 | 19 | return { 20 | label: 'Tasks', 21 | nodes: nodes, 22 | }; 23 | } 24 | 25 | module.exports = tree; 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "undertaker", 3 | "version": "2.0.0", 4 | "description": "Task registry that allows composition through series/parallel methods.", 5 | "author": "Gulp Team (https://gulpjs.com/)", 6 | "contributors": [ 7 | "Blaine Bublitz ", 8 | "Damien Lebrun " 9 | ], 10 | "repository": "gulpjs/undertaker", 11 | "license": "MIT", 12 | "engines": { 13 | "node": ">=10.13.0" 14 | }, 15 | "main": "index.js", 16 | "files": [ 17 | "LICENSE", 18 | "index.js", 19 | "lib" 20 | ], 21 | "scripts": { 22 | "lint": "eslint .", 23 | "pretest": "npm run lint", 24 | "test": "nyc mocha --async-only" 25 | }, 26 | "dependencies": { 27 | "bach": "^2.0.1", 28 | "fast-levenshtein": "^3.0.0", 29 | "last-run": "^2.0.0", 30 | "undertaker-registry": "^2.0.0" 31 | }, 32 | "devDependencies": { 33 | "async-once": "^2.0.0", 34 | "del": "^6.1.1", 35 | "eslint": "^7.32.0", 36 | "eslint-config-gulp": "^5.0.1", 37 | "eslint-plugin-node": "^11.1.0", 38 | "expect": "^27.5.1", 39 | "mocha": "^8.4.0", 40 | "nyc": "^15.1.0", 41 | "once": "^1.4.0", 42 | "through2": "^4.0.2", 43 | "undertaker-common-tasks": "^2.0.0", 44 | "undertaker-task-metadata": "^2.0.0", 45 | "vinyl-fs": "^4.0.0" 46 | }, 47 | "nyc": { 48 | "reporter": [ 49 | "lcov", 50 | "text-summary" 51 | ] 52 | }, 53 | "prettier": { 54 | "singleQuote": true 55 | }, 56 | "keywords": [ 57 | "registry", 58 | "runner", 59 | "task" 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /test/fixtures/taskTree/aliasNested.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | label: 'Tasks', 5 | nodes: [ 6 | { 7 | label: 'noop', 8 | type: 'task', 9 | nodes: [], 10 | }, 11 | { 12 | label: 'fn1', 13 | type: 'task', 14 | nodes: [], 15 | }, 16 | { 17 | label: 'fn2', 18 | type: 'task', 19 | nodes: [], 20 | }, 21 | { 22 | label: 'fn3', 23 | type: 'task', 24 | nodes: [], 25 | }, 26 | { 27 | label: 'ser', 28 | type: 'task', 29 | nodes: [ 30 | { 31 | label: '', 32 | type: 'function', 33 | branch: true, 34 | nodes: [ 35 | { 36 | label: 'noop', 37 | type: 'function', 38 | nodes: [], 39 | }, 40 | { 41 | label: '', 42 | type: 'function', 43 | nodes: [], 44 | }, 45 | { 46 | label: 'fn1', 47 | type: 'task', 48 | nodes: [], 49 | }, 50 | { 51 | label: 'fn2', 52 | type: 'task', 53 | nodes: [], 54 | }, 55 | { 56 | label: 'fn3', 57 | type: 'task', 58 | nodes: [], 59 | }, 60 | ], 61 | }, 62 | ], 63 | }, 64 | { 65 | label: 'par', 66 | type: 'task', 67 | nodes: [ 68 | { 69 | label: '', 70 | type: 'function', 71 | branch: true, 72 | nodes: [ 73 | { 74 | label: 'noop', 75 | type: 'function', 76 | nodes: [], 77 | }, 78 | { 79 | label: '', 80 | type: 'function', 81 | nodes: [], 82 | }, 83 | { 84 | label: 'fn1', 85 | type: 'task', 86 | nodes: [], 87 | }, 88 | { 89 | label: 'fn2', 90 | type: 'task', 91 | nodes: [], 92 | }, 93 | { 94 | label: 'fn3', 95 | type: 'task', 96 | nodes: [], 97 | }, 98 | ], 99 | }, 100 | ], 101 | }, 102 | ], 103 | }; 104 | -------------------------------------------------------------------------------- /test/fixtures/taskTree/aliasSimple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | label: 'Tasks', 5 | nodes: [ 6 | { 7 | label: 'noop', 8 | type: 'task', 9 | nodes: [], 10 | }, 11 | { 12 | label: 'fn1', 13 | type: 'task', 14 | nodes: [], 15 | }, 16 | { 17 | label: 'fn2', 18 | type: 'task', 19 | nodes: [], 20 | }, 21 | { 22 | label: 'fn3', 23 | type: 'task', 24 | nodes: [], 25 | }, 26 | { 27 | label: 'fn4', 28 | type: 'task', 29 | nodes: [], 30 | }, 31 | ], 32 | }; 33 | -------------------------------------------------------------------------------- /test/fixtures/taskTree/doubleLevel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | label: 'Tasks', 5 | nodes: [ 6 | { 7 | label: 'fn1', 8 | type: 'task', 9 | nodes: [], 10 | }, 11 | { 12 | label: 'fn2', 13 | type: 'task', 14 | nodes: [], 15 | }, 16 | { 17 | label: 'fn3', 18 | type: 'task', 19 | nodes: [ 20 | { 21 | label: '', 22 | type: 'function', 23 | branch: true, 24 | nodes: [ 25 | { 26 | label: 'fn1', 27 | type: 'task', 28 | nodes: [], 29 | }, 30 | { 31 | label: 'fn2', 32 | type: 'task', 33 | nodes: [], 34 | }, 35 | ], 36 | }, 37 | ], 38 | }, 39 | ], 40 | }; 41 | -------------------------------------------------------------------------------- /test/fixtures/taskTree/simple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | label: 'Tasks', 5 | nodes: [ 6 | 'test1', 7 | 'test2', 8 | 'test3', 9 | 'error', 10 | 'ser', 11 | 'par', 12 | 'serpar', 13 | 'serpar2', 14 | '', 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /test/fixtures/taskTree/singleLevel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | label: 'Tasks', 5 | nodes: [ 6 | { 7 | label: 'fn1', 8 | type: 'task', 9 | nodes: [], 10 | }, 11 | { 12 | label: 'fn2', 13 | type: 'task', 14 | nodes: [], 15 | }, 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /test/fixtures/taskTree/tripleLevel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | label: 'Tasks', 5 | nodes: [ 6 | { 7 | label: 'fn1', 8 | type: 'task', 9 | nodes: [ 10 | { 11 | label: '', 12 | type: 'function', 13 | branch: true, 14 | nodes: [ 15 | { 16 | label: '', 17 | type: 'function', 18 | nodes: [], 19 | }, 20 | { 21 | label: 'noop', 22 | type: 'function', 23 | nodes: [], 24 | }, 25 | ], 26 | }, 27 | ], 28 | }, 29 | { 30 | label: 'fn2', 31 | type: 'task', 32 | nodes: [ 33 | { 34 | label: '', 35 | type: 'function', 36 | branch: true, 37 | nodes: [ 38 | { 39 | label: '', 40 | type: 'function', 41 | nodes: [], 42 | }, 43 | { 44 | label: 'noop', 45 | type: 'function', 46 | nodes: [], 47 | }, 48 | ], 49 | }, 50 | ], 51 | }, 52 | { 53 | label: 'fn3', 54 | type: 'task', 55 | nodes: [ 56 | { 57 | label: '', 58 | type: 'function', 59 | branch: true, 60 | nodes: [ 61 | { 62 | label: 'fn1', 63 | type: 'task', 64 | nodes: [ 65 | { 66 | label: '', 67 | type: 'function', 68 | branch: true, 69 | nodes: [ 70 | { 71 | label: '', 72 | type: 'function', 73 | nodes: [], 74 | }, 75 | { 76 | label: 'noop', 77 | type: 'function', 78 | nodes: [], 79 | }, 80 | ], 81 | }, 82 | ], 83 | }, 84 | { 85 | label: 'fn2', 86 | type: 'task', 87 | nodes: [ 88 | { 89 | label: '', 90 | type: 'function', 91 | branch: true, 92 | nodes: [ 93 | { 94 | label: '', 95 | type: 'function', 96 | nodes: [], 97 | }, 98 | { 99 | label: 'noop', 100 | type: 'function', 101 | nodes: [], 102 | }, 103 | ], 104 | }, 105 | ], 106 | }, 107 | ], 108 | }, 109 | ], 110 | }, 111 | ], 112 | }; 113 | -------------------------------------------------------------------------------- /test/fixtures/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function helloWorld() { 4 | 5 | } 6 | 7 | module.exports = helloWorld; 8 | -------------------------------------------------------------------------------- /test/fixtures/testMore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function helloWorld() { 4 | 5 | } 6 | 7 | module.exports = helloWorld; 8 | -------------------------------------------------------------------------------- /test/integration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var os = require('os'); 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var vinyl = require('vinyl-fs'); 9 | var spawn = require('child_process').spawn; 10 | var once = require('once'); 11 | var aOnce = require('async-once'); 12 | var del = require('del'); 13 | var through = require('through2'); 14 | 15 | var Undertaker = require('../'); 16 | 17 | var isWindows = (os.platform() === 'win32'); 18 | 19 | function cleanup() { 20 | return del([ 21 | path.join(__dirname, './fixtures/out/'), 22 | path.join(__dirname, './fixtures/tmp/'), 23 | ]); 24 | } 25 | 26 | function noop() { } 27 | 28 | describe('integrations', function() { 29 | 30 | var taker; 31 | 32 | beforeEach(function(done) { 33 | taker = new Undertaker(); 34 | done(); 35 | }); 36 | 37 | beforeEach(cleanup); 38 | afterEach(cleanup); 39 | 40 | it('should handle vinyl streams', function(done) { 41 | taker.task('test', function() { 42 | return vinyl.src('./fixtures/test.js', { cwd: __dirname }) 43 | .pipe(vinyl.dest('./fixtures/out', { cwd: __dirname })); 44 | }); 45 | 46 | taker.parallel('test')(done); 47 | }); 48 | 49 | it('should exhaust vinyl streams', function(done) { 50 | taker.task('test', function() { 51 | return vinyl.src('./fixtures/test.js', { cwd: __dirname }); 52 | }); 53 | 54 | taker.parallel('test')(done); 55 | }); 56 | 57 | it('should handle a Transform stream return', function(done) { 58 | taker.task('test', function() { 59 | return vinyl.src('./fixtures/test.js', { cwd: __dirname }) 60 | .pipe(through.obj()); 61 | }); 62 | 63 | taker.parallel('test')(done); 64 | }); 65 | 66 | it('should handle a child process return', function(done) { 67 | taker.task('test', function() { 68 | if (isWindows) { 69 | return spawn('cmd', ['/c', 'dir']).on('error', noop); 70 | } 71 | 72 | return spawn('ls', ['-lh', __dirname]); 73 | }); 74 | 75 | taker.parallel('test')(done); 76 | }); 77 | 78 | it('should run dependencies once', function(done) { 79 | var count = 0; 80 | 81 | taker.task('clean', once(function() { 82 | count++; 83 | return del(['./fixtures/some-build.txt'], { cwd: __dirname }); 84 | })); 85 | 86 | taker.task('build-this', taker.series('clean', function(cb) { 87 | cb(); 88 | })); 89 | taker.task('build-that', taker.series('clean', function(cb) { 90 | cb(); 91 | })); 92 | taker.task('build', taker.series( 93 | 'clean', 94 | taker.parallel(['build-this', 'build-that']) 95 | )); 96 | 97 | taker.parallel('build')(function(err) { 98 | expect(count).toEqual(1); 99 | done(err); 100 | }); 101 | }); 102 | 103 | it('should run dependencies once', function(done) { 104 | var count = 0; 105 | 106 | taker.task('clean', aOnce(function(cb) { 107 | cb(); 108 | count++; 109 | del(['./fixtures/some-build.txt'], { cwd: __dirname }, cb); 110 | })); 111 | 112 | taker.task('build-this', taker.series('clean', function(cb) { 113 | cb(); 114 | })); 115 | taker.task('build-that', taker.series('clean', function(cb) { 116 | cb(); 117 | })); 118 | taker.task('build', taker.series( 119 | 'clean', 120 | taker.parallel(['build-this', 'build-that']) 121 | )); 122 | 123 | taker.parallel('build')(function(err) { 124 | expect(count).toEqual(1); 125 | done(err); 126 | }); 127 | }); 128 | 129 | it('can use lastRun with vinyl.src `since` option', function(done) { 130 | this.timeout(5000); 131 | 132 | var count = 0; 133 | 134 | function setup() { 135 | return vinyl.src('./fixtures/test*.js', { cwd: __dirname }) 136 | .pipe(vinyl.dest('./fixtures/tmp', { cwd: __dirname })); 137 | } 138 | 139 | function delay(cb) { 140 | setTimeout(cb, 2000); 141 | } 142 | 143 | // Some built 144 | taker.task('build', function() { 145 | return vinyl.src('./fixtures/tmp/*.js', { cwd: __dirname }) 146 | .pipe(vinyl.dest('./fixtures/out', { cwd: __dirname })); 147 | }); 148 | 149 | function userEdit(cb) { 150 | fs.appendFile(path.join(__dirname, './fixtures/tmp/testMore.js'), ' ', cb); 151 | } 152 | 153 | function countEditedFiles() { 154 | return vinyl.src('./fixtures/tmp/*.js', { cwd: __dirname, since: taker.lastRun('build') }) 155 | .pipe(through.obj(function(file, enc, cb) { 156 | count++; 157 | cb(); 158 | })); 159 | } 160 | 161 | taker.series(setup, delay, 'build', delay, userEdit, countEditedFiles)(function(err) { 162 | expect(count).toEqual(1); 163 | done(err); 164 | }); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /test/last-run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var Undertaker = require('../'); 6 | 7 | describe('lastRun', function() { 8 | var taker, test1, test2, error, alias; 9 | var defaultResolution = process.env.UNDERTAKER_TIME_RESOLUTION; 10 | 11 | beforeEach(function(done) { 12 | process.env.UNDERTAKER_TIME_RESOLUTION = '0'; 13 | taker = new Undertaker(); 14 | 15 | test1 = function(cb) { 16 | cb(); 17 | }; 18 | taker.task('test1', test1); 19 | 20 | test2 = function(cb) { 21 | cb(); 22 | }; 23 | test2.displayName = 'test2'; 24 | taker.task(test2); 25 | 26 | error = function(cb) { 27 | cb(new Error()); 28 | }; 29 | taker.task('error', error); 30 | 31 | alias = test1; 32 | taker.task('alias', alias); 33 | 34 | done(); 35 | }); 36 | 37 | afterEach(function(done) { 38 | process.env.UNDERTAKER_TIME_RESOLUTION = defaultResolution; 39 | done(); 40 | }); 41 | 42 | it('should only record time when task has completed', function(done) { 43 | var ts; 44 | var test = function(cb) { 45 | ts = taker.lastRun('test'); 46 | cb(); 47 | }; 48 | taker.task('test', test); 49 | taker.parallel('test')(function(err) { 50 | expect(ts).toEqual(undefined); 51 | done(err); 52 | }); 53 | }); 54 | 55 | it('should record tasks time execution', function(done) { 56 | taker.parallel('test1')(function(err) { 57 | expect(taker.lastRun('test1')).toBeTruthy(); 58 | expect(taker.lastRun('test1')).toBeLessThanOrEqual(Date.now()); 59 | expect(taker.lastRun(test2)).toBeFalsy(); 60 | expect(taker.lastRun(function() {})).toBeFalsy(); 61 | expect(taker.lastRun.bind(taker, 'notexists')).toThrow(Error); 62 | done(err); 63 | }); 64 | }); 65 | 66 | it('should record all tasks time execution', function(done) { 67 | taker.parallel('test1', test2)(function(err) { 68 | expect(taker.lastRun('test1')).toBeTruthy(); 69 | expect(taker.lastRun('test1')).toBeLessThanOrEqual(Date.now()); 70 | expect(taker.lastRun(test2)).toBeTruthy(); 71 | expect(taker.lastRun(test2)).toBeLessThanOrEqual(Date.now()); 72 | done(err); 73 | }); 74 | }); 75 | 76 | it('should record same tasks time execution for a string task and its original', function(done) { 77 | taker.series(test2)(function(err) { 78 | expect(taker.lastRun(test2)).toEqual(taker.lastRun('test2')); 79 | done(err); 80 | }); 81 | }); 82 | 83 | it('should record tasks time execution for an aliased task', function(done) { 84 | taker.series('alias')(function(err) { 85 | expect(taker.lastRun('alias')).toEqual(taker.lastRun('test1')); 86 | done(err); 87 | }); 88 | }); 89 | 90 | it('should give time with 1s resolution', function(done) { 91 | var resolution = 1000; // 1s 92 | var since = Date.now(); 93 | var expected = since - (since % resolution); 94 | 95 | taker.series('test1')(function() { 96 | expect(taker.lastRun('test1', resolution)).toEqual(expected); 97 | done(); 98 | }); 99 | }); 100 | 101 | it('should not record task start time on error', function(done) { 102 | taker.on('error', function() { 103 | // To keep the test from catching the emitted errors 104 | }); 105 | taker.series('error')(function(err) { 106 | expect(err).toBeTruthy(); 107 | expect(taker.lastRun('error')).toBeFalsy(); 108 | done(); 109 | }); 110 | }); 111 | 112 | }); 113 | -------------------------------------------------------------------------------- /test/parallel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var Undertaker = require('../'); 6 | 7 | function fn1(done) { 8 | done(null, 1); 9 | } 10 | 11 | function fn2(done) { 12 | setTimeout(function() { 13 | done(null, 2); 14 | }, 500); 15 | } 16 | 17 | function fn3(done) { 18 | done(null, 3); 19 | } 20 | 21 | function fnError(done) { 22 | done(new Error('An Error Occurred')); 23 | } 24 | 25 | describe('parallel', function() { 26 | var taker; 27 | 28 | beforeEach(function(done) { 29 | taker = new Undertaker(); 30 | taker.task('test1', fn1); 31 | taker.task('test2', fn2); 32 | taker.task('test3', fn3); 33 | taker.task('error', fnError); 34 | done(); 35 | }); 36 | 37 | it('should throw on non-valid tasks combined with valid tasks', function(done) { 38 | function fail() { 39 | taker.parallel('test1', 'test2', 'test3', {}); 40 | } 41 | 42 | expect(fail).toThrow(/Task never defined:/); 43 | done(); 44 | }); 45 | 46 | it('should throw on tasks array with both valid and non-valid tasks', function(done) { 47 | function fail() { 48 | taker.parallel(['test1', 'test2', 'test3', {}]); 49 | } 50 | 51 | expect(fail).toThrow(/Task never defined:/); 52 | done(); 53 | }); 54 | 55 | it('should throw on non-valid task', function(done) { 56 | function fail() { 57 | taker.parallel({}); 58 | } 59 | 60 | expect(fail).toThrow(/Task never defined:/); 61 | done(); 62 | }); 63 | 64 | it('should throw when no tasks specified', function(done) { 65 | function fail() { 66 | taker.parallel(); 67 | } 68 | 69 | expect(fail).toThrow(/One or more tasks should be combined using series or parallel/); 70 | done(); 71 | }); 72 | 73 | it('should throw on empty array of registered tasks', function(done) { 74 | function fail() { 75 | taker.parallel([]); 76 | } 77 | 78 | expect(fail).toThrow(/One or more tasks should be combined using series or parallel/); 79 | done(); 80 | }); 81 | 82 | it('should take only one array of registered tasks', function(done) { 83 | taker.parallel(['test1', 'test2', 'test3'])(function(err, results) { 84 | expect(results).toEqual([1, 2, 3]); 85 | done(err); 86 | }); 87 | }); 88 | 89 | it('should take all string names', function(done) { 90 | taker.parallel('test1', 'test2', 'test3')(function(err, results) { 91 | expect(results).toEqual([1, 2, 3]); 92 | done(err); 93 | }); 94 | }); 95 | 96 | it('should take all functions', function(done) { 97 | taker.parallel(fn1, fn2, fn3)(function(err, results) { 98 | expect(results).toEqual([1, 2, 3]); 99 | done(err); 100 | }); 101 | }); 102 | 103 | it('should take string names and functions', function(done) { 104 | taker.parallel('test1', fn2, 'test3')(function(err, results) { 105 | expect(results).toEqual([1, 2, 3]); 106 | done(err); 107 | }); 108 | }); 109 | 110 | it('should take nested parallel', function(done) { 111 | var parallel1 = taker.parallel('test1', 'test2', 'test3'); 112 | taker.parallel('test1', parallel1, 'test3')(function(err, results) { 113 | expect(results).toEqual([1, [1, 2, 3], 3]); 114 | done(err); 115 | }); 116 | }); 117 | 118 | it('should stop processing on error', function(done) { 119 | taker.on('error', function() { 120 | // To keep the test from catching the emitted errors 121 | }); 122 | taker.parallel('test1', 'error', 'test3')(function(err, results) { 123 | expect(err).toBeInstanceOf(Error); 124 | expect(results).toEqual([1, undefined, undefined]); 125 | done(); 126 | }); 127 | }); 128 | 129 | it('should throw on unregistered task', function(done) { 130 | function unregistered() { 131 | taker.parallel('unregistered'); 132 | } 133 | 134 | expect(unregistered).toThrow('Task never defined: unregistered'); 135 | done(); 136 | }); 137 | 138 | it('should process all functions if settle flag is true', function(done) { 139 | taker.on('error', function() { 140 | // To keep the test from catching the emitted errors 141 | }); 142 | taker._settle = true; 143 | taker.parallel(taker.parallel('test1', 'error'), 'test3')(function(err, results) { 144 | expect(err[0][0]).toBeInstanceOf(Error); 145 | expect(results).toEqual([3]); 146 | done(); 147 | }); 148 | }); 149 | 150 | it('should not register a displayName on the returned function by default', function(done) { 151 | var task = taker.parallel(fn1); 152 | expect(task.displayName).toEqual(undefined); 153 | done(); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /test/registry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var Undertaker = require('../'); 6 | 7 | var DefaultRegistry = require('undertaker-registry'); 8 | var CommonRegistry = require('undertaker-common-tasks'); 9 | var MetadataRegistry = require('undertaker-task-metadata'); 10 | 11 | function noop() {} 12 | 13 | function CustomRegistry() {} 14 | CustomRegistry.prototype.init = noop; 15 | CustomRegistry.prototype.get = noop; 16 | CustomRegistry.prototype.set = noop; 17 | CustomRegistry.prototype.tasks = noop; 18 | 19 | function SetNoReturnRegistry() { 20 | this._tasks = {}; 21 | } 22 | SetNoReturnRegistry.prototype.init = noop; 23 | SetNoReturnRegistry.prototype.get = function(name) { 24 | return this.tasks[name]; 25 | }; 26 | SetNoReturnRegistry.prototype.set = function(name, fn) { 27 | this.tasks[name] = fn; 28 | }; 29 | SetNoReturnRegistry.prototype.tasks = noop; 30 | 31 | function InvalidRegistry() {} 32 | 33 | function InvalidProtoRegistry() {} 34 | InvalidProtoRegistry.prototype = InvalidRegistry; 35 | 36 | describe('registry', function() { 37 | 38 | describe('method', function() { 39 | 40 | it('should return the current registry when no arguments are given', function(done) { 41 | var taker = new Undertaker(); 42 | expect(taker.registry()).toEqual(taker._registry); 43 | done(); 44 | }); 45 | 46 | it('should set the registry to the given registry instance argument', function(done) { 47 | var taker = new Undertaker(); 48 | var customRegistry = new CustomRegistry(); 49 | taker.registry(customRegistry); 50 | expect(taker.registry()).toEqual(customRegistry); 51 | done(); 52 | }); 53 | 54 | it('should validate the custom registry instance', function(done) { 55 | var taker = new Undertaker(); 56 | var invalid = new InvalidRegistry(); 57 | 58 | function invalidSet() { 59 | taker.registry(invalid); 60 | } 61 | 62 | expect(invalidSet).toThrow('Custom registry must have `get` function'); 63 | done(); 64 | }); 65 | 66 | it('should transfer all tasks from old registry to new', function(done) { 67 | var taker = new Undertaker(new CommonRegistry()); 68 | var customRegistry = new DefaultRegistry(); 69 | taker.registry(customRegistry); 70 | 71 | expect(typeof taker.task('clean')).toEqual('function'); 72 | expect(typeof taker.task('serve')).toEqual('function'); 73 | done(); 74 | }); 75 | 76 | it('allows multiple custom registries to used', function(done) { 77 | var taker = new Undertaker(); 78 | taker.registry(new CommonRegistry()); 79 | 80 | expect(typeof taker.task('clean')).toEqual('function'); 81 | expect(typeof taker.task('serve')).toEqual('function'); 82 | 83 | taker.registry(new MetadataRegistry()); 84 | taker.task('context', function(cb) { 85 | expect(this).toEqual({ name: 'context' }); 86 | cb(); 87 | done(); 88 | }); 89 | 90 | taker.registry(new DefaultRegistry()); 91 | 92 | expect(typeof taker.task('clean')).toEqual('function'); 93 | expect(typeof taker.task('serve')).toEqual('function'); 94 | expect(typeof taker.task('context')).toEqual('function'); 95 | 96 | taker.series('context')(); 97 | }); 98 | 99 | it('throws with a descriptive method when constructor is passed', function(done) { 100 | var taker = new Undertaker(); 101 | 102 | function ctor() { 103 | taker.registry(CommonRegistry); 104 | } 105 | 106 | expect(ctor).toThrow('Custom registries must be instantiated, but it looks like you passed a constructor'); 107 | done(); 108 | }); 109 | 110 | it('calls into the init function after tasks are transferred', function(done) { 111 | var taker = new Undertaker(new CommonRegistry()); 112 | 113 | var ogInit = DefaultRegistry.prototype.init; 114 | 115 | DefaultRegistry.prototype.init = function(inst) { 116 | expect(inst).toEqual(taker); 117 | expect(typeof inst.task('clean')).toEqual('function'); 118 | expect(typeof inst.task('serve')).toEqual('function'); 119 | }; 120 | 121 | taker.registry(new DefaultRegistry()); 122 | 123 | DefaultRegistry.prototype.init = ogInit; 124 | done(); 125 | }); 126 | }); 127 | 128 | describe('constructor', function() { 129 | it('should take a custom registry on instantiation', function(done) { 130 | var taker = new Undertaker(new CustomRegistry()); 131 | expect(taker.registry()).toBeInstanceOf(CustomRegistry); 132 | expect(taker.registry()).not.toBeInstanceOf(DefaultRegistry); 133 | done(); 134 | }); 135 | 136 | it('should default to undertaker-registry if not constructed with custom registry', function(done) { 137 | var taker = new Undertaker(); 138 | expect(taker.registry()).toBeInstanceOf(DefaultRegistry); 139 | expect(taker.registry()).not.toBeInstanceOf(CustomRegistry); 140 | done(); 141 | }); 142 | 143 | it('should take a registry that pre-defines tasks', function(done) { 144 | var taker = new Undertaker(new CommonRegistry()); 145 | expect(taker.registry()).toBeInstanceOf(CommonRegistry); 146 | expect(taker.registry()).toBeInstanceOf(DefaultRegistry); 147 | expect(typeof taker.task('clean')).toEqual('function'); 148 | expect(typeof taker.task('serve')).toEqual('function'); 149 | done(); 150 | }); 151 | 152 | it('should throw upon invalid registry', function(done) { 153 | var taker = new Undertaker(new CommonRegistry()); 154 | expect(function() { 155 | taker.registry(new InvalidProtoRegistry()); 156 | }).toThrow('Custom registry must have `get` function'); 157 | done(); 158 | }); 159 | 160 | it('should throw upon invalid registry', function(done) { 161 | /* eslint no-unused-vars: 0 */ 162 | var taker; 163 | 164 | function noGet() { 165 | taker = new Undertaker(new InvalidRegistry()); 166 | } 167 | 168 | expect(noGet).toThrow('Custom registry must have `get` function'); 169 | InvalidRegistry.prototype.get = noop; 170 | 171 | function noSet() { 172 | taker = new Undertaker(new InvalidRegistry()); 173 | } 174 | 175 | expect(noSet).toThrow('Custom registry must have `set` function'); 176 | InvalidRegistry.prototype.set = noop; 177 | 178 | function noInit() { 179 | taker = new Undertaker(new InvalidRegistry()); 180 | } 181 | 182 | expect(noInit).toThrow('Custom registry must have `init` function'); 183 | InvalidRegistry.prototype.init = noop; 184 | 185 | function noTasks() { 186 | taker = new Undertaker(new InvalidRegistry()); 187 | } 188 | 189 | expect(noTasks).toThrow('Custom registry must have `tasks` function'); 190 | InvalidRegistry.prototype.tasks = noop; 191 | 192 | taker = new Undertaker(new InvalidRegistry()); 193 | done(); 194 | }); 195 | }); 196 | 197 | it('does not require the `set` method to return a task', function(done) { 198 | var taker = new Undertaker(); 199 | taker.registry(new SetNoReturnRegistry()); 200 | taker.task('test', noop); 201 | taker.on('start', function(data) { 202 | expect(data.name).toEqual('test'); 203 | done(); 204 | }); 205 | taker.series('test')(); 206 | }); 207 | 208 | it('should fail if task name is of an inherited property', function(done) { 209 | var tasks = {}; 210 | tasks.__proto__ = { notOwnProp: 1 } 211 | 212 | function MyRegistry() {} 213 | MyRegistry.prototype.init = noop; 214 | MyRegistry.prototype.get = noop; 215 | MyRegistry.prototype.set = noop; 216 | MyRegistry.prototype.tasks = function() { return tasks; }; 217 | 218 | var registry = new MyRegistry(); 219 | var taker = new Undertaker(registry); 220 | 221 | function fail() { 222 | taker.series('notOwnProp'); 223 | } 224 | 225 | expect(fail).toThrow(/Task never defined: notOwnProp/); 226 | 227 | done(); 228 | }); 229 | 230 | it('should fail and offer tasks which are close in name', function(done) { 231 | var taker = new Undertaker(new CommonRegistry()); 232 | var customRegistry = new DefaultRegistry(); 233 | taker.registry(customRegistry); 234 | 235 | function fail() { 236 | taker.series('clear'); 237 | } 238 | 239 | expect(fail).toThrow(/Task never defined: clear - did you mean\? clean/); 240 | done(); 241 | }); 242 | }); 243 | -------------------------------------------------------------------------------- /test/series.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var Undertaker = require('../'); 6 | 7 | function fn1(done) { 8 | done(null, 1); 9 | } 10 | 11 | function fn2(done) { 12 | setTimeout(function() { 13 | done(null, 2); 14 | }, 500); 15 | } 16 | 17 | function fn3(done) { 18 | done(null, 3); 19 | } 20 | 21 | function fnError(done) { 22 | done(new Error('An Error Occurred')); 23 | } 24 | 25 | describe('series', function() { 26 | var taker; 27 | 28 | beforeEach(function(done) { 29 | taker = new Undertaker(); 30 | taker.task('test1', fn1); 31 | taker.task('test2', fn2); 32 | taker.task('test3', fn3); 33 | taker.task('error', fnError); 34 | done(); 35 | }); 36 | 37 | it('should throw on non-valid tasks combined with valid tasks', function(done) { 38 | function fail() { 39 | taker.series('test1', 'test2', 'test3', {}); 40 | } 41 | 42 | expect(fail).toThrow(/Task never defined:/); 43 | done(); 44 | }); 45 | 46 | it('should throw on tasks array with both valid and non-valid tasks', function(done) { 47 | function fail() { 48 | taker.series(['test1', 'test2', 'test3', {}]); 49 | } 50 | 51 | expect(fail).toThrow(/Task never defined:/); 52 | done(); 53 | }); 54 | 55 | it('should throw on non-valid task', function(done) { 56 | function fail() { 57 | taker.series({}); 58 | } 59 | 60 | expect(fail).toThrow(/Task never defined:/); 61 | done(); 62 | }); 63 | 64 | it('should throw when no tasks specified', function(done) { 65 | function fail() { 66 | taker.series(); 67 | } 68 | 69 | expect(fail).toThrow(/One or more tasks should be combined using series or parallel/); 70 | done(); 71 | }); 72 | 73 | it('should throw on empty array of registered tasks', function(done) { 74 | function fail() { 75 | taker.series([]); 76 | } 77 | 78 | expect(fail).toThrow(/One or more tasks should be combined using series or parallel/); 79 | done(); 80 | }); 81 | 82 | it('should take only one array of registered tasks', function(done) { 83 | taker.series(['test1', 'test2', 'test3'])(function(err, results) { 84 | expect(results).toEqual([1, 2, 3]); 85 | done(err); 86 | }); 87 | }); 88 | 89 | it('should take all string names', function(done) { 90 | taker.series('test1', 'test2', 'test3')(function(err, results) { 91 | expect(results).toEqual([1, 2, 3]); 92 | done(err); 93 | }); 94 | }); 95 | 96 | it('should take all functions', function(done) { 97 | taker.series(fn1, fn2, fn3)(function(err, results) { 98 | expect(results).toEqual([1, 2, 3]); 99 | done(err); 100 | }); 101 | }); 102 | 103 | it('should take string names and functions', function(done) { 104 | taker.series('test1', fn2, 'test3')(function(err, results) { 105 | expect(results).toEqual([1, 2, 3]); 106 | done(err); 107 | }); 108 | }); 109 | 110 | it('should take nested series', function(done) { 111 | var series1 = taker.series('test1', 'test2', 'test3'); 112 | taker.series('test1', series1, 'test3')(function(err, results) { 113 | expect(results).toEqual([1, [1, 2, 3], 3]); 114 | done(err); 115 | }); 116 | }); 117 | 118 | it('should stop processing on error', function(done) { 119 | taker.on('error', function() { 120 | // To keep the test from catching the emitted errors 121 | }); 122 | taker.series('test1', 'error', 'test3')(function(err, results) { 123 | expect(err).toBeInstanceOf(Error); 124 | expect(results).toEqual([1, undefined, undefined]); 125 | done(); 126 | }); 127 | }); 128 | 129 | it('should throw on unregistered task', function(done) { 130 | function unregistered() { 131 | taker.series('unregistered'); 132 | } 133 | 134 | expect(unregistered).toThrow('Task never defined: unregistered'); 135 | done(); 136 | }); 137 | 138 | it('should process all functions if settle flag is true', function(done) { 139 | taker.on('error', function() { 140 | // To keep the test from catching the emitted errors 141 | }); 142 | taker._settle = true; 143 | taker.series(taker.series('test1', 'error'), 'test3')(function(err, results) { 144 | expect(err[0][0]).toBeInstanceOf(Error); 145 | expect(results).toEqual([3]); 146 | done(); 147 | }); 148 | }); 149 | 150 | it('should not register a displayName on the returned function by default', function(done) { 151 | var task = taker.series(fn1); 152 | expect(task.displayName).toEqual(undefined); 153 | done(); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /test/task.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var Undertaker = require('../'); 6 | 7 | function noop(done) { 8 | done(); 9 | } 10 | 11 | var anon = function() {}; 12 | 13 | describe('task', function() { 14 | var taker; 15 | 16 | beforeEach(function(done) { 17 | taker = new Undertaker(); 18 | done(); 19 | }); 20 | 21 | it('should register a named function', function(done) { 22 | taker.task(noop); 23 | expect(taker.task('noop').unwrap()).toEqual(noop); 24 | done(); 25 | }); 26 | 27 | it('should register an anonymous function by string name', function(done) { 28 | taker.task('test1', anon); 29 | expect(taker.task('test1').unwrap()).toEqual(anon); 30 | done(); 31 | }); 32 | 33 | it('should register an anonymous function by displayName property', function(done) { 34 | anon.displayName = ''; 35 | taker.task(anon); 36 | expect(taker.task('').unwrap()).toEqual(anon); 37 | delete anon.displayName; 38 | done(); 39 | }); 40 | 41 | it('should throw on register an anonymous function without string name', function(done) { 42 | function noName() { 43 | taker.task(function() {}); 44 | } 45 | 46 | expect(noName).toThrow('Task name must be specified'); 47 | done(); 48 | }); 49 | 50 | it('should register a named function by string name', function(done) { 51 | taker.task('test1', noop); 52 | expect(taker.task('test1').unwrap()).toEqual(noop); 53 | done(); 54 | }); 55 | 56 | it('should not get a task that was not registered', function(done) { 57 | expect(taker.task('test1')).toEqual(undefined); 58 | done(); 59 | }); 60 | 61 | it('should get a task that was registered', function(done) { 62 | taker.task('test1', noop); 63 | expect(taker.task('test1').unwrap()).toEqual(noop); 64 | done(); 65 | }); 66 | 67 | it('should get the wrapped task, not original function', function(done) { 68 | var registry = taker.registry(); 69 | taker.task('test1', noop); 70 | expect(typeof taker.task('test1').unwrap).toEqual('function'); 71 | expect(taker.task('test1')).toEqual(registry.get('test1')); 72 | done(); 73 | }); 74 | 75 | it('provides an `unwrap` method to get the original function', function(done) { 76 | taker.task('test1', noop); 77 | expect(typeof taker.task('test1').unwrap).toEqual('function'); 78 | expect(taker.task('test1').unwrap()).toEqual(noop); 79 | done(); 80 | }); 81 | 82 | it('should return a function that was registered in some other way', function(done) { 83 | taker.registry()._tasks.test1 = noop; 84 | expect(taker.task('test1')).toEqual(noop); 85 | done(); 86 | }); 87 | 88 | it('should prefer displayName instead of name when both properties are defined', function(done) { 89 | function fn() {} 90 | fn.displayName = 'test1'; 91 | taker.task(fn); 92 | expect(taker.task('test1').unwrap()).toEqual(fn); 93 | done(); 94 | }); 95 | 96 | it('should allow different tasks to refer to the same function', function(done) { 97 | function fn() {} 98 | taker.task('foo', fn); 99 | taker.task('bar', fn); 100 | expect(taker.task('foo').unwrap()).toEqual(taker.task('bar').unwrap()); 101 | done(); 102 | }); 103 | 104 | it('should allow using aliased tasks in composite tasks', function(done) { 105 | var count = 0; 106 | function fn(cb) { 107 | count++; 108 | cb(); 109 | } 110 | 111 | taker.task('foo', fn); 112 | taker.task('bar', fn); 113 | 114 | var series = taker.series('foo', 'bar', function(cb) { 115 | expect(count).toEqual(2); 116 | cb(); 117 | }); 118 | 119 | var parallel = taker.parallel('foo', 'bar', function(cb) { 120 | setTimeout(function() { 121 | expect(count).toEqual(4); 122 | cb(); 123 | }, 500); 124 | }); 125 | 126 | taker.series(series, parallel)(done); 127 | }); 128 | 129 | it('should allow composite tasks tasks to be aliased', function(done) { 130 | var count = 0; 131 | function fn1(cb) { 132 | count += 1; 133 | cb(); 134 | } 135 | function fn2(cb) { 136 | count += 2; 137 | cb(); 138 | } 139 | 140 | taker.task('ser', taker.series(fn1, fn2)); 141 | taker.task('foo', taker.task('ser')); 142 | 143 | taker.task('par', taker.parallel(fn1, fn2)); 144 | taker.task('bar', taker.task('par')); 145 | 146 | var series = taker.series('foo', function(cb) { 147 | expect(count).toEqual(3); 148 | cb(); 149 | }); 150 | 151 | var parallel = taker.series('bar', function(cb) { 152 | setTimeout(function() { 153 | expect(count).toEqual(6); 154 | cb(); 155 | }, 500); 156 | }); 157 | 158 | taker.series(series, parallel)(done); 159 | }); 160 | 161 | }); 162 | -------------------------------------------------------------------------------- /test/tree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require('expect'); 4 | 5 | var Undertaker = require('../'); 6 | 7 | var simple = require('./fixtures/taskTree/simple'); 8 | var singleLevel = require('./fixtures/taskTree/singleLevel'); 9 | var doubleLevel = require('./fixtures/taskTree/doubleLevel'); 10 | var tripleLevel = require('./fixtures/taskTree/tripleLevel'); 11 | var aliasSimple = require('./fixtures/taskTree/aliasSimple'); 12 | var aliasNested = require('./fixtures/taskTree/aliasNested'); 13 | 14 | function noop(done) { 15 | done(); 16 | } 17 | 18 | describe('tree', function() { 19 | 20 | var taker; 21 | 22 | beforeEach(function(done) { 23 | taker = new Undertaker(); 24 | done(); 25 | }); 26 | 27 | it('should return a simple tree by default', function(done) { 28 | taker.task('test1', function(cb) { 29 | cb(); 30 | }); 31 | taker.task('test2', function(cb) { 32 | cb(); 33 | }); 34 | taker.task('test3', function(cb) { 35 | cb(); 36 | }); 37 | taker.task('error', function(cb) { 38 | cb(); 39 | }); 40 | 41 | var ser = taker.series('test1', 'test2'); 42 | var anon = function(cb) { 43 | cb(); 44 | }; 45 | anon.displayName = ''; 46 | 47 | taker.task('ser', taker.series('test1', 'test2')); 48 | taker.task('par', taker.parallel('test1', 'test2', 'test3')); 49 | taker.task('serpar', taker.series('ser', 'par')); 50 | taker.task('serpar2', taker.series(ser, anon)); 51 | taker.task(anon); 52 | 53 | var tree = taker.tree(); 54 | 55 | expect(tree).toEqual(simple); 56 | done(); 57 | }); 58 | 59 | it('should form a 1 level tree', function(done) { 60 | taker.task('fn1', function(cb) { 61 | cb(); 62 | }); 63 | taker.task('fn2', function(cb) { 64 | cb(); 65 | }); 66 | 67 | var tree = taker.tree({ deep: true }); 68 | 69 | expect(tree).toEqual(singleLevel); 70 | done(); 71 | }); 72 | 73 | it('should form a 2 level nested tree', function(done) { 74 | taker.task('fn1', function(cb) { 75 | cb(); 76 | }); 77 | taker.task('fn2', function(cb) { 78 | cb(); 79 | }); 80 | taker.task('fn3', taker.series('fn1', 'fn2')); 81 | 82 | var tree = taker.tree({ deep: true }); 83 | 84 | expect(tree).toEqual(doubleLevel); 85 | done(); 86 | }); 87 | 88 | it('should form a 3 level nested tree', function(done) { 89 | taker.task('fn1', taker.parallel(function(cb) { 90 | cb(); 91 | }, noop)); 92 | taker.task('fn2', taker.parallel(function(cb) { 93 | cb(); 94 | }, noop)); 95 | taker.task('fn3', taker.series('fn1', 'fn2')); 96 | 97 | var tree = taker.tree({ deep: true }); 98 | 99 | expect(tree).toEqual(tripleLevel); 100 | done(); 101 | }); 102 | 103 | it('should use the proper labels for aliased tasks (simple)', function(done) { 104 | var anon = function(cb) { 105 | cb(); 106 | }; 107 | taker.task(noop); 108 | taker.task('fn1', noop); 109 | taker.task('fn2', taker.task('noop')); 110 | taker.task('fn3', anon); 111 | taker.task('fn4', taker.task('fn3')); 112 | 113 | var tree = taker.tree({ deep: true }); 114 | 115 | expect(tree).toEqual(aliasSimple); 116 | done(); 117 | }); 118 | 119 | it('should use the proper labels for aliased tasks (nested)', function(done) { 120 | taker.task(noop); 121 | taker.task('fn1', noop); 122 | taker.task('fn2', taker.task('noop')); 123 | taker.task('fn3', function(cb) { 124 | cb(); 125 | }); 126 | taker.task('ser', taker.series(noop, function(cb) { 127 | cb(); 128 | }, 'fn1', 'fn2', 'fn3')); 129 | taker.task('par', taker.parallel(noop, function(cb) { 130 | cb(); 131 | }, 'fn1', 'fn2', 'fn3')); 132 | 133 | var tree = taker.tree({ deep: true }); 134 | 135 | expect(tree).toEqual(aliasNested); 136 | done(); 137 | }); 138 | 139 | }); 140 | --------------------------------------------------------------------------------