├── .editorconfig
├── .eslintrc.json
├── .gitattributes
├── .gitignore
├── .npmrc
├── .travis.yml
├── .verb.md
├── LICENSE
├── README.md
├── appveyor.yml
├── changelog.md
├── docs
├── events.md
└── execution.md
├── examples
├── dynamic-tasks-events.js
├── dynamic-tasks.js
├── generator.js
├── mixed.js
├── parallel-skip.js
├── parallel.js
├── series.js
└── task-noop.js
├── index.js
├── lib
├── generator.js
├── parse.js
├── task.js
├── tasks.js
├── timer.js
└── utils.js
├── package.json
└── test
├── app.default-generator.js
├── app.findGenerator.js
├── app.generate.js
├── app.generator.js
├── app.getGenerator.js
├── app.register.js
├── app.task.js
├── app.tasks.js
├── app.toAlias.js
├── events.js
├── parallel.js
├── parse-tasks.js
├── series.js
├── task.js
├── tasks.js
└── test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org/
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [{**/{actual,fixtures,expected,templates}/**,*.md}]
13 | trim_trailing_whitespace = false
14 | insert_final_newline = false
15 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended"
4 | ],
5 |
6 | "env": {
7 | "browser": false,
8 | "es6": true,
9 | "node": true,
10 | "mocha": true
11 | },
12 |
13 | "parserOptions":{
14 | "ecmaVersion": 9,
15 | "sourceType": "module",
16 | "ecmaFeatures": {
17 | "modules": true,
18 | "experimentalObjectRestSpread": true
19 | }
20 | },
21 |
22 | "globals": {
23 | "document": false,
24 | "navigator": false,
25 | "window": false
26 | },
27 |
28 | "rules": {
29 | "accessor-pairs": 2,
30 | "arrow-spacing": [2, { "before": true, "after": true }],
31 | "block-spacing": [2, "always"],
32 | "brace-style": [2, "1tbs", { "allowSingleLine": true }],
33 | "comma-dangle": [2, "never"],
34 | "comma-spacing": [2, { "before": false, "after": true }],
35 | "comma-style": [2, "last"],
36 | "constructor-super": 2,
37 | "curly": [2, "multi-line"],
38 | "dot-location": [2, "property"],
39 | "eol-last": 2,
40 | "eqeqeq": [2, "allow-null"],
41 | "generator-star-spacing": [2, { "before": true, "after": true }],
42 | "handle-callback-err": [2, "^(err|error)$" ],
43 | "indent": [2, 2, { "SwitchCase": 1 }],
44 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
45 | "keyword-spacing": [2, { "before": true, "after": true }],
46 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
47 | "new-parens": 2,
48 | "no-array-constructor": 2,
49 | "no-caller": 2,
50 | "no-class-assign": 2,
51 | "no-cond-assign": 2,
52 | "no-const-assign": 2,
53 | "no-control-regex": 2,
54 | "no-debugger": 2,
55 | "no-delete-var": 2,
56 | "no-dupe-args": 2,
57 | "no-dupe-class-members": 2,
58 | "no-dupe-keys": 2,
59 | "no-duplicate-case": 2,
60 | "no-empty-character-class": 2,
61 | "no-eval": 2,
62 | "no-ex-assign": 2,
63 | "no-extend-native": 2,
64 | "no-extra-bind": 2,
65 | "no-extra-boolean-cast": 2,
66 | "no-extra-parens": [2, "functions"],
67 | "no-fallthrough": 2,
68 | "no-floating-decimal": 2,
69 | "no-func-assign": 2,
70 | "no-implied-eval": 2,
71 | "no-inner-declarations": [2, "functions"],
72 | "no-invalid-regexp": 2,
73 | "no-irregular-whitespace": 2,
74 | "no-iterator": 2,
75 | "no-label-var": 2,
76 | "no-labels": 2,
77 | "no-lone-blocks": 2,
78 | "no-mixed-spaces-and-tabs": 2,
79 | "no-multi-spaces": 2,
80 | "no-multi-str": 2,
81 | "no-multiple-empty-lines": [2, { "max": 1 }],
82 | "no-native-reassign": 0,
83 | "no-negated-in-lhs": 2,
84 | "no-new": 2,
85 | "no-new-func": 2,
86 | "no-new-object": 2,
87 | "no-new-require": 2,
88 | "no-new-wrappers": 2,
89 | "no-obj-calls": 2,
90 | "no-octal": 2,
91 | "no-octal-escape": 2,
92 | "no-proto": 0,
93 | "no-redeclare": 2,
94 | "no-regex-spaces": 2,
95 | "no-return-assign": 2,
96 | "no-self-compare": 2,
97 | "no-sequences": 2,
98 | "no-shadow-restricted-names": 2,
99 | "no-spaced-func": 2,
100 | "no-sparse-arrays": 2,
101 | "no-this-before-super": 2,
102 | "no-throw-literal": 2,
103 | "no-trailing-spaces": 0,
104 | "no-undef": 2,
105 | "no-undef-init": 2,
106 | "no-unexpected-multiline": 2,
107 | "no-unneeded-ternary": [2, { "defaultAssignment": false }],
108 | "no-unreachable": 2,
109 | "no-unused-vars": [2, { "vars": "all", "args": "none" }],
110 | "no-useless-call": 0,
111 | "no-with": 2,
112 | "one-var": [0, { "initialized": "never" }],
113 | "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }],
114 | "padded-blocks": [0, "never"],
115 | "quotes": [2, "single", "avoid-escape"],
116 | "radix": 2,
117 | "semi": [2, "always"],
118 | "semi-spacing": [2, { "before": false, "after": true }],
119 | "space-before-blocks": [2, "always"],
120 | "space-before-function-paren": [2, "never"],
121 | "space-in-parens": [2, "never"],
122 | "space-infix-ops": 2,
123 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
124 | "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }],
125 | "use-isnan": 2,
126 | "valid-typeof": 2,
127 | "wrap-iife": [2, "any"],
128 | "yoda": [2, "never"]
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Enforce Unix newlines
2 | * text eol=lf
3 |
4 | # binaries
5 | *.ai binary
6 | *.psd binary
7 | *.jpg binary
8 | *.gif binary
9 | *.png binary
10 | *.jpeg binary
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # always ignore files
2 | *.DS_Store
3 | .idea
4 | .vscode
5 | *.sublime-*
6 |
7 | # test related, or directories generated by tests
8 | test/actual
9 | actual
10 | coverage
11 | .nyc*
12 |
13 | # npm
14 | node_modules
15 | npm-debug.log
16 |
17 | # yarn
18 | yarn.lock
19 | yarn-error.log
20 |
21 | # misc
22 | _gh_pages
23 | _draft
24 | _drafts
25 | bower_components
26 | vendor
27 | temp
28 | tmp
29 | TODO.md
30 | package-lock.json
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | os:
3 | - linux
4 | - osx
5 | language: node_js
6 | node_js:
7 | - node
8 | - '10'
9 | - '9'
10 | - '8'
11 |
--------------------------------------------------------------------------------
/.verb.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 |
4 | ```js
5 | // Create an instance of `Composer`
6 | const Composer = require('{%= name %}');
7 | const composer = new Composer();
8 |
9 | // Define tasks with the .task() method
10 | composer.task('foo', callback => {
11 | callback(); // do stuff
12 | });
13 | composer.task('bar', callback => {
14 | callback(); // do stuff
15 | });
16 |
17 | composer.task('baz', ['foo'. 'bar']);
18 |
19 | // Run tasks with the .build() method
20 | composer.build('baz')
21 | .then(() => console.log('done!'))
22 | .catch(console.error);
23 | ```
24 |
25 | ## API
26 |
27 | ### Tasks
28 | {%= apidocs("lib/tasks.js") %}
29 |
30 | ### Generators
31 | {%= apidocs("lib/generator.js") %}
32 |
33 |
34 | ## Events
35 |
36 | ### task
37 |
38 | ```js
39 | app.on('task', function(task) {
40 | switch (task.status) {
41 | case 'starting':
42 | // Task is running
43 | break;
44 | case 'finished':
45 | // Task is finished running
46 | break;
47 | }
48 | });
49 | ```
50 |
51 | ### task-pending
52 |
53 | Emitted after a task is registered.
54 |
55 |
56 | ### task-preparing
57 |
58 | Emitted when a task is preparing to run, right before it's called. You can use this event to dynamically skip tasks by updating `task.skip` to `true` or a function.
59 |
60 |
61 | ## Release history
62 |
63 | See the [changelog](./CHANGELOG.md).
64 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-present, Brian Woodward.
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # composer [](https://www.npmjs.com/package/composer) [](https://npmjs.org/package/composer) [](https://npmjs.org/package/composer) [](https://travis-ci.org/doowb/composer) [](https://ci.appveyor.com/project/doowb/composer)
2 |
3 | > Run and compose async tasks. Easily define groups of tasks to run in series or parallel.
4 |
5 | Please consider following this project's author, [Brian Woodward](https://github.com/doowb), and consider starring the project to show your :heart: and support.
6 |
7 | - [Install](#install)
8 | - [Usage](#usage)
9 | - [API](#api)
10 | * [Tasks](#tasks)
11 | * [Generators](#generators)
12 | - [Events](#events)
13 | * [task](#task)
14 | * [task-pending](#task-pending)
15 | * [task-preparing](#task-preparing)
16 | - [Release history](#release-history)
17 | - [About](#about)
18 |
19 | _(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_
20 |
21 | ## Install
22 |
23 | Install with [npm](https://www.npmjs.com/):
24 |
25 | ```sh
26 | $ npm install --save composer
27 | ```
28 |
29 | ## Usage
30 |
31 | ```js
32 | // Create an instance of `Composer`
33 | const Composer = require('composer');
34 | const composer = new Composer();
35 |
36 | // Define tasks with the .task() method
37 | composer.task('foo', callback => {
38 | callback(); // do stuff
39 | });
40 | composer.task('bar', callback => {
41 | callback(); // do stuff
42 | });
43 |
44 | composer.task('baz', ['foo'. 'bar']);
45 |
46 | // Run tasks with the .build() method
47 | composer.build('baz')
48 | .then(() => console.log('done!'))
49 | .catch(console.error);
50 | ```
51 |
52 | ## API
53 |
54 | ### [.factory](lib/tasks.js#L27)
55 |
56 | Factory for creating a custom `Tasks` class that extends the given `Emitter`. Or, simply call the factory function to use the built-in emitter.
57 |
58 | **Params**
59 |
60 | * `Emitter` **{function}**: Event emitter.
61 | * `returns` **{Class}**: Returns a custom `Tasks` class.
62 |
63 | **Example**
64 |
65 | ```js
66 | // custom emitter
67 | const Emitter = require('events');
68 | const Tasks = require('composer/lib/tasks')(Emitter);
69 | // built-in emitter
70 | const Tasks = require('composer/lib/tasks')();
71 | const composer = new Tasks();
72 | ```
73 |
74 | ### [Tasks](lib/tasks.js#L42)
75 |
76 | Create an instance of `Tasks` with the given `options`.
77 |
78 | **Params**
79 |
80 | * `options` **{object}**
81 |
82 | **Example**
83 |
84 | ```js
85 | const Tasks = require('composer').Tasks;
86 | const composer = new Tasks();
87 | ```
88 |
89 | ### [.task](lib/tasks.js#L86)
90 |
91 | Define a task. Tasks run asynchronously, either in series (by default) or parallel (when `options.parallel` is true). In order for the build to determine when a task is complete, _one of the following_ things must happen: 1) the callback must be called, 2) a promise must be returned, or 3) a stream must be returned. Inside tasks, the "this" object is a composer Task instance created for each task with useful properties like the task name, options and timing information, which can be useful for logging, etc.
92 |
93 | **Params**
94 |
95 | * `name` **{String}**: The task name.
96 | * `deps` **{Object|Array|String|Function}**: Any of the following: task dependencies, callback(s), or options object, defined in any order.
97 | * `callback` **{Function}**: (optional) If the last argument is a function, it will be called after all of the task's dependencies have been run.
98 | * `returns` **{undefined}**
99 |
100 | **Example**
101 |
102 | ```js
103 | // 1. callback
104 | app.task('default', cb => {
105 | // do stuff
106 | cb();
107 | });
108 | // 2. promise
109 | app.task('default', () => {
110 | return Promise.resolve(null);
111 | });
112 | // 3. stream (using vinyl-fs or your stream of choice)
113 | app.task('default', function() {
114 | return vfs.src('foo/*.js');
115 | });
116 | ```
117 |
118 | ### [.build](lib/tasks.js#L209)
119 |
120 | Run one or more tasks.
121 |
122 | **Params**
123 |
124 | * `tasks` **{object|array|string|function}**: One or more tasks to run, options, or callback function. If no tasks are defined, the default task is automatically run.
125 | * `callback` **{function}**: (optional)
126 | * `returns` **{undefined}**
127 |
128 | **Example**
129 |
130 | ```js
131 | const build = app.series(['foo', 'bar', 'baz']);
132 | // promise
133 | build().then(console.log).catch(console.error);
134 | // or callback
135 | build(function() {
136 | if (err) return console.error(err);
137 | });
138 | ```
139 |
140 | ### [.series](lib/tasks.js#L251)
141 |
142 | Compose a function to run the given tasks in series.
143 |
144 | **Params**
145 |
146 | * `tasks` **{object|array|string|function}**: Tasks to run, options, or callback function. If no tasks are defined, the `default` task is automatically run, if one exists.
147 | * `callback` **{function}**: (optional)
148 | * `returns` **{promise|undefined}**: Returns a promise if no callback is passed.
149 |
150 | **Example**
151 |
152 | ```js
153 | const build = app.series(['foo', 'bar', 'baz']);
154 | // promise
155 | build().then(console.log).catch(console.error);
156 | // or callback
157 | build(function() {
158 | if (err) return console.error(err);
159 | });
160 | ```
161 |
162 | ### [.parallel](lib/tasks.js#L304)
163 |
164 | Compose a function to run the given tasks in parallel.
165 |
166 | **Params**
167 |
168 | * `tasks` **{object|array|string|function}**: Tasks to run, options, or callback function. If no tasks are defined, the `default` task is automatically run, if one exists.
169 | * `callback` **{function}**: (optional)
170 | * `returns` **{promise|undefined}**: Returns a promise if no callback is passed.
171 |
172 | **Example**
173 |
174 | ```js
175 | // call the returned function to start the build
176 | const build = app.parallel(['foo', 'bar', 'baz']);
177 | // promise
178 | build().then(console.log).catch(console.error);
179 | // callback
180 | build(function() {
181 | if (err) return console.error(err);
182 | });
183 | // example task usage
184 | app.task('default', build);
185 | ```
186 |
187 | ### [.create](lib/tasks.js#L388)
188 |
189 | Static method for creating a custom Tasks class with the given `Emitter.
190 |
191 | **Params**
192 |
193 | * `Emitter` **{Function}**
194 | * `returns` **{Class}**: Returns the custom class.
195 |
196 | ### [.create](lib/generator.js#L30)
197 |
198 | Static factory method for creating a custom `Composer` class that extends the given `Emitter`.
199 |
200 | **Params**
201 |
202 | * `Emitter` **{Function}**: Event emitter.
203 | * `returns` **{Class}**: Returns a custom `Composer` class.
204 |
205 | **Example**
206 |
207 | ```js
208 | // Composer extends a basic event emitter by default
209 | const Composer = require('composer');
210 | const composer = new Composer();
211 |
212 | // Create a custom Composer class with your even emitter of choice
213 | const Emitter = require('some-emitter');
214 | const CustomComposer = Composer.create(Emitter);
215 | const composer = new CustomComposer();
216 | ```
217 |
218 | **Params**
219 |
220 | * `name` **{String}**
221 | * `options` **{Object}**
222 | * `returns` **{Object}**: Returns an instance of Composer.
223 |
224 | **Example**
225 |
226 | ```js
227 | const composer = new Composer();
228 | ```
229 |
230 | Create a wrapped generator function with the given `name`, `config`, and `fn`.
231 |
232 | **Params**
233 |
234 | * `name` **{String}**
235 | * `config` **{Object}**: (optional)
236 | * `fn` **{Function}**
237 | * `returns` **{Function}**
238 |
239 | Returns true if the given value is a Composer generator object.
240 |
241 | **Params**
242 |
243 | * `val` **{Object}**
244 | * `returns` **{Boolean}**
245 |
246 | ### [.register](lib/generator.js#L167)
247 |
248 | Alias to `.setGenerator`.
249 |
250 | **Params**
251 |
252 | * `name` **{String}**: The generator's name
253 | * `options` **{Object|Function|String}**: or generator
254 | * `generator` **{Object|Function|String}**: Generator function, instance or filepath.
255 | * `returns` **{Object}**: Returns the generator instance.
256 |
257 | **Example**
258 |
259 | ```js
260 | app.register('foo', function(app, base) {
261 | // "app" is a private instance created for the generator
262 | // "base" is a shared instance
263 | });
264 | ```
265 |
266 | ### [.generator](lib/generator.js#L190)
267 |
268 | Get and invoke generator `name`, or register generator `name` with the given `val` and `options`, then invoke and return the generator instance. This method differs from `.register`, which lazily invokes generator functions when `.generate` is called.
269 |
270 | **Params**
271 |
272 | * `name` **{String}**
273 | * `fn` **{Function|Object}**: Generator function, instance or filepath.
274 | * `returns` **{Object}**: Returns the generator instance or undefined if not resolved.
275 |
276 | **Example**
277 |
278 | ```js
279 | app.generator('foo', function(app, options) {
280 | // "app" - private instance created for generator "foo"
281 | // "options" - options passed to the generator
282 | });
283 | ```
284 |
285 | ### [.setGenerator](lib/generator.js#L222)
286 |
287 | Store a generator by file path or instance with the given `name` and `options`.
288 |
289 | **Params**
290 |
291 | * `name` **{String}**: The generator's name
292 | * `options` **{Object|Function|String}**: or generator
293 | * `generator` **{Object|Function|String}**: Generator function, instance or filepath.
294 | * `returns` **{Object}**: Returns the generator instance.
295 |
296 | **Example**
297 |
298 | ```js
299 | app.setGenerator('foo', function(app, options) {
300 | // "app" - new instance of Generator created for generator "foo"
301 | // "options" - options passed to the generator
302 | });
303 | ```
304 |
305 | ### [.getGenerator](lib/generator.js#L247)
306 |
307 | Get generator `name` from `app.generators`, same as [findGenerator], but also invokes the returned generator with the current instance. Dot-notation may be used for getting sub-generators.
308 |
309 | **Params**
310 |
311 | * `name` **{String}**: Generator name.
312 | * `returns` **{Object|undefined}**: Returns the generator instance or undefined.
313 |
314 | **Example**
315 |
316 | ```js
317 | const foo = app.getGenerator('foo');
318 |
319 | // get a sub-generator
320 | const baz = app.getGenerator('foo.bar.baz');
321 | ```
322 |
323 | ### [.findGenerator](lib/generator.js#L280)
324 |
325 | Find generator `name`, by first searching the cache, then searching the cache of the `base` generator. Use this to get a generator without invoking it.
326 |
327 | **Params**
328 |
329 | * `name` **{String}**
330 | * `options` **{Function}**: Optionally supply a rename function on `options.toAlias`
331 | * `returns` **{Object|undefined}**: Returns the generator instance if found, or undefined.
332 |
333 | **Example**
334 |
335 | ```js
336 | // search by "alias"
337 | const foo = app.findGenerator('foo');
338 |
339 | // search by "full name"
340 | const foo = app.findGenerator('generate-foo');
341 | ```
342 |
343 | **Params**
344 |
345 | * `name` **{String}**
346 | * `returns` **{Boolean}**
347 |
348 | **Example**
349 |
350 | ```js
351 | console.log(app.hasGenerator('foo'));
352 | console.log(app.hasGenerator('foo.bar'));
353 | ```
354 |
355 | ### [.generate](lib/generator.js#L362)
356 |
357 | Run one or more tasks or sub-generators and returns a promise.
358 |
359 | **Params**
360 |
361 | * `name` **{String}**
362 | * `tasks` **{String|Array}**
363 | * `returns` **{Promise}**
364 |
365 | **Events**
366 |
367 | * `emits`: `generate` with the generator `name` and the array of `tasks` that are queued to run.
368 |
369 | **Example**
370 |
371 | ```js
372 | // run tasks `bar` and `baz` on generator `foo`
373 | app.generate('foo', ['bar', 'baz']);
374 |
375 | // or use shorthand
376 | app.generate('foo:bar,baz');
377 |
378 | // run the `default` task on generator `foo`
379 | app.generate('foo');
380 |
381 | // run the `default` task on the `default` generator, if defined
382 | app.generate();
383 | ```
384 |
385 | ### [.toAlias](lib/generator.js#L413)
386 |
387 | Create a generator alias from the given `name`. By default, `generate-` is stripped from beginning of the generator name.
388 |
389 | **Params**
390 |
391 | * `name` **{String}**
392 | * `options` **{Object}**
393 | * `returns` **{String}**: Returns the alias.
394 |
395 | **Example**
396 |
397 | ```js
398 | // customize the alias
399 | const app = new Generate({ toAlias: require('camel-case') });
400 | ```
401 |
402 | ### [.isGenerators](lib/generator.js#L434)
403 |
404 | Returns true if every name in the given array is a registered generator.
405 |
406 | **Params**
407 |
408 | * `names` **{Array}**
409 | * `returns` **{Boolean}**
410 |
411 | ### [.formatError](lib/generator.js#L446)
412 |
413 | Format task and generator errors.
414 |
415 | **Params**
416 |
417 | * `name` **{String}**
418 | * `returns` **{Error}**
419 |
420 | ### [.disableInspect](lib/generator.js#L466)
421 |
422 | Disable inspect. Returns a function to re-enable inspect. Useful for debugging.
423 |
424 | ### [.base](lib/generator.js#L504)
425 |
426 | Get the first ancestor instance of Composer. Only works if `generator.parent` is
427 | defined on child instances.
428 |
429 | ### [.name](lib/generator.js#L517)
430 |
431 | Get or set the generator name.
432 |
433 | **Params**
434 |
435 | * **{String}**
436 |
437 | * `returns` **{String}**
438 |
439 | ### [.alias](lib/generator.js#L534)
440 |
441 | Get or set the generator `alias`. By default, the generator alias is created
442 | by passing the generator name to the [.toAlias](#toAlias) method.
443 |
444 | **Params**
445 |
446 | * **{String}**
447 |
448 | * `returns` **{String}**
449 |
450 | ### [.namespace](lib/generator.js#L551)
451 |
452 | Get the generator namespace. The namespace is created by joining the generator's `alias`
453 | to the alias of each ancestor generator.
454 |
455 | **Params**
456 |
457 | * **{String}**
458 |
459 | * `returns` **{String}**
460 |
461 | ### [.depth](lib/generator.js#L564)
462 |
463 | Get the depth of a generator - useful for debugging. The root generator
464 | has a depth of `0`, sub-generators add `1` for each level of nesting.
465 |
466 | * `returns` **{Number}**
467 |
468 | ### [Composer#parse](lib/generator.js#L577)
469 |
470 | Static method that returns a function for parsing task arguments.
471 |
472 | **Params**
473 |
474 | * `register` **{Function}**: Function that receives a name of a task or generator that cannot be found by the parse function. This allows the `register` function to dynamically register tasks or generators.
475 | * `returns` **{Function}**: Returns a function for parsing task args.
476 |
477 | ### [Composer#isGenerator](lib/generator.js#L590)
478 |
479 | Static method that returns true if the given `val` is an instance of Generate.
480 |
481 | **Params**
482 |
483 | * `val` **{Object}**
484 | * `returns` **{Boolean}**
485 |
486 | ### [Composer#create](lib/generator.js#L603)
487 |
488 | Static method for creating a custom Composer class with the given `Emitter.
489 |
490 | **Params**
491 |
492 | * `Emitter` **{Function}**
493 | * `returns` **{Class}**: Returns the custom class.
494 |
495 | ### [Composer#Tasks](lib/generator.js#L617)
496 |
497 | Static getter for getting the Tasks class with the same `Emitter` class as Composer.
498 |
499 | **Params**
500 |
501 | * `Emitter` **{Function}**
502 | * `returns` **{Class}**: Returns the Tasks class.
503 |
504 | ### [Composer#Task](lib/generator.js#L633)
505 |
506 | Static getter for getting the `Task` class.
507 |
508 | **Example**
509 |
510 | ```js
511 | const { Task } = require('composer');
512 | ```
513 |
514 | ## Events
515 |
516 | ### task
517 |
518 | ```js
519 | app.on('task', function(task) {
520 | switch (task.status) {
521 | case 'starting':
522 | // Task is running
523 | break;
524 | case 'finished':
525 | // Task is finished running
526 | break;
527 | }
528 | });
529 | ```
530 |
531 | ### task-pending
532 |
533 | Emitted after a task is registered.
534 |
535 | ### task-preparing
536 |
537 | Emitted when a task is preparing to run, right before it's called. You can use this event to dynamically skip tasks by updating `task.skip` to `true` or a function.
538 |
539 | ## Release history
540 |
541 | See the [changelog](./CHANGELOG.md).
542 |
543 | ## About
544 |
545 |
546 | Contributing
547 |
548 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
549 |
550 |
551 |
552 |
553 | Running Tests
554 |
555 | Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
556 |
557 | ```sh
558 | $ npm install && npm test
559 | ```
560 |
561 |
562 |
563 |
564 | Building docs
565 |
566 | _(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
567 |
568 | To generate the readme, run the following command:
569 |
570 | ```sh
571 | $ npm install -g verbose/verb#dev verb-generate-readme && verb
572 | ```
573 |
574 |
575 |
576 | ### Related projects
577 |
578 | You might also be interested in these projects:
579 |
580 | * [assemble](https://www.npmjs.com/package/assemble): Get the rocks out of your socks! Assemble makes you fast at creating web projects… [more](https://github.com/assemble/assemble) | [homepage](https://github.com/assemble/assemble "Get the rocks out of your socks! Assemble makes you fast at creating web projects. Assemble is used by thousands of projects for rapid prototyping, creating themes, scaffolds, boilerplates, e-books, UI components, API documentation, blogs, building websit")
581 | * [enquirer](https://www.npmjs.com/package/enquirer): Stylish, intuitive and user-friendly prompt system. Fast and lightweight enough for small projects, powerful and… [more](https://github.com/enquirer/enquirer) | [homepage](https://github.com/enquirer/enquirer "Stylish, intuitive and user-friendly prompt system. Fast and lightweight enough for small projects, powerful and extensible enough for the most advanced use cases.")
582 | * [generate](https://www.npmjs.com/package/generate): Command line tool and developer framework for scaffolding out new GitHub projects. Generate offers the… [more](https://github.com/generate/generate) | [homepage](https://github.com/generate/generate "Command line tool and developer framework for scaffolding out new GitHub projects. Generate offers the robustness and configurability of Yeoman, the expressiveness and simplicity of Slush, and more powerful flow control and composability than either.")
583 | * [update](https://www.npmjs.com/package/update): Be scalable! Update is a new, open source developer framework and CLI for automating updates… [more](https://github.com/update/update) | [homepage](https://github.com/update/update "Be scalable! Update is a new, open source developer framework and CLI for automating updates of any kind in code projects.")
584 | * [verb](https://www.npmjs.com/package/verb): Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used… [more](https://github.com/verbose/verb) | [homepage](https://github.com/verbose/verb "Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used on hundreds of projects of all sizes to generate everything from API docs to readmes.")
585 |
586 | ### Contributors
587 |
588 | | **Commits** | **Contributor** |
589 | | --- | --- |
590 | | 227 | [doowb](https://github.com/doowb) |
591 | | 72 | [jonschlinkert](https://github.com/jonschlinkert) |
592 |
593 | ### Author
594 |
595 | **Brian Woodward**
596 |
597 | * [GitHub Profile](https://github.com/doowb)
598 | * [Twitter Profile](https://twitter.com/doowb)
599 | * [LinkedIn Profile](https://linkedin.com/in/woodwardbrian)
600 |
601 | ### License
602 |
603 | Copyright © 2018, [Brian Woodward](https://github.com/doowb).
604 | Released under the [MIT License](LICENSE).
605 |
606 | ***
607 |
608 | _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on November 11, 2018._
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | # Test against this version of Node.js
2 | environment:
3 | matrix:
4 | # node.js
5 | - nodejs_version: "9.0"
6 | - nodejs_version: "8.0"
7 |
8 | # Install scripts. (runs after repo cloning)
9 | install:
10 | # Get the latest stable version of Node.js or io.js
11 | - ps: Install-Product node $env:nodejs_version
12 | # install modules
13 | - npm install
14 |
15 | # Post-install test scripts.
16 | test_script:
17 | # Output useful info for debugging.
18 | - node --version
19 | - npm --version
20 | # run tests
21 | - npm test
22 |
23 | # Don't actually build.
24 | build: off
25 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | ### v3.0.0
2 |
3 | **Breaking changes**
4 |
5 | - Support for custom inspect methods was removed. Please follow Node's [recommended practices](https://nodejs.org/api/util.html#util_util_inspect_custom) for creating a custom inspect method.
6 |
7 | ### v3.0.0
8 |
9 | - Completely refactored from the ground up.
10 | - For the most part, `.task`, `.build`, `.series` and `.parallel` work the same way. However, event handling has changed. Please see the readme for more information and documentation.
11 |
12 | ### v2.0.0
13 |
14 | - Now requires Node.js v4.0 or higher
15 |
16 | ### v1.0.0
17 |
18 | - Updates the events that are emitted and adds statuses to the objects emitted on the events. see issues [#20](../../issues/20) and [#21](../../issues/21)
19 | - Updates the event objects to expose human readable durations. [see issue #23](../../issues/23)
20 | - Removes unused properties. [see issue #24](../../issues/24)
21 | - Updates `.build` to return a promise when the callback is not passed in. [see issue #28](../../issues/28)
22 |
23 | ### v0.14.0
24 |
25 | - Updates [bach][] to `1.0.0`.
26 | - Errors emitted from inside a task now have the `'in task "foo":'` prefixed to the error message. [see issue #22](../../issues/22)
27 | - Expose `.runInfo` on the task object for use in event listeners and task functions.
28 | - Add `.duration` to the `.run/.runInfo` object that shows the duration in a human friendly format. This will also show the current duration from the time the task started to the time it's called if used inside a task function. [see issue #23](../../issues/23)
29 |
30 | ```js
31 | app.task('foo', function(cb) {
32 | console.log(this.runInfo.duration);
33 | });
34 | ```
35 |
36 | ### v0.13.0
37 |
38 | - Skip tasks by setting the `options.skip` option to the name of the task or an array of task names.
39 | - Making additional `err` properties non-enumerable to cut down on error output.
40 |
41 | ### v0.12.0
42 |
43 | - You can no longer get a task from the `.task()` method by passing only the name. Instead do `var task = app.tasks[name];`
44 | - Passing only a name and no dependencies to `.task()` will result in a `noop` task being created.
45 | - `options` may be passed to `.build()`, `.series()` and `.parallel()`
46 | - `options` passed to `.build()` will be merged onto task options before running the task.
47 | - Skip tasks by setting their `options.run` option to `false`.
48 |
49 | ### v0.11.3
50 |
51 | - Allow passing es2015 javascript generator functions to `.task()`.
52 |
53 | ### v0.11.2
54 |
55 | - Allow using glob patterns for task dependencies.
56 |
57 | ### v0.11.0
58 |
59 | - **BREAKING CHANGE**: Removed `.watch()`. Watch functionality can be added to [base][] applications using [base-watch][].
60 |
61 | ### v0.10.0
62 |
63 | - Removes `session`.
64 |
65 | ### v0.9.0
66 |
67 | - Use `default` when no tasks are passed to `.build()`.
68 |
69 | ### v0.8.4
70 |
71 | - Ensure task dependencies are unique.
72 |
73 | ### v0.8.2
74 |
75 | - Emitting `task` when adding a task through `.task()`
76 | - Returning task when calling `.task(name)` with only a name.
77 |
78 | ### v0.8.0
79 |
80 | - Emitting `task:*` events instead of generic `*` events. See [event docs](#events) for more information.
81 |
82 | ### v0.7.0
83 |
84 | - No longer returning the current task when `.task()` is called without a name.
85 | - Throwing an error when `.task()` is called without a name.
86 |
87 | ### v0.6.0
88 |
89 | - Adding properties to `err` instances and emitting instead of emitting multiple parameters.
90 | - Adding series and parallel flows/methods.
91 |
92 | ### v0.5.0
93 |
94 | - **BREAKING CHANGE** Renamed `.run()` to `.build()`
95 |
96 | ### v0.4.2
97 |
98 | - `.watch` returns an instance of `FSWatcher`
99 |
100 | ### v0.4.1
101 |
102 | - Currently running task returned when calling `.task()` without a name.
103 |
104 | ### v0.4.0
105 |
106 | - Add session-cache to enable per-task data contexts.
107 |
108 | ### v0.3.0
109 |
110 | - Event bubbling/emitting changed.
111 |
112 | ### v0.1.0
113 |
114 | - Initial release.
115 |
--------------------------------------------------------------------------------
/docs/events.md:
--------------------------------------------------------------------------------
1 | [composer][] is an event emitter that may emit the following events:
2 |
3 | ### build
4 |
5 | This event is emitted when the build is starting and when it's finished. The event emits an object containing the build runtime information.
6 |
7 | ```js
8 | app.on('build', build => {});
9 | ```
10 |
11 | #### `build` properties
12 |
13 | * `.app` (object) - instance of Composer
14 | * `.status` (string) - current build status[^1], either `register`, `starting` or `finished`.
15 | * `.date` (object) - with a `.start` property indicating start time as a `Date` object.
16 | * `.hr` (object) - with a `.start` property indicating the start time as an `hrtime` array.
17 | * `.duration` (string) - that will provide the duration in a human readable format.
18 | * `.diff` (string) - diff between the start and end times.
19 | * `.offset` (string) offset between the start date and the start `hr` time
20 |
21 | [^1]: When `build.status` is `finished`, the `.hr` object also has `.duration` and `.diff` properties containing timing information calculated using `process.hrtime`.
22 |
23 | ### task
24 |
25 | This event is emitted when the task is registered, starting, and when it's finished. The event emits 2 arguments, the current instance of the task object and an object containing the task runtime information.
26 |
27 | ```js
28 | app.on('task', (task, run) => {});
29 | ```
30 |
31 | #### `task` properties
32 |
33 | * `.status` (string) - current status[^2] of the task. May be `register`, `starting`, or `finished`.
34 |
35 | #### `run` properties
36 |
37 | * `.date` (object) - has a `.start` property indicating the start time as a `Date` object.
38 | * `.hr` (object) - has a `.start` property indicating the start time as an `hrtime` array.
39 | * `.duration` (string) that will provide the duration in a human readable format.
40 | * `.diff` (string) that will provide the diff between the start and end times.
41 | * `.offset` (string) offset between the start date and the start hr time
42 |
43 | [^2]: When `task.status` is `finished`, the `.hr` object also has `.duration` and `.diff` properties containing timing information calculated using `process.hrtime`.
44 |
45 |
46 | ### error
47 |
48 | This event is emitted when an error occurrs during a `build`. The event emits an `Error` object with extra properties for debugging the _build and task_ that were running when the error occurred.
49 |
50 | ```js
51 | app.on('error', err => {});
52 | ```
53 |
54 | #### err properties
55 |
56 | * `app`: current composer instance running the build
57 | * `build`: current build runtime information
58 | * `task`: current task instance running when the error occurred
59 | * `run`: current task runtime information
60 |
--------------------------------------------------------------------------------
/docs/execution.md:
--------------------------------------------------------------------------------
1 | When an individual task is run, a new [Run](lib/run.js) instance is created with start, end, and duration information. This `run` object is emitted with [some events](#taskstarting) and also exposed on the `task` instance as the `.runInfo` property.
2 |
3 | ### properties
4 |
5 | The `run` instance has the the following properties
6 |
7 | **.date**
8 |
9 | The `.date` property is an object containing the `.start` and `.end` date timestamps created with `new Date()`.
10 |
11 | **.hr**
12 |
13 | The `.hr` property is an object containing the `.start`, `.end` and `.duration` properties that are created by using `process.hrtime()`. These properties are the actual arrays returned from `process.hrtime()`.
14 | There is also `.diff` and `.offset` computed properties that use the other properties to calculate the difference between `.start` and `.end` times (`.diff`) and the offset (error for time calculations) between the `.duration` and the `.diff` (this is usually very small).
15 |
16 | **.duration**
17 |
18 | The `.duration` property is a computed property that uses [pretty-time][] to format the `.hr.duration` value into a human readable format.
19 |
--------------------------------------------------------------------------------
/examples/dynamic-tasks-events.js:
--------------------------------------------------------------------------------
1 | const Composer = require('..');
2 | const app = new Composer({ skip: [] });
3 | const task = cb => cb();
4 |
5 | app.on('build', function(build) {
6 | console.log('build', build.status, build.time.duration);
7 | });
8 |
9 | app.on('task', function(task) {
10 | if (task.status === 'finished' || task.status === 'starting') {
11 | console.log(task.status, 'task', task.name, task.time.duration);
12 |
13 | if (task.status === 'finished') {
14 | app.options.skip.push(task.name);
15 | }
16 | }
17 | });
18 |
19 | app.task('foo', ['qux', 'fez'], task);
20 | app.task('bar', ['qux', 'fez'], task);
21 | app.task('baz', ['qux', 'fez'], task);
22 | app.task('qux', task);
23 | app.task('fez', task);
24 |
25 | app.task('default', ['foo', 'bar', 'baz']);
26 | app.build('default')
27 | .then(() => console.log('done'))
28 | .catch(console.error);
29 |
--------------------------------------------------------------------------------
/examples/dynamic-tasks.js:
--------------------------------------------------------------------------------
1 | const names = [];
2 | const Composer = require('..');
3 | const app = new Composer({skip: names});
4 |
5 | const task = function(cb) {
6 | console.log('running', this.name);
7 | names.push(this.name);
8 | cb();
9 | };
10 |
11 | app.task('foo', ['qux', 'fez'], task);
12 | app.task('bar', ['qux', 'fez'], task);
13 | app.task('baz', ['qux', 'fez'], task);
14 | app.task('qux', task);
15 | app.task('fez', task);
16 |
17 | app.task('default', ['foo', 'bar', 'baz']);
18 | app.build('default')
19 | .then(() => console.log('done'))
20 | .catch(console.error);
21 |
--------------------------------------------------------------------------------
/examples/generator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | console.time('total');
4 | const fs = require('fs');
5 | const path = require('path');
6 | const util = require('util');
7 | const Composer = require('..');
8 | const composer = new Composer();
9 | const writeFile = util.promisify(fs.writeFile);
10 | const argv = require('minimist')(process.argv.slice(2));
11 |
12 | composer.register('git', git => {
13 | git.task('ignore', () => {
14 | return writeFile('.gitignore', '*.sublime-*');
15 | });
16 |
17 | git.task('attributes', () => {
18 | return writeFile('.gitattributes', '* text eol=lf');
19 | });
20 |
21 | git.task('default', ['ignore', 'attributes']);
22 | });
23 |
24 | composer.register('npm', npm => {
25 | npm.task('default', () => {
26 | return writeFile('.npmrc', 'package-lock=false');
27 | });
28 | });
29 |
30 | composer.register('foo', foo => {
31 | foo.register('dotfiles', dotfiles => {
32 | dotfiles.task('git', () => composer.generate('git'));
33 | dotfiles.task('npm', () => composer.generate('npm'));
34 | dotfiles.task('default', ['git', 'npm']);
35 | });
36 | });
37 |
38 | composer.generate(argv._.length ? argv._ : 'git')
39 | .then(() => console.timeEnd('total'))
40 | .catch(console.log)
41 |
42 | // class App {
43 | // constructor(alias) {
44 | // this.alias = alias;
45 | // this.cache = {};
46 | // }
47 |
48 | // register(alias, fn) {
49 | // this.cache[alias] = fn;
50 | // return this;
51 | // }
52 |
53 | // generate(alias) {
54 | // const fn = this.cache[alias];
55 | // const app = new App(alias);
56 | // fn(app);
57 | // return app;
58 | // }
59 | // }
60 |
61 | // const app = new App('base');
62 |
63 | // app.register('foo', function(foo) {
64 | // console.log('GENERATOR ALIAS:', foo);
65 | // });
66 |
67 | // app.register('bar', function(bar) {
68 | // console.log('GENERATOR ALIAS:', bar);
69 | // });
70 |
71 | // app.register('baz', function(baz) {
72 | // console.log('GENERATOR ALIAS:', baz);
73 | // });
74 |
75 | // app.generate('foo');
76 | // app.generate('bar');
77 | // app.generate('baz');
78 |
79 | // console.log(app)
80 |
--------------------------------------------------------------------------------
/examples/mixed.js:
--------------------------------------------------------------------------------
1 | const Composer = require('..');
2 | const app = new Composer();
3 | let names = [];
4 |
5 | app.task('foo', function(cb) {
6 | setTimeout(function() {
7 | names.push('foo');
8 | cb();
9 | }, 20);
10 | });
11 |
12 | app.task('bar', function(cb) {
13 | names.push('bar');
14 | cb();
15 | });
16 |
17 | app.task('baz', function(cb) {
18 | setTimeout(function() {
19 | names.push('baz');
20 | cb();
21 | }, 10);
22 | });
23 |
24 | app.task('names', function(cb) {
25 | setTimeout(function() {
26 | console.log(names);
27 | names = [];
28 | cb();
29 | }, 30);
30 | });
31 |
32 | app.task('one', app.series(['foo', 'bar', 'baz', 'names']));
33 | app.task('two', app.parallel(['foo', 'bar', 'baz', 'names']));
34 | app.task('three', app.series(['foo', 'bar', 'baz', 'names']));
35 | app.task('default', ['one', 'two', 'three']);
36 | app.build({ parallel: true }, err => err && console.error(err));
37 |
--------------------------------------------------------------------------------
/examples/parallel-skip.js:
--------------------------------------------------------------------------------
1 | const Composer = require('..');
2 | const app = new Composer({ skip: ['bar', 'baz'] });
3 | let names = [];
4 |
5 | app.task('foo', function(cb) {
6 | setTimeout(function() {
7 | names.push('foo');
8 | cb();
9 | }, 20);
10 | });
11 |
12 | app.task('bar', function(cb) {
13 | names.push('bar');
14 | cb();
15 | });
16 |
17 | app.task('baz', function(cb) {
18 | setTimeout(function() {
19 | names.push('baz');
20 | cb();
21 | }, 10);
22 | });
23 |
24 | app.task('names', function(cb) {
25 | setTimeout(function() {
26 | console.log(names);
27 | names = [];
28 | cb();
29 | }, 30);
30 | });
31 |
32 | app.task('one', app.parallel(['foo', 'bar', 'baz', 'names']));
33 | app.task('two', app.parallel(['foo', 'bar', 'baz', 'names']));
34 | app.task('default', ['one', 'two']);
35 | app.build(err => err && console.error(err));
36 |
--------------------------------------------------------------------------------
/examples/parallel.js:
--------------------------------------------------------------------------------
1 | const Composer = require('..');
2 | const app = new Composer();
3 | let names = [];
4 |
5 | app.task('foo', function(cb) {
6 | setTimeout(function() {
7 | names.push('foo');
8 | cb();
9 | }, 20);
10 | });
11 |
12 | app.task('bar', function(cb) {
13 | names.push('bar');
14 | cb();
15 | });
16 |
17 | app.task('baz', function(cb) {
18 | setTimeout(function() {
19 | names.push('baz');
20 | cb();
21 | }, 10);
22 | });
23 |
24 | app.task('names', function(cb) {
25 | setTimeout(function() {
26 | console.log(names);
27 | names = [];
28 | cb();
29 | }, 30);
30 | });
31 |
32 | app.task('one', app.parallel(['foo', 'bar', 'baz', 'names']));
33 | app.task('two', app.parallel(['foo', 'bar', 'baz', 'names']));
34 | app.task('default', ['one', 'two']);
35 | app.build(err => err && console.error(err));
36 |
--------------------------------------------------------------------------------
/examples/series.js:
--------------------------------------------------------------------------------
1 | const Composer = require('..');
2 | const app = new Composer();
3 | let names = [];
4 |
5 | app.task('foo', function(cb) {
6 | setTimeout(function() {
7 | names.push('foo');
8 | cb();
9 | }, 20);
10 | });
11 |
12 | app.task('bar', function(cb) {
13 | names.push('bar');
14 | cb();
15 | });
16 |
17 | app.task('baz', function(cb) {
18 | setTimeout(function() {
19 | names.push('baz');
20 | cb();
21 | }, 10);
22 | });
23 |
24 | app.task('names', function(cb) {
25 | setTimeout(function() {
26 | console.log(names);
27 | names = [];
28 | cb();
29 | }, 30);
30 | });
31 |
32 | app.task('one', app.series(['foo', 'bar', 'baz', 'names']));
33 | app.task('two', app.series(['foo', 'bar', 'baz', 'names']));
34 | app.task('default', ['one', 'two']);
35 | app.build('default')
36 | .then(() => console.log('done'))
37 | .catch(console.error);
38 |
--------------------------------------------------------------------------------
/examples/task-noop.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Composer = require('..');
4 | const composer = new Composer();
5 |
6 | /**
7 | * Listen for tasks
8 | */
9 |
10 | composer.on('task', task => console.log(task.status.padStart(9), task.name));
11 |
12 | /**
13 | * Example of a noop task with a callback.
14 | */
15 |
16 | composer.task('noop-callback', cb => cb());
17 |
18 | /**
19 | * Example of a noop task that returns a promise
20 | */
21 |
22 | composer.task('noop-promise', () => Promise.resolve(null));
23 |
24 | /**
25 | * Example of how to run them
26 | */
27 |
28 | composer.build(['noop-promise', 'noop-callback'])
29 | .then(() => console.log('Done!'))
30 | .catch(console.error);
31 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/generator');
2 |
--------------------------------------------------------------------------------
/lib/generator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const util = require('util');
4 | const Task = require('./task');
5 | const Tasks = require('./tasks');
6 | const parse = require('./parse');
7 | const Events = require('events');
8 | const { define } = require('./utils');
9 |
10 | /**
11 | * Static factory method for creating a custom `Composer` class that
12 | * extends the given `Emitter`.
13 | *
14 | * ```js
15 | * // Composer extends a basic event emitter by default
16 | * const Composer = require('composer');
17 | * const composer = new Composer();
18 | *
19 | * // Create a custom Composer class with your even emitter of choice
20 | * const Emitter = require('some-emitter');
21 | * const CustomComposer = Composer.create(Emitter);
22 | * const composer = new CustomComposer();
23 | * ```
24 | * @name .create
25 | * @param {Function} `Emitter` Event emitter.
26 | * @return {Class} Returns a custom `Composer` class.
27 | * @api public
28 | */
29 |
30 | const factory = (Emitter = Events) => {
31 |
32 | /**
33 | * Create a new instance of Composer.
34 | *
35 | * ```js
36 | * const composer = new Composer();
37 | * ```
38 | * @extends EventEmitter
39 | * @param {String} `name`
40 | * @param {Object} `options`
41 | * @return {Object} Returns an instance of Composer.
42 | * @api public
43 | */
44 |
45 | class Generator extends Tasks.create(Emitter) {
46 | constructor(name, options = {}) {
47 | if (name && typeof name !== 'string') {
48 | options = name;
49 | name = void 0;
50 | }
51 | // ensure that options aren't passed to generic
52 | // emitter to prevent unintended side-effects
53 | super(!/(Event)?Emitter/i.test(Emitter.name) ? options : null);
54 | if (this.setMaxListeners) this.setMaxListeners(0);
55 | this.name = name;
56 | this.options = options;
57 | this.namespaces = new Map();
58 | this.generators = new Map();
59 | this.isGenerate = true;
60 | if (!this.use) {
61 | require('use')(this);
62 | }
63 | }
64 |
65 | /**
66 | * Create a wrapped generator function with the given `name`, `config`, and `fn`.
67 | *
68 | * @param {String} `name`
69 | * @param {Object} `config` (optional)
70 | * @param {Function} `fn`
71 | * @return {Function}
72 | * @api public
73 | */
74 |
75 | toGenerator(name, config, fn) {
76 | if (typeof config === 'function' || this.isGenerator(config)) {
77 | fn = config;
78 | config = fn || {};
79 | }
80 |
81 | let alias = this.toAlias(name);
82 | let generator = options => {
83 | if (generator.instance && generator.once !== false) {
84 | return generator.instance;
85 | }
86 |
87 | let opts = Object.assign({}, config, options);
88 | let app = this.isGenerator(fn) ? fn : new this.constructor(opts);
89 | this.run(app);
90 |
91 | generator.instance = app;
92 | generator.called++;
93 | fn.called = generator.called;
94 |
95 | app.createGenerator = generator;
96 | app.alias = alias;
97 | app.name = name;
98 | app.fn = fn;
99 |
100 | define(app, 'parent', this);
101 | this.emit('generator', app);
102 |
103 | // emit errors that happen on initialization
104 | let listeners = {};
105 | let bubble = events => {
106 | for (let name of events) {
107 | let listener = listeners[name] || (listeners[name] = this.emit.bind(this, name));
108 | app.off(name, listener);
109 | app.on(name, listener);
110 | }
111 | };
112 |
113 | bubble(['error', 'task', 'build', 'plugin']);
114 |
115 | if (typeof fn === 'function') {
116 | fn.call(app, app, opts);
117 | // re-register emitters that we just registered a few lines ago,
118 | // to ensure that errors are bubbled up in the correct order
119 | bubble(['error', 'task', 'build', 'plugin']);
120 | }
121 |
122 | if (opts && opts.once === false) {
123 | generator.once = false;
124 | }
125 | return app;
126 | };
127 |
128 | define(generator, 'name', alias);
129 | define(generator, 'parent', this);
130 | define(generator, 'instance', null);
131 | generator.called = 0;
132 | generator.isGenerator = true;
133 | generator.alias = alias;
134 | generator.fn = fn;
135 | return generator;
136 | }
137 |
138 | /**
139 | * Returns true if the given value is a Composer generator object.
140 | *
141 | * @param {Object} `val`
142 | * @return {Boolean}
143 | * @api public
144 | */
145 |
146 | isGenerator(val) {
147 | return this.constructor.isGenerator(val);
148 | }
149 |
150 | /**
151 | * Alias to `.setGenerator`.
152 | *
153 | * ```js
154 | * app.register('foo', function(app, base) {
155 | * // "app" is a private instance created for the generator
156 | * // "base" is a shared instance
157 | * });
158 | * ```
159 | * @name .register
160 | * @param {String} `name` The generator's name
161 | * @param {Object|Function|String} `options` or generator
162 | * @param {Object|Function|String} `generator` Generator function, instance or filepath.
163 | * @return {Object} Returns the generator instance.
164 | * @api public
165 | */
166 |
167 | register(...args) {
168 | return this.setGenerator(...args);
169 | }
170 |
171 | /**
172 | * Get and invoke generator `name`, or register generator `name` with
173 | * the given `val` and `options`, then invoke and return the generator
174 | * instance. This method differs from `.register`, which lazily invokes
175 | * generator functions when `.generate` is called.
176 | *
177 | * ```js
178 | * app.generator('foo', function(app, options) {
179 | * // "app" - private instance created for generator "foo"
180 | * // "options" - options passed to the generator
181 | * });
182 | * ```
183 | * @name .generator
184 | * @param {String} `name`
185 | * @param {Function|Object} `fn` Generator function, instance or filepath.
186 | * @return {Object} Returns the generator instance or undefined if not resolved.
187 | * @api public
188 | */
189 |
190 | generator(name, options, fn) {
191 | if (typeof options === 'function') {
192 | fn = options;
193 | options = {};
194 | }
195 |
196 | if (typeof fn !== 'function') {
197 | return this.getGenerator(name, options);
198 | }
199 |
200 | this.setGenerator(name, options, fn);
201 | return this.getGenerator(name);
202 | }
203 |
204 | /**
205 | * Store a generator by file path or instance with the given
206 | * `name` and `options`.
207 | *
208 | * ```js
209 | * app.setGenerator('foo', function(app, options) {
210 | * // "app" - new instance of Generator created for generator "foo"
211 | * // "options" - options passed to the generator
212 | * });
213 | * ```
214 | * @name .setGenerator
215 | * @param {String} `name` The generator's name
216 | * @param {Object|Function|String} `options` or generator
217 | * @param {Object|Function|String} `generator` Generator function, instance or filepath.
218 | * @return {Object} Returns the generator instance.
219 | * @api public
220 | */
221 |
222 | setGenerator(name, options, fn) {
223 | const generator = this.toGenerator(name, options, fn);
224 | const alias = generator.alias;
225 | this.base.namespaces.set(`${this.namespace}.${alias}`, generator);
226 | this.generators.set(alias, generator);
227 | return this;
228 | }
229 |
230 | /**
231 | * Get generator `name` from `app.generators`, same as [findGenerator], but also invokes
232 | * the returned generator with the current instance. Dot-notation may be used for getting
233 | * sub-generators.
234 | *
235 | * ```js
236 | * const foo = app.getGenerator('foo');
237 | *
238 | * // get a sub-generator
239 | * const baz = app.getGenerator('foo.bar.baz');
240 | * ```
241 | * @name .getGenerator
242 | * @param {String} `name` Generator name.
243 | * @return {Object|undefined} Returns the generator instance or undefined.
244 | * @api public
245 | */
246 |
247 | getGenerator(name, options) {
248 | const fn = this.findGenerator(name);
249 |
250 | if (!this.isGenerator(fn)) {
251 | throw this.formatError(name);
252 | }
253 |
254 | if (typeof fn === 'function') {
255 | // return the generator instance if one has already created,
256 | // otherwise call the generator function with the parent instance
257 | return fn.instance || fn.call(fn.parent, options);
258 | }
259 | return fn;
260 | }
261 |
262 | /**
263 | * Find generator `name`, by first searching the cache, then searching the
264 | * cache of the `base` generator. Use this to get a generator without invoking it.
265 | *
266 | * ```js
267 | * // search by "alias"
268 | * const foo = app.findGenerator('foo');
269 | *
270 | * // search by "full name"
271 | * const foo = app.findGenerator('generate-foo');
272 | * ```
273 | * @name .findGenerator
274 | * @param {String} `name`
275 | * @param {Function} `options` Optionally supply a rename function on `options.toAlias`
276 | * @return {Object|undefined} Returns the generator instance if found, or undefined.
277 | * @api public
278 | */
279 |
280 | findGenerator(name) {
281 | if (!name) return null;
282 | let cached = this.base.namespaces.get(name);
283 | let names = [];
284 | let app = this;
285 |
286 | if (this.isGenerator(cached)) {
287 | return cached;
288 | }
289 |
290 | names = typeof name === 'string'
291 | ? name.split('.').map(n => this.toAlias(n))
292 | : name;
293 |
294 | let key = names.join('.');
295 |
296 | if (names.length === 1) {
297 | app = this.generators.get(key);
298 |
299 | } else {
300 | do {
301 | let alias = names.shift();
302 | let gen = app.generators.get(alias);
303 |
304 | if (!this.isGenerator(gen)) {
305 | return null;
306 | }
307 |
308 | // only invoke the generator if it's not the last one
309 | if (names.length) {
310 | app = gen.instance || app.getGenerator(alias);
311 | } else {
312 | app = gen;
313 | }
314 |
315 | } while (app && names.length);
316 | }
317 |
318 | return this.isGenerator(app) ? app : null;
319 | }
320 |
321 | /**
322 | * Returns true if the given name is a registered generator. Dot-notation may be
323 | * used to check for sub-generators.
324 | *
325 | * ```js
326 | * console.log(app.hasGenerator('foo'));
327 | * console.log(app.hasGenerator('foo.bar'));
328 | * ```
329 | * @param {String} `name`
330 | * @return {Boolean}
331 | * @api public
332 | */
333 |
334 | hasGenerator(name) {
335 | return this.findGenerator(name) != null;
336 | }
337 |
338 | /**
339 | * Run one or more tasks or sub-generators and returns a promise.
340 | *
341 | * ```js
342 | * // run tasks `bar` and `baz` on generator `foo`
343 | * app.generate('foo', ['bar', 'baz']);
344 | *
345 | * // or use shorthand
346 | * app.generate('foo:bar,baz');
347 | *
348 | * // run the `default` task on generator `foo`
349 | * app.generate('foo');
350 | *
351 | * // run the `default` task on the `default` generator, if defined
352 | * app.generate();
353 | * ```
354 | * @name .generate
355 | * @emits `generate` with the generator `name` and the array of `tasks` that are queued to run.
356 | * @param {String} `name`
357 | * @param {String|Array} `tasks`
358 | * @return {Promise}
359 | * @api public
360 | */
361 |
362 | async generate(...args) {
363 | let parsed = this.parseTasks(...args);
364 | let { tasks, missing, options, callback } = parsed;
365 |
366 | let promise = new Promise(async(resolve, reject) => {
367 | if (missing.length > 0) {
368 | reject(new Error('Invalid task(s) or generator(s): ' + missing.join(', ')));
369 | return;
370 | }
371 |
372 | let generator = name => {
373 | let app = this.hasGenerator(name) ? this.getGenerator(name, options) : this;
374 | this.emit('generate', app);
375 | return app;
376 | };
377 |
378 | if (options.parallel === true) {
379 | let pending = [];
380 | for (let ele of tasks) pending.push(generator(ele.name).build(ele.tasks));
381 | Promise.all(pending).then(resolve).catch(reject);
382 | } else {
383 | for (let ele of tasks) {
384 | await generator(ele.name).build(ele.tasks).catch(reject);
385 | }
386 | resolve();
387 | }
388 | });
389 |
390 | if (typeof callback === 'function') {
391 | promise.then(() => callback()).catch(callback);
392 | return;
393 | }
394 |
395 | return promise;
396 | }
397 |
398 | /**
399 | * Create a generator alias from the given `name`. By default, `generate-`
400 | * is stripped from beginning of the generator name.
401 | *
402 | * ```js
403 | * // customize the alias
404 | * const app = new Generate({ toAlias: require('camel-case') });
405 | * ```
406 | * @name .toAlias
407 | * @param {String} `name`
408 | * @param {Object} `options`
409 | * @return {String} Returns the alias.
410 | * @api public
411 | */
412 |
413 | toAlias(name, options) {
414 | if (typeof options === 'function') {
415 | return options(name);
416 | }
417 | if (options && typeof options.toAlias === 'function') {
418 | return options.toAlias(name);
419 | }
420 | if (typeof this.options.toAlias === 'function') {
421 | return this.options.toAlias(name);
422 | }
423 | return name ? name.replace(/^generate-/, '') : '';
424 | }
425 |
426 | /**
427 | * Returns true if every name in the given array is a registered generator.
428 | * @name .isGenerators
429 | * @param {Array} `names`
430 | * @return {Boolean}
431 | * @api public
432 | */
433 |
434 | isGenerators(names) {
435 | return names.every(name => this.hasGenerator(name));
436 | }
437 |
438 | /**
439 | * Format task and generator errors.
440 | * @name .formatError
441 | * @param {String} `name`
442 | * @return {Error}
443 | * @api public
444 | */
445 |
446 | formatError(name, type = 'generator', appname = 'generator') {
447 | let key = this.namespace || 'default';
448 | let suffix = '.';
449 |
450 | // if not the base instance, remove the first name segment
451 | if (this !== this.base) {
452 | key = key.split('.').slice(1).join('.');
453 | suffix = ` on ${appname} "${key}"`;
454 | }
455 |
456 | let message = `${type} "${name}" is not registered`;
457 | return new Error(message + suffix);
458 | }
459 |
460 | /**
461 | * Disable inspect. Returns a function to re-enable inspect. Useful for debugging.
462 | * @name .disableInspect
463 | * @api public
464 | */
465 |
466 | disableInspect() {
467 | let inspect = this[util.inspect.custom];
468 | this[util.inspect.custom] = void 0;
469 |
470 | return () => {
471 | define(this, util.inspect.custom, inspect);
472 | };
473 | }
474 |
475 | /**
476 | * Parse task arguments into an array of task configuration objects.
477 | */
478 |
479 | parseTasks(...args) {
480 | return parse(this.options.register)(this, ...args);
481 | }
482 |
483 | /**
484 | * Custom inspect function
485 | */
486 |
487 | [util.inspect.custom]() {
488 | if (typeof this.options.inspectFn === 'function') {
489 | return this.options.inspectFn(this);
490 | }
491 | const names = this.generators ? [...this.generators.keys()].join(', ') : '';
492 | const tasks = this.tasks ? [...this.tasks.keys()].join(', ') : '';
493 | return ``;
494 | }
495 |
496 | /**
497 | * Get the first ancestor instance of Composer. Only works if `generator.parent` is
498 | * defined on child instances.
499 | * @name .base
500 | * @getter
501 | * @api public
502 | */
503 |
504 | get base() {
505 | return this.parent ? this.parent.base : this;
506 | }
507 |
508 | /**
509 | * Get or set the generator name.
510 | * @name .name
511 | * @getter
512 | * @param {String} [name="root"]
513 | * @return {String}
514 | * @api public
515 | */
516 |
517 | set name(val) {
518 | define(this, '_name', val);
519 | }
520 | get name() {
521 | return this._name || 'generate';
522 | }
523 |
524 | /**
525 | * Get or set the generator `alias`. By default, the generator alias is created
526 | * by passing the generator name to the [.toAlias](#toAlias) method.
527 | * @name .alias
528 | * @getter
529 | * @param {String} [alias="generate"]
530 | * @return {String}
531 | * @api public
532 | */
533 |
534 | set alias(val) {
535 | define(this, '_alias', val);
536 | }
537 | get alias() {
538 | return this._alias || this.toAlias(this.name, this.options);
539 | }
540 |
541 | /**
542 | * Get the generator namespace. The namespace is created by joining the generator's `alias`
543 | * to the alias of each ancestor generator.
544 | * @name .namespace
545 | * @getter
546 | * @param {String} [namespace="root"]
547 | * @return {String}
548 | * @api public
549 | */
550 |
551 | get namespace() {
552 | return this.parent ? this.parent.namespace + '.' + this.alias : this.alias;
553 | }
554 |
555 | /**
556 | * Get the depth of a generator - useful for debugging. The root generator
557 | * has a depth of `0`, sub-generators add `1` for each level of nesting.
558 | * @name .depth
559 | * @getter
560 | * @return {Number}
561 | * @api public
562 | */
563 |
564 | get depth() {
565 | return this.parent ? this.parent.depth + 1 : 0;
566 | }
567 |
568 | /**
569 | * Static method that returns a function for parsing task arguments.
570 | * @name Composer#parse
571 | * @param {Function} `register` Function that receives a name of a task or generator that cannot be found by the parse function. This allows the `register` function to dynamically register tasks or generators.
572 | * @return {Function} Returns a function for parsing task args.
573 | * @api public
574 | * @static
575 | */
576 |
577 | static parseTasks(register) {
578 | return parse(register);
579 | }
580 |
581 | /**
582 | * Static method that returns true if the given `val` is an instance of Generate.
583 | * @name Composer#isGenerator
584 | * @param {Object} `val`
585 | * @return {Boolean}
586 | * @api public
587 | * @static
588 | */
589 |
590 | static isGenerator(val) {
591 | return val instanceof this || (typeof val === 'function' && val.isGenerator === true);
592 | }
593 |
594 | /**
595 | * Static method for creating a custom Composer class with the given `Emitter.
596 | * @name Composer#create
597 | * @param {Function} `Emitter`
598 | * @return {Class} Returns the custom class.
599 | * @static
600 | * @api public
601 | */
602 |
603 | static create(Emitter) {
604 | return factory(Emitter);
605 | }
606 |
607 | /**
608 | * Static getter for getting the Tasks class with the same `Emitter` class as Composer.
609 | * @name Composer#Tasks
610 | * @param {Function} `Emitter`
611 | * @return {Class} Returns the Tasks class.
612 | * @getter
613 | * @static
614 | * @api public
615 | */
616 |
617 | static get Tasks() {
618 | return Tasks.create(Emitter);
619 | }
620 |
621 | /**
622 | * Static getter for getting the `Task` class.
623 | *
624 | * ```js
625 | * const { Task } = require('composer');
626 | * ```
627 | * @name Composer#Task
628 | * @getter
629 | * @static
630 | * @api public
631 | */
632 |
633 | static get Task() {
634 | return Task;
635 | }
636 | }
637 |
638 | return Generator;
639 | };
640 |
641 | /**
642 | * Expose `factory` function
643 | */
644 |
645 | module.exports = factory();
646 |
--------------------------------------------------------------------------------
/lib/parse.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { isObject } = require('./utils');
4 | const noop = () => {};
5 |
6 | /**
7 | * Parse task expressions from the argv._ (splat) array
8 | */
9 |
10 | module.exports = (register = noop) => {
11 | return function parse(app, ...rest) {
12 | if (rest.length === 1 && Array.isArray(rest[0])) rest = rest[0];
13 | let options = rest.find(val => isObject(val) && val.isTask !== true);
14 | rest = rest.filter(val => val !== options);
15 |
16 | let callback = rest.find(val => typeof val === 'function');
17 | let args = rest.filter(val => val !== options && val !== callback);
18 | let opts = { ...app.options, ...options };
19 |
20 | if (typeof args[0] === 'string' && Array.isArray(args[1])) {
21 | args = [args[0] + ':' + args[1].join(',')];
22 | }
23 |
24 | args = args.join(' ').split(' ');
25 | let missing = [];
26 | let result = [];
27 |
28 | for (const arg of args) {
29 | let segs = arg.split(':');
30 | if (segs.length > 2) {
31 | throw new SyntaxError('spaces must be used to separate multiple generator names');
32 | }
33 |
34 | let tasks = segs[1] ? segs[1].split(',') : segs[0].split(',');
35 | let name = segs[1] ? segs[0] : null;
36 | let task = { name: null, tasks: [] };
37 |
38 | tasks.forEach(val => {
39 | if (!app.tasks.has(val) && app.hasGenerator(name + '.' + val)) {
40 | result.push({ name: [name, val].join('.'), tasks: ['default'] });
41 | tasks = tasks.filter(v => v !== val);
42 | }
43 | });
44 |
45 | if (segs.length === 2 && tasks.length) {
46 | task.name = name;
47 | task.tasks = tasks;
48 | result.push(task);
49 | continue;
50 | }
51 |
52 | for (let key of tasks) {
53 | if ((!app.tasks.has(key) || app.taskStack.has(key)) && app.hasGenerator(key)) {
54 | if (app.name === 'default' && !key.startsWith('default.')) {
55 | key = `default.${key}`;
56 | }
57 | result.push({ name: key, tasks: ['default' ]});
58 | } else if (key && app.tasks.has(key)) {
59 | task.name = 'default';
60 | task.tasks.push(key);
61 | } else if (key) {
62 | missing.push(key);
63 | }
64 | }
65 |
66 | if (task.name) {
67 | result.push(task);
68 | }
69 | }
70 |
71 | if (rest.length && !result.length && app.name !== 'default') {
72 | if (app.hasGenerator('default')) {
73 | return parse(app.getGenerator('default'), ...rest);
74 | }
75 | }
76 |
77 | if (!rest.length && !result.length && !missing.length) {
78 | result = [{ name: 'default', tasks: ['default'] }];
79 | }
80 |
81 | if (missing.length && register(missing) === true) {
82 | return parse(app, ...rest);
83 | }
84 |
85 | register(result.map(task => task.name));
86 |
87 | return { options: opts, callback, tasks: result, missing };
88 | };
89 | };
90 |
--------------------------------------------------------------------------------
/lib/task.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const util = require('util');
4 | const Events = require('events');
5 | const Timer = require('./timer');
6 | const { define, noop } = require('./utils');
7 |
8 | class Task extends Events {
9 | constructor(task = {}) {
10 | if (typeof task.name !== 'string') {
11 | throw new TypeError('expected task name to be a string');
12 | }
13 | super();
14 | define(this, 'isTask', true);
15 | define(this, 'app', task.app);
16 | this.name = task.name;
17 | this.status = 'pending';
18 | this.options = Object.assign({ deps: [] }, task.options);
19 | this.callback = task.callback || noop;
20 | this.deps = [...task.deps || [], ...this.options.deps];
21 | this.time = new Timer();
22 | if (this.setMaxListeners) {
23 | this.setMaxListeners(0);
24 | }
25 | }
26 |
27 | [util.inspect.custom]() {
28 | return ``;
29 | }
30 |
31 | run(options) {
32 | let finished = false;
33 | let orig = Object.assign({}, this.options);
34 | this.options = Object.assign({}, this.options, options);
35 | this.status = 'preparing';
36 | this.emit('preparing', this);
37 |
38 | if (this.skip(options)) {
39 | return () => Promise.resolve(null);
40 | }
41 |
42 | this.time = new Timer();
43 | this.time.start();
44 | this.status = 'starting';
45 | this.emit('starting', this);
46 |
47 | return () => new Promise(async(resolve, reject) => {
48 | const finish = (err, value) => {
49 | if (finished) return;
50 | finished = true;
51 | try {
52 | this.options = orig;
53 | this.time.end();
54 | this.status = 'finished';
55 | this.emit('finished', this);
56 | if (err) {
57 | define(err, 'task', this);
58 | reject(err);
59 | this.emit('error', err);
60 | } else {
61 | resolve(value);
62 | }
63 | } catch (err) {
64 | reject(err);
65 | }
66 | };
67 |
68 | try {
69 | if (typeof this.callback !== 'function') {
70 | finish();
71 | return;
72 | }
73 |
74 | let res = this.callback.call(this, finish);
75 | if (res instanceof Promise) {
76 | let val = await res;
77 | if (val) res = val;
78 | }
79 |
80 | if (isEmitter(res)) {
81 | res.on('error', finish);
82 | res.on('finish', finish);
83 | res.on('end', finish);
84 | return;
85 | }
86 |
87 | if (this.callback.length === 0) {
88 | if (res && res.then) {
89 | res.then(() => finish());
90 | } else {
91 | finish(null, res);
92 | }
93 | }
94 |
95 | } catch (err) {
96 | finish(err);
97 | }
98 | });
99 | }
100 |
101 | skip(options) {
102 | let app = this.app || {};
103 | let opts = Object.assign({}, app.options, this.options, options);
104 |
105 | if (opts.run === false) {
106 | return true;
107 | }
108 |
109 | if (Array.isArray(opts.skip)) {
110 | return opts.skip.includes(this.name);
111 | }
112 |
113 | switch (typeof opts.skip) {
114 | case 'boolean':
115 | return opts.skip === true;
116 | case 'function':
117 | return opts.skip(this) === true;
118 | case 'string':
119 | return opts.skip === this.name;
120 | default: {
121 | return false;
122 | }
123 | }
124 | }
125 | }
126 |
127 | function isEmitter(val) {
128 | return val && (typeof val.on === 'function' || typeof val.pipe === 'function');
129 | }
130 |
131 | module.exports = Task;
132 |
--------------------------------------------------------------------------------
/lib/tasks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Task = require('./task');
4 | const Timer = require('./timer');
5 | const Events = require('events');
6 | const { createOptions, flatten, noop } = require('./utils');
7 |
8 | /**
9 | * Factory for creating a custom `Tasks` class that extends the
10 | * given `Emitter`. Or, simply call the factory function to use
11 | * the built-in emitter.
12 | *
13 | * ```js
14 | * // custom emitter
15 | * const Emitter = require('events');
16 | * const Tasks = require('composer/lib/tasks')(Emitter);
17 | * // built-in emitter
18 | * const Tasks = require('composer/lib/tasks')();
19 | * const composer = new Tasks();
20 | * ```
21 | * @name .factory
22 | * @param {function} `Emitter` Event emitter.
23 | * @return {Class} Returns a custom `Tasks` class.
24 | * @api public
25 | */
26 |
27 | const factory = (Emitter = Events) => {
28 |
29 | /**
30 | * Create an instance of `Tasks` with the given `options`.
31 | *
32 | * ```js
33 | * const Tasks = require('composer').Tasks;
34 | * const composer = new Tasks();
35 | * ```
36 | * @class
37 | * @name Tasks
38 | * @param {object} `options`
39 | * @api public
40 | */
41 |
42 | class Tasks extends Emitter {
43 | constructor(options = {}) {
44 | super(!Emitter.name.includes('Emitter') ? options : null);
45 | this.options = options;
46 | this.taskStack = new Map();
47 | this.tasks = new Map();
48 | this.taskId = 0;
49 |
50 | if (this.off === void 0 && typeof this.removeListener === 'function') {
51 | this.off = this.removeListener.bind(this);
52 | }
53 | }
54 |
55 | /**
56 | * Define a task. Tasks run asynchronously, either in series (by default) or parallel
57 | * (when `options.parallel` is true). In order for the build to determine when a task is
58 | * complete, _one of the following_ things must happen: 1) the callback must be called, 2) a
59 | * promise must be returned, or 3) a stream must be returned. Inside tasks, the "this"
60 | * object is a composer Task instance created for each task with useful properties like
61 | * the task name, options and timing information, which can be useful for logging, etc.
62 | *
63 | * ```js
64 | * // 1. callback
65 | * app.task('default', cb => {
66 | * // do stuff
67 | * cb();
68 | * });
69 | * // 2. promise
70 | * app.task('default', () => {
71 | * return Promise.resolve(null);
72 | * });
73 | * // 3. stream (using vinyl-fs or your stream of choice)
74 | * app.task('default', function() {
75 | * return vfs.src('foo/*.js');
76 | * });
77 | * ```
78 | * @name .task
79 | * @param {String} `name` The task name.
80 | * @param {Object|Array|String|Function} `deps` Any of the following: task dependencies, callback(s), or options object, defined in any order.
81 | * @param {Function} `callback` (optional) If the last argument is a function, it will be called after all of the task's dependencies have been run.
82 | * @return {undefined}
83 | * @api public
84 | */
85 |
86 | task(name, ...rest) {
87 | if (typeof name !== 'string') {
88 | throw new TypeError('expected task "name" to be a string');
89 | }
90 | const { options, tasks } = createOptions(this, false, ...rest);
91 | const callback = typeof tasks[tasks.length - 1] === 'function' ? tasks.pop() : noop;
92 | return this.setTask(name, options, tasks, callback);
93 | }
94 |
95 | /**
96 | * Set a task on `app.tasks`
97 | * @name .setTask
98 | * @param {string} name Task name
99 | * @param {object} name Task options
100 | * @param {object|array|string|function} `deps` Task dependencies
101 | * @param {Function} `callback` (optional) Final callback function to call after all task dependencies have been run.
102 | * @return {object} Returns the instance.
103 | */
104 |
105 | setTask(name, options = {}, deps = [], callback) {
106 | const task = new Task({ name, options, deps, callback, app: this });
107 | const emit = (key = 'task') => this.emit(key, task);
108 | task.on('error', this.emit.bind(this, 'error'));
109 | task.on('preparing', () => emit('task-preparing'));
110 | task.on('starting', task => {
111 | this.taskStack.set(task.name, task);
112 | emit();
113 | });
114 | task.on('finished', task => {
115 | this.taskStack.delete(task.name);
116 | emit();
117 | });
118 | this.tasks.set(name, task);
119 | task.status = 'registered';
120 | emit('task-registered');
121 | return this;
122 | }
123 |
124 | /**
125 | * Get a task from `app.tasks`.
126 | * @name .getTask
127 | * @param {string} name
128 | * @return {object} Returns the task object.
129 | */
130 |
131 | getTask(name) {
132 | if (!this.tasks.has(name)) {
133 | throw this.formatError(name, 'task');
134 | }
135 | return this.tasks.get(name);
136 | }
137 |
138 | /**
139 | * Returns true if all values in the array are registered tasks.
140 | * @name .isTasks
141 | * @param {array} tasks
142 | * @return {boolean}
143 | */
144 |
145 | isTasks(arr) {
146 | return Array.isArray(arr) && arr.every(name => this.tasks.has(name));
147 | }
148 |
149 | /**
150 | * Create an array of tasks to run by resolving registered tasks from the values
151 | * in the given array.
152 | * @name .expandTasks
153 | * @param {...[string|function|glob]} tasks
154 | * @return {array}
155 | */
156 |
157 | expandTasks(...args) {
158 | let vals = flatten(args).filter(Boolean);
159 | let keys = [...this.tasks.keys()];
160 | let tasks = [];
161 |
162 | for (let task of vals) {
163 | if (typeof task === 'function') {
164 | let name = `task-${this.taskId++}`;
165 | this.task(name, task);
166 | tasks.push(name);
167 | continue;
168 | }
169 |
170 | if (typeof task === 'string') {
171 | if (/\*/.test(task)) {
172 | let matches = match(keys, task);
173 | if (matches.length === 0) {
174 | throw new Error(`glob "${task}" does not match any registered tasks`);
175 | }
176 | tasks.push.apply(tasks, matches);
177 | continue;
178 | }
179 |
180 | tasks.push(task);
181 | continue;
182 | }
183 |
184 | let msg = 'expected task dependency to be a string or function, but got: ';
185 | throw new TypeError(msg + typeof task);
186 | }
187 | return tasks;
188 | }
189 |
190 | /**
191 | * Run one or more tasks.
192 | *
193 | * ```js
194 | * const build = app.series(['foo', 'bar', 'baz']);
195 | * // promise
196 | * build().then(console.log).catch(console.error);
197 | * // or callback
198 | * build(function() {
199 | * if (err) return console.error(err);
200 | * });
201 | * ```
202 | * @name .build
203 | * @param {object|array|string|function} `tasks` One or more tasks to run, options, or callback function. If no tasks are defined, the default task is automatically run.
204 | * @param {function} `callback` (optional)
205 | * @return {undefined}
206 | * @api public
207 | */
208 |
209 | async build(...args) {
210 | let state = { status: 'starting', time: new Timer(), app: this };
211 | state.time.start();
212 | this.emit('build', state);
213 |
214 | args = flatten(args);
215 | let cb = typeof args[args.length - 1] === 'function' ? args.pop() : null;
216 |
217 | let { options, tasks } = createOptions(this, true, ...args);
218 | if (!tasks.length) tasks = ['default'];
219 |
220 | let each = options.parallel ? this.parallel : this.series;
221 | let build = each.call(this, options, ...tasks);
222 | let promise = build()
223 | .then(() => {
224 | state.time.end();
225 | state.status = 'finished';
226 | this.emit('build', state);
227 | });
228 |
229 | return resolveBuild(promise, cb);
230 | }
231 |
232 | /**
233 | * Compose a function to run the given tasks in series.
234 | *
235 | * ```js
236 | * const build = app.series(['foo', 'bar', 'baz']);
237 | * // promise
238 | * build().then(console.log).catch(console.error);
239 | * // or callback
240 | * build(function() {
241 | * if (err) return console.error(err);
242 | * });
243 | * ```
244 | * @name .series
245 | * @param {object|array|string|function} `tasks` Tasks to run, options, or callback function. If no tasks are defined, the `default` task is automatically run, if one exists.
246 | * @param {function} `callback` (optional)
247 | * @return {promise|undefined} Returns a promise if no callback is passed.
248 | * @api public
249 | */
250 |
251 | series(...args) {
252 | let stack = new Set();
253 | let compose = this.iterator('series', async(tasks, options, resolve) => {
254 | for (let ele of tasks) {
255 | let task = this.getTask(ele);
256 | task.series = true;
257 |
258 | if (task.skip(options) || stack.has(task)) {
259 | continue;
260 | }
261 |
262 | task.once('finished', () => stack.delete(task));
263 | task.once('starting', () => stack.add(task));
264 | let run = task.run(options);
265 |
266 | if (task.deps.length) {
267 | let opts = Object.assign({}, options, task.options);
268 | let each = opts.parallel ? this.parallel : this.series;
269 | let build = each.call(this, ...task.deps);
270 | await build();
271 | }
272 |
273 | await run();
274 | }
275 |
276 | resolve();
277 | });
278 |
279 | return compose(...args);
280 | }
281 |
282 | /**
283 | * Compose a function to run the given tasks in parallel.
284 | *
285 | * ```js
286 | * // call the returned function to start the build
287 | * const build = app.parallel(['foo', 'bar', 'baz']);
288 | * // promise
289 | * build().then(console.log).catch(console.error);
290 | * // callback
291 | * build(function() {
292 | * if (err) return console.error(err);
293 | * });
294 | * // example task usage
295 | * app.task('default', build);
296 | * ```
297 | * @name .parallel
298 | * @param {object|array|string|function} `tasks` Tasks to run, options, or callback function. If no tasks are defined, the `default` task is automatically run, if one exists.
299 | * @param {function} `callback` (optional)
300 | * @return {promise|undefined} Returns a promise if no callback is passed.
301 | * @api public
302 | */
303 |
304 | parallel(...args) {
305 | let stack = new Set();
306 | let compose = this.iterator('parallel', (tasks, options, resolve) => {
307 | let pending = [];
308 |
309 | for (let ele of tasks) {
310 | let task = this.getTask(ele);
311 | task.parallel = true;
312 |
313 | if (task.skip(options) || stack.has(task)) {
314 | continue;
315 | }
316 |
317 | task.once('finished', () => stack.delete(task));
318 | task.once('starting', () => stack.add(task));
319 | let run = task.run(options);
320 |
321 | if (task.deps.length) {
322 | let opts = Object.assign({}, options, task.options);
323 | let each = opts.parallel ? this.parallel : this.series;
324 | let build = each.call(this, ...task.deps);
325 | pending.push(build().then(() => run()));
326 | } else {
327 | pending.push(run());
328 | }
329 | }
330 |
331 | resolve(Promise.all(pending));
332 | });
333 |
334 | return compose(...args);
335 | }
336 |
337 | /**
338 | * Create an async iterator function that ensures that either a promise is
339 | * returned or the user-provided callback is called.
340 | * @param {function} `fn` Function to invoke inside the promise.
341 | * @return {function}
342 | */
343 |
344 | iterator(type, fn) {
345 | return (...args) => {
346 | let { options, tasks } = createOptions(this, true, ...args);
347 |
348 | return cb => {
349 | let promise = new Promise(async(resolve, reject) => {
350 | if (tasks.length === 0) {
351 | resolve();
352 | return;
353 | }
354 |
355 | try {
356 | let p = fn(tasks, options, resolve);
357 | if (type === 'series') await p;
358 | } catch (err) {
359 | reject(err);
360 | }
361 | });
362 |
363 | return resolveBuild(promise, cb);
364 | };
365 | };
366 | }
367 |
368 | /**
369 | * Format task and generator errors.
370 | * @name .formatError
371 | * @param {String} `name`
372 | * @return {Error}
373 | */
374 |
375 | formatError(name) {
376 | return new Error(`task "${name}" is not registered`);
377 | }
378 |
379 | /**
380 | * Static method for creating a custom Tasks class with the given `Emitter.
381 | * @name .create
382 | * @param {Function} `Emitter`
383 | * @return {Class} Returns the custom class.
384 | * @api public
385 | * @static
386 | */
387 |
388 | static create(Emitter) {
389 | return factory(Emitter);
390 | }
391 | }
392 | return Tasks;
393 | };
394 |
395 | function resolveBuild(promise, cb) {
396 | if (typeof cb === 'function') {
397 | promise.then(val => cb(null, val)).catch(cb);
398 | } else {
399 | return promise;
400 | }
401 | }
402 |
403 | function match(keys, pattern) {
404 | let chars = [...pattern].map(ch => ({ '*': '.*?', '.': '\\.' }[ch] || ch));
405 | let regex = new RegExp(chars.join(''));
406 | return keys.filter(key => regex.test(key));
407 | }
408 |
409 | module.exports = factory();
410 |
--------------------------------------------------------------------------------
/lib/timer.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { nano } = require('./utils');
4 |
5 | class Timer {
6 | constructor() {
7 | this.date = {};
8 | this.hr = {};
9 | }
10 |
11 | start() {
12 | this.date.start = new Date();
13 | this.hr.start = process.hrtime();
14 | return this;
15 | }
16 |
17 | end() {
18 | this.date.end = new Date();
19 | this.hr.end = process.hrtime();
20 | this.hr.duration = process.hrtime(this.hr.start);
21 | return this;
22 | }
23 |
24 | get diff() {
25 | return nano(this.hr.end) - nano(this.hr.start);
26 | }
27 |
28 | get duration() {
29 | return this.hr.duration ? require('pretty-time')(this.hr.duration) : '';
30 | }
31 | }
32 |
33 | /**
34 | * Expose `Timer`
35 | */
36 |
37 | module.exports = Timer;
38 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Get hr time in nanoseconds
5 | */
6 |
7 | const nano = time => +time[0] * 1e9 + +time[1];
8 |
9 | /**
10 | * Flatten an array
11 | */
12 |
13 | const flatten = arr => [].concat.apply([], arr);
14 |
15 | /**
16 | * Return true if `val` is an object
17 | */
18 |
19 | const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val);
20 |
21 | /**
22 | * Create an options object from the given arguments.
23 | * @param {object} `app`
24 | * @param {...[function|string|object]} `rest`
25 | * @return {object}
26 | */
27 |
28 | const createOptions = (app, expand, ...rest) => {
29 | const args = flatten(rest);
30 | const config = args.find(val => isObject(val) && !val.isTask) || {};
31 | const options = Object.assign({}, app.options, config);
32 | const tasks = expand === true
33 | ? app.expandTasks(args.filter(val => val && val !== config))
34 | : args.filter(val => val && val !== config);
35 | return { tasks, options };
36 | };
37 |
38 | /**
39 | * Noop for tasks
40 | */
41 |
42 | const noop = cb => cb();
43 |
44 | /**
45 | * Create a non-enumerable property on `obj`
46 | */
47 |
48 | const define = (obj, key, value) => {
49 | Reflect.defineProperty(obj, key, {
50 | configurable: true,
51 | enumerable: false,
52 | writable: true,
53 | value
54 | });
55 | };
56 |
57 | /**
58 | * Expose "utils"
59 | */
60 |
61 | module.exports = {
62 | createOptions,
63 | define,
64 | flatten,
65 | isObject,
66 | nano,
67 | noop
68 | };
69 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "composer",
3 | "description": "Run and compose async tasks. Easily define groups of tasks to run in series or parallel.",
4 | "version": "4.1.0",
5 | "homepage": "https://github.com/doowb/composer",
6 | "author": "Brian Woodward (https://github.com/doowb)",
7 | "contributors": [
8 | "Brian Woodward (https://twitter.com/doowb)",
9 | "Jon Schlinkert (http://twitter.com/jonschlinkert)"
10 | ],
11 | "repository": "doowb/composer",
12 | "bugs": {
13 | "url": "https://github.com/doowb/composer/issues"
14 | },
15 | "license": "MIT",
16 | "files": [
17 | "index.js",
18 | "lib"
19 | ],
20 | "main": "index.js",
21 | "engines": {
22 | "node": ">=8"
23 | },
24 | "scripts": {
25 | "test": "mocha"
26 | },
27 | "dependencies": {
28 | "pretty-time": "^1.1.0",
29 | "use": "^3.1.1"
30 | },
31 | "devDependencies": {
32 | "gulp-format-md": "^1.0.0",
33 | "minimist": "^1.2.0",
34 | "mocha": "^5.2.0",
35 | "through2": "^2.0.3"
36 | },
37 | "keywords": [
38 | "async",
39 | "await",
40 | "build",
41 | "build-system",
42 | "compose",
43 | "composer",
44 | "composition",
45 | "control",
46 | "flow",
47 | "run",
48 | "system",
49 | "task",
50 | "workflow"
51 | ],
52 | "verb": {
53 | "toc": true,
54 | "layout": "default",
55 | "data": {
56 | "author": {
57 | "linkedin": "woodwardbrian",
58 | "twitter": "doowb"
59 | }
60 | },
61 | "tasks": [
62 | "readme"
63 | ],
64 | "plugins": [
65 | "gulp-format-md"
66 | ],
67 | "related": {
68 | "list": [
69 | "enquirer",
70 | "assemble",
71 | "generate",
72 | "update",
73 | "verb"
74 | ]
75 | },
76 | "lint": {
77 | "reflinks": true
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/test/app.default-generator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const Generator = require('..');
6 | let app;
7 |
8 | describe('default generator', () => {
9 | beforeEach(() => {
10 | app = new Generator();
11 | });
12 |
13 | it('should throw an error when a default generator is not found', () => {
14 | return app.generate('default')
15 | .catch(err => {
16 | assert(err);
17 | assert(/invalid/i.test(err.message));
18 | });
19 | });
20 |
21 | it('should throw an error when a default task on default generator is not found', cb => {
22 | app.register('default', () => {});
23 | app.generate('default', 'default')
24 | .catch(err => {
25 | assert(err);
26 | assert(/task/i.test(err.message));
27 | assert(/is not registered/i.test(err.message));
28 | cb();
29 | });
30 | });
31 |
32 | it('should throw an error when a default task on default generator is not found', cb => {
33 | app.register('default', () => {});
34 | app.generate('default', 'default')
35 | .catch(err => {
36 | assert(err);
37 | assert(/task/i.test(err.message));
38 | assert(/is not registered/i.test(err.message));
39 | cb();
40 | });
41 | });
42 |
43 | it('should prefer tasks on the default generator over anything on the app instance', () => {
44 | let count = { app: 0, default: 0 };
45 |
46 | app.task('foo', next => {
47 | count.app++;
48 | next();
49 | });
50 |
51 | app.register('foo', foo => {
52 | foo.task('default', next => {
53 | count.app++;
54 | next();
55 | });
56 | });
57 |
58 | app.register('default', gen => {
59 | // I should win
60 | gen.task('foo', next => {
61 | count.default++;
62 | next();
63 | });
64 | });
65 |
66 | return app.generate('foo')
67 | .then(() => {
68 | assert.equal(count.app, 0);
69 | assert.equal(count.default, 1);
70 | });
71 | });
72 |
73 | it('should prefer generators on the default generator over anything on the app instance', () => {
74 | let count = { app: 0, default: 0 };
75 |
76 | app.task('foo', next => {
77 | count.app++;
78 | next();
79 | });
80 |
81 | app.register('foo', foo => {
82 | foo.task('default', next => {
83 | count.app++;
84 | next();
85 | });
86 | });
87 |
88 | app.register('default', gen => {
89 | gen.task('foo', next => {
90 | // I should win
91 | count.default++;
92 | next();
93 | });
94 |
95 | gen.register('foo', foo => {
96 | foo.task('default', next => {
97 | next();
98 | });
99 | });
100 | });
101 |
102 | return app.generate('foo')
103 | .then(() => {
104 | assert.equal(count.app, 0);
105 | assert.equal(count.default, 1);
106 | });
107 | });
108 |
109 | it('should run default tasks on an array of nested sub-generators', () => {
110 | let count = 0;
111 | const task = async() => count++;
112 | const def = gen => gen.task('default', task);
113 |
114 | app.register('default', function() {
115 | this.register('foo', app => {
116 | app.register('one', def);
117 | app.register('two', def);
118 | });
119 |
120 | this.register('bar', app => {
121 | app.register('one', def);
122 | app.register('two', def);
123 | app.register('three', function() {
124 | this.task('zzz', task);
125 | this.task('default', ['zzz']);
126 | });
127 | });
128 | });
129 |
130 | return app.generate(['foo.one', 'foo.two', 'bar.one', 'bar.two', 'bar.three'])
131 | .then(() => {
132 | assert.equal(count, 5);
133 | });
134 | });
135 |
136 | it('should throw an error when default task is called and missing on default generator', () => {
137 | let count = { app: 0, default: 0 };
138 |
139 | app.register('default', gen => {
140 | gen.task('default', next => {
141 | count.default++;
142 | next();
143 | });
144 | });
145 |
146 | return app.generate('default')
147 | .then(() => {
148 | assert.equal(count.default, 1);
149 | });
150 | });
151 |
152 | it('should throw an error when default task is called and missing on default generator', () => {
153 | app.register('default', gen => {});
154 |
155 | return app.generate('default')
156 | .catch(err => {
157 | assert(err);
158 | assert(/default/.test(err.message));
159 | assert(/not registered/.test(err.message));
160 | });
161 | });
162 |
163 | it('should handle errors from default generator tasks', () => {
164 | let errors = [];
165 |
166 | app.register('default', gen => {
167 | gen.task('foo', next => {
168 | next(new Error('huge error!'));
169 | });
170 | });
171 |
172 | app.on('error', err => {
173 | errors.push(err);
174 | });
175 |
176 | return app.generate('foo')
177 | .catch(err => {
178 | assert(err);
179 | assert(err === errors[0]);
180 | assert(/huge error/.test(err.message));
181 | });
182 | });
183 |
184 | it('should handle error events from default generator tasks', () => {
185 | app.register('default', gen => {
186 | gen.task('foo', next => {
187 | next(new Error('huge error!'));
188 | });
189 | });
190 |
191 | return app.generate('foo')
192 | .then(() => {
193 | throw new Error('expected an error');
194 | })
195 | .catch(err => {
196 | assert(err);
197 | assert(/huge error/.test(err.message));
198 | });
199 | });
200 | });
201 |
--------------------------------------------------------------------------------
/test/app.findGenerator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const Generator = require('..');
6 | let base;
7 |
8 | describe('.findGenerator', () => {
9 | beforeEach(() => {
10 | base = new Generator();
11 | });
12 |
13 | it('should not throw an error when a generator is not found', () => {
14 | assert.doesNotThrow(() => base.findGenerator('whatever'), /cannot find/);
15 | });
16 |
17 | it('should not throw an error when a generator is not found on a nested instance', () => {
18 | base.register('foo', () => {});
19 | assert.doesNotThrow(() => base.findGenerator('foo.whatever'), /cannot find/);
20 | });
21 |
22 | it('should get a generator function from the base instance', () => {
23 | base.register('abc', () => {});
24 | const generator = base.findGenerator('abc');
25 | assert.equal(typeof generator, 'function');
26 | });
27 |
28 | it('should not invoke nested generators', () => {
29 | let count = 0;
30 |
31 | base.register('abc', () => {});
32 | base.register('xyz', app => {
33 | app.register('one', () => {
34 | const generator = this.base.findGenerator('abc');
35 | assert(generator);
36 | assert.equal(typeof generator, 'function');
37 | assert.equal(generator.name, 'abc');
38 | count++;
39 | });
40 |
41 | app.register('two', () => {
42 | const generator = base.findGenerator('abc');
43 | assert(generator);
44 | assert.equal(typeof generator, 'object');
45 | assert.equal(generator.name, 'abc');
46 | count++;
47 | });
48 |
49 | app.register('three', function(three) {
50 | const generator = three.base.findGenerator('abc');
51 | assert(generator);
52 | assert.equal(typeof generator, 'object');
53 | assert.equal(generator.name, 'abc');
54 | count++;
55 | });
56 | });
57 |
58 | assert.equal(typeof base.findGenerator('xyz.one'), 'function');
59 | assert.equal(typeof base.findGenerator('xyz.two'), 'function');
60 | assert.equal(typeof base.findGenerator('xyz.three'), 'function');
61 | assert.equal(count, 0);
62 | });
63 |
64 | it('should get a generator from the base instance using `this`', () => {
65 | base.register('abc', () => {});
66 | base.register('xyz', app => {
67 | app.register('sub', function(sub) {
68 | const generator = this.findGenerator('abc');
69 | assert(generator);
70 | assert.equal(typeof generator, 'object');
71 | assert.equal(generator.name, 'abc');
72 | });
73 | });
74 | base.findGenerator('xyz');
75 | });
76 |
77 | it('should get a base generator from "app" from a nested generator', () => {
78 | base.register('abc', () => {});
79 | base.register('xyz', app => {
80 | app.register('sub', function(sub) {
81 | const generator = app.findGenerator('abc');
82 | assert(generator);
83 | assert.equal(typeof generator, 'object');
84 | assert.equal(generator.name, 'abc');
85 | });
86 | });
87 | base.findGenerator('xyz');
88 | });
89 |
90 | it('should get a nested generator', () => {
91 | base.register('abc', function(abc) {
92 | abc.register('def', function(def) {});
93 | });
94 |
95 | const generator = base.findGenerator('abc.def');
96 | assert(generator);
97 | assert.equal(typeof generator, 'function');
98 | assert.equal(generator.name, 'def');
99 | });
100 |
101 | it('should get a deeply nested generator', () => {
102 | base.register('abc', function(abc) {
103 | abc.register('def', function(def) {
104 | def.register('ghi', function(ghi) {
105 | ghi.register('jkl', function(jkl) {
106 | jkl.register('mno', () => {});
107 | });
108 | });
109 | });
110 | });
111 |
112 | const generator = base.findGenerator('abc.def.ghi.jkl.mno');
113 | assert(generator);
114 | assert.equal(typeof generator, 'function');
115 | assert.equal(generator.name, 'mno');
116 | });
117 | });
118 |
--------------------------------------------------------------------------------
/test/app.generate.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const Generator = require('..');
6 | let base;
7 |
8 | const ensureError = () => {
9 | throw new Error('expected an error');
10 | };
11 |
12 | describe('.generate', () => {
13 | beforeEach(() => {
14 | base = new Generator();
15 | });
16 |
17 | describe('generators', () => {
18 | it('should throw an error when a generator is not found', () => {
19 | return base.generate('fdsslsllsfjssl')
20 | .then(ensureError)
21 | .catch(err => {
22 | assert(err);
23 | assert(/invalid/i.test(err.message));
24 | });
25 | });
26 |
27 | it('should throw an error when the default task is not found', () => {
28 | base.register('foo', () => {});
29 | return base.generate('foo', ['default'])
30 | .then(ensureError)
31 | .catch(err => {
32 | assert(err);
33 | assert(/is not registered/i.test(err.message));
34 | });
35 | });
36 |
37 | it('should throw an error when a task is not found (task array)', () => {
38 | base.register('fdsslsllsfjssl', () => {});
39 | return base.generate('fdsslsllsfjssl', ['foo'])
40 | .then(ensureError)
41 | .catch(err => {
42 | assert(err);
43 | assert(/task/i.test(err.message));
44 | assert(/is not registered/i.test(err.message));
45 | });
46 | });
47 |
48 | it('should throw an error when a task is not found (task string)', () => {
49 | base.register('fdsslsllsfjssl', () => {});
50 | return base.generate('fdsslsllsfjssl', 'foo')
51 | .then(ensureError)
52 | .catch(err => {
53 | assert(err);
54 | assert(/task/i.test(err.message));
55 | assert(/invalid/i.test(err.message));
56 | });
57 | });
58 |
59 | it('should handle task errors', () => {
60 | base.task('default', next => {
61 | next(new Error('whatever'));
62 | });
63 |
64 | return base.generate('default')
65 | .then(ensureError)
66 | .catch(err => {
67 | assert(err);
68 | assert.equal(err.message, 'whatever');
69 | });
70 | });
71 |
72 | it('should handle task error event', () => {
73 | let errors = [];
74 | base.task('default', next => {
75 | next(new Error('whatever'));
76 | });
77 |
78 | base.on('error', err => {
79 | errors.push(err);
80 | });
81 |
82 | return base.generate('default')
83 | .then(ensureError)
84 | .catch(err => {
85 | assert(err);
86 | assert(err === errors[0]);
87 | assert.equal(err.message, 'whatever');
88 | });
89 | });
90 |
91 | it('should run a task on the instance', () => {
92 | base.task('abc123', next => next());
93 | return base.generate('abc123');
94 | });
95 |
96 | it('should run a task on the instance', () => {
97 | base.task('abc123', next => next());
98 | return base.generate('abc123');
99 | });
100 |
101 | it('should run same-named task instead of a generator', () => {
102 | base.register('123xyz', app => {
103 | throw new Error('expected the task to run first');
104 | });
105 |
106 | base.task('123xyz', cb => cb());
107 | return base.generate('123xyz');
108 | });
109 |
110 | it('should run a task instead of a generator with a default task', () => {
111 | base.register('123xyz', app => {
112 | app.task('default', () => {
113 | return Promise.resolve(new Error('expected the task to run first'));
114 | });
115 | });
116 | base.task('123xyz', cb => cb());
117 | return base.generate('123xyz');
118 | });
119 |
120 | it('should run a task on a same-named generator when the task is specified', () => {
121 | let count = 0;
122 | base.register('foo', app => {
123 | app.task('default', next => {
124 | count++;
125 | next();
126 | });
127 | });
128 |
129 | base.task('foo', () => {
130 | throw new Error('expected the generator to run');
131 | });
132 |
133 | return base.generate('foo', ['default'])
134 | .then(() => {
135 | assert.equal(count, 1);
136 | });
137 | });
138 |
139 | it('should run a generator from inside a task with the same name', () => {
140 | base.register('foo', app => {
141 | app.task('default', next => {
142 | next();
143 | });
144 | });
145 |
146 | base.task('foo', () => base.generate('foo'));
147 | return base.build('foo');
148 | });
149 |
150 | it('should run the default task on a generator as a promise', () => {
151 | base.register('foo', app => app.task('default', next => next()));
152 |
153 | return base.generate('foo');
154 | });
155 |
156 | it('should run the default task on a generator with a callback', cb => {
157 | base.register('foo', function(app) {
158 | app.task('default', function(next) {
159 | next();
160 | });
161 | });
162 |
163 | base.generate('foo', cb);
164 | });
165 |
166 | it('should run a list of tasks on the instance', () => {
167 | let count = 0;
168 | base.task('a', next => {
169 | count++;
170 | next();
171 | });
172 | base.task('b', next => {
173 | count++;
174 | next();
175 | });
176 | base.task('c', next => {
177 | count++;
178 | next();
179 | });
180 |
181 | return base.generate('a', 'b', 'c')
182 | .then(() => {
183 | assert.equal(count, 3);
184 | });
185 | });
186 |
187 | it('should run tasks in parallel', cb => {
188 | const app = new Generator({ parallel: true });
189 | const actual = [];
190 |
191 | app.task('foo', function(next) {
192 | setTimeout(() => {
193 | actual.push('foo');
194 | next();
195 | }, 5);
196 | });
197 |
198 | app.task('bar', function(next) {
199 | setTimeout(() => {
200 | actual.push('bar');
201 | next();
202 | }, 1);
203 | });
204 |
205 | app.task('baz', function(next) {
206 | actual.push('baz');
207 | next();
208 | });
209 |
210 | app.generate(['foo', 'bar', 'baz'], err => {
211 | if (err) return cb(err);
212 | assert.deepEqual(actual, ['baz', 'bar', 'foo']);
213 | cb();
214 | });
215 | });
216 |
217 | it('should run an array of tasks on the instance', () => {
218 | let count = 0;
219 | base.task('a', next => {
220 | count++;
221 | next();
222 | });
223 | base.task('b', next => {
224 | count++;
225 | next();
226 | });
227 | base.task('c', next => {
228 | count++;
229 | next();
230 | });
231 |
232 | return base.generate(['a', 'b', 'c'])
233 | .then(() => {
234 | assert.equal(count, 3);
235 | });
236 | });
237 |
238 | it('should run the default task on a registered generator', () => {
239 | let count = 0;
240 |
241 | base.register('foo', app => {
242 | app.task('default', next => {
243 | count++;
244 | next();
245 | });
246 | });
247 |
248 | return base.generate('foo')
249 | .then(() => {
250 | assert.equal(count, 1);
251 | });
252 | });
253 |
254 | it('should run an array of generators', () => {
255 | let count = 0;
256 | base.register('foo', app => {
257 | app.task('default', next => {
258 | count++;
259 | next();
260 | });
261 | });
262 |
263 | base.register('bar', app => {
264 | app.task('default', next => {
265 | count++;
266 | next();
267 | });
268 | });
269 |
270 | return base.generate(['foo', 'bar'])
271 | .then(() => {
272 | assert.equal(count, 2);
273 | });
274 | });
275 |
276 | it('should throw an error when an invalid name is passed on an array', () => {
277 | let count = 0;
278 | base.register('foo', app => {
279 | app.task('default', next => {
280 | count++;
281 | next();
282 | });
283 | });
284 |
285 | base.register('bar', app => {
286 | app.task('default', next => {
287 | count++;
288 | next();
289 | });
290 | });
291 |
292 | return base.generate(['foo', 'bar', 'lfdsflsjfsjflksjfsfjs'])
293 | .then(() => {
294 | throw new Error('expected an error');
295 | })
296 | .catch(err => {
297 | assert(/invalid/i.test(err));
298 | })
299 | });
300 |
301 | it('should run the default tasks on an array of generators', () => {
302 | let count = 0;
303 | base.register('a', function(app) {
304 | this.task('default', cb => {
305 | count++;
306 | cb();
307 | });
308 | });
309 |
310 | base.register('b', function(app) {
311 | this.task('default', cb => {
312 | count++;
313 | cb();
314 | });
315 | });
316 |
317 | base.register('c', function(app) {
318 | this.task('default', cb => {
319 | count++;
320 | cb();
321 | });
322 | });
323 |
324 | return base.generate(['a', 'b', 'c'])
325 | .then(() => {
326 | assert.equal(count, 3);
327 | });
328 | });
329 | });
330 |
331 | describe('options', cb => {
332 | it('should pass options to generator.options', () => {
333 | let count = 0;
334 | base.register('default', (app, options) => {
335 | app.task('default', next => {
336 | count++;
337 | assert.equal(options.foo, 'bar');
338 | next();
339 | });
340 | });
341 |
342 | return base.generate({ foo: 'bar' })
343 | .then(() => {
344 | assert.equal(count, 1);
345 | });
346 | });
347 |
348 | it('should expose options on generator options', () => {
349 | let count = 0;
350 | base.register('default', (app, options) => {
351 | app.task('default', next => {
352 | count++;
353 | assert.equal(options.foo, 'bar');
354 | next();
355 | });
356 | });
357 |
358 | return base.generate({foo: 'bar'})
359 | .then(() => {
360 | assert.equal(count, 1);
361 | });
362 | });
363 |
364 | it('should not mutate options on parent instance', () => {
365 | let count = 0;
366 | base.register('default', function(app, options) {
367 | app.task('default', next => {
368 | count++;
369 | assert.equal(options.foo, 'bar');
370 | assert(!base.options.foo);
371 | next();
372 | });
373 | });
374 |
375 | return base.generate({foo: 'bar'})
376 | .then(() => {
377 | assert.equal(count, 1);
378 | });
379 | });
380 | });
381 |
382 | describe('default tasks', cb => {
383 | it('should run the default task on the base instance', () => {
384 | let count = 0;
385 | base.task('default', next => {
386 | count++;
387 | next();
388 | });
389 |
390 | return base.generate()
391 | .then(() => {
392 | assert.equal(count, 1);
393 | });
394 | });
395 |
396 | it('should run the default task on the _default_ generator', () => {
397 | let count = 0;
398 | base.register('default', function(app) {
399 | app.task('default', next => {
400 | count++;
401 | next();
402 | });
403 | });
404 |
405 | return base.generate()
406 | .then(() => {
407 | assert.equal(count, 1);
408 | });
409 | });
410 |
411 | it('should run the default task on the _specified_ generator', () => {
412 | let count = 0;
413 |
414 | base.register('foo', app => {
415 | app.task('default', next => {
416 | count++;
417 | next();
418 | });
419 | });
420 |
421 | return base.generate('foo')
422 | .then(() => {
423 | assert.equal(count, 1);
424 | });
425 | });
426 | });
427 |
428 | describe('specified tasks', cb => {
429 | it('should run the specified task on a registered generator', () => {
430 | let count = 0;
431 | base.register('foo', app => {
432 | app.task('default', next => {
433 | count++;
434 | next();
435 | });
436 |
437 | app.task('abc', next => {
438 | count++;
439 | next();
440 | });
441 | });
442 |
443 | return base.generate('foo', ['abc'])
444 | .then(() => {
445 | assert.equal(count, 1);
446 | });
447 | });
448 | });
449 |
450 | describe('sub-generators', cb => {
451 | it('should run the default task on a registered sub-generator', () => {
452 | let count = 0;
453 | base.register('foo', app => {
454 | app.register('sub', function(sub) {
455 | sub.task('default', next => {
456 | count++;
457 | next();
458 | });
459 |
460 | sub.task('abc', next => {
461 | count++;
462 | next();
463 | });
464 | });
465 | });
466 |
467 | return base.generate('foo.sub')
468 | .then(() => {
469 | assert.equal(count, 1);
470 | });
471 | });
472 |
473 | it('should run the specified task array on a registered sub-generator', () => {
474 | let count = 0;
475 | base.register('foo', app => {
476 | app.register('sub', function(sub) {
477 | sub.task('default', next => {
478 | count++;
479 | next();
480 | });
481 |
482 | sub.task('abc', next => {
483 | count++;
484 | next();
485 | });
486 | });
487 | });
488 |
489 | return base.generate('foo.sub', ['abc'])
490 | .then(() => {
491 | assert.equal(count, 1);
492 | });
493 | });
494 |
495 | it('should run default tasks on an array of nested sub-generators', () => {
496 | let count = 0;
497 | base.register('foo', app => {
498 | app.register('one', function(one) {
499 | one.task('default', next => {
500 | count++;
501 | next();
502 | });
503 | });
504 |
505 | app.register('two', function(two) {
506 | two.task('default', next => {
507 | count++;
508 | next();
509 | });
510 | });
511 | });
512 |
513 | return base.generate(['foo.one', 'foo.two'])
514 | .then(() => {
515 | assert.equal(count, 2);
516 | });
517 | });
518 |
519 | it('should run an array of nested sub-generators', () => {
520 | let count = 0;
521 | base.register('foo', app => {
522 | app.register('one', function(one) {
523 | one.task('default', next => {
524 | count++;
525 | next();
526 | });
527 | });
528 |
529 | app.register('two', function(two) {
530 | two.task('default', next => {
531 | count++;
532 | next();
533 | });
534 | });
535 | });
536 |
537 | return base.generate('foo', ['one', 'two'])
538 | .then(() => {
539 | assert.equal(count, 2);
540 | });
541 | });
542 |
543 | it('should run an array of tasks on a registered sub-generator', () => {
544 | let count = 0;
545 | base.register('foo', app => {
546 | app.register('bar', function(bar) {
547 | bar.task('default', next => {
548 | count++;
549 | next();
550 | });
551 |
552 | bar.task('a', next => {
553 | count++;
554 | next();
555 | });
556 |
557 | bar.task('b', next => {
558 | count++;
559 | next();
560 | });
561 |
562 | bar.task('c', next => {
563 | count++;
564 | next();
565 | });
566 | });
567 | });
568 |
569 | return base.generate('foo.bar', ['a', 'b', 'c'])
570 | .then(() => {
571 | assert.equal(count, 3);
572 | });
573 | });
574 | });
575 |
576 | describe('cross-generator', cb => {
577 | it('should run a generator from another generator', () => {
578 | var res = '';
579 |
580 | base.register('foo', function(app, two) {
581 | app.register('sub', function(sub) {
582 | sub.task('default', next => {
583 | res += 'foo > sub > default ';
584 | base.generate('bar.sub')
585 | .then(() => next())
586 | .catch(next);
587 | });
588 | });
589 | });
590 |
591 | base.register('bar', app => {
592 | app.register('sub', function(sub) {
593 | sub.task('default', next => {
594 | res += 'bar > sub > default ';
595 | next();
596 | });
597 | });
598 | });
599 |
600 | return base.generate('foo.sub')
601 | .then(() => {
602 | assert.equal(res, 'foo > sub > default bar > sub > default ');
603 | });
604 | });
605 |
606 | it('should run the specified task on a registered sub-generator', () => {
607 | let count = 0;
608 | base.register('foo', app => {
609 | app.register('sub', function(sub) {
610 | sub.task('default', next => {
611 | count++;
612 | next();
613 | });
614 |
615 | sub.task('abc', next => {
616 | count++;
617 | next();
618 | });
619 | });
620 | });
621 |
622 | return base.generate('foo.sub', ['abc'])
623 | .then(() => {
624 | assert.equal(count, 1);
625 | });
626 | });
627 | });
628 | });
629 |
--------------------------------------------------------------------------------
/test/app.generator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const Generator = require('..');
6 | let base;
7 |
8 | describe('.generator', () => {
9 | beforeEach(() => {
10 | base = new Generator();
11 | });
12 |
13 | describe('generator naming', () => {
14 | it('should cache a generator by alias when alias is given', () => {
15 | base.generator('foo', () => {});
16 | assert(base.generators.has('foo'));
17 | });
18 |
19 | it('should cache a generator by alias when by full name is given', () => {
20 | base.generator('generate-abc', () => {});
21 | assert(base.generators.has('abc'));
22 | });
23 |
24 | it('should get a generator by alias, when registered with full name', () => {
25 | base.register('generate-foo', () => {});
26 | const app = base.getGenerator('foo');
27 | assert.equal(app.name, 'generate-foo');
28 | assert.equal(app.alias, 'foo');
29 | });
30 |
31 | it('should get a generator by alias, when registered with alias', () => {
32 | base.register('foo', () => {});
33 | const app = base.getGenerator('foo');
34 | assert.equal(app.name, 'foo');
35 | });
36 |
37 | it('should get a generator by full name, when registered with full name', () => {
38 | base.register('generate-foo', () => {});
39 | const app = base.getGenerator('generate-foo');
40 | assert.equal(app.name, 'generate-foo');
41 | assert.equal(app.alias, 'foo');
42 | });
43 |
44 | it('should get a generator by full name, when registered with alias', () => {
45 | base.register('foo', () => {});
46 | const app = base.getGenerator('generate-foo');
47 | assert.equal(app.name, 'foo');
48 | });
49 | });
50 |
51 | describe('generators', () => {
52 | it('should invoke a registered generator when `getGenerator` is called', cb => {
53 | base.register('foo', app => {
54 | app.task('default', () => {});
55 | cb();
56 | });
57 |
58 | base.getGenerator('foo');
59 | });
60 |
61 | it('should expose the generator instance on `app`', cb => {
62 | base.register('foo', app => {
63 | app.task('default', next => {
64 | assert.equal(app.a, 'b');
65 | next();
66 | });
67 | });
68 |
69 | const foo = base.getGenerator('foo');
70 | foo.a = 'b';
71 | foo.build('default', err => {
72 | if (err) return cb(err);
73 | cb();
74 | });
75 | });
76 |
77 | it('should expose the "base" instance on `app.base`', cb => {
78 | base.x = 'z';
79 | base.register('foo', app => {
80 | app.task('default', next => {
81 | assert.equal(app.base.x, 'z');
82 | next();
83 | });
84 | });
85 |
86 | const foo = base.getGenerator('foo');
87 | foo.build('default', err => {
88 | if (err) return cb(err);
89 | cb();
90 | });
91 | });
92 |
93 | it('should expose an app\'s generators on app.generators', cb => {
94 | base.register('foo', app => {
95 | app.register('a', () => {});
96 | app.register('b', () => {});
97 |
98 | app.generators.has('a');
99 | app.generators.has('b');
100 | cb();
101 | });
102 |
103 | base.getGenerator('foo');
104 | });
105 |
106 | it('should expose all root generators on base.generators', cb => {
107 | base.register('foo', app => {
108 | app.base.generators.has('foo');
109 | app.base.generators.has('bar');
110 | app.base.generators.has('baz');
111 | cb();
112 | });
113 |
114 | base.register('bar', function(app, base) {});
115 | base.register('baz', function(app, base) {});
116 | base.getGenerator('foo');
117 | });
118 | });
119 |
120 | describe('cross-generators', () => {
121 | it('should get a generator from another generator', cb => {
122 | base.register('foo', app => {
123 | const bar = app.base.getGenerator('bar');
124 | assert(bar);
125 | cb();
126 | });
127 |
128 | base.register('bar', function(app, base) {});
129 | base.register('baz', function(app, base) {});
130 | base.getGenerator('foo');
131 | });
132 |
133 | it('should set options on another generator instance', cb => {
134 | base.generator('foo', app => {
135 | app.task('default', next => {
136 | assert.equal(app.options.abc, 'xyz');
137 | next();
138 | });
139 | });
140 |
141 | base.generator('bar', app => {
142 | const foo = app.base.getGenerator('foo');
143 | foo.options.abc = 'xyz';
144 | foo.build(err => {
145 | if (err) return cb(err);
146 | cb();
147 | });
148 | });
149 | });
150 | });
151 |
152 | describe('tasks', () => {
153 | it('should expose a generator\'s tasks on app.tasks', cb => {
154 | base.register('foo', app => {
155 | app.task('a', () => {});
156 | app.task('b', () => {});
157 | assert(app.tasks.has('a'));
158 | assert(app.tasks.has('b'));
159 | cb();
160 | });
161 |
162 | base.getGenerator('foo');
163 | });
164 |
165 | it('should get tasks from another generator', cb => {
166 | base.register('foo', app => {
167 | const baz = app.base.getGenerator('baz');
168 | const task = baz.tasks.get('aaa');
169 | assert(task);
170 | cb();
171 | });
172 |
173 | base.register('bar', function(app, base) {});
174 | base.register('baz', function(app, base) {
175 | app.task('aaa', () => {});
176 | });
177 |
178 | base.getGenerator('foo');
179 | });
180 | });
181 |
182 | describe('namespace', () => {
183 | it('should expose `app.namespace`', cb => {
184 | base.generator('foo', app => {
185 | assert(typeof app.namespace, 'string');
186 | cb();
187 | });
188 | });
189 |
190 | it('should create namespace from generator alias', cb => {
191 | base.generator('generate-foo', app => {
192 | assert.equal(app.namespace, 'generate.foo');
193 | cb();
194 | });
195 | });
196 |
197 | it('should create sub-generator namespace from parent namespace and alias', cb => {
198 | base.generator('generate-foo', app => {
199 | assert(app !== base);
200 | assert(app.base === base);
201 | assert.equal(app.namespace, `${base.name}.foo`);
202 |
203 | app.generator('generate-bar', bar => {
204 | assert(bar !== app);
205 | assert(bar !== base);
206 | assert(bar.base === base);
207 | assert.equal(bar.namespace, `${base.name}.foo.bar`);
208 |
209 | bar.generator('generate-baz', baz => {
210 | assert(baz !== bar);
211 | assert(baz !== app);
212 | assert(baz !== base);
213 | assert(baz.base === base);
214 | assert.equal(baz.namespace, `${base.name}.foo.bar.baz`);
215 |
216 | baz.generator('generate-qux', qux => {
217 | assert(qux !== baz);
218 | assert(qux !== bar);
219 | assert(qux !== app);
220 | assert(qux !== base);
221 | assert(qux.base === base);
222 | assert.equal(qux.namespace, `${base.name}.foo.bar.baz.qux`);
223 | cb();
224 | });
225 | });
226 | });
227 | });
228 | });
229 |
230 | it('should expose namespace on `this`', cb => {
231 | base.generator('generate-foo', function(app) {
232 | assert.equal(this.namespace, 'generate.foo');
233 |
234 | this.generator('generate-bar', function() {
235 | assert.equal(this.namespace, 'generate.foo.bar');
236 |
237 | this.generator('generate-baz', function() {
238 | assert.equal(this.namespace, 'generate.foo.bar.baz');
239 |
240 | this.generator('generate-qux', function() {
241 | assert.equal(this.namespace, 'generate.foo.bar.baz.qux');
242 | assert.equal(app.namespace, 'generate.foo');
243 | assert.equal(this.base.namespace, 'generate');
244 | assert.equal(app.base.namespace, 'generate');
245 | cb();
246 | });
247 | });
248 | });
249 | });
250 | });
251 | });
252 | });
253 |
--------------------------------------------------------------------------------
/test/app.getGenerator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const Generator = require('..');
6 | let base;
7 |
8 | describe('.getGenerator', () => {
9 | beforeEach(() => {
10 | base = new Generator();
11 | });
12 |
13 | it('should get a generator from the base instance', () => {
14 | base.register('abc', () => {});
15 | const generator = base.getGenerator('abc');
16 | assert(Generator.isGenerator(generator));
17 | assert.equal(generator.name, 'abc');
18 | });
19 |
20 | it('should throw an error when a generator is not found', () => {
21 | base.register('foo', () => {});
22 | assert.throws(() => base.getGenerator('whatever'), /is not registered/);
23 | assert.throws(() => base.getGenerator('foo.whatever'), /is not registered/);
24 | });
25 |
26 | it('should get a generator from the base instance from a nested generator', () => {
27 | let count = 0;
28 |
29 | base.register('abc', () => {});
30 | base.register('xyz', app => {
31 | app.register('one', function() {
32 | const generator = this.base.getGenerator('abc');
33 | assert(generator);
34 | assert.equal(typeof generator, 'object');
35 | assert.equal(generator.name, 'abc');
36 | count++;
37 | });
38 |
39 | app.register('two', () => {
40 | const generator = base.getGenerator('abc');
41 | assert(generator);
42 | assert.equal(typeof generator, 'object');
43 | assert.equal(generator.name, 'abc');
44 | count++;
45 | });
46 |
47 | app.register('three', function(three) {
48 | const generator = three.base.getGenerator('abc');
49 | assert(generator);
50 | assert.equal(typeof generator, 'object');
51 | assert.equal(generator.name, 'abc');
52 | count++;
53 | });
54 | });
55 |
56 | base.getGenerator('xyz.one');
57 | base.getGenerator('xyz.two');
58 | base.getGenerator('xyz.three');
59 | assert.equal(count, 3);
60 | });
61 |
62 | it('should get a generator from the base instance using `this`', () => {
63 | base.register('abc', () => {});
64 | base.register('xyz', app => {
65 | app.register('sub', function(sub) {
66 | const generator = this.getGenerator('abc');
67 | assert(generator);
68 | assert.equal(typeof generator, 'object');
69 | assert.equal(generator.name, 'abc');
70 | });
71 | });
72 | base.getGenerator('xyz');
73 | });
74 |
75 | it('should get a base generator from "app" from a nested generator', () => {
76 | base.register('abc', () => {});
77 | base.register('xyz', app => {
78 | app.register('sub', function(sub) {
79 | const generator = app.getGenerator('abc');
80 | assert(generator);
81 | assert.equal(typeof generator, 'object');
82 | assert.equal(generator.name, 'abc');
83 | });
84 | });
85 | base.getGenerator('xyz');
86 | });
87 |
88 | it('should get a nested generator', () => {
89 | base.register('abc', function(abc) {
90 | abc.register('def', function(def) {});
91 | });
92 |
93 | const generator = base.getGenerator('abc.def');
94 | assert(generator);
95 | assert.equal(typeof generator, 'object');
96 | assert.equal(generator.name, 'def');
97 | });
98 |
99 | it('should get a deeply nested generator', () => {
100 | base.register('abc', function(abc) {
101 | abc.register('def', function(def) {
102 | def.register('ghi', function(ghi) {
103 | ghi.register('jkl', function(jkl) {
104 | jkl.register('mno', () => {});
105 | });
106 | });
107 | });
108 | });
109 |
110 | const generator = base.getGenerator('abc.def.ghi.jkl.mno');
111 | assert(generator);
112 | assert.equal(typeof generator, 'object');
113 | assert.equal(generator.name, 'mno');
114 | });
115 |
116 | it('should set deeply nested generators on the base instance cache', () => {
117 | base.disableInspect();
118 |
119 | base.register('abc', function(abc) {
120 | this.register('def', function(def) {
121 | this.register('ghi', function(ghi) {
122 | this.register('jkl', function(jkl) {
123 | this.register('mno', () => {});
124 | });
125 | });
126 | });
127 | });
128 |
129 | base.getGenerator('abc.def.ghi.jkl.mno');
130 |
131 | assert(base.namespaces.has('generate.abc'));
132 | assert(base.namespaces.has('generate.abc.def'));
133 | assert(base.namespaces.has('generate.abc.def.ghi'));
134 | assert(base.namespaces.has('generate.abc.def.ghi.jkl'));
135 | assert(base.namespaces.has('generate.abc.def.ghi.jkl.mno'));
136 | });
137 |
138 | it('should allow a generator to be called on multiple instances', () => {
139 | const parents = [];
140 |
141 | function generator() {
142 | parents.push(this.parent.name);
143 | }
144 |
145 | base.register('one', function() {
146 | this.register('sub', generator);
147 | });
148 |
149 | base.register('two', function() {
150 | this.register('sub', generator);
151 | });
152 |
153 | assert.equal(typeof base.getGenerator('one.sub'), 'object');
154 | assert.equal(typeof base.getGenerator('two.sub'), 'object');
155 | assert.equal(base.findGenerator('one.sub').called, 1);
156 | assert.equal(base.findGenerator('two.sub').called, 1);
157 | });
158 |
159 | it('should only invoke generators once by default', () => {
160 | let called = 0;
161 |
162 | base.register('one', function() {
163 | this.register('child', function() {
164 | called++;
165 | });
166 | });
167 |
168 | // _shouldn't_ be called by ".findGenerator"
169 | const wrappedFn = base.findGenerator('one.child');
170 |
171 | assert.equal(called, 0);
172 | assert.equal(base.findGenerator('one.child').called, 0);
173 |
174 | // _should_ be called by ".getGenerator"
175 | base.getGenerator('one.child');
176 | assert.equal(called, 1);
177 | assert.equal(base.findGenerator('one.child').called, called);
178 |
179 | wrappedFn.call(base, base);
180 | assert.equal(called, 1);
181 | assert.equal(base.findGenerator('one.child').called, called);
182 |
183 | wrappedFn.call(base, base);
184 | assert.equal(called, 1);
185 | assert.equal(base.findGenerator('one.child').called, called);
186 | });
187 |
188 | it('should invoke generator functions multiple times when options.once is false', () => {
189 | let called = 0;
190 |
191 | base.register('one', function() {
192 | this.register('child', { once: false }, function() {
193 | called++;
194 | });
195 | });
196 |
197 | // _shouldn't_ be called by ".findGenerator"
198 | const wrappedFn = base.findGenerator('one.child');
199 |
200 | assert.equal(called, 0);
201 | assert.equal(base.findGenerator('one.child').called, 0);
202 |
203 | // _should_ be called by ".getGenerator"
204 | base.getGenerator('one.child');
205 | assert.equal(called, 1);
206 | assert.equal(base.findGenerator('one.child').called, called);
207 |
208 | wrappedFn.call(base, base);
209 | assert.equal(called, 2);
210 | assert.equal(base.findGenerator('one.child').called, called);
211 |
212 | wrappedFn.call(base, base);
213 | assert.equal(called, 3);
214 | assert.equal(base.findGenerator('one.child').called, called);
215 | });
216 |
217 | it('should keep the original (unwrapped) function on generator.fn', () => {
218 | let called = 0;
219 |
220 | base.register('one', function() {
221 | this.register('child', function() {
222 | called++;
223 | });
224 | });
225 |
226 | // the wrapped generator function should not be called, but the
227 | // original function will be invoked each time
228 | const originalFn = base.findGenerator('one.child').fn;
229 | assert.equal(called, 0);
230 | assert.equal(base.findGenerator('one.child').called, 0);
231 |
232 | originalFn.call(base, base);
233 | assert.equal(called, 1);
234 | assert.equal(base.findGenerator('one.child').called, 0);
235 |
236 | originalFn.call(base, base);
237 | assert.equal(called, 2);
238 | assert.equal(base.findGenerator('one.child').called, 0);
239 |
240 | originalFn.call(base, base);
241 | assert.equal(called, 3);
242 | assert.equal(base.findGenerator('one.child').called, 0);
243 | });
244 | });
245 |
--------------------------------------------------------------------------------
/test/app.register.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const Generator = require('..');
6 | let base;
7 |
8 | describe('.register', () => {
9 | beforeEach(() => {
10 | base = new Generator('base');
11 | });
12 |
13 | describe('function', () => {
14 | it('should register a generator a function', () => {
15 | base.register('foo', () => {});
16 | const foo = base.getGenerator('foo');
17 | assert(foo);
18 | assert.equal(foo.name, 'foo');
19 | });
20 |
21 | it('should get a task from a generator registered as a function', () => {
22 | base.register('foo', function(foo) {
23 | foo.task('default', () => {});
24 | });
25 | const generator = base.getGenerator('foo');
26 | assert(generator);
27 | assert(generator.tasks);
28 | assert(generator.tasks.has('default'));
29 | });
30 |
31 | it('should get a generator from a generator registered as a function', () => {
32 | base.register('foo', function(foo) {
33 | foo.register('bar', () => {});
34 | });
35 |
36 | const generator = base.getGenerator('foo.bar');
37 | assert(generator);
38 | assert.equal(generator.name, 'bar');
39 | });
40 |
41 | it('should get a sub-generator from a generator registered as a function', () => {
42 | base.register('foo', function(foo) {
43 | foo.register('bar', function(bar) {
44 | bar.task('something', () => {});
45 | });
46 | });
47 | const bar = base.getGenerator('foo.bar');
48 | assert(bar);
49 | assert(bar.tasks);
50 | assert(bar.tasks.has('something'));
51 | });
52 |
53 | it('should get a deeply-nested sub-generator registered as a function', () => {
54 | base.register('foo', function(foo) {
55 | foo.register('bar', function(bar) {
56 | bar.register('baz', function(baz) {
57 | baz.register('qux', function(qux) {
58 | qux.task('qux-one', () => {});
59 | });
60 | });
61 | });
62 | });
63 |
64 | const qux = base.getGenerator('foo.bar.baz.qux');
65 | assert(qux);
66 | assert(qux.tasks);
67 | assert(qux.tasks.has('qux-one'));
68 | });
69 |
70 | it('should expose the instance from each generator', () => {
71 | base.register('foo', function(foo) {
72 | foo.register('bar', function(bar) {
73 | bar.register('baz', function(baz) {
74 | baz.register('qux', function(qux) {
75 | qux.task('qux-one', () => {});
76 | });
77 | });
78 | });
79 | });
80 |
81 | const qux = base
82 | .getGenerator('foo')
83 | .getGenerator('bar')
84 | .getGenerator('baz')
85 | .getGenerator('qux');
86 |
87 | assert(qux);
88 | assert(qux.tasks);
89 | assert(qux.tasks.has('qux-one'));
90 | });
91 |
92 | it('should throw an error when getting a generator that does not exist', () => {
93 | base.register('foo', function(foo) {
94 | foo.register('bar', function(bar) {
95 | bar.register('baz', function(baz) {
96 | baz.register('qux', function(qux) {
97 | });
98 | });
99 | });
100 | });
101 |
102 | assert.throws(() => base.getGenerator('foo.bar.fez'), /is not registered/);
103 | });
104 |
105 | it('should expose the `base` instance as the second param', cb => {
106 | base.register('foo', function(foo) {
107 | assert(foo.base.generators.has('foo'));
108 | cb();
109 | });
110 | base.getGenerator('foo');
111 | });
112 |
113 | it('should expose sibling generators on the `base` instance', cb => {
114 | base.register('foo', function(foo) {
115 | foo.task('abc', () => {});
116 | });
117 | base.register('bar', function(bar) {
118 | assert(this.base.generators.has('foo'));
119 | assert(this.base.generators.has('bar'));
120 | cb();
121 | });
122 |
123 | base.getGenerator('foo');
124 | base.getGenerator('bar');
125 | });
126 | });
127 |
128 | describe('name', () => {
129 | it('should use a custom function to create the name', () => {
130 | base.options.toAlias = name => name.slice(name.lastIndexOf('-') + 1);
131 |
132 | base.register('base-abc-xyz', () => {});
133 | const xyz = base.getGenerator('xyz');
134 | assert(xyz);
135 | assert.equal(xyz.name, 'base-abc-xyz');
136 | assert.equal(xyz.alias, 'xyz');
137 | });
138 | });
139 |
140 | describe('instance', () => {
141 | it('should register an instance', () => {
142 | base.register('base-inst', new Generator());
143 | assert(base.generators.has('base-inst'));
144 | });
145 |
146 | it('should get a generator that was registered as an instance', () => {
147 | const foo = new Generator('foo');
148 | foo.task('default', () => {});
149 | base.register('foo', foo);
150 | assert(base.getGenerator('foo'));
151 | });
152 |
153 | it('should register multiple instances', () => {
154 | const foo = new Generator('foo');
155 | const bar = new Generator('bar');
156 | const baz = new Generator('baz');
157 | base.register('foo', foo);
158 | base.register('bar', bar);
159 | base.register('baz', baz);
160 | assert(base.getGenerator('foo'));
161 | assert(base.getGenerator('bar'));
162 | assert(base.getGenerator('baz'));
163 | });
164 |
165 | it('should get tasks from a generator that was registered as an instance', () => {
166 | const foo = new Generator();
167 | foo.task('default', () => {});
168 | base.register('foo', foo);
169 | const generator = base.getGenerator('foo');
170 | assert(generator);
171 | assert(generator.tasks.has('default'));
172 | });
173 |
174 | it('should get sub-generators from a generator registered as an instance', () => {
175 | const foo = new Generator();
176 | foo.register('bar', () => {});
177 | base.register('foo', foo);
178 | const generator = base.getGenerator('foo.bar');
179 | assert(generator);
180 | });
181 |
182 | it('should get tasks from sub-generators registered as an instance', () => {
183 | const foo = new Generator();
184 |
185 | foo.options.isFoo = true;
186 | foo.register('bar', function(bar) {
187 | bar.task('whatever', () => {});
188 | });
189 |
190 | base.register('foo', foo);
191 | const generator = base.getGenerator('foo.bar');
192 | assert(generator.tasks);
193 | assert(generator.tasks.has('whatever'));
194 | });
195 | });
196 | });
197 |
--------------------------------------------------------------------------------
/test/app.task.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const Generator = require('..');
6 | let base;
7 |
8 | describe('.task', () => {
9 | beforeEach(() => {
10 | base = new Generator();
11 | });
12 |
13 | it('should register a task', () => {
14 | const fn = cb => cb();
15 | base.task('default', fn);
16 | assert.equal(typeof base.tasks.get('default'), 'object');
17 | assert.equal(base.tasks.get('default').callback, fn);
18 | });
19 |
20 | it('should register a task with an array of dependencies', cb => {
21 | let count = 0;
22 | base.task('foo', next => {
23 | count++;
24 | next();
25 | });
26 | base.task('bar', next => {
27 | count++;
28 | next();
29 | });
30 | base.task('default', ['foo', 'bar'], next => {
31 | count++;
32 | next();
33 | });
34 | assert.equal(typeof base.tasks.get('default'), 'object');
35 | assert.deepEqual(base.tasks.get('default').deps, ['foo', 'bar']);
36 | base.build('default', err => {
37 | if (err) return cb(err);
38 | assert.equal(count, 3);
39 | cb();
40 | });
41 | });
42 |
43 | it('should run a glob of tasks', cb => {
44 | let count = 0;
45 | base.task('foo', next => {
46 | count++;
47 | next();
48 | });
49 | base.task('bar', next => {
50 | count++;
51 | next();
52 | });
53 | base.task('baz', next => {
54 | count++;
55 | next();
56 | });
57 | base.task('qux', next => {
58 | count++;
59 | next();
60 | });
61 | base.task('default', ['b*']);
62 | assert.equal(typeof base.tasks.get('default'), 'object');
63 | base.build('default', err => {
64 | if (err) return cb(err);
65 | assert.equal(count, 2);
66 | cb();
67 | });
68 | });
69 |
70 | it('should register a task with a list of strings as dependencies', () => {
71 | base.task('default', 'foo', 'bar', cb => {
72 | cb();
73 | });
74 | assert.equal(typeof base.tasks.get('default'), 'object');
75 | assert.deepEqual(base.tasks.get('default').deps, ['foo', 'bar']);
76 | });
77 |
78 | it('should run a task', cb => {
79 | let count = 0;
80 | base.task('default', cb => {
81 | count++;
82 | cb();
83 | });
84 |
85 | base.build('default', err => {
86 | if (err) return cb(err);
87 | assert.equal(count, 1);
88 | cb();
89 | });
90 | });
91 |
92 | it('should throw an error when a task with unregistered dependencies is run', cb => {
93 | base.task('default', ['foo', 'bar']);
94 | base.build('default', err => {
95 | assert(err);
96 | cb();
97 | });
98 | });
99 |
100 | it('should throw an error when a task does not exist', () => {
101 | return base.build('default')
102 | .then(() => {
103 | throw new Error('expected an error');
104 | })
105 | .catch(err => {
106 | assert(/registered/.test(err.message));
107 | });
108 | });
109 |
110 | it('should emit task events', () => {
111 | const expected = [];
112 |
113 | base.on('task-registered', function(task) {
114 | expected.push(task.status + '.' + task.name);
115 | });
116 |
117 | base.on('task-preparing', function(task) {
118 | expected.push(task.status + '.' + task.name);
119 | });
120 |
121 | base.on('task', function(task) {
122 | expected.push(task.status + '.' + task.name);
123 | });
124 |
125 | base.task('foo', cb => cb());
126 | base.task('bar', ['foo'], cb => cb());
127 | base.task('default', ['bar']);
128 |
129 | return base.build('default')
130 | .then(() => {
131 | assert.deepEqual(expected, [
132 | 'registered.foo',
133 | 'registered.bar',
134 | 'registered.default',
135 | 'preparing.default',
136 | 'starting.default',
137 | 'preparing.bar',
138 | 'starting.bar',
139 | 'preparing.foo',
140 | 'starting.foo',
141 | 'finished.foo',
142 | 'finished.bar',
143 | 'finished.default'
144 | ]);
145 | });
146 | });
147 |
148 | it('should emit an error event when an error is returned in a task callback', cb => {
149 | base.on('error', err => {
150 | assert(err);
151 | assert.equal(err.message, 'This is an error');
152 | });
153 | base.task('default', cb => {
154 | return cb(new Error('This is an error'));
155 | });
156 | base.build('default', err => {
157 | if (err) return cb();
158 | cb(new Error('Expected an error'));
159 | });
160 | });
161 |
162 | it('should emit an error event when an error is thrown in a task', cb => {
163 | base.on('error', err => {
164 | assert(err);
165 | assert.equal(err.message, 'This is an error');
166 | });
167 | base.task('default', cb => {
168 | cb(new Error('This is an error'));
169 | });
170 | base.build('default', err => {
171 | assert(err);
172 | cb();
173 | });
174 | });
175 |
176 | it('should run dependencies before running the dependent task', cb => {
177 | const expected = [];
178 |
179 | base.task('foo', cb => {
180 | expected.push('foo');
181 | cb();
182 | });
183 | base.task('bar', cb => {
184 | expected.push('bar');
185 | cb();
186 | });
187 | base.task('default', ['foo', 'bar'], cb => {
188 | expected.push('default');
189 | cb();
190 | });
191 |
192 | base.build('default', err => {
193 | if (err) return cb(err);
194 | assert.deepEqual(expected, ['foo', 'bar', 'default']);
195 | cb();
196 | });
197 | });
198 | });
199 |
--------------------------------------------------------------------------------
/test/app.tasks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const util = require('util');
6 | const Generator = require('..');
7 | let app;
8 |
9 | describe('.tasks', () => {
10 | beforeEach(() => {
11 | app = new Generator();
12 | });
13 |
14 | it('should throw an error when a name is not given for a task', () => {
15 | assert.throws(() => app.task(), /expected/);
16 | });
17 |
18 | it('should register a task', () => {
19 | app.task('default', () => {});
20 | assert(app.tasks.get('default'));
21 | assert.equal(typeof app.tasks.get('default'), 'object');
22 | assert.equal(typeof app.tasks.get('default').callback, 'function');
23 | });
24 |
25 | it('should register a noop task when only name is given', () => {
26 | app.task('default');
27 | assert(app.tasks.get('default'));
28 | assert.equal(typeof app.tasks.get('default'), 'object');
29 | assert.equal(typeof app.tasks.get('default').callback, 'function');
30 | });
31 |
32 | it('should register a noop task when a name and an empty dependencies array is given', () => {
33 | app.task('default', []);
34 | assert(app.tasks.get('default'));
35 | assert.equal(typeof app.tasks.get('default'), 'object');
36 | assert.equal(typeof app.tasks.get('default').callback, 'function');
37 | });
38 |
39 | it('should register a task with an array of named dependencies', () => {
40 | app.task('default', ['foo', 'bar'], cb => cb());
41 | assert(app.tasks.get('default'));
42 | assert.equal(typeof app.tasks.get('default'), 'object');
43 | assert.deepEqual(app.tasks.get('default').deps, ['foo', 'bar']);
44 | });
45 |
46 | it('should register a task with an array of function dependencies', () => {
47 | const bar = cb => cb();
48 | const Baz = cb => cb();
49 |
50 | const deps = ['foo', bar, Baz, cb => cb()];
51 | app.task('default', deps, cb => cb());
52 |
53 | assert.equal(typeof app.tasks.get('default'), 'object');
54 | assert.deepEqual(app.tasks.get('default').deps.length, 4);
55 | });
56 |
57 | it('should register a task with a list of strings as dependencies', () => {
58 | app.task('default', 'foo', 'bar', cb => cb());
59 | assert.equal(typeof app.tasks.get('default'), 'object');
60 | assert.deepEqual(app.tasks.get('default').deps.length, 2);
61 | assert.deepEqual(app.tasks.get('default').deps, ['foo', 'bar']);
62 | });
63 |
64 | it('should register a task as a noop function when only dependencies are given', () => {
65 | app.task('default', ['foo', 'bar']);
66 | assert.equal(typeof app.tasks.get('default'), 'object');
67 | assert.equal(typeof app.tasks.get('default').callback, 'function');
68 | });
69 |
70 | it('should register a task with options as the second argument', () => {
71 | app.task('default', { one: 'two' }, ['foo', 'bar']);
72 | assert.equal(typeof app.tasks.get('default'), 'object');
73 | assert.equal(typeof app.tasks.get('default').callback, 'function');
74 | assert.equal(app.tasks.get('default').options.one, 'two');
75 | });
76 |
77 | it('should run a task', cb => {
78 | let count = 0;
79 | app.task('default', cb => {
80 | count++;
81 | cb();
82 | });
83 |
84 | app.build('default', err => {
85 | if (err) return cb(err);
86 | assert.equal(count, 1);
87 | cb();
88 | });
89 | });
90 |
91 | it('should run a task and return a promise', () => {
92 | let count = 0;
93 | app.task('default', cb => {
94 | count++;
95 | cb();
96 | });
97 |
98 | return app.build('default')
99 | .then(() => {
100 | assert.equal(count, 1);
101 | });
102 | });
103 |
104 | it('should run a task with options', cb => {
105 | let count = 0;
106 | app.task('default', { silent: false }, function(next) {
107 | assert.equal(this.options.silent, false);
108 | count++;
109 | next();
110 | });
111 |
112 | app.build('default', err => {
113 | if (err) return cb(err);
114 | assert.equal(count, 1);
115 | cb();
116 | });
117 | });
118 |
119 | it('should run a task with options defined on .build', cb => {
120 | let count = 0;
121 | app.task('default', { silent: false }, function(next) {
122 | assert.equal(this.options.silent, true);
123 | assert.equal(this.options.foo, 'bar');
124 | count++;
125 | next();
126 | });
127 |
128 | app.build('default', { silent: true, foo: 'bar' }, err => {
129 | if (err) return cb(err);
130 | assert.equal(count, 1);
131 | cb();
132 | });
133 | });
134 |
135 | it('should run the `default` task when no task is given', cb => {
136 | let count = 0;
137 |
138 | app.task('default', next => {
139 | count++;
140 | next();
141 | });
142 |
143 | app.build(err => {
144 | if (err) return cb(err);
145 | assert.equal(count, 1);
146 | cb();
147 | });
148 | });
149 |
150 | it('should skip tasks when `run === false`', cb => {
151 | const expected = [];
152 | function callback() {
153 | return function(next) {
154 | expected.push(this.name);
155 | next();
156 | };
157 | }
158 |
159 | app.task('foo', callback());
160 | app.task('bar', {run: false}, callback());
161 | app.task('baz', callback());
162 | app.task('bang', {run: false}, callback());
163 | app.task('beep', callback());
164 | app.task('boop', callback());
165 |
166 | app.task('default', ['foo', 'bar', 'baz', 'bang', 'beep', 'boop']);
167 | app.build(err => {
168 | if (err) return cb(err);
169 | assert.deepEqual(expected, ['foo', 'baz', 'beep', 'boop']);
170 | cb();
171 | });
172 | });
173 |
174 | it('should skip tasks when `run === false` (with deps skipped)', cb => {
175 | const expected = [];
176 | function callback() {
177 | return function(next) {
178 | expected.push(this.name);
179 | next();
180 | };
181 | }
182 |
183 | app.task('foo', callback());
184 | app.task('bar', { run: false }, ['foo'], callback());
185 | app.task('baz', ['bar'], callback());
186 | app.task('bang', { run: false }, ['baz'], callback());
187 | app.task('beep', ['bang'], callback());
188 | app.task('boop', ['beep'], callback());
189 |
190 | app.task('default', ['boop']);
191 | app.build(err => {
192 | if (err) return cb(err);
193 | assert.deepEqual(expected, ['beep', 'boop']);
194 | cb();
195 | });
196 | });
197 |
198 | it('should skip tasks when `run === false` (complex flow)', cb => {
199 | const expected = [];
200 |
201 | app.task('foo', function(next) {
202 | expected.push(this.name);
203 | // disable running the "bar" task
204 | app.tasks.get('bar').options.run = false;
205 | next();
206 | });
207 |
208 | app.task('bar', function(next) {
209 | expected.push(this.name);
210 | next();
211 | });
212 |
213 | app.task('baz', function(next) {
214 | expected.push(this.name);
215 | // enable running the "bang" task
216 | app.tasks.get('bang').options.run = true;
217 | next();
218 | });
219 |
220 | app.task('bang', {run: false}, function(next) {
221 | expected.push(this.name);
222 | next();
223 | });
224 |
225 | app.task('beep', function(next) {
226 | expected.push(this.name);
227 | next();
228 | });
229 |
230 | app.task('boop', function(next) {
231 | expected.push(this.name);
232 | next();
233 | });
234 |
235 | app.task('default', ['foo', 'bar', 'baz', 'bang', 'beep', 'boop']);
236 | app.build(err => {
237 | if (err) return cb(err);
238 | assert.deepEqual(expected, ['foo', 'baz', 'bang', 'beep', 'boop']);
239 | cb();
240 | });
241 | });
242 |
243 | it('should throw an error when a task with unregistered dependencies is built', cb => {
244 | let count = 0;
245 | app.task('default', ['foo', 'bar'], cb => {
246 | count++;
247 | cb();
248 | });
249 |
250 | app.build('default', err => {
251 | if (!err) return cb(new Error('Expected an error to be thrown.'));
252 | assert.equal(count, 0);
253 | cb();
254 | });
255 | });
256 |
257 | it('should throw an error when a task with globbed dependencies cannot be found', cb => {
258 | let count = 0;
259 | app.task('default', ['a-*'], cb => {
260 | count++;
261 | cb();
262 | });
263 |
264 | app.build('default', err => {
265 | if (!err) return cb(new Error('Expected an error to be thrown.'));
266 | assert.equal(count, 0);
267 | cb();
268 | });
269 | });
270 |
271 | it('should emit task events', cb => {
272 | const events = [];
273 | const push = task => events.push(task.status + '.' + task.name);
274 |
275 | app.on('task', push);
276 | app.on('task-registered', push);
277 | app.on('task-preparing', push);
278 |
279 | app.on('error', err => {
280 | if (!err.build) {
281 | events.push('error.' + err.task.name);
282 | }
283 | });
284 |
285 | app.task('foo', cb => cb());
286 | app.task('bar', ['foo'], cb => cb());
287 |
288 | app.task('default', ['bar']);
289 | app.build('default', err => {
290 | if (err) return cb(err);
291 | assert.deepEqual(events, [
292 | 'registered.foo',
293 | 'registered.bar',
294 | 'registered.default',
295 | 'preparing.default',
296 | 'starting.default',
297 | 'preparing.bar',
298 | 'starting.bar',
299 | 'preparing.foo',
300 | 'starting.foo',
301 | 'finished.foo',
302 | 'finished.bar',
303 | 'finished.default'
304 | ]);
305 | cb();
306 | });
307 | });
308 |
309 | it('should emit an error event when an error is returned in a callback', cb => {
310 | let count = 0;
311 | app.on('error', err => {
312 | assert(err);
313 | count++;
314 | });
315 |
316 | app.task('default', cb => {
317 | cb(new Error('This is an error'));
318 | });
319 |
320 | app.build('default', err => {
321 | assert(err);
322 | assert.equal(count, 1);
323 | cb();
324 | });
325 | });
326 |
327 | it('should emit an error event when an error is thrown in a task', cb => {
328 | let count = 0;
329 | app.on('error', err => {
330 | assert(err);
331 | count++;
332 | });
333 |
334 | app.task('default', () => {
335 | throw new Error('This is an error');
336 | });
337 |
338 | app.build('default', err => {
339 | assert(err);
340 | assert(/This is an error/.test(err.message));
341 | assert.equal(count, 1);
342 | cb();
343 | });
344 | });
345 |
346 | it('should emit build events', () => {
347 | const events = [];
348 | const errors = [];
349 |
350 | app.on('build', function(build) {
351 | events.push(build.status);
352 | });
353 |
354 | app.on('error', err => {
355 | errors.push(err);
356 | });
357 |
358 | app.task('foo', cb => cb());
359 | app.task('bar', ['foo'], cb => cb());
360 |
361 | app.task('default', ['bar']);
362 | return app.build('default')
363 | .then(() => {
364 | assert.equal(errors.length, 0);
365 | assert.deepEqual(events, ['starting', 'finished']);
366 | });
367 | });
368 |
369 | it('should emit a build error event when an error is passed back in a task', cb => {
370 | let count = 0;
371 |
372 | app.on('error', err => {
373 | assert(err);
374 | count++;
375 | });
376 |
377 | app.task('default', cb => {
378 | cb(new Error('This is an error'));
379 | });
380 |
381 | app.build('default', err => {
382 | assert(err);
383 | assert.equal(count, 1);
384 | cb();
385 | });
386 | });
387 |
388 | it('should emit a build error event when an error is passed back in a task (with promise)', () => {
389 | let count = 0;
390 |
391 | app.on('error', err => {
392 | assert(err);
393 | count++;
394 | });
395 |
396 | app.task('default', next => {
397 | next(new Error('This is an error'));
398 | });
399 |
400 | return app.build('default')
401 | .catch(err => {
402 | assert(err);
403 | assert.equal(count, 1);
404 | });
405 | });
406 |
407 | it('should emit a build error event when an error is thrown in a task', cb => {
408 | let count = 0;
409 |
410 | app.on('error', err => {
411 | assert(err);
412 | assert(/This is an error/.test(err.message));
413 | count++;
414 | });
415 |
416 | app.task('default', () => {
417 | throw new Error('This is an error');
418 | });
419 |
420 | app.build('default', err => {
421 | assert(err);
422 | assert.equal(count, 1);
423 | cb();
424 | });
425 | });
426 |
427 | it('should run dependencies before running the dependent task.', cb => {
428 | const seq = [];
429 | app.task('foo', cb => {
430 | seq.push('foo');
431 | cb();
432 | });
433 |
434 | app.task('bar', cb => {
435 | seq.push('bar');
436 | cb();
437 | });
438 |
439 | app.task('default', ['foo', 'bar'], cb => {
440 | seq.push('default');
441 | cb();
442 | });
443 |
444 | app.build('default', err => {
445 | if (err) return cb(err);
446 | assert.deepEqual(seq, ['foo', 'bar', 'default']);
447 | cb();
448 | });
449 | });
450 |
451 | it('should add inspect function to tasks.', () => {
452 | app.task('foo', cb => {
453 | cb();
454 | });
455 |
456 | app.task('bar', cb => {
457 | cb();
458 | });
459 |
460 | app.task('default', ['foo', 'bar'], cb => {
461 | cb();
462 | });
463 | assert.equal(app.tasks.get('foo')[util.inspect.custom](), '');
464 | assert.equal(app.tasks.get('bar')[util.inspect.custom](), '');
465 | assert.equal(app.tasks.get('default')[util.inspect.custom](), '');
466 | });
467 |
468 | it('should add custom inspect function to tasks.', () => {
469 | app.on('task-registered', task => {
470 | task[util.inspect.custom] = function(task) {
471 | return '';
474 | };
475 | });
476 |
477 | app.task('foo', cb => {
478 | cb();
479 | });
480 |
481 | app.task('bar', cb => {
482 | cb();
483 | });
484 |
485 | app.task('default', ['foo', 'bar'], cb => {
486 | cb();
487 | });
488 |
489 | assert.equal(app.tasks.get('foo')[util.inspect.custom](), '');
490 | assert.equal(app.tasks.get('bar')[util.inspect.custom](), '');
491 | assert.equal(app.tasks.get('default')[util.inspect.custom](), '');
492 | });
493 |
494 | it('should run globbed dependencies before running the dependent task.', cb => {
495 | const actual = [];
496 | app.task('a-foo', cb => {
497 | actual.push('a-foo');
498 | cb();
499 | });
500 |
501 | app.task('a-bar', cb => {
502 | actual.push('a-bar');
503 | cb();
504 | });
505 |
506 | app.task('b-foo', cb => {
507 | actual.push('b-foo');
508 | cb();
509 | });
510 |
511 | app.task('b-bar', cb => {
512 | actual.push('b-bar');
513 | cb();
514 | });
515 |
516 | app.task('default', ['a-*'], cb => {
517 | actual.push('default');
518 | cb();
519 | });
520 |
521 | app.build(err => {
522 | if (err) return cb(err);
523 | assert.deepEqual(actual, ['a-foo', 'a-bar', 'default']);
524 | cb();
525 | });
526 | });
527 |
528 | it('should get the current task name from `this`', cb => {
529 | const actual = [];
530 | const tasks = [];
531 |
532 | const callback = function(next) {
533 | actual.push(this.name);
534 | next();
535 | };
536 |
537 | for (let i = 0; i < 10; i++) {
538 | tasks.push(String(i));
539 | app.task(String(i), callback);
540 | }
541 |
542 | app.build(tasks, err => {
543 | if (err) return cb(err);
544 | assert.equal(actual.length, 10);
545 | assert.deepEqual(actual, ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']);
546 | cb();
547 | });
548 | });
549 | });
550 |
--------------------------------------------------------------------------------
/test/app.toAlias.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const Generator = require('..');
6 | let base;
7 |
8 | describe('.toAlias', () => {
9 | beforeEach(() => {
10 | base = new Generator('base');
11 | });
12 |
13 | it('should not create an alias when no prefix is given', () => {
14 | assert.equal(base.toAlias('foo-bar'), 'foo-bar');
15 | });
16 |
17 | it('should create an alias using the `options.toAlias` function', () => {
18 | const alias = base.toAlias('one-two-three', {
19 | toAlias: function(name) {
20 | return name.slice(name.indexOf('-') + 1);
21 | }
22 | });
23 | assert.equal(alias, 'two-three');
24 | });
25 |
26 | it('should create an alias using the given function', () => {
27 | const alias = base.toAlias('one-two-three', function(name) {
28 | return name.slice(name.lastIndexOf('-') + 1);
29 | });
30 | assert.equal(alias, 'three');
31 | });
32 |
33 | it('should create an alias using base.options.toAlias function', () => {
34 | base.options.toAlias = function(name) {
35 | return name.slice(name.lastIndexOf('-') + 1);
36 | };
37 |
38 | const alias = base.toAlias('one-two-three');
39 | assert.equal(alias, 'three');
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/test/events.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const Generator = require('..');
6 | let base;
7 |
8 | describe('generator.events', () => {
9 | beforeEach(() => {
10 | base = new Generator();
11 | });
12 |
13 | it('should emit generator when a generator is registered', cb => {
14 | base.on('generator', generator => {
15 | assert.equal(generator.name, 'foo');
16 | cb();
17 | });
18 |
19 | base.generator('foo', () => {});
20 | });
21 |
22 | it('should emit generator when base.generators.get is called', cb => {
23 | base.on('generator', generator => {
24 | assert.equal(generator.name, 'foo');
25 | cb();
26 | });
27 |
28 | base.register('foo', () => {});
29 | base.getGenerator('foo');
30 | });
31 |
32 | it('should emit generator.get when base.generators.get is called', cb => {
33 | base.on('generator', generator => {
34 | assert.equal(generator.name, 'foo');
35 | cb();
36 | });
37 |
38 | base.register('foo', () => {});
39 | base.getGenerator('foo');
40 | });
41 |
42 | it('should emit error on base when a base generator emits an error', cb => {
43 | let called = 0;
44 |
45 | base.on('error', err => {
46 | assert.equal(err.message, 'whatever');
47 | called++;
48 | });
49 |
50 | base.register('foo', app => {
51 | app.emit('error', new Error('whatever'));
52 | });
53 |
54 | base.getGenerator('foo');
55 | assert.equal(called, 1);
56 | cb();
57 | });
58 |
59 | it('should emit error on base when a base generator throws an error', () => {
60 | let called = 0;
61 |
62 | base.on('error', err => {
63 | assert.equal(err.message, 'whatever');
64 | called++;
65 | });
66 |
67 | base.register('foo', app => {
68 | app.task('default', cb => {
69 | cb(new Error('whatever'));
70 | });
71 | });
72 |
73 | return base.getGenerator('foo')
74 | .build(err => {
75 | assert(err);
76 | assert.equal(called, 1);
77 | });
78 | });
79 |
80 | it('should emit errors on base from deeply nested generators', cb => {
81 | let called = 0;
82 |
83 | base.on('error', err => {
84 | assert.equal(err.message, 'whatever');
85 | called++;
86 | });
87 |
88 | base.register('a', function() {
89 | this.register('b', function() {
90 | this.register('c', function() {
91 | this.register('d', function() {
92 | this.task('default', function(next) {
93 | next(new Error('whatever'));
94 | });
95 | });
96 | });
97 | });
98 | });
99 |
100 | base.getGenerator('a.b.c.d')
101 | .build(err => {
102 | assert(err);
103 | assert.equal(called, 1);
104 | cb();
105 | });
106 | });
107 |
108 | it('should bubble up errors to all parent generators', cb => {
109 | const names = [];
110 | let called = 0;
111 |
112 | function count() {
113 | names.push(this.namespace);
114 | called++;
115 | }
116 |
117 | base.name = 'root';
118 | base.on('error', err => {
119 | assert.equal(err.message, 'whatever');
120 | called++;
121 | });
122 |
123 | base.register('a', function() {
124 | this.on('error', count);
125 |
126 | this.register('b', function() {
127 | this.on('error', count);
128 |
129 | this.register('c', function() {
130 | this.on('error', count);
131 |
132 | this.register('d', function() {
133 | this.on('error', count);
134 |
135 | this.task('default', function(next) {
136 | next(new Error('whatever'));
137 | });
138 | });
139 | });
140 | });
141 | });
142 |
143 | base.getGenerator('a.b.c.d')
144 | .build(err => {
145 | assert(err);
146 | assert.deepEqual(names, [ 'root.a.b.c.d', 'root.a.b.c', 'root.a.b', 'root.a' ]);
147 | assert.equal(called, 5);
148 | assert.equal(err.message, 'whatever');
149 | cb();
150 | })
151 | .catch(cb);
152 | });
153 | });
154 |
--------------------------------------------------------------------------------
/test/parallel.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const Composer = require('..');
6 | let app;
7 |
8 | describe('parallel', () => {
9 | beforeEach(() => {
10 | app = new Composer();
11 | });
12 |
13 | describe('callback', () => {
14 | it('should compose tasks into a function that runs in parallel', cb => {
15 | const actual = [];
16 |
17 | app.task('foo', function(next) {
18 | setTimeout(() => {
19 | actual.push('foo');
20 | next();
21 | }, 6);
22 | });
23 |
24 | app.task('bar', function(next) {
25 | setTimeout(() => {
26 | actual.push('bar');
27 | next();
28 | }, 1);
29 | });
30 |
31 | app.task('baz', function(next) {
32 | actual.push('baz');
33 | next();
34 | });
35 |
36 | const build = app.parallel(['foo', 'bar', 'baz']);
37 |
38 | build(err => {
39 | if (err) return cb(err);
40 | assert.deepEqual(actual, ['baz', 'bar', 'foo']);
41 | cb();
42 | });
43 | });
44 |
45 | it('should return an error when no functions are passed to parallel', cb => {
46 | const build = app.parallel();
47 |
48 | build(err => {
49 | assert(/actual/, err.message);
50 | assert(/tasks/, err.message);
51 | cb();
52 | });
53 | });
54 |
55 | it('should compose tasks with options into a function that runs in parallel', () => {
56 | const actual = [];
57 | const task = t => {
58 | return next => {
59 | setTimeout(() => {
60 | actual.push(t);
61 | next();
62 | }, t);
63 | };
64 | };
65 |
66 | const build = app.parallel([task(20), task(15), task(10), task(5), task(0)]);
67 |
68 | return build()
69 | .then(() => {
70 | assert.deepEqual(actual, [0, 5, 10, 15, 20]);
71 | });
72 | });
73 |
74 | it('should compose tasks with additional options into a function that runs in parallel', () => {
75 | const actual = [];
76 |
77 | app.task('foo', { silent: false }, function(next) {
78 | assert.equal(this.options.silent, true);
79 | assert.equal(this.options.foo, 'bar');
80 |
81 | setTimeout(() => {
82 | actual.push('foo');
83 | next();
84 | }, 2);
85 | });
86 |
87 | const options = { silent: true, foo: 'bar' };
88 |
89 | const build = app.parallel('foo', options, next => {
90 | actual.push('bar');
91 | next();
92 | });
93 |
94 | return build()
95 | .then(() => {
96 | assert.deepEqual(actual, ['bar', 'foo']);
97 | });
98 | });
99 |
100 | it('should run task dependencies in parallel', () => {
101 | const actual = [];
102 |
103 | app.task('foo', ['baz'], next => {
104 | setTimeout(() => {
105 | actual.push('foo');
106 | next();
107 | }, 15);
108 | });
109 |
110 | app.task('bar', ['qux'], next => {
111 | setTimeout(() => {
112 | actual.push('bar');
113 | next();
114 | }, 10);
115 | });
116 |
117 | app.task('baz', next => {
118 | setTimeout(() => {
119 | actual.push('baz');
120 | next();
121 | }, 5);
122 | });
123 |
124 | app.task('qux', next => {
125 | setTimeout(() => {
126 | actual.push('qux');
127 | next();
128 | }, 0);
129 | });
130 |
131 | const build = app.parallel(['foo', 'bar']);
132 |
133 | return build()
134 | .then(() => {
135 | assert.deepEqual(actual, ['qux', 'baz', 'bar', 'foo']);
136 | });
137 | });
138 | });
139 |
140 | describe('promise', () => {
141 | it('should run registered tasks in parallel', () => {
142 | const actual = [];
143 |
144 | app.task('foo', next => {
145 | setTimeout(() => {
146 | actual.push('foo');
147 | next();
148 | }, 8);
149 | });
150 |
151 | app.task('bar', next => {
152 | setTimeout(() => {
153 | actual.push('bar');
154 | next();
155 | }, 1);
156 | });
157 |
158 | app.task('baz', next => {
159 | actual.push('baz');
160 | next();
161 | });
162 |
163 | const build = app.parallel(['foo', 'bar', 'baz']);
164 |
165 | return build()
166 | .then(() => {
167 | assert.deepEqual(actual, ['baz', 'bar', 'foo']);
168 | });
169 | });
170 |
171 | it('should return an error when no functions are passed to parallel', cb => {
172 | const build = app.parallel();
173 |
174 | build(err => {
175 | assert(/actual/, err.message);
176 | assert(/tasks/, err.message);
177 | cb();
178 | });
179 | });
180 |
181 | it('should compose tasks with options into a function that runs in parallel', () => {
182 | const res = [];
183 | const task = function(t) {
184 | return function(next) {
185 | setTimeout(() => {
186 | res.push(t);
187 | next();
188 | }, t);
189 | };
190 | };
191 |
192 | const build = app.parallel([task(20), task(15), task(10), task(5), task(0)]);
193 |
194 | return build()
195 | .then(() => {
196 | assert.deepEqual(res, [0, 5, 10, 15, 20]);
197 | });
198 | });
199 |
200 | it('should compose tasks with additional options into a function that runs in parallel', () => {
201 | const actual = [];
202 |
203 | app.task('foo', { silent: false }, function(next) {
204 | assert.equal(this.options.silent, true);
205 | assert.equal(this.options.foo, 'bar');
206 |
207 | setTimeout(() => {
208 | actual.push('foo');
209 | next();
210 | }, 2);
211 | });
212 |
213 | const options = { silent: true, foo: 'bar' };
214 |
215 | const build = app.parallel('foo', options, function(next) {
216 | actual.push('bar');
217 | next();
218 | });
219 |
220 | return build()
221 | .then(() => {
222 | assert.deepEqual(actual, ['bar', 'foo']);
223 | });
224 | });
225 |
226 | it('should return a promise when called without a callback function', () => {
227 | const actual = [];
228 | let count = 0;
229 |
230 | app.on('error', err => {
231 | actual.push('error');
232 | assert.equal(err.message, 'bar error');
233 | count++;
234 | });
235 |
236 | app.task('foo', next => {
237 | setTimeout(() => {
238 | actual.push('foo');
239 | count++;
240 | next();
241 | }, 2);
242 | });
243 |
244 | const build = app.parallel('foo', next => {
245 | next(new Error('bar error'));
246 | });
247 |
248 | return build()
249 | .then(() => {
250 | throw new Error('expected an error');
251 | })
252 | .catch(err => {
253 | assert(err);
254 | assert.equal(count, 1);
255 | assert.deepEqual(actual, ['error']);
256 | });
257 | });
258 | });
259 | });
260 |
--------------------------------------------------------------------------------
/test/parse-tasks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const parseTasks = require('../lib/parse');
6 | const Generate = require('..');
7 | let parse;
8 | let app;
9 |
10 | describe('parse-tasks', () => {
11 | beforeEach(() => {
12 | app = new Generate();
13 | app.task('foo', () => {});
14 | app.task('bar', () => {});
15 |
16 | app.register('foo', foo => {
17 | foo.task('default', () => {});
18 | foo.register('one', one => {
19 | one.task('default', () => {});
20 | });
21 | });
22 |
23 | app.register('bar', bar => {
24 | bar.task('default', () => {});
25 | bar.register('two', two => {
26 | two.task('default', () => {});
27 | });
28 | });
29 |
30 | app.register('baz', () => {});
31 | app.register('qux', () => {});
32 | parse = (...tasks) => parseTasks()(app, ...tasks);
33 | });
34 |
35 | it('should parse task arguments', () => {
36 | assert.deepEqual(parse('foo').tasks, [{ name: 'default', tasks: ['foo'] }]);
37 |
38 | assert.deepEqual(parse('foo:default').tasks, [{ name: 'foo', tasks: ['default'] }]);
39 |
40 | assert.deepEqual(parse('foo:bar').tasks, [{ name: 'foo', tasks: ['bar'] }]);
41 | assert.deepEqual(parse('foo', ['bar']).tasks, [{ name: 'foo', tasks: ['bar'] }]);
42 |
43 | assert.deepEqual(parse('foo,bar').tasks, [{ name: 'default', tasks: ['foo', 'bar'] }]);
44 |
45 | assert.deepEqual(parse('baz,qux').tasks, [
46 | { name: 'baz', tasks: ['default'] },
47 | { name: 'qux', tasks: ['default'] }
48 | ]);
49 |
50 |
51 | assert.deepEqual(parse('foo,bar baz').tasks, [
52 | { name: 'default', tasks: ['foo', 'bar'] },
53 | { name: 'baz', tasks: ['default'] }
54 | ]);
55 |
56 | assert.deepEqual(parse('foo bar baz').tasks, [
57 | { name: 'default', tasks: ['foo'] },
58 | { name: 'default', tasks: ['bar'] },
59 | { name: 'baz', tasks: ['default'] }
60 | ]);
61 |
62 | assert.deepEqual(parse('foo:default bar:default baz').tasks, [
63 | { name: 'foo', tasks: ['default'] },
64 | { name: 'bar', tasks: ['default'] },
65 | { name: 'baz', tasks: ['default'] }
66 | ]);
67 |
68 | assert.deepEqual(parse('foo:default,bar baz').tasks, [
69 | { name: 'foo', tasks: ['default', 'bar'] },
70 | { name: 'baz', tasks: ['default'] }
71 | ]);
72 |
73 | assert.deepEqual(parse('foo:default,bar,baz baz').tasks, [
74 | { name: 'foo', tasks: ['default', 'bar', 'baz'] },
75 | { name: 'baz', tasks: ['default'] }
76 | ]);
77 |
78 | assert.deepEqual(parse('foo:default bar:default baz').tasks, [
79 | { name: 'foo', tasks: ['default'] },
80 | { name: 'bar', tasks: ['default'] },
81 | { name: 'baz', tasks: ['default'] }
82 | ]);
83 |
84 | assert.deepEqual(parse('foo:default bar:default baz,qux').tasks, [
85 | { name: 'foo', tasks: ['default'] },
86 | { name: 'bar', tasks: ['default'] },
87 | { name: 'baz', tasks: ['default'] },
88 | { name: 'qux', tasks: ['default'] }
89 | ]);
90 |
91 | assert.deepEqual(parse('foo,bar baz,qux').tasks, [
92 | { name: 'default', tasks: ['foo', 'bar'] },
93 | { name: 'baz', tasks: ['default'] },
94 | { name: 'qux', tasks: ['default'] }
95 | ]);
96 |
97 | assert.deepEqual(parse(['foo,bar', 'baz,qux']).tasks, [
98 | { name: 'default', tasks: ['foo', 'bar'] },
99 | { name: 'baz', tasks: ['default'] },
100 | { name: 'qux', tasks: ['default'] }
101 | ]);
102 |
103 | assert.deepEqual(parse('foo.one bar.two').tasks, [
104 | { name: 'foo.one', tasks: ['default'] },
105 | { name: 'bar.two', tasks: ['default'] }
106 | ]);
107 |
108 | assert.deepEqual(parse('foo.one:default bar.two:default').tasks, [
109 | { name: 'foo.one', tasks: ['default'] },
110 | { name: 'bar.two', tasks: ['default'] }
111 | ]);
112 |
113 | assert.deepEqual(parse('foo.one:abc bar.two:xyz').tasks, [
114 | { name: 'foo.one', tasks: ['abc'] },
115 | { name: 'bar.two', tasks: ['xyz'] }
116 | ]);
117 |
118 | assert.deepEqual(parse('foo.one:abc bar.two:xyz'), {
119 | callback: void 0,
120 | options: {},
121 | tasks: [{ name: 'foo.one', tasks: ['abc'] }, { name: 'bar.two', tasks: ['xyz'] }],
122 | missing: []
123 | });
124 |
125 | assert.deepEqual(parse(['foo.one:abc', { foo: 'bar' }, 'bar.two:xyz']), {
126 | callback: void 0,
127 | options: { foo: 'bar' },
128 | tasks: [{ name: 'foo.one', tasks: ['abc'] }, { name: 'bar.two', tasks: ['xyz'] }],
129 | missing: []
130 | });
131 | });
132 | });
133 |
--------------------------------------------------------------------------------
/test/series.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const Composer = require('..');
6 | let app;
7 |
8 | describe('series', () => {
9 | beforeEach(() => {
10 | app = new Composer();
11 | });
12 |
13 | describe('callback', () => {
14 | it('should compose tasks into a function that runs in series', cb => {
15 | const actual = [];
16 |
17 | app.task('foo', cb => {
18 | setTimeout(() => {
19 | actual.push('foo');
20 | cb();
21 | }, 20);
22 | });
23 |
24 | app.task('bar', cb => {
25 | setTimeout(() => {
26 | actual.push('bar');
27 | cb();
28 | }, 10);
29 | });
30 |
31 | app.task('baz', cb => {
32 | actual.push('baz');
33 | cb();
34 | });
35 |
36 | const build = app.series(['foo', 'bar', 'baz']);
37 |
38 | build(err => {
39 | if (err) return cb(err);
40 | assert.deepEqual(actual, ['foo', 'bar', 'baz']);
41 | cb();
42 | });
43 | });
44 |
45 | it('should compose tasks with options into a function that runs in series', cb => {
46 | const actual = [];
47 |
48 | app.task('foo', { silent: false }, function(next) {
49 | assert.equal(this.options.silent, false);
50 | actual.push('foo');
51 | next();
52 | });
53 |
54 | const build = app.series('foo', function bar(next) {
55 | actual.push('bar');
56 | next();
57 | });
58 |
59 | build(err => {
60 | if (err) return cb(err);
61 | assert.deepEqual(actual, ['foo', 'bar']);
62 | cb();
63 | });
64 | });
65 |
66 | it('should return an error when no functions are passed to series', cb => {
67 | const build = app.series();
68 |
69 | build(err => {
70 | assert(/actual/, err.message);
71 | assert(/tasks/, err.message);
72 | cb();
73 | });
74 | });
75 |
76 | it('should compose tasks with additional options into a function that runs in series', cb => {
77 | const actual = [];
78 |
79 | app.task('foo', { silent: false }, function(next) {
80 | assert.equal(this.options.silent, true);
81 | assert.equal(this.options.foo, 'bar');
82 | actual.push('foo');
83 | next();
84 | });
85 |
86 | const options = { silent: true, foo: 'bar' };
87 | const build = app.series('foo', function(next) {
88 | actual.push('bar');
89 | next();
90 | }, options);
91 |
92 | build(err => {
93 | if (err) {
94 | cb(err);
95 | return;
96 | }
97 | assert.deepEqual(actual, ['foo', 'bar']);
98 | cb();
99 | });
100 | });
101 |
102 | it('should not throw an error when `build` is called without a callback function.', cb => {
103 | const actual = [];
104 |
105 | app.task('foo', next => {
106 | actual.push('foo');
107 | next();
108 | });
109 |
110 | const build = app.series('foo', next => {
111 | actual.push('bar');
112 | next();
113 | });
114 |
115 | build();
116 |
117 | setTimeout(() => {
118 | assert.deepEqual(actual, ['foo', 'bar']);
119 | cb();
120 | }, 10);
121 | });
122 |
123 | it('should handle errors', () => {
124 | const actual = [];
125 | let count = 0;
126 |
127 | app.on('error', err => {
128 | actual.push(err.message);
129 | count++;
130 | });
131 |
132 | app.task('foo', cb => {
133 | actual.push('foo');
134 | count++;
135 | cb();
136 | });
137 |
138 | const build = app.series('foo', cb => {
139 | count++;
140 | cb(new Error('an error'));
141 | });
142 |
143 | return build()
144 | .then(() => {
145 | throw new Error('actual an error');
146 | })
147 | .catch(() => {
148 | assert.equal(count, 3);
149 | assert.deepEqual(actual, ['foo', 'an error']);
150 | });
151 | });
152 | });
153 |
154 | describe('promise', () => {
155 | it('should compose tasks into a function that runs in series', () => {
156 | const actual = [];
157 |
158 | app.task('foo', cb => {
159 | actual.push('foo');
160 | cb();
161 | });
162 |
163 | const build = app.series('foo', function bar(cb) {
164 | actual.push('bar');
165 | cb();
166 | });
167 |
168 | return build()
169 | .then(() => {
170 | assert.deepEqual(actual, ['foo', 'bar']);
171 | });
172 | });
173 |
174 | it('should compose tasks with options into a function that runs in series', () => {
175 | const actual = [];
176 |
177 | app.task('foo', { silent: false }, function(next) {
178 | assert.equal(this.options.silent, false);
179 | actual.push('foo');
180 | next();
181 | });
182 |
183 | const build = app.series('foo', function(next) {
184 | actual.push('bar');
185 | next();
186 | });
187 |
188 | return build()
189 | .then(() => {
190 | assert.deepEqual(actual, ['foo', 'bar']);
191 | });
192 | });
193 |
194 | it('should return an error when no functions are passed to series', cb => {
195 | const build = app.series();
196 |
197 | return build(err => {
198 | assert(err);
199 | cb();
200 | });
201 | });
202 |
203 | it('should compose tasks with additional options into a function that runs in series', () => {
204 | const actual = [];
205 |
206 | app.task('foo', { silent: false }, function(next) {
207 | assert.equal(this.options.silent, true);
208 | assert.equal(this.options.foo, 'bar');
209 | actual.push('foo');
210 | next();
211 | });
212 |
213 | const options = { silent: true, foo: 'bar' };
214 | const build = app.series('foo', function(next) {
215 | actual.push('bar');
216 | next();
217 | }, options);
218 |
219 | return build()
220 | .then(() => {
221 | assert.deepEqual(actual, ['foo', 'bar']);
222 | });
223 | });
224 | });
225 | });
226 |
--------------------------------------------------------------------------------
/test/task.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const through = require('through2');
6 | const { Task } = require('..');
7 |
8 | describe('task', () => {
9 | it('should throw an error when Task is not instantiated', () => {
10 | assert.throws(() => Task(), /cannot be invoked without 'new'/);
11 | });
12 |
13 | it('should throw an error when nothing is passed to new Task', () => {
14 | assert.throws(() => new Task(), /expected/i);
15 | });
16 |
17 | it('should throw an error when `name` is not passed on `task`.', () => {
18 | assert.throws(() => new Task({}), /expected/i);
19 | });
20 |
21 | it('should create a new task with a given `name`', () => {
22 | const task = new Task({ name: 'default' });
23 | assert.equal(task.name, 'default');
24 | });
25 |
26 | it('should create a new task with a given task function', () => {
27 | const callback = cb => cb();
28 | const task = new Task({ name: 'default', callback });
29 | assert.equal(task.callback, callback);
30 | });
31 |
32 | it('should create a new task with the given dependencies', () => {
33 | const task = new Task({ name: 'default', deps: ['foo', 'bar'] });
34 | assert.deepEqual(task.deps, ['foo', 'bar']);
35 | });
36 |
37 | it('should create a new task with deps from the `options` property', () => {
38 | const task = new Task({ name: 'default', options: { deps: ['foo', 'bar'] } });
39 | assert.deepEqual(task.deps, ['foo', 'bar']);
40 | });
41 |
42 | it('should run a task function when `.run` is called', () => {
43 | let count = 0;
44 | const callback = cb => {
45 | count++;
46 | cb();
47 | };
48 |
49 | const task = new Task({ name: 'default', callback });
50 | const run = task.run();
51 | return run()
52 | .then(() => {
53 | assert.equal(count, 1);
54 | });
55 | });
56 |
57 | it('should run a task function that returns a promise when `.run` is called', () => {
58 | let count = 0;
59 | const callback = function() {
60 | return new Promise(function(resolve) {
61 | setImmediate(() => {
62 | count++;
63 | resolve();
64 | });
65 | });
66 | };
67 |
68 | const task = new Task({ name: 'default', callback });
69 | const run = task.run();
70 | return run()
71 | .then(() => {
72 | assert.equal(count, 1);
73 | });
74 | });
75 |
76 | it('should skip a task function when `.options.run === false`', () => {
77 | let count = 0;
78 | const callback = cb => {
79 | count++;
80 | cb();
81 | };
82 |
83 | const task = new Task({ name: 'default', callback, options: { run: false } });
84 | const run = task.run();
85 |
86 | return run()
87 | .then(() => {
88 | assert.equal(count, 0);
89 | });
90 | });
91 | it('should skip a task function when `.options.skip` is the task name', () => {
92 | let count = 0;
93 | const callback = cb => {
94 | count++;
95 | cb();
96 | };
97 |
98 | const task = new Task({
99 | name: 'foo',
100 | callback,
101 | options: { skip: 'foo' }
102 | });
103 |
104 | const run = task.run();
105 | return run().then(() => {
106 | assert.equal(count, 0);
107 | });
108 | });
109 |
110 | it('should skip a task function when `.options.skip` is an array with the task name', () => {
111 | let count = 0;
112 | const callback = cb => {
113 | count++;
114 | cb();
115 | };
116 |
117 | const task = new Task({
118 | name: 'foo',
119 | callback,
120 | options: { skip: ['bar', 'baz', 'foo'] }
121 | });
122 |
123 | const run = task.run();
124 | return run().then(() => {
125 | assert.equal(count, 0);
126 | });
127 | });
128 |
129 | it('should not skip a task function when `.options.skip` is an array without the task name', () => {
130 | let count = 0;
131 | const callback = cb => {
132 | count++;
133 | cb();
134 | };
135 |
136 | const task = new Task({name: 'foo', callback, options: { skip: ['bar', 'baz'] } });
137 | const run = task.run();
138 |
139 | return run()
140 | .then(() => {
141 | assert.equal(count, 1);
142 | });
143 | });
144 |
145 | it('should run a task function that returns a stream when `.run` is called', () => {
146 | let count = 0;
147 |
148 | const callback = function() {
149 | const stream = through.obj(function(data, enc, next) {
150 | count++;
151 | next(null);
152 | });
153 | setImmediate(() => {
154 | stream.write(count);
155 | stream.end();
156 | });
157 | return stream;
158 | };
159 |
160 | const task = new Task({ name: 'default', callback });
161 | const run = task.run();
162 |
163 | return run()
164 | .then(() => {
165 | assert.equal(count, 1);
166 | });
167 | });
168 |
169 | it('should run a task that returns a non-stream when `.run` is called', () => {
170 | let count = 0;
171 | const callback = cb => {
172 | setImmediate(() => {
173 | count++;
174 | cb();
175 | });
176 | return count;
177 | };
178 |
179 | const task = new Task({ name: 'default', callback });
180 | const run = task.run();
181 |
182 | return run()
183 | .then(() => {
184 | assert.equal(count, 1);
185 | });
186 | });
187 |
188 | it('should emit a `starting` event when the task starts running', () => {
189 | let count = 0;
190 | const callback = cb => {
191 | count++;
192 | cb();
193 | };
194 |
195 | const task = new Task({ name: 'default', callback });
196 | task.on('starting', () => {
197 | count++;
198 | });
199 |
200 | const run = task.run();
201 | return run()
202 | .then(() => {
203 | assert.equal(count, 2);
204 | });
205 | });
206 |
207 | it('should emit a `finished` event when the task finishes running', () => {
208 | let count = 0;
209 | const callback = cb => {
210 | count++;
211 | cb();
212 | };
213 | const task = new Task({ name: 'default', callback });
214 | task.on('finished', () => {
215 | count++;
216 | });
217 | const run = task.run();
218 | return run()
219 | .then(() => {
220 | assert.equal(count, 2);
221 | });
222 | });
223 |
224 | it('should emit an `error` event when there is an error during task execution', () => {
225 | let count = 0;
226 |
227 | const callback = cb => cb(new Error('expected an error'));
228 | const task = new Task({ name: 'default', callback });
229 |
230 | task.on('error', () => {
231 | count++;
232 | });
233 |
234 | const run = task.run();
235 | return run()
236 | .catch(err => {
237 | assert.equal(count, 1);
238 | assert.equal(err.message, 'expected an error');
239 | });
240 | });
241 |
242 | it('should have the current task set as `this` inside the function', () => {
243 | const results = [];
244 | const tasks = [];
245 |
246 | const callback = function(next) {
247 | results.push(this.name);
248 | next();
249 | };
250 |
251 | for (let i = 0; i < 10; i++) {
252 | tasks.push(new Task({ name: 'task-' + i, callback }));
253 | }
254 |
255 | const series = async() => {
256 | for (const task of tasks) {
257 | await task.run()();
258 | }
259 | };
260 |
261 | return series().then(() => {
262 | assert.equal(results.length, 10);
263 | assert.deepEqual(results, [
264 | 'task-0',
265 | 'task-1',
266 | 'task-2',
267 | 'task-3',
268 | 'task-4',
269 | 'task-5',
270 | 'task-6',
271 | 'task-7',
272 | 'task-8',
273 | 'task-9'
274 | ]);
275 | });
276 | });
277 | });
278 |
--------------------------------------------------------------------------------
/test/tasks.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const through = require('through2');
6 | const { Tasks } = require('..');
7 | let app;
8 |
9 | describe('tasks', () => {
10 | beforeEach(() => {
11 | app = new Tasks();
12 | });
13 |
14 | it('should run a task using a noop when `.run` is called', () => {
15 | const events = [];
16 | const push = task => events.push(task.name + ':' + task.status);
17 |
18 | app.on('task', push);
19 | app.on('task-registered', push);
20 | app.on('task-preparing', push);
21 |
22 | app.task('foo');
23 | app.task('default', ['foo']);
24 |
25 | return app.build('default').then(() => {
26 | assert.deepEqual(events, [
27 | 'foo:registered',
28 | 'default:registered',
29 | 'default:preparing',
30 | 'default:starting',
31 | 'foo:preparing',
32 | 'foo:starting',
33 | 'foo:finished',
34 | 'default:finished'
35 | ]);
36 | });
37 | });
38 |
39 | it('should cause an error if invalid deps are resolved `.run` is called', () => {
40 | const tasks = [];
41 | app = new Tasks();
42 | app.on('task', task => tasks.push(`${task.name}:${task.status}`));
43 |
44 | app.task('foo');
45 | app.task('default', { deps: ['foo', { foo: 'bar' }, { bang: 'baz' }] });
46 |
47 | return app.build('default')
48 | .then(() => {
49 | throw new Error('exected an error');
50 | })
51 | .catch(err => {
52 | assert(/expected/.test(err.message));
53 | });
54 | });
55 |
56 | it('should signal that a task is complete when a stream is returned', () => {
57 | app = new Tasks();
58 | const events = [];
59 | let count = 0;
60 |
61 | app.on('task-registered', task => events.push(task.status));
62 | app.on('task-preparing', task => events.push(task.status));
63 | app.on('task', task => events.push(task.status));
64 | app.task('default', () => {
65 | const stream = through.obj(function(data, enc, next) {
66 | count++;
67 | next();
68 | });
69 | stream.write(count);
70 | stream.end();
71 | return stream;
72 | });
73 |
74 | return app.build('default')
75 | .then(() => {
76 | assert.deepEqual(events, [ 'registered', 'preparing', 'starting', 'finished' ]);
77 | assert.equal(count, 1);
78 | });
79 | });
80 | });
81 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('mocha');
4 | const assert = require('assert');
5 | const util = require('util');
6 | const Composer = require('..');
7 | let app;
8 |
9 | describe('composer', () => {
10 | beforeEach(() => {
11 | app = new Composer();
12 | });
13 |
14 | it('should throw an error when a name is not given for a task', () => {
15 | assert.throws(() => app.task(), /expected/);
16 | });
17 |
18 | it('should expose static methods', () => {
19 | assert(Composer.create);
20 | assert(Composer.isGenerator);
21 | });
22 |
23 | it('should register a task', () => {
24 | app.task('default', () => {});
25 | assert(app.tasks.get('default'));
26 | assert.equal(typeof app.tasks.get('default'), 'object');
27 | assert.equal(app.tasks.get('default').callback.name, '');
28 | });
29 |
30 | it('should register a noop task when only name is given', () => {
31 | app.task('default');
32 | assert(app.tasks.get('default'));
33 | assert.equal(typeof app.tasks.get('default'), 'object');
34 | assert.equal(typeof app.tasks.get('default').callback, 'function');
35 | });
36 |
37 | it('should register a noop task when a name and an empty dependencies array is given', () => {
38 | app.task('default', []);
39 | assert(app.tasks.get('default'));
40 | assert.equal(typeof app.tasks.get('default'), 'object');
41 | assert.equal(typeof app.tasks.get('default').callback, 'function');
42 | });
43 |
44 | it('should register a task with an array of named dependencies', () => {
45 | app.task('default', ['foo', 'bar'], cb => cb());
46 | assert(app.tasks.get('default'));
47 | assert.equal(typeof app.tasks.get('default'), 'object');
48 | assert.deepEqual(app.tasks.get('default').deps, ['foo', 'bar']);
49 | });
50 |
51 | it('should register a task with a list of strings as dependencies', () => {
52 | app.task('default', 'foo', 'bar', cb => cb());
53 | assert.equal(typeof app.tasks.get('default'), 'object');
54 | assert.deepEqual(app.tasks.get('default').deps, ['foo', 'bar']);
55 | });
56 |
57 | it('should register a task as a noop function when only dependencies are given', () => {
58 | app.task('default', ['foo', 'bar']);
59 | assert.equal(typeof app.tasks.get('default'), 'object');
60 | assert.equal(typeof app.tasks.get('default').callback, 'function');
61 | });
62 |
63 | it('should register a task with options as the second argument', () => {
64 | app.task('default', { one: 'two' }, ['foo', 'bar']);
65 | assert.equal(typeof app.tasks.get('default'), 'object');
66 | assert.equal(typeof app.tasks.get('default').callback, 'function');
67 | assert.equal(app.tasks.get('default').options.one, 'two');
68 | });
69 |
70 | it('should register a task as a prompt task', () => {
71 | app.task('default', 'Run task?', 'foo');
72 | assert.equal(typeof app.tasks.get('default'), 'object');
73 | });
74 |
75 | it('should run a task', cb => {
76 | let count = 0;
77 | app.task('default', cb => {
78 | count++;
79 | cb();
80 | });
81 |
82 | app.build('default', err => {
83 | if (err) return cb(err);
84 | assert.equal(count, 1);
85 | cb();
86 | });
87 | });
88 |
89 | it('should run a task and return a promise', () => {
90 | let count = 0;
91 | app.task('default', cb => {
92 | count++;
93 | cb();
94 | });
95 |
96 | return app.build('default').then(() => {
97 | assert.equal(count, 1);
98 | });
99 | });
100 |
101 | it('should run a task with options', cb => {
102 | let count = 0;
103 | app.task('default', { silent: false }, function(cb) {
104 | assert.equal(this.options.silent, false);
105 | count++;
106 | cb();
107 | });
108 |
109 | app.build('default', err => {
110 | if (err) return cb(err);
111 | assert.equal(count, 1);
112 | cb();
113 | });
114 | });
115 |
116 | it('should run a task with additional options', cb => {
117 | let count = 0;
118 | app.task('default', { silent: false }, function(cb) {
119 | assert.equal(this.options.silent, true);
120 | assert.equal(this.options.foo, 'bar');
121 | count++;
122 | cb();
123 | });
124 |
125 | app.build('default', { silent: true, foo: 'bar' }, err => {
126 | if (err) return cb(err);
127 | assert.equal(count, 1);
128 | cb();
129 | });
130 | });
131 |
132 | it('should run the `default` task when no task is given', cb => {
133 | let count = 0;
134 | app.task('default', cb => {
135 | count++;
136 | cb();
137 | });
138 |
139 | app.build(err => {
140 | if (err) return cb(err);
141 | assert.equal(count, 1);
142 | cb();
143 | });
144 | });
145 |
146 | it('should skip tasks when `run === false`', cb => {
147 | const expected = [];
148 | function callback() {
149 | return function(cb) {
150 | expected.push(this.name);
151 | cb();
152 | };
153 | }
154 |
155 | app.task('foo', callback());
156 | app.task('bar', { run: false }, callback());
157 | app.task('baz', callback());
158 | app.task('bang', { run: false }, callback());
159 | app.task('beep', callback());
160 | app.task('boop', callback());
161 |
162 | app.task('default', ['foo', 'bar', 'baz', 'bang', 'beep', 'boop']);
163 | app.build(err => {
164 | if (err) return cb(err);
165 | assert.deepEqual(expected, ['foo', 'baz', 'beep', 'boop']);
166 | cb();
167 | });
168 | });
169 |
170 | it('should skip tasks when `run === false` (with deps skipped)', cb => {
171 | const expected = [];
172 | function callback() {
173 | return function(cb) {
174 | expected.push(this.name);
175 | cb();
176 | };
177 | }
178 |
179 | app.task('foo', callback());
180 | app.task('bar', { run: false }, ['foo'], callback());
181 | app.task('baz', ['bar'], callback());
182 | app.task('bang', { run: false }, ['baz'], callback());
183 | app.task('beep', ['bang'], callback());
184 | app.task('boop', ['beep'], callback());
185 |
186 | app.task('default', ['boop']);
187 | app.build(err => {
188 | if (err) return cb(err);
189 | assert.deepEqual(expected, ['beep', 'boop']);
190 | cb();
191 | });
192 | });
193 |
194 | it('should skip tasks when `run === false` (complex flow)', cb => {
195 | const expected = [];
196 |
197 | app.task('foo', function(next) {
198 | expected.push(this.name);
199 | // disable running the "bar" task
200 | app.tasks.get('bar').options.run = false;
201 | next();
202 | });
203 |
204 | app.task('bar', function(next) {
205 | expected.push(this.name);
206 | next();
207 | });
208 |
209 | app.task('baz', function(next) {
210 | expected.push(this.name);
211 | // enable running the "bang" task
212 | app.tasks.get('bang').options.run = true;
213 | next();
214 | });
215 |
216 | app.task('bang', { run: false }, function(next) {
217 | expected.push(this.name);
218 | next();
219 | });
220 |
221 | app.task('beep', function(next) {
222 | expected.push(this.name);
223 | next();
224 | });
225 |
226 | app.task('boop', function(next) {
227 | expected.push(this.name);
228 | next();
229 | });
230 |
231 | app.task('default', ['foo', 'bar', 'baz', 'bang', 'beep', 'boop']);
232 | app.build(err => {
233 | if (err) return cb(err);
234 | assert.deepEqual(expected, ['foo', 'baz', 'bang', 'beep', 'boop']);
235 | cb();
236 | });
237 | });
238 |
239 | it('should throw an error when a task with unregistered dependencies is run', cb => {
240 | let count = 0;
241 | app.task('default', ['foo', 'bar'], cb => {
242 | count++;
243 | cb();
244 | });
245 |
246 | app.build('default', err => {
247 | if (!err) return cb(new Error('Expected an error to be thrown.'));
248 | assert.equal(count, 0);
249 | cb();
250 | });
251 | });
252 |
253 | it('should throw an error when a task with globbed dependencies cannot be found', cb => {
254 | let count = 0;
255 | app.task('default', ['a-*'], cb => {
256 | count++;
257 | cb();
258 | });
259 |
260 | app.build('default', err => {
261 | if (!err) return cb(new Error('Expected an error to be thrown.'));
262 | assert.equal(count, 0);
263 | cb();
264 | });
265 | });
266 |
267 | it('should emit task events', cb => {
268 | const events = [];
269 | app.on('task', function(task) {
270 | events.push(task.status + '.' + task.name);
271 | });
272 |
273 | app.on('error', err => {
274 | if (err.build) {
275 | return;
276 | }
277 | events.push('error.' + err.task.name);
278 | });
279 |
280 | app.task('foo', cb => cb());
281 |
282 | app.task('bar', ['foo'], cb => cb());
283 |
284 | app.task('default', ['bar']);
285 | app.build('default', err => {
286 | if (err) return cb(err);
287 | assert.deepEqual(events, [
288 | 'starting.default',
289 | 'starting.bar',
290 | 'starting.foo',
291 | 'finished.foo',
292 | 'finished.bar',
293 | 'finished.default'
294 | ]);
295 | cb();
296 | });
297 | });
298 |
299 | it('-should emit an error event when an error is passed back in a task', cb => {
300 | const errors = [];
301 | app.on('error', err => {
302 | errors.push(err);
303 | });
304 |
305 | app.task('default', next => {
306 | next(new Error('This is an error'));
307 | });
308 |
309 | app.build('default', err => {
310 | assert(err);
311 | assert.equal(errors.length, 1);
312 | cb();
313 | });
314 | });
315 |
316 | it('should emit build events', () => {
317 | const events = [];
318 |
319 | app.on('build', function(build) {
320 | events.push(build.status);
321 | });
322 |
323 | app.on('error', () => {
324 | events.push('error');
325 | });
326 |
327 | app.task('foo', cb => cb());
328 | app.task('bar', ['foo'], cb => cb());
329 | app.task('default', ['bar']);
330 |
331 | return app.build('default').then(() => {
332 | assert.deepEqual(events, ['starting', 'finished']);
333 | });
334 | });
335 |
336 | it('should emit a build error event when an error is passed back in a task', () => {
337 | let count = 0;
338 |
339 | app.on('error', () => {
340 | count++;
341 | });
342 |
343 | app.task('default', cb => {
344 | cb(new Error('This is an error'));
345 | });
346 |
347 | return app
348 | .build('default')
349 | .then(() => {
350 | throw new Error('exected an error');
351 | })
352 | .catch(() => {
353 | assert.equal(count, 1);
354 | });
355 | });
356 |
357 | it('should stop build and return errors when thrown in a task', () => {
358 | let count = 0;
359 |
360 | app.task('foo', () => {
361 | throw new Error('This is an error');
362 | });
363 |
364 | app.task('bar', () => {
365 | count++;
366 | });
367 |
368 | return app.build(['foo', 'bar'])
369 | .catch(err => {
370 | assert(err);
371 | assert.equal(count, 0);
372 | });
373 | });
374 |
375 | it('should emit an error event when an error is thrown in a task', () => {
376 | let count = 0;
377 |
378 | app.on('error', () => {
379 | count++;
380 | });
381 |
382 | app.task('default', () => {
383 | throw new Error('This is an error');
384 | });
385 |
386 | return app
387 | .build('default')
388 | .then(() => {
389 | throw new Error('exected an error');
390 | })
391 | .catch(() => {
392 | assert.equal(count, 1);
393 | });
394 | });
395 |
396 | it('should run dependencies before running the dependent task.', () => {
397 | const events = [];
398 |
399 | app.task('foo', cb => {
400 | events.push('foo');
401 | cb();
402 | });
403 |
404 | app.task('bar', cb => {
405 | events.push('bar');
406 | cb();
407 | });
408 |
409 | app.task('default', ['foo', 'bar'], cb => {
410 | events.push('default');
411 | cb();
412 | });
413 |
414 | return app.build('default').then(() => {
415 | assert.deepEqual(events, ['foo', 'bar', 'default']);
416 | });
417 | });
418 |
419 | it('should add inspect function to tasks.', () => {
420 | app.task('foo', cb => cb());
421 | app.task('bar', cb => cb());
422 | app.task('default', ['foo', 'bar'], cb => cb());
423 |
424 | assert.equal(app.tasks.get('foo')[util.inspect.custom](), '');
425 | assert.equal(app.tasks.get('bar')[util.inspect.custom](), '');
426 | assert.equal(app.tasks.get('default')[util.inspect.custom](), '');
427 | });
428 |
429 | it('should disable inspect function on tasks.', () => {
430 | app.options = { inspectFn: false };
431 |
432 | app.task('foo', cb => cb());
433 | app.task('bar', cb => cb());
434 | app.task('default', ['foo', 'bar'], cb => cb());
435 |
436 | assert.equal(typeof app.tasks.get('foo').inspect, 'undefined');
437 | assert.equal(typeof app.tasks.get('bar').inspect, 'undefined');
438 | assert.equal(typeof app.tasks.get('default').inspect, 'undefined');
439 | });
440 |
441 | it('should run globbed dependencies before running the dependent task.', () => {
442 | const events = [];
443 | const task = function(cb) {
444 | events.push(this.name);
445 | cb();
446 | };
447 |
448 | app.task('foo', task);
449 | app.task('bar', task);
450 | app.task('baz', task);
451 | app.task('qux', task);
452 | app.task('default', ['b*'], task);
453 |
454 | return app.build('default').then(() => {
455 | assert.deepEqual(events, ['bar', 'baz', 'default']);
456 | });
457 | });
458 |
459 | it('should get the current task name from `this`', () => {
460 | const names = [];
461 | const tasks = [];
462 |
463 | const callback = function(cb) {
464 | names.push(this.name);
465 | cb();
466 | };
467 |
468 | for (let i = 0; i < 10; i++) {
469 | tasks.push(String(i));
470 | app.task(String(i), callback);
471 | }
472 |
473 | return app.build(tasks).then(() => {
474 | assert.deepEqual(names, ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']);
475 | });
476 | });
477 | });
478 |
--------------------------------------------------------------------------------