DEV Community

loading...

Taskmachine: a pseudo-make made with javascript

vonheikemen profile image Heiker ・3 min read

It turns out that making a task runner is not that difficult. Well... making a good task runner might be difficult but building something that just runs functions is not.

Just for fun I took a look inside the code of @ygor/task and saw how simple it was. Decided to make myself a version of that, one that was designed not to live inside javascript project. So I could have something like this.

task do-stuff
# stuff happens

That's it. The thing lives here: taskmachine.

Why?

make and a million other tools do this. Then why? I was bored. Also, a Makefile gets kinda weird as soon as you start doing some bash-kung-fu inside of it. If I'm going to do some kung-fu fighting might as well do it in javascript, at least I could understand it a little bit better and without google help.

Show me the money

I mentioned that this tool wasn't created to be in a javascript project. This is actually better as lonely script in an alias.

alias task='node /path/to/taskmachine.js ./make.js'

Now you can create a make.js file.

// make.js
const log = (name) => (...args) => console.log(name, args[0]);

module.exports = function(tasks) {
  tasks
    .add('test', 'Run some tests',log('test'))
    .add('cover', log('cover'));
}

You call one of the tasks.

task test

And you should have.

[15:32:33] Starting 'test' ...
test { _: [] }
[15:32:33] Finished 'test' (10ms)

If you want to add some arguments, you're more than welcome.

task test --foo 1234 hi --foo 5678 there -- --foo 2468
[15:33:56] Starting 'test' ...
test { _: [ 'hi', 'there', '--foo', '2468' ], foo: '5678' }
[15:33:56] Finished 'test' (9ms)

See that foo outside the array? Is a property of args[0]. You get the flags from the cli as a plain object. The _ gets the positional arguments and the -- stops the argument parsing, so everything after that is included as is.

If you omit the task name it will try to run a task named default. If it can't find the default task then it shows a list of available tasks.

Available tasks:
* test: Run some tests
* cover

If you do have a default task but you still want that list, use the addlist method.

const log = (name) => (...args) => console.log(name, args[0]);

 module.exports = function(tasks) {
   tasks
     .add('test', 'Run some tests',log('test')) 
     .add('cover', log('cover'))
+    .add('default', log('default'))
+    .addlist('list');
 }

Calling task list gets you.

[22:30:58] Starting 'list' ...

Available tasks:
* test: Run some tests
* cover
* default
* list: List available tasks

[22:30:58] Finished 'list' (5ms)

If you don't want the timestamps use the --quiet/-q flag.

One cool trick this thing can do is creating subtasks. Yes, tasks within tasks.

// make.js

function childA1() { console.log('hi from a1'); }
function childA2() { console.log('hi from a2'); }

function parentA(cli, { tasks }) {
  // Subtasks
  return tasks()
    .add('1', childA1)
    .add('2', childA2);
}

function childB1() { console.log('hi from b1'); }
function childB2() { console.log('hi from b2'); }

function parentB(cli, { tasks }) {
    // Subtasks
    return tasks()
        .add('1', childB1)
        .add('2', childB2);
}

module.exports = function(tasks) {
  tasks
    .add('a', parentA)
    .add('b', parentB);
}

Call task b 1 --quiet.

hi from b1

At some point you would want to call external commands, so I added execa.command. You can access it from the second argument of the function as sh.

async function build(args, { sh }) {
  await sh('serious-command --important --not-really');
}

module.exports = function(tasks) {
  tasks
    .add('build', build)
}

sh doesn't use a shell so don't do any kung-fu in it. It's just there to call other commands.

Should people use this?

No. This is a toy project. Don't do anything serious with this. If you really want something like this, use this one: just.

Discussion

pic
Editor guide