Skip to content

apaleslimghost/tache

Repository files navigation

tâche

javascript task runner & build tool

what & why

tâche is a tool for running functions in a javascript module from the command line. it's also a collection of libraries to let you write build scripts as async functions. it's meant as a modern, javascript-based alternative to tools like make.

how

basic task

here's how to write a module to use with tâche:

hello-world.js

exports.helloWorld = function() {
	console.log('hello world')
}

and to run it from the command line, using npx (so you don't have to have tâche installed globally to run tasks):

npx tache hello-world.js helloWorld

which outputs:

 start   │ helloWorld({})
hello world
 done    ✓ helloWorld

shebang

in a UNIX-like shell, tâche can be run using a shebang at the start of your file and marking your file as executable:

#!/usr/bin/env npx tache

exports.helloWorld = function() {
	console.log('hello world')
}
chmod +x hello-world.js

which can then be run using:

./hello-world.js helloWorld

logging

tâche includes command-line logging to make it easier to debug which tasks are running, and what they're doing. you can also import this logger to output your own logs in the same style:

const { log } = require('tache')

exports.helloWorld = function() {
	log.log('hello world')
}

the task now outputs:

 start   │ helloWorld({})
         │ hello world
 done    ✓ helloWorld

the logger includes a few different styles. see @tache/logger for what's available

command line argument parsing

each whitespace-separated argument passed to tâche on the command line is a separate task. the part before the first colon in the argument (or the whole argument if there's no colon) is the name of the task to run, then the rest of the argument after the first colon is parsed as HJSON (a format similar to JSON but with a more lightweight syntax; in practice, this means you can omit most {} curly braces and "" double quotes), and passed as the single argument to the task function. if the task argument needs whitespace, surround the whole thing with double quotes, which are parsed by your shell as a single argument.

adding an argument to the helloWorld task, with a default value, to show this (note that the task logging outputs the arguments the task was called with, to help debugging):

const { log } = require('tache')

exports.helloWorld = function({ message = 'world' }) {
	log.log(`hello ${message}`)
}
⟩ ./tachefile.js helloWorld

 start   │ helloWorld({})
         │ hello world
 done    ✓ helloWorld

⟩ ./tachefile.js helloWorld:message:folks

 start   │ helloWorld({ message: 'folks' })
         │ hello folks
 done    ✓ helloWorld

⟩ ./tachefile.js "helloWorld:message:'stuff and things'" helloWorld:message:again

 start   │ helloWorld({ message: 'stuff and things' })
         │ hello stuff and things
 done    ✓ helloWorld

 start   │ helloWorld({ message: 'again' })
         │ hello again
 done    ✓ helloWorld

async tasks

if your task function returns a Promise, e.g. if the function is an async function, tâche will wait for the task to complete before running the next task, and log timing if it took more than 20 milliseconds:

const { log } = require('tache')
const delay = require('delay') // https://www.npmjs.com/package/delay

exports.helloWorld = async function() {
	log.log('one')
	await delay(500)
	log.log('two')
}

running this task twice from the command line will give:

 start   │ helloWorld({})
         │ one
         │ two
 done    ✓ helloWorld (506ms)

 start   │ helloWorld({})
         │ one
         │ two
 done    ✓ helloWorld (504ms)

error handling

when a task throws an error, or returns a promise that rejects, tâche will output the stack trace, and the name of the task that originally threw the error. it will also exit to the command line with a non-zero exit code to indicate failure.

exports.helloWorld = function() {
	throw new Error('something went wrong')
}
 start   │ helloWorld({})
 failed  ✕ helloWorld
         │ Error (from task helloWorld): something went wrong
         │     at exports.helloWorld (tache/packages/example/tachefile.js:15:8)
         │     at Object.helloWorld (tache/packages/cli/wrap.js:13:24)
         │     at <anonymous>
         │     at process._tickCallback (internal/process/next_tick.js:188:7)
         │     at Function.Module.runMain (module.js:695:11)
         │     at findNodeScript.then.existing (/usr/local/lib/node_modules/npm/node_modules/libnpx/index.js:268:14)
         │     at <anonymous>

task dependencies

tâche doesn't have any kind of abstraction or special handling for task dependencies. because tasks are functions, to run a dependency, call a function, using await if the dependency is async. to run dependencies in parallel, use Promise.all. all functions that are exported are treated as tasks and wrapped with logging and error handling.

const { log } = require('tache')

exports.upperCase = string => string.toUpperCase()

exports.helloWorld = function() {
	const upper = exports.upperCase('hello world')
	log.log(upper)
}
 start   │ helloWorld({})
 start   │ upperCase('hello world')
         │ HELLO WORLD
 done    ✓ upperCase
 done    ✓ helloWorld

bootstrapping & dependencies

some tasks may require dependencies, but also need to be able to run without the dependencies installed, such as if you have a task to actually do the install. tâche provides a load function, which uses npx to install the package. when using load, the package must either export a function, or export an object containing functions. the package isn't installed until a function from it is called, allowing independent tasks to install only the dependencies they need. calling a function from load always returns a promise, even if the package's functions are synchronous, because the installation is asynchronous.

const { load, log } = require('tache')
const upperCase = load('upper-case')

exports.helloWorld = async function() {
	const upper = await upperCase('hello world')
	log.log(upper)
}
 start   │ helloWorld({})
         │ HELLO WORLD
 done    ✓ helloWorld

loading libraries of tasks

loading tasks from npm using load works, but doesn't include the same wrapping for logging and error handling. for tasks, you should use load.tasks instead (since all functions can be run as tasks, here we can wrap upper-case as a task):

const { load, log } = require('tache')
const upperCase = load.tasks('upper-case')

exports.helloWorld = async function() {
	const upper = await upperCase('hello world')
	log.log(upper)
}
 start   │ helloWorld({})
 start   │ upperCase('hello world')
         │ HELLO WORLD
 done    ✓ upperCase
 done    ✓ helloWorld

About

async function task runner

Resources

Stars

Watchers

Forks

Packages

No packages published