taskflare
v1.14.0
Published
A light and fast task runner for Node.js projects
Maintainers
Readme
taskflare
A small TypeScript-friendly task runner for Node.js projects.
Taskflare gives you a simple taskflare <task> command for project automation: build steps, release checks, local scripts, CI tasks, or anything else you would normally hide behind long package scripts. It is intentionally lightweight and familiar if you have used tools like Grunt.
Install
npm install --save-dev taskflarepnpm add -D taskflareyarn add -D taskflareQuick Start
Create a taskflare.ts file in your project root:
import { registerTask } from 'taskflare'
registerTask('build', 'Build the project', function () {
console.log('Building...')
})
registerTask('test', 'Run tests', async function () {
console.log('Testing...')
})
registerTask('release', 'Run all release checks', ['test', 'build'])
registerTask('default', ['release'])Run tasks:
taskflare build
taskflare release
taskflareRunning Shell Commands
Taskflare does not force a shell-command library on you. If you want a modern promise-based API, execa works well:
pnpm add -D execaimport { execa } from 'execa'
import { registerTask } from 'taskflare'
registerTask('typecheck', 'Check TypeScript types', async function () {
const done = this.async()
const result = await execa('tsc', ['--noEmit'], { stdio: 'inherit', reject: false })
return done(result.exitCode === 0)
})Task Files
By default, Taskflare looks for:
taskflare.js
taskflare.tsUse --file when your task file has another name:
taskflare --file tasks.ts build
taskflare -f tasks.ts build
taskflare --config tasks.ts buildTask Types
Synchronous
registerTask('clean', 'Clean generated files', function () {
// Throw an error to fail the task.
})Promise-Based
registerTask('build', 'Build the project', async function () {
await buildProject()
})Callback-Based
Use this.async() when you want Grunt-style completion control:
registerTask('publish', 'Publish the package', async function () {
const done = this.async()
try {
await publishPackage()
return done(true)
} catch {
return done(false)
}
})Taskflare warns when a task keeps running for more than 30 seconds. This is especially useful when a callback task calls this.async() but never calls done(). The warning does not fail the task.
import { setTaskflareOptions } from 'taskflare'
setTaskflareOptions({
hangWarningMs: 60_000,
})Set hangWarningMs to 0 to disable the warning.
Use timeoutMs on a task when it should fail after a fixed time:
registerTask(
'integration-test',
{
timeoutMs: 120_000,
},
async function () {
await runIntegrationTests()
},
)Set timeoutMs to 0 or leave it unset when the task should not have a timeout.
Composite
Composite tasks run other tasks in sequence and stop on the first failure:
registerTask('ci', 'Run CI checks', ['typecheck', 'test', 'build'])Taskflare detects circular composite tasks and fails them cleanly instead of recursing forever.
Use a nested array when independent tasks can run at the same time:
import { registerTask } from 'taskflare'
registerTask('ci', 'Run CI checks', ['typecheck', ['lint', 'test'], 'build'])In this example, typecheck runs first, lint and test run together, and build runs after both parallel tasks finish.
Set continueOnFailure when you want composite tasks to attempt every task even after one fails. The composite task still returns false when any task failed.
import { setTaskflareOptions } from 'taskflare'
setTaskflareOptions({
continueOnFailure: true,
})Taskflare prints a duration summary after each run:
[TASK] Duration summary:
Total 2.59s
3 completed, 0 failed, 0 skipped, 0 missing, 0 circular
OK lint 642ms
OK test 1.42s slowest
OK build 530msDisable it with:
setTaskflareOptions({
showSummary: false,
})Lifecycle Hooks
Use lifecycle hooks when you want custom logging, metrics, timing, cleanup, or CI annotations around every task attempt.
import { setTaskflareOptions } from 'taskflare'
setTaskflareOptions({
beforeTask({ task }) {
console.log(`starting ${task}`)
},
afterTask({ durationMs, status, task }) {
console.log(`${task} finished with ${status} in ${durationMs}ms`)
},
})beforeTask runs before normal tasks, composite tasks, skipped tasks, missing tasks, circular checks, and parallel groups. If beforeTask throws, the task is marked as failed and the task body does not run.
afterTask runs with task, kind, status, and durationMs after each task attempt. Errors thrown from afterTask are logged and do not change the task result.
CLI
taskflare [options] [task]Options:
-f, --file <path> Load tasks from a specific file
--config <path> Alias for --file
--dry-run Show the task plan without running it
--json Print JSON output with --list or --dry-run
-l, --list List registered tasks
-v, --version Print the current version
-h, --help Show helpExamples:
taskflare --list
taskflare --list --json
taskflare --dry-run release
taskflare --dry-run --json release
taskflare --version
taskflare --file tasks.ts releaseAPI
Most task files only need registerTask:
import { registerTask } from 'taskflare'Taskflare ships both ESM and CommonJS builds:
import { registerTask } from 'taskflare'const { registerTask } = require('taskflare')registerTask(name, task)
registerTask('build', async function () {})registerTask(name, description, task)
registerTask('build', 'Build the project', async function () {})registerTask(name, options, task)
Use task options when you need a description, conditional execution, or a task timeout:
registerTask(
'open-browser',
{
description: 'Open the local app in a browser',
enabled: !process.env.CI,
timeoutMs: 10_000,
},
async function () {
await openBrowser()
},
)enabled can be a boolean or a function. When it resolves to false, the task is skipped and counted as successful.
timeoutMs fails the task if it does not finish in time. This works for promise tasks and callback tasks using this.async().
Programmatic API
These exports are useful when you want to embed Taskflare, write tests around tasks, or build tooling on top of it:
import {
clearTasks,
getTaskPlan,
listTaskDetails,
listTasks,
runTask,
setTaskflareOptions,
type TaskflareTask,
type TaskflareTaskDefinition,
type TaskflareEnabled,
} from 'taskflare'runTask(name)
Runs a task programmatically and resolves to true or false.
const success = await runTask('build')listTasks()
Returns registered task names.
listTaskDetails()
Returns task names and optional descriptions. This is what powers taskflare --list.
getTaskPlan(name)
Returns the task execution plan without running the task. This is what powers taskflare --dry-run.
const plan = getTaskPlan('release')setTaskflareOptions(options)
Configures runner behavior.
setTaskflareOptions({
beforeTask({ task }) {
console.log(`starting ${task}`)
},
afterTask({ status, task }) {
console.log(`${task}: ${status}`)
},
continueOnFailure: true,
hangWarningMs: 60_000,
showSummary: true,
})Releases
This repository is configured for semantic-release:
- pushes to
devcreate GitHub prereleases such asv1.2.3-rc.1 - pushes or merges to
prodcreate stable GitHub releases and publish to npm
Requirements
Taskflare requires Node.js 18 or newer and has no runtime dependencies.
TypeScript configuration files (taskflare.ts) run natively on Node.js versions with type stripping (>= 22.6 with --experimental-strip-types, >= 23.6 by default). On older Node.js versions, install the optional tsx peer dependency:
npm install --save-dev tsxJavaScript configuration files (taskflare.js) work everywhere without extra setup.
License
MIT
