Task API
Everything do.ts can export, in one place.
defineTasks(map)
The single default export. Keys are task names; values are plain task objects or task() results. Dep names are validated across entries at compile time.
task(def)
The builder that unlocks typed env and args — the context type is inferred from what you declare. Plain objects and task() entries are otherwise identical.
optional(name)
A dep entry for a task that may not exist in this do.ts — silently dropped when absent, a normal hard edge when present. See conditional and optional deps.
from(name, taskValue)
A typed dep entry: carries the producer's run() return type into the consumer's ctx.deps[name]. See task outputs as data.
Task definition fields
| field | type | notes |
|---|---|---|
desc | string | task list one-liner |
group | string | task list heading |
deps | readonly DepEntry[] | names, { task, if?, optional? } objects, optional(), from() — names compile-time checked |
env task() only | StandardSchemaV1 | zod / valibot / arktype schema, validated pre-run |
args task() only | Record<string, ArgSpec> | typed CLI flags |
inputs | readonly string[] | cache fingerprint globs (supports ! negation) |
outputs | readonly string[] | must exist for a cache hit |
watch | readonly string[] | watch-mode triggers (defaults to inputs) |
persistent | boolean | service semantics; incompatible with outputs, retry, timeoutMs |
ready | { log | url | port, host?, timeoutMs? } | readiness probe; persistent only, exactly one probe kind |
retry | number | { attempts, delayMs?, backoff? } | opt-in retries; number = total attempts; backoff: "fixed" | "exponential" |
timeoutMs | number | per-attempt budget; cooperative abort → grace → kill |
cwd | string | working dir, relative to do.ts |
run | (ctx) => R | the body; omit for aggregation tasks. The awaited return value is the task's JSON data output |
Validation at load (exit 3): ready requires persistent; exactly one of log/url/port; host only with port; retry.attempts >= 1; positive timeoutMs; persistent tasks reject outputs/retry/timeoutMs.
The context
Hover each field: $Bun Shell ($)Pre-bound to the task's cwd and resolved env. Interpolations are auto-escaped — injection-safe by default. · envschema output | Record<string, string | undefined>The validated output of your env schema (coercions applied), or the raw resolved env map when no schema is declared. · argsinferred from ArgSpec recordParsed per-task CLI flags with defaults applied. required/default narrow away undefined. · depsinferred from dep entriesData outputs of this task's dependencies — their run() return values. from() entries are fully typed; plain string entries are unknown. Replayed from the cache on hits. · reststring[]Raw arguments after --. Delivered only to tasks named on the command line, never to dependencies. · signalAbortSignalFires on Ctrl-C, fail-fast aborts, timeouts, and watch supersession. Respect it in long loops — ignoring it means a tree-kill after 5s. · logTaskLoggerinfo/warn/error/debug — prefixed with the task name, safe under concurrent multiplexed output. · cwdstringAbsolute working directory of this task. · namestringThe task's name as defined in do.ts.
run: async ({ $, env, args, rest, signal, log, cwd, name }) => {
log.info("starting");
const out = await $`git rev-parse --short HEAD`.text();
if (signal.aborted) {
return;
}
await $`echo building ${out.trim()}`;
},Type-level behavior worth knowing
import { defineTasks, task } from "@meslzy/outdo";
import { z } from "zod";
export default defineTasks({
build: {},
broken: {
deps: ["biuld"], // compile error: Did you mean "build"?
},
typed: task({
env: z.object({ PORT: z.coerce.number() }),
args: { fast: { type: "boolean" } },
run: ({ env, args }) => {
env.PORT; // number — schema output, not string
args.fast; // boolean — never undefined
// @ts-expect-error unknown env keys are compile errors too
env.MISSING;
},
}),
// env/args on a PLAIN object is rejected — the builder powers the inference
// invalid: { env: z.object({}) }, ← type error
});- Everything in outdo's public surface is exported from
"outdo":defineTasks,task,optional,from, and the types (TaskCtx,ArgSpec,DepEntry,ReadyProbe,RetrySpec,StandardSchemaV1, …). - Task names can be any string —
"db:reset","deploy"— quotes required only when not a valid identifier. from()producers propagate return-type changes to every consumer automatically; conditional/optional entries widen the consumer's view toT | undefined.