Skip to content

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

fieldtypenotes
descstringtask list one-liner
groupstringtask list heading
depsreadonly DepEntry[]names, { task, if?, optional? } objects, optional(), from() — names compile-time checked
env task() onlyStandardSchemaV1zod / valibot / arktype schema, validated pre-run
args task() onlyRecord<string, ArgSpec>typed CLI flags
inputsreadonly string[]cache fingerprint globs (supports ! negation)
outputsreadonly string[]must exist for a cache hit
watchreadonly string[]watch-mode triggers (defaults to inputs)
persistentbooleanservice semantics; incompatible with outputs, retry, timeoutMs
ready{ log | url | port, host?, timeoutMs? }readiness probe; persistent only, exactly one probe kind
retrynumber | { attempts, delayMs?, backoff? }opt-in retries; number = total attempts; backoff: "fixed" | "exponential"
timeoutMsnumberper-attempt budget; cooperative abort → grace → kill
cwdstringworking dir, relative to do.ts
run(ctx) => Rthe 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.

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

ts
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 to T | undefined.

Released under the MIT License.