Build steps as a task list
Run a list of async steps under a task list that animates on a terminal, prints each step once as it settles in CI, and stops at the first failure.
A build script runs steps in order and should say which one failed. On a terminal the list is drawn with the running step animated; in CI each step is one line, written when it finishes.
import { setTimeout as sleep } from 'node:timers/promises';
import { hoist } from 'flagstaff/loop';
import { tasks } from 'flagstaff/tasks';
import { json, rt } from './rt.mjs';
const steps = [
{ title: 'install', run: () => sleep(100) },
{ title: 'build', run: () => sleep(100) },
{ title: 'test', run: () => Promise.reject(new Error('2 failing')) },
{ title: 'publish', run: () => sleep(100) },
];
const list = steps.map(({ title }) => ({ title, status: 'pending' }));
const flag = hoist(tasks(), rt, { tasks: list }, { json });
for (const [i, step] of steps.entries()) {
list[i] = { title: step.title, status: 'running' };
flag.update({ tasks: [...list] });
try {
await step.run();
list[i] = { title: step.title, status: 'ok' };
} catch (error) {
list[i] = { title: step.title, status: 'fail', detail: error.message };
process.exitCode = 1;
break;
}
}
flag.lower({ tasks: [...list] });✔ install
✔ build
✖ testpublish never ran and never settled, so nothing is printed for it. Pass a new array to each
update() rather than mutating the one you hoisted with: the loop compares text, and a list you
changed in place is the list it already printed.
The script sets exit code 1, as a failing build should. Under --json an agent gets every transition, pending and running
included, with the error message in detail.
Coming from cli-table3
A cli-table3 alternative with a drop-in path: import Table from flagstaff/cli-table3, graded 29 / 29 by cli-table3's own test suite — then a table component whose static projection gives a pipe and an agent one line of header: value pairs per row.
Progress in logs
Keep a progress bar readable in a CI log by updating it in steps — one line per tenth of the work instead of one per item.