Git Product home page Git Product logo

undertaker's Introduction

undertaker

NPM version Downloads Build Status Coveralls Status

Task registry that allows composition through series/parallel methods.

Usage

var fs = require('fs');
var Undertaker = require('undertaker');

var taker = new Undertaker();

taker.task('task1', function(cb){
  // do things

  cb(); // when everything is done
});

taker.task('task2', function(){
  return fs.createReadStream('./myFile.js')
    .pipe(fs.createWriteStream('./myFile.copy.js'));
});

taker.task('task3', function(){
  return new Promise(function(resolve, reject){
    // do things

    resolve(); // when everything is done
  });
});

taker.task('combined', taker.series('task1', 'task2'));

taker.task('all', taker.parallel('combined', 'task3'));

API

Task functions can be completed in any of the ways supported by async-done

new Undertaker([registryInstance])

The constructor is used to create a new instance of Undertaker. Each instance of Undertaker gets its own instance of a registry. By default, the registry is an instance of undertaker-registry but it can be an instance of any other registry that follows the Custom Registries API.

To use a custom registry, pass a custom registry instance (new CustomRegistry([options])) when instantiating a new Undertaker instance. This will use the custom registry instance for that Undertaker instance.

task([taskName,] fn)

Both a getter and setter for tasks.

If a string (taskName) is given as the only argument, it behaves as a getter and returns the wrapped task (not the original function). The wrapped task has a unwrap method that will return the original function.

If a function (fn) and optionally a string (taskName) is given, it behaves as a setter and will register the task by the taskName. If taskName is not specified, the name or displayName property of the function is used as the taskName.

Will throw if:

  • As a getter: taskName is missing or not a string.
  • As a setter: taskName is missing and fn is anonymous.
  • As a setter: fn is missing or not a function.

series(taskName || fn...)

Takes a variable amount of strings (taskName) and/or functions (fn) and returns a function of the composed tasks or functions. Any taskNames are retrieved from the registry using the get method.

When the returned function is executed, the tasks or functions will be executed in series, each waiting for the prior to finish. If an error occurs, execution will stop.

parallel(taskName || fn...)

Takes a variable amount of strings (taskName) and/or functions (fn) and returns a function of the composed tasks or functions. Any taskNames are retrieved from the registry using the get method.

When the returned function is executed, the tasks or functions will be executed in parallel, all being executed at the same time. If an error occurs, all execution will complete.

registry([registryInstance])

Optionally takes an instantiated registry object. If no arguments are passed, returns the current registry object. If an instance of a registry (customRegistry) is passed the tasks from the current registry will be transferred to it and the current registry will be replaced with the new registry.

The ability to assign new registries will allow you to pre-define/share tasks or add custom functionality to your registries. See Custom Registries for more information.

tree([options])

Optionally takes an options object and returns an object representing the tree of registered tasks. The object returned is archy compatible. Also, each node has a type property that can be used to determine if the node is a task or function.

options

options.deep

Whether or not the whole tree should be returned.

Type: Boolean

Default: false

lastRun(task, [precision])

Takes a string or function (task) and returns a timestamp of the last time the task was run successfully. The time will be the time the task started.

Returns undefined if the task has not been run.

If a task errors, the result of lastRun will be undefined because the task should probably be re-run from scratch to get into a good state again.

The timestamp is always given in millisecond but the time resolution can be rounded using the precision parameter. The use case is to be able to compare a build time to a file time attribute.

Assuming undertakerInst.lastRun('someTask') returns 1426000001111, undertakerInst.lastRun('someTask', 1000) returns 1426000001000.

The default time resolution is 1.

Custom Registries

Custom registries are constructor functions allowing you to pre-define/share tasks or add custom functionality to your registries.

A registry's prototype should define:

  • init(taker): receives the undertaker instance to set pre-defined tasks using the task(taskName, fn) method.
  • get(taskName): returns the task with that name or undefined if no task is registered with that name.
  • set(taskName, fn): add task to the registry. If set modifies a task, it should return the new task.
  • tasks(): returns an object listing all tasks in the registry.

You should not call these functions yourself; leave that to Undertaker, so it can keep its metadata consistent.

The easiest way to create a custom registry is to inherit from undertaker-registry:

var util = require('util');

var DefaultRegistry = require('undertaker-registry');

function MyRegistry(){
  DefaultRegistry.call(this);
}

util.inherits(MyRegistry, DefaultRegistry);

module.exports = MyRegistry;

Sharing tasks

To share common tasks with all your projects, you can expose an init method on the registry prototype and it will receive the Undertaker instance as the only argument. You can then use undertaker.task(name, fn) to register pre-defined tasks.

For example you might want to share a clean task:

var fs = require('fs');
var util = require('util');

var DefaultRegistry = require('undertaker-registry');
var del = require('del');

function CommonRegistry(opts){
  DefaultRegistry.call(this);

  opts = opts || {};

  this.buildDir = opts.buildDir || './build';
}

util.inherits(CommonRegistry, DefaultRegistry);

CommonRegistry.prototype.init = function(takerInst){
  var buildDir = this.buildDir;
  var exists = fs.existsSync(buildDir);

  if(exists){
    throw new Error('Cannot initialize common tasks. ' + buildDir + ' directory exists.');
  }

  takerInst.task('clean', function(){
    return del([buildDir]);
  });
}

module.exports = CommonRegistry;

Then to use it in a project:

var Undertaker = require('undertaker');
var CommonRegistry = require('myorg-common-tasks');

var taker = new Undertaker(CommonRegistry({ buildDir: '/dist' }));

taker.task('build', taker.series('clean', function build(cb) {
  // do things
  cb();
}));

Sharing Functionalities

By controlling how tasks are added to the registry, you can decorate them.

For example if you wanted all tasks to share some data, you can use a custom registry to bind them to that data. Be sure to return the altered task, as per the description of registry methods above:

var util = require('util');

var Undertaker = require('undertaker');
var DefaultRegistry = require('undertaker-registry');

// Some task defined somewhere else
var BuildRegistry = require('./build.js');
var ServeRegistry = require('./serve.js');

function ConfigRegistry(config){
  DefaultRegistry.call(this);
  this.config = config;
}

util.inherits(ConfigRegistry, DefaultRegistry);

ConfigRegistry.prototype.set = function set(name, fn) {
  // The `DefaultRegistry` uses `this._tasks` for storage.
  var task = this._tasks[name] = fn.bind(this.config);
  return task;
};

var taker = new Undertaker();

taker.registry(new BuildRegistry());
taker.registry(new ServeRegistry());

// `taker.registry` will reset each task in the registry with
// `ConfigRegistry.prototype.set` which will bind them to the config object.
taker.registry(new ConfigRegistry({
  src: './src',
  build: './build',
  bindTo: '0.0.0.0:8888'
}));

taker.task('default', taker.series('clean', 'build', 'serve', function(cb) {
  console.log('Server bind to ' + this.bindTo);
  console.log('Serving' + this.build);
  cb();
}));

In the wild

License

MIT

undertaker's People

Contributors

coreyfarrell avatar dinoboff avatar douglasduteil avatar erikkemperman avatar fabpopa avatar github-actions[bot] avatar jpeer264 avatar kahwee avatar orta avatar pdehaan avatar phated avatar rosskevin avatar sttk avatar trysound avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

undertaker's Issues

meta is undefined

I'm using latest in gulpjs/gulp#4.0. When I try to use any of the commands like gulp --tasks or when WebStorm tries to run gulp --tasks-json I get an error.

 return meta.tree;
            ^

TypeError: Cannot read property 'tree' of undefined

I can post the full stack trace if needed. This may just be a known issue because it's in the middle of development but I thought I'd at least get it logged in case not.

get / set

I'm really interested in inheriting undertaking in a couple of applications I'm building/have. Any chance you might consider using more semantically-specific method names than .get() and .set()?

The rationale is that, given how common those two method names are, this creates an implementation challenge for an application to inherit undertaker if those methods are already on the prototype. Otherwise it would just be as simple as Undertaker.call(this) etc., as you're doing with gulp. I know there are ways around it, but if the method names were something like .getTask() and .setTask() implementation be a bit easier.

I also realize this is late in the game, and if using undertaker as a building block in other applications isn't really a goal feel free to close. thx

Binding a context to task functions referenced in series and parallel

I have a config registry that's basically the same thing as what's in the docs. I also have about a dozen functions that represent different steps of a build process I have. I only expose three tasks to the actual gulp CLI tool (clean, build and serve).

My build task looks similar to this

gulp.task('build', gulp
    .series(cleanBuild, gulp
        .parallel(copyHtml, copyImages, babelCompile, prepareAppIndex, copyVendorFiles, lessCompile)));

Now my problem is that the functions (copyHtml, copyImages, etc) are not registered as tasks with my ConfigRegistry. That makes sense, they're not really "tasks". At least not ones I want to expose directly because that would just cause static/cruft for devs who just want an easy to use build process. So what happens is the functions try to access this.config but it's never been binded because the individual functions are not registered as tasks so I get a classic "Cannot read property 'config' of undefined" TypeError.

Any ideas how I can keep this elegant or how undertaker could be updated to make this easier? The only way I can see to do it somewhat elegantly right now is to wrap each of my functions in a factory function. The function would then close (in the closure sense) over the factory function's scope which would contain a config object I pass in (or I could just bind the config to the function).

function copyHtmlFactory(config) {
    function copyHtml() {
        // do something with the config parameter or this.config if using bind
    }

    return copyHtml; // or return copyHtml.bind({config: config});
}

I think another way I could do it would be to bind the function references (without the use of a factory function) before they are passed to .series() and .parallel().

How to see that all tasks finished?

E.g I want to disconnect from database when gulp is done.

How can I detect and do that?

In the past, gulp.on('stop') with orchestrator could help, but now stop works on every task.

settle not working with es2015 async functions

When async function throw Error, process terminated with node errors.
was expecting it will settle and error will be available in callback.

import * as Undertaker from 'undertaker';
process.env.UNDERTAKER_SETTLE = 'true'

const tasker = new Undertaker();
console.log(tasker._settle)
function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
tasker.task('test1', async () => {
    await delay(1000);
    console.log('in test1 ...');
    return 1;
});
tasker.task('test2', async () => {
    console.log('in test2 ...');
    // return 2;
    throw new Error('test6 error')
});
tasker.task('test3', async () => {
    await delay(2000);
    console.log('in test3 ...');
    return 3;
});

tasker.task('test4', async () => {
    await delay(1000);
    console.log('in test4 ...');
    return 4;
});
tasker.task('test5', async () => {
    console.log('in test5 ...');
    return 5;
});
tasker.task('test6', async () => {
    await delay(1000);
    console.log('in test6 ...');
    return 6;
});

tasker.task('ser', tasker.series('test1', 'test2'));
tasker.task('par', tasker.parallel( 'test3', 'test4', 'test5'));
tasker.task('serpar', tasker.series('ser', 'par', 'test6'));
tasker.task('serpar1', tasker.series(tasker.series('test1', 'test2'), tasker.parallel('test3', 'test4', 'test5'), 'test6'));
tasker.task('serpar2', tasker.parallel(tasker.series('test1', 'test2'), tasker.parallel('test3', 'test4', 'test5'),'test6'));


(async () => {
    try {
        // let tree = tasker.tree({deep: true});
        // console.log('tree', tree);

        /** case 1 **/
        tasker.task('serpar1')((err, res) => {
            if(err) {
                console.log('final error', err)
            }
            console.log('res', res);
            console.log('lastRun for test3', tasker.lastRun('test6'))
        });

    } catch (err) {
        console.log('Error:::',err)
    }
})();

Output

 ts-node src/run2.ts 
true
in test1 ...
in test2 ...
(node:13530) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: Uncaught, unspecified "error" event. ([object Object])
(node:13530) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Task Never Defined

Never is a long time. You might want to change the message to "Task not defined".

Ran across this in Gulp 4 - they pointed me here.

Error handling

I've been using Undertaker in the following way:

function uberTask() {
    return new Promise( ( resolve ) => {
        const taker = new Undertaker();

        taker.task( 'a', () => {
        } );

        taker.task( 'b', () => {
        } );

       taker.series( 'a', 'b', resolve )();
    } );
}

If any of the tasks throws, then I get some useless stack on the console:

[10:41:25] Using gulpfile /www/ckeditor5/ckeditor5/gulpfile.js
[10:41:25] Starting 'test'...
events.js:165
      throw err;
      ^

Error: Uncaught, unspecified "error" event. ([object Object])
    at Undertaker.emit (events.js:163:17)
    at Object.error (/www/ckeditor5/ckeditor5/node_modules/@ckeditor/ckeditor5-dev-tests/node_modules/undertaker/lib/helpers/createExtensions.js:61:10)
    at handler (/www/ckeditor5/ckeditor5/node_modules/@ckeditor/ckeditor5-dev-tests/node_modules/now-and-later/lib/mapSeries.js:43:14)
    at f (/www/ckeditor5/ckeditor5/node_modules/@ckeditor/ckeditor5-dev-tests/node_modules/once/once.js:25:25)
    at f (/www/ckeditor5/ckeditor5/node_modules/@ckeditor/ckeditor5-dev-tests/node_modules/once/once.js:25:25)
    at done (/www/ckeditor5/ckeditor5/node_modules/@ckeditor/ckeditor5-dev-tests/node_modules/async-done/index.js:24:15)
    at Domain.onError (/www/ckeditor5/ckeditor5/node_modules/@ckeditor/ckeditor5-dev-tests/node_modules/async-done/index.js:32:12)
    at Domain.g (events.js:286:16)
    at emitOne (events.js:96:13)
    at Domain.emit (events.js:188:7)
    at Domain.errorHandler [as _errorHandler] (domain.js:97:23)
    at process._fatalException (node.js:265:33)

I guess that I should handle the errors in this way:

return new Promise( ( resolve, reject ) => {
    const taker = new Undertaker();

    taker.task( 'a', () => {
        throw new Error( 'a' );
    } );

    taker.task( 'b', () => {
    } );

    taker.on( 'error', ( error ) => {
        reject( error.error );
    } );

    taker.series( 'a', 'b', resolve )();
} );

Am I right? I haven't found this in the documentation.

lastRun should return undefined on first run

Hi, I think I'm seeing an issue with Gulp 4.0 after the dependency for undertaker was bumped from ^0.7.0 to ^0.11.0 a week ago. My tasks using lastRun aren't building, since

gulp.task('build:scripts', function() {
  console.log(gulp.lastRun('build:scripts'));    // this outputs the timestamp on first run, not undefined
  return gulp.src('app/**/*.js', { since: gulp.lastRun('build:scripts') })
    .pipe(gulp.dest('dist/app'));
});

outputs the timestamp even on first run, so no change is registered in the files.
Is this just me? Thank you.

How run a task programmatically ?

Hi @phated,

I think I missed or misunderstood something.

But how can I start a task defined by Undertaker without Gulp?

Example:

var Undertaker = require('undertaker');

var taker = new Undertaker();

taker.task('awesome', function(cb) {
  console.log('awesome');
  cb();
});

taker.task('raccoon', function(cb) {
  console.log('raccoon');
  cb();
});

taker.task('default', taker.series('awesome', 'raccoon'));

I would run default task by something like taker.run('default') or taker.start('default').

Custom registry must have `get` function

In my Gulp build step I get the following error.

`
throw err;
^

AssertionError [ERR_ASSERTION]: Custom registry must have get function
at validateRegistry (C:\Users\john\Source\Repos\Contoso\ProductTasksExtension\node_modules\undertaker\lib\helpers\validateRegistry.js:28:5)
at Gulp.registry (C:\Users\john\Source\Repos\Contoso\ProductTasksExtension\node_modules\undertaker\lib\registry.js:17:3)
at Object. (C:\Users\john\Source\Repos\Contoso\ProductTasksExtension\gulpfile.js:10:6)
at Module._compile (module.js:660:30)
at Object.Module._extensions..js (module.js:671:10)
at Module.load (module.js:573:32)
at tryModuleLoad (module.js:513:12)
at Function.Module._load (module.js:505:3)
at Module.require (module.js:604:17)
at require (internal/module.js:11:18)
`
How do I troubleshoot is error?

Thanks
Stefan

Task definition order is important

But why do they have to be?

gulp.task('first', gulp.series('one', 'two'));
gulp.task('one', function(done) {
  // do stuff
  done();
});

gulp.task('two', function(done) {
  // do stuff
  done();
});

throws:

assert.js:89
  throw new assert.AssertionError({
  ^
AssertionError: Task never defined: one

stack trace is unintelligible since error is not serialized properly

This line:

causes my stacktrace to look like this

Error: Uncaught, unspecified "error" event. ([object Object])
    at Gulp.emit (events.js:163:17)
    at Object.error (<cut>/createExtensions.js:62:10)
    at handler (<cut>/node_modules/now-and-later/lib/map.js:46:14)
    at f (<cut>/node_modules/once/once.js:25:25)
    at f (<cut>/node_modules/once/once.js:25:25)
    at done (<cut>/node_modules/async-done/index.js:24:15)
    at _combinedTickCallback (internal/process/next_tick.js:77:11)
    at Immediate._tickDomainCallback (internal/process/next_tick.js:128:9)
    at runCallback (timers.js:672:20)
    at tryOnImmediate (timers.js:645:5)

The underlying error has being thrown with JSON.stringify method:

{ TypeError: Cannot create property 'serializeMetadata' on string 'ast'
    at new JsonSerializer (<cut>/node_modules/raml-1-parser/dist/util/jsonSerializer.js:96:44)
    at ApiImpl.BasicNodeImpl.toJSON (<cut>/node_modules/raml-1-parser/dist/parser/wrapped-ast/parserCore.js:276:20)
    at Object.stringify (native)
    at client (<cut>/buildClient.js:13:54)
    at bound (domain.js:280:14)
    at runBound (domain.js:293:12)
    at asyncRunner (<cut>/node_modules/async-done/index.js:39:18)
    at _combinedTickCallback (internal/process/next_tick.js:73:7)
    at process._tickDomainCallback (internal/process/next_tick.js:128:9)
  domain: 
   Domain {
     domain: null,
     _events: {},
     _eventsCount: 0,
     _maxListeners: undefined,
     members: [] },
  domainThrown: true }

Another underlying error that triggered the same unintelligible stack trace:

{ AssertionError: Task never defined: undefined
    at getFunction (<cut>r/node_modules/undertaker/lib/helpers/normalizeArgs.js:15:5)
    at map (<cut>/node_modules/arr-map/index.js:20:14)
    at normalizeArgs (<cut>/node_modules/undertaker/lib/helpers/normalizeArgs.js:22:10)
    at Gulp.series (<cut>/node_modules/undertaker/lib/series.js:13:14)
    at <cut>/update.js:97:39
    at next (native)
    at fulfilled (<cut>/update.js:4:58)
  name: 'AssertionError',
  actual: undefined,
  expected: true,
  operator: '==',
  message: 'Task never defined: undefined',
  generatedMessage: false }

util.inspect is what I used just before aforementioned string (createExtensions.js#L65) to get the error description. I don't know if it's a proper fix for this issue. It probably isn't.
The error is displayed correctly when there is no gulp in the pipeline.

Can't get task description and flags because returning wrapped function

By modification that gulp.task(name) returns wrapped function instead of original function, gulp-cli can't get task description and flags now.

Since gulp.task(name).unwrap() returns original function, we can get description and flags by gulp.task(name).unwrap().description and gulp.task(name).unwrap().flags.

Is this intentional way?

Skipping null tasks

It would be cool to implement accepting null values in gulp.series and gulp.parallel. My case is

gulp.task('build', gulp.series(
    env.clean ? 'clean' : null,
    gulp.parallel(
        conf.markup ? 'markup' : null,
        'script',
        'style',
        'image'
    )
));

For now I should pass something like

function (done) {
  done();
}

But I don't like to pass anonymous or named task, I just would like to skip it.

Integration tests

I want to create tests that have common gulp uses in order to make sure we don't break the use cases like I did in a few versions of orchestrator.

Error - "Task function must be specified"

I've updated my GULP to v4 and i'm getting this error. Here's the log:

C:\xampp\htdocs\project>gulp

assert.js:89
  throw new assert.AssertionError({
  ^
 AssertionError: Task function must be specified
    at Gulp.set [as _setTask] (C:\xampp\htdocs\project\node_modules\gulp\node_modules\undertaker\lib\set-task.js:12:3)
    at Gulp.task (C:\xampp\htdocs\project\node_modules\gulp\node_modules\undertaker\lib\task.js:13:8)
    at Object.<anonymous> (C:\xampp\htdocs\project\gulpfile.js:47:6)
    at Module._compile (module.js:425:26)
    at Object.Module._extensions..js (module.js:432:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:311:12)
    at Module.require (module.js:366:17)
    at require (module.js:385:17)
    at Liftoff.handleArguments (C:\Users\vsync\AppData\Roaming\npm\node_modules\gulp\bin\gulp.js:116:3)

Add jscs

Use jscs-preset-gulp as the preset

Tasks composition without gulp

A followup of #66.

Down the rabbit hole...

I'm a framework/library author (namely, CKEditor). We need to provide a set of dev tools such as compilers for different types of assets, watchers, test runners, bundlers, scaffolders, etc.

For us, the library is the thing we work on, but for the community it's just a small piece of their projects. This changes quite a lot.

  • We're using gulp as a task runner, they may not.
  • We need 20 tasks for all the processes we use, they may need 5 of those and 3 different ones which are a different composition of the granular sub-tasks which we expose.
  • We can pass some args to the tasks on CLI, they may want to hardcode them.
  • And last but not the least – we're perfectionists. We want to have a control e.g. over what gets logged – to give an example – I don't want to register every sub-task to gulp, because a longer process would log a lot of unnecessary things.

After a few failed attempts to organise this code we decided to simply expose a set of functions. Those functions represents granular sub-tasks, accept options as parameters and, depending on what they do, they can be synchronous or return a stream or a promise. Sounds super obvious, I know :D.

What happens now? We use gulp, so our gulpfile.js looked like this:

const compiler = require( '@ckeditor/ckeditor5-dev-compiler' );

gulp.task( 'compile:js', () => compiler.compileJS( { format: 'esnext' } ) ); // Let's say this is a stream.
gulp.task( 'compile:sass', () => compiler.compileSass( sassOpts ) ); // This returns a promise.
gulp.task( 'compile:icons', () => compiler.compileIcons( iconsOpts ) ); // And this is synchronous

gulp.task( 'compile', [ 'compile:js', 'compile:sass', 'compile:icons' ] );

Now, there are couple of things which may happen (actually, they all happened :D).

Our gulpfile grew so we wanted to combine some sub-tasks into functions representing bigger chunks (e.g. compiler.compile() should run internally compiler.compileJS(), compiler.compileSass() and compiler.compileIcons()).

Exposing the popular tasks as bigger chunks would also simplify the community's gulpfiles which is an important thing if you want a good DX. Instead of the 4 tasks above, you get now:

const compiler = require( '@ckeditor/ckeditor5-dev-compiler' );

// Mind also the changed name – for the community it's not anymore "compile".
// Compiling CKEditor is just a sub-step of "compile my project".
gulp.task( 'compile:ckeditor', compiler.compile( { all options here } ) );

What also happened was that a compiler for tests may need different options than a compiler for documentation builder or for a specific release. So at some point we had something like:

gulp.task( 'compile:js', () => compiler.compileJS( getOptsFromCLI() ) );
gulp.task( 'compile:js:esnext', () => compiler.compileJS( { format: 'esnext'} ) );
gulp.task( 'compile:js:amd', () => compiler.compileJS( { format: 'amd' } ) );

gulp.task( 'test', [ 'compile:js:amd' ], () => tests.run() );
gulp.task( 'test', [ 'compile:js:esnext' ], () => docsBuilder.build() );

Horror... :D

What also happened was that we wanted to postpone certain require() calls, because combination of all possible dev tasks was loading a ridiculously huge number of dependencies. This complicated the gulpfile even more.

And then a developer comes to you and says that they are not using gulp at all in their project and they don't want it as a dependency, so how to combine now the compilation process with linting and testing.

Way out

At some point we realised how we'd like to solve all this. We needed a tool to sequence or run in parallel the sub-tasks so we can easily expose the less granular ones (like compile()) and that we can tell community how to run our code when they are not using gulp. Also, by registering only the end point tasks, and not the intermediate ones, we're able to keep the gulp -T short and prevent from logging too many things on the output.

After some research we found undertaker. "Task registry that allows composition through series/parallel methods." sounds for me exactly like what I wrote above :D Just, you said it shouldn't.

Anyway, this is how we use it now: https://github.com/ckeditor/ckeditor5-dev-compiler/blob/93d521e2e1b0e63fbf62133c1dff9ca9518a51f1/lib/tasks.js#L303-L330.

I hope that this better explains what we want to achieve (and what we don't want to do) and how undertaker could help.

Test fails on Node v6.9.1+

In tree.js the test should form a 3 level nested tree fails as var anon = fn() is not directly an anonymous function. The name of the task is anon as the expected <anonymous>

var anon = function(cb) {
    cb();
};
taker.task('fn1', taker.parallel(anon, noop)); // anon should be <anonymous>

If you want, I can make an PR in writing the anonymous function directly in taker.parallel, this would fix the issue.

taker.task('fn1', taker.parallel(function (cb) {
    cb()
}, noop));

EDIT:

same for the last test: should use the proper labels for aliased tasks (nested)

sharing memory structure between tasks

I have a serve task that starts a Browsersync server. When my tasks are done running I can then call browserSyncServer.reload() to reload the page in browsers. I'm doing this with a watch. It would be nice if I could split the watch task into its own module that is isolated from the serve task.

I should mention that these tasks are registered with a custom registry that require's the individual task functions which are located in separate modules. The custom registry uses a configuration object. I could just place a reference to the Browsersync server somewhere on the configuration object and access it from the other tasks.

Is that going to be the smoothest way to accomplish sharing data between tasks forward into the future of undertaker and g4 or have you thought about any other APIs that could make it more straight forward?

[suggestion] pass the arguments to the task for running task in parallel

The user story: In many cases, we need to perform file tasks in parallel, simultaneous processing of multiple files.

Could you add an option object(optional) argument to pass into the task. like this:

taker.task('processFile', function(file){
  return new Promise(function(resolve, reject){
    // process a file
    ...
    resolve();
  });
});

taker.task('processAll', function(files, cb){
  Promise.map(files, function(file){
    return take.task('processFile', file)
  }).nodeify(cb);
});

name/displayName order

What should we do when we have displayName and name at the same time?

IMHO displayName should have a higher order than name and task name should be resolved as name = fn.displayName || fn.name; instead of name = fn.name || fn.displayName;

What do you think?

Error in parallel

I run in parallel 4 tasks: markup, script, style, image. If at least one of them is failed, build breaks in the same time. I think end of all tasks in parallel should be waited and then errored. Paralleled tasks do not depends on each other so they should be ended correctly.

[02:23:51] Using gulpfile D:\_host\github\case\gulpfile.js
[02:23:51] Starting 'build'...
[02:23:51] Starting 'clean (skipped)'...
[02:23:51] Finished 'clean (skipped)' after 2.09 ms
[02:23:51] Starting '<parallel>'...
[02:23:51] Starting 'markup'...
[02:23:51] Starting 'script'...
[02:23:51] Starting 'style'...
[02:23:51] Starting 'image'...
[02:23:52] Finished 'image' after 1.14 s
[02:23:52] 'markup' errored after 1.16 s
[02:23:52] Error in plugin 'gulp-file-include'
Message:
    ENOENT, no such file or directory 'D:\_host\github\case\app\markup\sss'
Details:
    domainEmitter: [object Object]
    domain: [object Object]
    domainThrown: false
[02:23:52] '<parallel>' errored after 1.17 s
[02:23:52] 'build' errored after 1.18 s
[02:23:52] The following tasks did not complete: script, style
[02:23:52] Did you forget to signal async completion?

tree() returns an array

I'm consuming the tree() function from the gulp implementation and I get an array of task names returned. The documentation indicates that an object graph should be returned. Can someone clarify how this is supposed to work?

Late to the party

So yeah 😄, anyway, I write this here in case it is something you find interesting.

tl;dr proposal for a non-evented approach for undertaker, maybe not using what I've done but something better.

I have been working for a while now around the same idea of composing async functions using async-done and have come up with a different approach for all of this with no events and one method for series and/or parallel. This is how it looks like:

var tornado = require('tornado').create(); // <- the "registry"

tornado.set(':handle(\\d+)', function(next){
 console.log(this.params.handle);
  setTimeout(next, Math.random()*10); 
});

function handle(next){ 
  setTimeout(next, Math.random()*10); 
}

var series = tornado.stack('1 2', handle, {wait: true}); // => function
var parallel = tornado.stack('3 4', handle); // => function
series(1, 2); parallel(1, 2); 
// passing arguments to each handle works from here or with next(null, 1, 2);

The same logic applies for nesting one "stack" with another as you do with bach. As you see I have added path to regex to the mix (because why not) and string arguments don't have to necessarily be separated by a comma. A space is just fine.

More consise task definition or update example

I was looking at your example that returns a promise and was wondering why you would return a promise that way.

taker.task('task3', function(){
  return new Promise(function(resolve, reject){
    // do things
    resolve();
  });
});

What's the use case? Wanting to call reject? If so, something like this seems more elegant and also handles the callback use case.

taker.task('task1', function(resolve, reject){
  // do things
  resolve(); // when everything is done
});

However, it still does make sense to support promises if you are using a library that returns promises.

var http = require('some-http-library');

taker.task('task1', function(){
  return http.get('http://github.com'); // returns a promise
});

I just don't understand why you would ever want to define a promise within a task. Not that big of a deal though.

New release

Any chance you could bump a patch release? The task naming issue is fixed in master, but current gulp4 is hardly usable with currently tagged undertaker version. Thanks!

Deprecate 'done' callback?

Maybe done for tasks can be deprecated now? Example:

taker.task('task1', function(done){
  done();
});

If ES6 now support promises why need to support 2 ways handling async things?

How to see that all tasks finished?

That's a follow-up of #55, because the answer to that issue is obviously wrong, it was closed on a mistake.

How to see that all tasks are finished? In Gulp3 it was possible: gulp.on('stop') or gulp.on('err').
But in Gulp4, there seems to be no such way.

An example of a use case would be to disconnect from database when gulp is done or tests are passed.

P.S. process.on('exit') does not help here at all, because the process don't exit when gulp finishes.

P.P.S. I also guess that might be not undertaker-related, but include the overall architecture.

record task finish times

an object with the times that tasks last finished would be really useful for incremental builds.

this might belong in the default registry, putting it here because im not sure

Using series and parallel within a custom registry

When using Custom Registries with an Undertaker instance, is there a recommended way to use series and parallel within the custom registry?

Right now the only way I can figure out how to do so would be to pass in the Undertaker instance on instantiation

function CustomRegistry (taker) {
  DefaultRegistry.call(this)
  this.set('task1', function () {/* .. */})
  this.set('task2', function () {/* .. */})
  this.set('task3', taker.parallel('task1', 'task2'))
}
utils.inherits(CustomRegistry, DefaultRegistry)

and then to consume it...

taker.registry(new CustomRegistry(taker))

The only problem with this is that I get an error:

assert.js:86
  throw new assert.AssertionError({
        ^
AssertionError: Task never defined: task1

I assume this is because the Registry doesn't actually set the tasks, but just accrues them and passes them to Undertaker after instantiation, while parallel on the other hand, requires a task lookup immediately.

The current workaround I have for this is executing the parallel within a wrapper function:

  this.set('task3', function () {
    return taker.parallel('task1', 'task2')()
  })

Obviously this isn't exactly ideal, and all of these series and parallel tasks are labeled as <anonymous>

Is there a suggested approach to this? Passing in the taker instance feels kind of gross and hacky.

Add a task uniq id as an event attribute

Currently, events come with a uid attribute (to match a "start" event to a "stop"/"error" event) and the name (a provided string or the function name). The task name is not necessary unique.

Is it possible to add task id attribute unique to each task?

Edit documentation to register tasks to gulp alongside taker.

On the "Sharing Functionalities" section of the documentation, it goes through an example on how to collaborate tasks into the main file for usage in default.

Most people will want to be able to run individual gulp tasks from the command line. Since gulp-hub is not up to date with the latest undertaker functionality, we now have to register undertaker tasks instead of gulp tasks to properly share them through the registry.

I've found that by editing the example set function to register the gulp task, it saves a lot of headache and lines of code to register the gulp task at the same time we are setting the taker task (obviously requiring the gulp module in order to do so).

What I did was this:

    ConfigRegistry.prototype.set = function set(name, fn) {
      var task = this._tasks[name] = fn.bind();
      gulp.task(name, fn);
      return task;
    };

We run CI that use SSH commands for gulp (i.e. gulp test, gulp deploy:rebuild, etc.)

Am I doing something wrong, leading me to have to insert this code? Or is this future functionality that is not completed yet?

Should tasks inherit the `branch` flag

@erikkemperman
I am taking a look at implementing gulpjs/gulp#1411 (comment) and gulpjs/gulp#1330 through the use of the branch flag you added to the metadata (awesome idea, btw). However, when we do something like the following, that flag isn't attached to the metadata for the new task.

gulp.task('default', gulp.series(...));

Do you think that the task should inherit the branch flag if we already have metadata and the metadata tells us it is a branch?

Is it replacement for orchestrator?

@phated I'm trying to follow all the efforts leading to the new task system in Gulp4 and this new project took me by surprise :-) Is it meant to replace orchestrator? Or build on top of it?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.