zuke
zuke-build/zuke/llms-full.txt
The complete typed public surface of every package. Each section is one package: the tasks you call and the fluent settings each accepts. Use these signatures verbatim — there is a typed wrapper for every tool, so there is no need to fall back to raw shell. Regenerate with ./zuke apiDocs. ======================================================================== # @zuke/core ======================================================================== Zuke — a code-first, strongly-typed build automation system for Deno. Public API. Define a build by extending {@link Build}, declare targets with the {@link target}…
- Reads credentials
- Installs packages
# Zuke — full API reference
The complete typed public surface of every package. Each section is one
package: the tasks you call and the fluent settings each accepts. Use these
signatures verbatim — there is a typed wrapper for every tool, so there is
no need to fall back to raw shell.
Regenerate with `./zuke apiDocs`.
========================================================================
# @zuke/core
========================================================================
Zuke — a code-first, strongly-typed build automation system for Deno.
Public API. Define a build by extending {@link Build}, declare targets with
the {@link target} fluent builder, and make the file runnable with
{@link run}:
```ts
import { Build, target, run } from "@zuke/core";
import { $ } from "@zuke/core/shell";
class MyBuild extends Build {
test = target()
.description("Run the test suite")
.executes(async () => { await $`deno test -A`; });
}
await run(MyBuild);
```
The shell helper `$` lives in the `./shell` submodule
(`jsr:@zuke/core/shell`).
@module
function absolutePath(first: string, ...rest: string[]): AbsolutePath
Build an {@link AbsolutePath} from one or more segments. The first segment
(after joining) must be absolute — start with `/` or a drive letter, or build
from an absolute base — otherwise an error is thrown.
```ts
const root = absolutePath("/app");
root("src", "main.ts").path; // "/app/src/main.ts"
root.join("..", "shared").path; // "/shared"
absolutePath("C:\\repo", "x").path; // "C:/repo/x"
```
@param first
The first path segment; must make the result absolute.
@param rest
Additional segments to append.
async function acquireLease(store: StateStore, prefix: string, runId: string, actor: string, now: () => string, ttlMs: number, runUrl?: string): Promise<HeldLease | null>
Take a lease named `prefix` over `runId`, or `null` if a live holder has it.
While held, the lease renews on a background heartbeat until
{@link HeldLease.release}. That timer never keeps the process alive.
Only an explicit refusal counts as loss. A store reports `false` from a
renewal when the claim is demonstrably somebody else's; it throws for a
filesystem mutex it could not take in time, or an HTTP 503, or a DNS blip —
none of which say anything about who holds the lease. Treating those as loss
would abort a healthy build because the state service had a bad second, so
they are swallowed and the next tick tries again. A store that stays
unreachable lets the claim lapse at its TTL, which is the documented backstop
and the honest outcome.
function affectedTargets(order: readonly TargetBuilder[], changed: readonly string[]): Set<TargetBuilder>
Compute the set of targets in `order` affected by the given `changed` files.
`order` must be a valid execution order (dependencies before dependents, as
produced by {@link plan}/{@link planGraph}) so each target's dependencies are
already decided when it is visited. A target is affected when its own inputs
cover a changed file, when it declares no inputs (unprovable — treated as
affected), when a dependency is affected, or when an affected target triggers
it.
function appendJobSummary(markdown: string): boolean
Append `markdown` to the Actions job summary, returning whether it was
written. Outside Actions (no `GITHUB_STEP_SUMMARY`) it is a no-op returning
`false`, so the same code path works locally.
Best-effort by design: an unwritable summary file reports `false` rather than
throwing. A report that could not be displayed must never fail the build
that produced it — the build's own result is the signal that matters.
async function archiveOutputs(outputs: readonly string[], host: OutputHost): Promise<Uint8Array>
Archive a target's `outputs` into a gzipped tar of their current contents.
A declared output that does not exist is skipped, as is anything under a
`.git` or `.zuke` directory.
function assert(condition: unknown, message: string): asserts condition
Assert that `condition` is truthy, narrowing it for the rest of the scope.
Throws an {@link AssertionError} with `message` otherwise.
async function assertDirectoryExists(path: PathLike): Promise<void>
Assert that `path` exists and is a directory. Async (stats the filesystem).
function assertExists<T>(value: T, message: string): NonNullable<T>
Assert that `value` is neither `null` nor `undefined`, returning it narrowed
to its non-nullable type so it can be used inline.
```ts
const token = assertExists(Deno.env.get("TOKEN"), "TOKEN is required");
```
async function assertFileExists(path: PathLike): Promise<void>
Assert that `path` exists and is a file. Async (stats the filesystem).
function assertSafeEntryName(name: string): void
Reject an archive entry whose name would escape the destination directory — an
absolute path or one with a `..` segment (a "zip slip"). A downloaded or
poisoned archive must never place files outside where it is being unpacked.
function assertSafeLinkTarget(entryName: string, target: string): void
Reject a symlink whose target would resolve outside the destination directory
— an absolute target, or a relative one that climbs (with `..`) above the
extraction root once resolved against the link's own directory. A file entry's
name is bounded by {@link assertSafeEntryName}; a symlink adds a second escape
vector (its target), so a poisoned tarball can't plant `bin/x -> ../../etc`.
function bearerChallenge(parts: BearerChallenge): string
A `WWW-Authenticate: Bearer` challenge built from `parts`.
Returns a bare `Bearer` when nothing usable is supplied, and drops any single
part that survives filtering as empty, so a caller cannot produce a malformed
header by passing an odd string.
async function cancelRun(build: Build, options: CancelOptions): Promise<CancelResult>
Cancel the run `options.runId` for `build`: transition it to `cancelling`
(exactly one canceller drives the walk; a live owning process observes the
change and aborts), run the compensations of every succeeded target in reverse
order, and settle the record as `cancelled`. Idempotent — cancelling an
already-terminal run is a friendly no-op.
@throws
if no state store is configured, or the run does not exist.
function ciHost(env: (name: string) => string | undefined): string
A short identifier for the detected CI host, or `"local"` when not on CI.
Recognises GitHub Actions, GitLab CI, Azure Pipelines, Bitbucket Pipelines,
and — as the generic `"ci"` — the common markers other systems set, including
Jenkins, Buildkite, CircleCI, Travis and TeamCity.
Prefer {@link detectCiHost} for new code: its values match {@link CiProvider}.
This function is kept for compatibility and uses longer, host-specific names.
@param env
The environment reader, injectable so detection can be unit-tested
hermetically; defaults to the process environment. {@link detectCiHost} has
taken one since it was written, and this takes the same one so a caller that
wants "am I on CI?" does not have to reach for the host-specific function to
get a testable answer.
function cicd(spec: CiFileSpec): CiFile
Declare a CI file as a build field. Running the build regenerates it (and the
`generate-ci` command writes it on demand), so the committed configuration is
generated from code rather than hand-maintained.
The provider is the only required field: `cicd({ provider: "github" })`
declares a workflow at `.github/workflows/ci.yml` that runs the build on
push/PR to `main`. Override only what else you need.
```ts
class MyBuild extends Build {
ci = cicd({ provider: "github" }); // sensible default workflow
// …or customise:
gitlab = cicd({ provider: "gitlab", pipeline: { jobs: [{ steps: [...] }] } });
}
```
async function createTarGzip(files: PathLike[], dest: PathLike, options: { cwd?: string; }): Promise<void>
Read `files` (relative to `cwd`), pack them into a tar archive named by their
path relative to `cwd`, gzip it, and write the result to `dest`.
function defaultMcpAuthorize(identity: McpIdentity, call: McpCall): McpAuthorization
The shipped default policy.
| Call | Needs |
| --- | --- |
| `list_*` / `show_*` / `describe_*` | `read` |
| `run:<target>` | `run`, and the target's `requiresRole` when it declares one |
| a run-scoped mutation | the run's initiator, or `operator` |
| a sweep over every run | `operator` |
A run whose initiator is a service may be steered by any caller holding
`run`: a scheduler's run has no person to ask, and treating the scheduler as
its owner would mean nobody could intervene. Documented, and overridable by
`Build.mcpAuthorize`.
function defaultReadEnv(name: string): string | undefined
Read an environment variable, tolerating a denied `--allow-env` permission by
returning `undefined` rather than throwing. The default env reader every
command uses when the caller doesn't inject one.
function denoExecutable(standalone: boolean): string
The executable that runs Deno here — what to spawn when a build needs `deno`
itself, rather than a tool it installed.
Under `deno run` the executable running this code is Deno, so
`Deno.execPath()` names it and nothing has to be looked up: a project whose
Deno came from the `./zuke` launcher rather than from `PATH` still works, and
the subprocess is the same version as the parent.
A build compiled with `deno compile` is not Deno. There `Deno.execPath()` is
the compiled binary, so spawning it re-enters the build instead of running
Deno — `deno test` becomes the build running itself with `test` as a target,
and `deno doc <spec>` becomes the build being asked for a target named `doc`.
The compiled case therefore resolves the bare name and lets the OS find a
real Deno on `PATH`. `Deno.build.standalone` is what tells the two apart.
```ts
import { denoExecutable } from "@zuke/core";
const deno = new Deno.Command(denoExecutable(), { args: ["doc", "jsr:@zuke/core"] });
```
`@zuke/cli` answers the same question with one more step — a compiled global
`zuke` falls back to the launchers' bootstrap directory when `PATH` has no
Deno — because it is the command a user installs on a machine that may have
no Deno at all. That fallback is the CLI's, and this is the decision the two
share.
@param standalone
Whether this process is a `deno compile` binary rather than
Deno itself; defaults to the running host. It is a parameter so both
answers are reachable from an ordinary `deno test` run, which is never
standalone.
function describeCli(build: Build, options: DescribeCliOptions): CliDescription
Describe a build's full CLI surface — reserved commands, option flags, targets
(with descriptions and dependencies), and declared parameters — as a plain
object ready for JSON. This is the same data `zuke --list --json` prints, made
available to tooling and agents that introspect a build in code.
```ts
import { describeCli } from "@zuke/core";
const surface = describeCli(new MyBuild());
console.log(surface.targets.map((t) => t.name));
```
Pass `{ omitSecrets: true }` to drop `.secret()` parameters from the result —
the posture the build registry uses, so a secret never becomes a spawnable
MCP input or crosses the run boundary (`zuke register` writes this form).
function detectCiHost(env: (name: string) => string | undefined): CiHost
Detect the CI host from the environment. Recognises GitHub Actions
(`GITHUB_ACTIONS`), GitLab CI (`GITLAB_CI`), Azure Pipelines (`TF_BUILD`), and
Bitbucket Pipelines (`BITBUCKET_BUILD_NUMBER`); anything else is `"local"`.
The reader is injectable so detection can be unit-tested hermetically.
function discoverCiFiles(build: Build): CiFile[]
Find every {@link CiFile} declared on a build instance. A fan-out file is
resolved here — its jobs are expanded from the build's targets — so the
returned files render the same whether they fan out or not.
function discoverGroups(build: Build): Map<string, Group>
Discover all parallel {@link Group} batches declared on a build instance,
binding each its property path (for labelling, e.g. in the graph). Groups
that are not assigned to a build property simply stay unnamed.
function discoverParameters(build: object): Map<string, AnyParameter>
Discover all parameters declared on a build instance: scan its fields
(recursing into plain-object component bundles) for {@link Parameter} values,
bind each its dotted property path, and return a name → parameter map
preserving declaration order.
function discoverTargets(build: Build): Map<string, TargetBuilder>
Discover all targets declared on a build instance.
Scans the instance's fields (recursing into plain-object component bundles)
for {@link TargetBuilder} values, assigns each its dotted property path, and
returns a name → target map preserving declaration order.
@throws
if two properties reference the same builder instance under different
names (a programming error that would corrupt naming).
function envBuildRegistry(readEnv: (name: string) => string | undefined, host: StateHost): BuildRegistry | undefined
Resolve a {@link BuildRegistry} from the environment, or `undefined` when none
is configured. `ZUKE_REGISTRY_URL` (with an optional `ZUKE_REGISTRY_TOKEN`)
selects an {@link HttpBuildRegistry}; otherwise `ZUKE_REGISTRY_DIR` selects a
{@link FileSystemBuildRegistry}.
function envCacheStore(readEnv: (name: string) => string | undefined): RemoteCacheStore | undefined
Resolve a {@link RemoteCacheStore} from the environment, or `undefined` when
none is configured. `ZUKE_REMOTE_CACHE_URL` (with an optional
`ZUKE_REMOTE_CACHE_TOKEN`) selects an {@link HttpCacheStore}; otherwise
`ZUKE_REMOTE_CACHE_DIR` selects a {@link FileSystemCacheStore}.
function envStateStore(readEnv: (name: string) => string | undefined, host: StateHost): StateStore | undefined
Resolve a {@link StateStore} from the environment, or `undefined` when none is
configured. `ZUKE_STATE_URL` (with an optional `ZUKE_STATE_TOKEN`) selects an
{@link HttpStateStore}; otherwise `ZUKE_STATE_DIR` selects a
{@link FileSystemStateStore}.
function envVarName(name: string): string
The environment variable for a parameter: its path in SCREAMING_SNAKE_CASE.
function execSecret(configure: Configure<ExecSecretSettings>): SecretSource
A {@link SecretSource} that runs a command and takes its standard output as
the secret value. Configure it through an {@link ExecSecretSettings} lambda.
```ts
parameter("Vault token").secret().from(
execSecret((s) => s.command("vault").arg("kv", "get", "-field=token", "secret/ci")),
);
```
async function execute(build: Build, root: TargetBuilder, options: ExecuteOptions): Promise<BuildResult>
Execute the requested target and its transitive dependencies.
Runs the build's `onStart`/`onFinish` lifecycle hooks around the plan. By
default targets run sequentially in deterministic order; with `parallel`,
independent targets run concurrently while dependencies still complete first.
Stops launching after the first failure, marks unreached targets as skipped,
and returns a failing result.
function executionSet(root: TargetBuilder): Set<TargetBuilder>
Compute the execution set for a requested target: the target plus the
transitive closure of its hard dependencies.
function externalSignal(name: string): WaitTrigger
A trigger satisfied when a signal named `name` has been delivered to the run
(via `zuke resume <id> --signal <name>`). The signal's payload is exposed to
target bodies through {@link "./target.ts".TargetContext} `signals`.
async function extractTarGzip(src: PathLike, destDir: PathLike, options: ExtractOptions): Promise<void>
Read the `.tar.gz` at `src`, gunzip and unpack it, and write each entry under
`destDir` (creating parent directories as needed). Symlink entries are
recreated as symlinks and directory entries as directories; pass
{@link ExtractOptions.strip} to drop leading path components.
async function extractZip(src: PathLike, destDir: PathLike, options: ExtractOptions): Promise<void>
Read the `.zip` at `src`, unpack it, and write each entry under `destDir`
(creating parent directories as needed) — the zip counterpart of
{@link extractTarGzip}. Entry names are validated so a malicious archive
cannot escape `destDir`.
function fail(message: string): never
Throw an {@link AssertionError} with `message`. Never returns.
function fanOutPipeline(targets: Map<string, TargetBuilder>, base: CiPipeline, options: FanOutOptions): CiPipeline
Expand a build's target graph into a fanned-out pipeline: one CI job per
runnable target, wired together with `needs:` edges that mirror the targets'
`dependsOn` dependencies — so independent targets run in parallel and a
target's job waits for its prerequisites. Each job runs just its own target;
upstream outputs are shared through the {@link "./remote_cache.ts" | remote
cache}, so configure one (e.g. `ZUKE_REMOTE_CACHE_*` on the jobs) to avoid
rebuilding dependencies in every job.
`base` contributes the pipeline-level fields (name, triggers, permissions,
concurrency); its `jobs` are ignored in favour of the generated ones. Targets
with no body, and (unless {@link FanOutOptions.includeUnlisted}) `unlisted`
targets, are omitted, and `needs` edges to omitted targets are dropped.
function fileSecret(configure: Configure<FileSecretSettings>): SecretSource
A {@link SecretSource} that reads a file and takes its content as the secret
value — for a mounted Kubernetes/Docker secret or a CI-provided file.
Configure it through a {@link FileSecretSettings} lambda.
```ts
parameter("Registry password").secret().from(
fileSecret((s) => s.path("/run/secrets/registry_password")),
);
```
function findCycle(targets: Map<string, TargetBuilder>): string[] | null
Detect a cycle in the hard-dependency (`dependsOn`) graph across all targets.
@return
the cycle as a path of names (e.g. `["a", "b", "a"]`) or `null`.
async function forceTarget(build: Build, options: ForceOptions): Promise<ForceResult>
Record an operator's forced outcome for one target of a run.
The write is a compare-and-swap, retried against a re-read record, so two
operators forcing different targets at once cannot lose each other's
decision. Every refusal is re-checked on each attempt, because the thing that
beat us to the record may be the target settling.
function generateCi(pipeline: CiPipeline, provider: CiProvider): string
Render `pipeline` as the YAML configuration for `provider`:
`.github/workflows/*.yml`, `.gitlab-ci.yml`, `azure-pipelines.yml`, or
`bitbucket-pipelines.yml`. The pipeline may be empty (`{}`) to accept every
default.
async function gitChangedFiles(base: string, run: (args: string[]) => Promise<string>): Promise<string[]>
List the files changed since `base` (default `HEAD`) via git: tracked changes
versus `base` plus untracked files not covered by `.gitignore`. `run` invokes
git and returns stdout (defaults to a real `git` subprocess); override it to
test without a repository.
async function glob(pattern: string, options: GlobOptions): Promise<string[]>
Expand a glob pattern to the matching paths, sorted for determinism. The walk
starts at the pattern's static prefix, so anchor patterns (e.g.
`src/**\/*.ts`) to avoid scanning the whole tree. Symlinked directories are
not followed.
A relative pattern is resolved against `cwd` and its matches are returned
relative to it. An absolute pattern (a leading `/`, or a `C:`-style drive)
names its own root: `cwd` plays no part and the matches come back absolute.
function globToRegExp(pattern: string): RegExp
Compile a glob pattern into an anchored {@link RegExp} that matches a full
path. Exposed (and pure) for testing and custom matching.
function group(): Group
Create a parallel {@link Group}. Targets join it with
{@link TargetBuilder.partOf}, and a downstream target can depend on the whole
batch by passing the group to {@link TargetBuilder.dependsOn}.
```ts
checks = group();
lint = target().partOf(this.checks).executes(...);
format = target().partOf(this.checks).executes(...);
deploy = target().dependsOn(this.checks).executes(...);
```
async function gunzip(data: Uint8Array): Promise<Uint8Array>
Gunzip-decompress `data` using the platform `DecompressionStream`.
async function gzip(data: Uint8Array): Promise<Uint8Array>
Gzip-compress `data` using the platform `CompressionStream`.
function hostPlatform(): Platform
The current host's {@link Platform} (from `Deno.build`, with the OS
normalised) — the analogue of {@link "./host.ts".isCI} for "what machine am I
running on". Its `os` is a Zuke {@link OperatingSystem} (`macos`, not
`darwin`); use the `osLabel`/`archLabel` helpers to name it for a download URL.
```ts
const p = hostPlatform();
p.os; // "linux" | "macos" | "windows"
const cpu = p.archLabel({ x86_64: "amd64", aarch64: "arm64" });
```
async function httpDownload(url: string, dest: PathLike, options: HttpOptions): Promise<void>
Download `url` to `dest`, streaming the response body to the file. Creates or
truncates `dest`. Throws {@link HttpError} on a non-2xx status.
async function httpJson<T = unknown>(url: string, options: HttpOptions): Promise<T>
Fetch `url` and parse its body as JSON. Throws {@link HttpError} on non-2xx.
async function httpText(url: string, options: HttpOptions): Promise<string>
Fetch `url` and return its body as text. Throws {@link HttpError} on non-2xx.
function initiatorOf(run: RunRecord | RunSummary): string
Who a run is attributed to for a reader that wants its owner rather than
its last writer: the recorded initiator, else the actor.
The fallback is not a guess — on a record written before the initiator
existed, and on any run that was never resumed, `actor` still holds exactly
the value the initiator would have been stamped with.
async function installNpmTool(spec: NpmToolSpec, options: InstallNpmToolOptions): Promise<AbsolutePath>
Provision an npm-registry package as a version-pinned, cached tool and return
the installed bin's {@link AbsolutePath} — hand it straight to a wrapper's
`.toolPath(...)`.
The package installs under `<destDir>/npm/<name>@<version>` via
`npm install --prefix <dir> --no-save <name>@<version>`; a marker file records
the pinned `{ name, version }`, so a later run whose marker matches and whose
bin is still present is reused without invoking npm again. `npm` must be on
`PATH` (it resolves and downloads the package).
Throws — without recording a marker — if `spec` is malformed (an unsafe name,
version, or bin), if npm fails, or if npm succeeds but the expected bin is
absent (a typo'd `bin`, or a package that ships no executable), so a bad
install fails loudly here instead of at a later `.toolPath(...)`.
The marker is written only after the bin is verified present, so a matching
marker always has its bin — a reader never sees a half-written install.
Concurrent installs of the same pin into the same directory are not isolated;
they just do redundant work (the documented ceiling — a build resolves its
toolchain once, and distinct pins use distinct directories).
async function installRelease(options: InstallReleaseOptions): Promise<AbsolutePath>
Download and install a release binary, returning its {@link AbsolutePath}.
The path is ready to hand to a wrapper's `.toolPath(...)` (or `CmdTasks`).
With a {@link InstallReleaseOptions.checksum}, the download is verified before
anything is installed, and a matching prior install is reused without
downloading again — so pinning a checksum makes the install both hermetic
(tamper-evident) and cached.
async function installTree(options: InstallTreeOptions): Promise<AbsolutePath>
Download and unpack a whole archive tree — a multi-file runtime such as
Node.js, which ships `bin/node`, `bin/npm`, `bin/npx`, and
`lib/node_modules/**` in one tarball — and return the {@link AbsolutePath} of
its (stripped) root. `installRelease` extracts a single binary; `installTree`
keeps the entire directory, symlinks included.
Because {@link AbsolutePath} is callable, the root doubles as an accessor:
`root("bin", "node")` is the node binary and `root("bin")` is the directory to
put on `PATH` (with `prependPath`). Declared {@link InstallTreeOptions.bins}
are marked executable on POSIX. With a {@link InstallTreeOptions.checksum} the
archive is verified before unpacking and a matching prior install is reused.
function isCI(env: (name: string) => string | undefined): boolean
Whether the build appears to be running in a CI environment.
Broader than `detectCiHost(env) !== "local"`, which answers only whether the
host is one of the four Zuke names: a system it has no specific support for is
still CI, and this says so.
@param env
The environment reader; defaults to the process environment.
function listStoreLocks(store: StateStore): Promise<HeldLockEntry[]>
The locks `store` holds, or a friendly failure when the backend cannot
enumerate them.
An empty array reads as "nobody holds anything", which is the worst possible
answer to give someone looking at a wedged resource, so a store with no
{@link StateStore.listLocks} says so rather than answering.
@throws {Error}
If the store does not support listing.
function lockKey(...parts: Array<string | number>): string
Join parts into a lock key that is safe to use as a filename and URL segment.
Each part is sanitised (non-`[A-Za-z0-9._-]` runs become `_`) and empty parts
are dropped, so `lockKey("deploy", repo)` is stable and injection-free.
function logoLines(color: boolean, options: LogoOptions): string[]
The logo as printable lines: the {@link ZUKE_LOGO} art painted two-tone when
`color` is on (letters bright, shadow dimmed), plus the optional tagline.
Pure — no I/O and no environment reads — so callers that manage their own
output (like the `zuke` CLI) can route the lines through any sink.
function metadataDocument(settings: ProtectedResourceSettings): Record<string, unknown>
The metadata document itself, as the JSON object RFC 9728 §2 defines.
Fields with no values are omitted rather than serialised empty — §3.2 makes
that a MUST, and an empty `scopes_supported` would in any case advertise
"this resource accepts no scopes".
function metadataPath(resource: string): string
The path the metadata document is published at: the well-known suffix with
the resource's own path inserted after it.
For a resource that is a bare origin this is the root well-known path, which
is then the conformant location for that identifier. A trailing slash is
dropped before insertion, as RFC 9728 §3.1 requires.
function metadataUrl(resource: string): string
The absolute URL a `WWW-Authenticate` challenge points at: always the
path-inserted location, which is the one a client can validate under both
halves of RFC 9728 §3.3.
function operatingSystem(os: typeof Deno.build.os): OperatingSystem
The operating system as a Zuke {@link OperatingSystem}: `darwin` becomes
`macos`, `windows` stays `windows`, and every other Unix (`linux`, the BSDs,
`solaris`, …) is reported as `linux`. Pass a raw `Deno.build.os` value to
normalise it; defaults to the running host — the platform analogue of
{@link isCI}.
```ts
import { operatingSystem } from "@zuke/core";
if (operatingSystem() === "macos") { ... }
```
function ownsRun(record: RunRecord, buildId: string | undefined): boolean
Whether a process whose origin is `buildId` may recover `record`.
True unless both origins are known and differ — see the module documentation
for why an absent origin abstains rather than refusing.
function parameter(description?: string): Parameter<string, string | undefined>
Create a new build parameter (a `string` by default). Configure it fluently:
`.number()`/`.boolean()` change the kind, `.options(...)` restricts a string,
`.default(v)`/`.required()` set optionality, and `.env(name)` overrides the
environment variable.
function parseDuration(value: string | number): number
Parse a duration to milliseconds. Accepts a number (already milliseconds) or a
string of a non-negative amount and a unit — `ms`, `s`, `m`, `h`, or `d`
(e.g. `"90s"`, `"4h"`, `"1.5h"`). Throws a friendly error on anything else.
function plan(root: TargetBuilder, extra: readonly OrderingEdge[]): TargetBuilder[]
Topologically sort the execution set for `root`, honouring hard dependencies
and the soft `before`/`after` ordering hints (the latter only between nodes
that are both in the set).
@return
target builders in a valid execution order.
@throws {GraphError}
if the planned graph contains a cycle (which can happen
via soft edges even when the hard graph is acyclic).
function prependPath(dir: PathLike, os: typeof Deno.build.os): string
Prepend `dir` to the process `PATH`, and return the new value. A tool
provisioned into `dir` (e.g. the `bin` directory of an {@link "./install.ts".installTree}
runtime) then resolves for the rest of the build: the shell `$`, `Command`,
and every tool wrapper spawn subprocesses that inherit `Deno.env`, so the
`node_modules/.bin` shims and `NpmTasks` that assume a `node`/`npm` on `PATH`
find the provisioned one.
Idempotent — a directory already on `PATH` is left in place, not duplicated —
and uses the platform separator (`;` on Windows, `:` elsewhere).
@param dir
the directory to place first on `PATH`.
@param os
the OS whose `PATH` separator to use; defaults to the host (a test
seam, mirroring {@link operatingSystem}).
@return
the resulting `PATH` string.
function protectedResource(resource: string): ProtectedResourceSettings
Begin a protected-resource declaration for `resource`, the canonical URI of
this MCP endpoint (`https://build.example.com/mcp`).
```ts
import { Build, protectedResource } from "@zuke/core";
class CI extends Build {
override mcpProtectedResource() {
return protectedResource("https://build.example.com/mcp")
.authorizationServer("https://acme.eu.auth0.com")
.scopes("zuke:run")
.name("Acme build server");
}
}
```
function remoteCacheKey(name: string, fingerprint: string): string
The store key for a target's outputs: its name and input `fingerprint`. The
name is sanitised so the key is safe as a filename and a URL path segment.
function repoRoot(...segments: string[]): AbsolutePath
The absolute path of the repository root — the directory containing
{@link CONFIG_FILE} — with any `segments` appended. The returned value is an
{@link AbsolutePath}, so it is itself callable for further joining.
```ts
repoRoot(); // <root>
repoRoot("src", "main.ts"); // <root>/src/main.ts
repoRoot().join("dist"); // <root>/dist
```
The root is located by walking up from the current working directory, so the
path is resolved at runtime and never hard-coded into a committed file.
@throws
if no {@link CONFIG_FILE} is found in the cwd or any ancestor.
function reportSummary(pairs: SummaryPairs): void
Report `key: value` notes into the running target's row of the
end-of-build summary — the ambient form of
{@link "./target.ts".TargetContext.reportSummary}, for code that has no
context in hand: a tool wrapper reporting the counts its tool printed, or a
helper called from a body.
```ts
reportSummary({ Tests: 837, Passed: 837, Failed: 0 });
```
Notes accumulate across calls in the same target, and reporting a key again
replaces its value. Outside a running target (a wrapper called from a plain
script, a compensation) there is no row to report into, so the call is a
no-op rather than an error — a wrapper never has to ask where it runs.
function reportTestCounts(counts: TestCounts): void
Report a test run's counts into the running target's row of the
end-of-build summary, in the shape every test-runner wrapper shares:
```text
test Succeeded 8.1s // Tests: 837 · Passed: 835 · Failed: 0 · Skipped: 2
```
`Tests` is the sum of every category; `Passed` and `Failed` always appear,
and `Skipped`, `Todo` and `Flaky` only when non-zero — mirroring the
runners, which print their optional counts the same way. The ambient form
of reporting applies (see {@link reportSummary}): a wrapper calls this from
its `onOutput` hook with what it parsed, and outside a running target the
call is a no-op.
function resolveBuildId(readEnv: (name: string) => string | undefined): string | undefined
The origin of the build running in this process — `ZUKE_BUILD_ID`, else
`GITHUB_REPOSITORY`, else `undefined` when neither is set.
Recorded on a run at creation and compared by every recovery path. An empty
value counts as unset, so an exported-but-empty variable does not become an
origin that matches nothing.
function resolveBuildRegistry(option: BuildRegistry | false | undefined, declared: BuildRegistry | undefined, options: ResolveRegistryOptions): BuildRegistry | undefined
Pick the build registry by precedence: an explicit `option` wins (`false`
disables the registry entirely), then a `declared` registry (a build's
`registry()` override), then the {@link envBuildRegistry} environment
fallback, then — only when {@link ResolveRegistryOptions.enableDefault} — a
filesystem registry under `<root>/.zuke/builds`.
function resolveDocSpec(spec: string, cwd: string): string
Resolve a `zuke doc` argument to the specifier `deno doc` is given.
`cwd` is the caller's working directory, taken as an argument rather than
read here: `deno doc` is run from an isolated empty directory, so a relative
path has to be made absolute while the caller's directory is still known,
and passing it keeps this function pure and testable without one.
Bare names gain the `@zuke` scope (`core` becomes `jsr:@zuke/core`) and a
scoped name gains only the scheme (`@scope/pkg` becomes `jsr:@scope/pkg`).
Anything that looks like a path — leading `.`, an embedded `/`, or a module
file extension — is joined to `cwd` instead, and a specifier that already
carries a URL scheme or is absolute is returned untouched.
A joined path comes back normalised, through {@link absolutePath}: the
`.` and `..` segments are resolved rather than carried into the specifier,
so `deno doc` is handed a canonical path and an error that echoes it names
a path the reader recognises. `cwd` must itself be absolute, which is what
makes that possible; {@link absolutePath} throws if it is not.
function resolveRemoteStore(option: RemoteCacheStore | false | undefined, declared: RemoteCacheStore | undefined, readEnv: (name: string) => string | undefined): RemoteCacheStore | undefined
Pick the remote store for a run by precedence: an explicit `option` wins
(`false` disables the remote cache entirely), then a `declared` store (a
build's `remoteCache()` override), then the {@link envCacheStore} environment
fallback.
function resolveStateStore(option: StateStore | false | undefined, declared: StateStore | undefined, options: ResolveStateOptions): StateStore | undefined
Pick the state store for a run by precedence: an explicit `option` wins
(`false` disables state entirely), then a `declared` store (a build's
`stateStore()` override), then the {@link envStateStore} environment
fallback, then — only when {@link ResolveStateOptions.enableDefault} — a
filesystem store under `<root>/.zuke/runs`. A plain build with no durable
feature and no configuration gets `undefined`, so it carries zero overhead.
async function restoreOutputs(artifact: Uint8Array, host: OutputHost, outputs?: readonly string[], maxBytes: number): Promise<string[]>
Restore the files in `artifact` (a gzipped tar produced by
{@link archiveOutputs}) to disk, returning the paths written.
Every entry is validated before anything is written, so a rejected archive
leaves no half-written, partially-trusted output tree. An entry is refused
when its name is absolute or escapes the workspace with `..`, when it is a
symlink or directory entry (which {@link archiveOutputs} never produces),
when it lands under `.git` or `.zuke`, when — given `outputs` — it falls
outside the target's declared outputs, and when the path it would be written
to passes through, or is, a symlink that already exists on disk.
That last refusal is what makes the confinement real rather than lexical. The
archive cannot plant a link, but a workspace can already hold one at a
declared output — `dist -> /tmp/build`, a checked-out `bazel-bin`, a Windows
junction — and `writeFile` follows it. Such a workspace no longer restores
from the remote cache and rebuilds instead; the link is left alone, because
the layout is the owner's and silently replacing it would be its own
surprise.
@param maxBytes
The most the artifact may decompress to before it is refused,
defaulting to 2 GiB. The bound is applied while decompressing, so a small
archive that expands without limit is refused rather than buffered first.
It is larger than the bound a store puts on the compressed bytes because
outputs compress; both exist to stop memory exhaustion.
@param outputs
The declaring target's {@link TargetBuilder.outputs}. Pass them
whenever they are known, which is what the executor does: an archive built
from those outputs can only contain paths under them, so anything else is a
store that has been written to by something other than a Zuke build, and
restoring it would let that writer choose files anywhere in the workspace —
a `deno.json`, a lockfile, a script a later target runs. Omitting them keeps
the older, name-only confinement for a caller that has no output list.
async function resumeCheck(build: Build, options: Omit<ResumeOptions, "runId" | "signal" | "data"> & { runId?: string; }): Promise<{ checked: number; failed: number; }>
Re-attempt every suspended run in the store (or just `runId`): predicate-based
waits are re-evaluated and expired waits time out. Signal-based waits with no
new signal simply re-suspend. Returns the number of runs that ended in
failure. This is the sweep a cron or webhook drives (`zuke resume --check`).
A run whose record is {@link "./state/types.ts".RunRecord.degraded} is
counted as failed on every sweep until an operator resolves it: it cannot
be advanced without deciding whether its targets are safe to repeat, and a
non-zero result is the only channel a cron watches. Its refusal is reported
through the reporter (the console unless silenced) so the cause is visible,
and it stays `suspended`, so a later sweep with
{@link ResumeOptions.resumeDegraded} still picks it up.
async function resumeRun(build: Build, options: ResumeOptions): Promise<BuildResult>
Resume the suspended run `options.runId` for `build`. Transitions it to
`running` (exactly one resumer wins), optionally delivers a signal, checks the
graph still matches, and continues via {@link "./executor.ts".execute},
re-running only the not-yet-succeeded targets.
@throws {AlreadyResumedError}
if another process already resumed it.
@throws
if the run does not exist, is not suspended, the build lacks its root
target, the graph drifted (unless {@link ResumeOptions.forceGraph}), or the
record is degraded (unless {@link ResumeOptions.resumeDegraded}).
function resumeWhen(check: () => boolean | Promise<boolean>, options: ResumeWhenOptions): WaitTrigger
A trigger satisfied when an async `check` predicate returns `true`. Zuke does
not poll on its own — the predicate is evaluated when the target is reached
and on each `zuke resume <id> --check`, so a cron or webhook nudging `--check`
drives it. Use it to wait on state Zuke can query (a row, a file, an API).
async function run(BuildClass: new () => Build, options: RunOptions): Promise<void>
Public entry point. Instantiate the build, parse arguments, run, and set the
process exit code.
Call it at the bottom of your build file — no `import.meta.main` guard
needed. `run` acts only when its module is the program's entry point; when
the file is imported instead (for example under test) it does nothing.
```ts
await run(MyBuild);
// …with plugins:
await run(MyBuild, { plugins: [timing] });
```
function satisfiesRole(held: readonly string[], required: string): boolean
Whether a caller holding `held` satisfies a requirement for `required`.
A built-in role is satisfied by any built-in at or above it; anything else is
satisfied only by holding that exact name. The two rules cannot be collapsed:
ordering a name the model does not know would mean guessing where an identity
provider's group sits in a hierarchy it never agreed to.
function service(): ServiceBuilder
Create a service target — a long-lived process kept running while its
dependents execute. Configure it with {@link ServiceBuilder.start} /
{@link ServiceBuilder.readyWhen} and depend on it from a {@link target}.
async function sha256Hex(data: string | Uint8Array): Promise<string>
The SHA-256 digest of `data` — a UTF-8 string or raw bytes — as a lowercase
hex string.
Bytes are copied into a fresh `ArrayBuffer`-backed view so the digest input
type is unambiguous whatever buffer the source view sits on (e.g. a
`SharedArrayBuffer`).
async function syncCiFiles(files: readonly CiFile[], options: CiSyncOptions): Promise<CiSyncResult[]>
Bring each declared {@link CiFile} on disk in line with its definition. By
default a changed file is rewritten; in `check` mode it is reported `stale`
instead (so CI can fail when the committed config has drifted).
function tar(entries: TarEntry[]): Uint8Array
Create a `ustar` archive from the given entries (in order).
function target(): TargetBuilder
Create a new, empty target builder.
async function tcpReachable(address: string): Promise<boolean>
Whether a TCP `host:port` is accepting connections — the usual readiness
probe for a server. Resolves `true` once a connection succeeds (it is closed
immediately), `false` while the port is still refused/unreachable, so it
plugs straight into {@link ServiceBuilder.readyWhen}.
```ts
.readyWhen(() => tcpReachable("localhost:5432"))
```
function toolchain(configure?: (t: Toolchain) => void): Toolchain
Create a {@link Toolchain}. Configure it inline with a callback, or chain
{@link Toolchain.tool} on the returned instance.
```ts
const tools = toolchain((t) =>
t.tool((s) => s.name("helm").url(helmUrl))
.tool((s) => s.name("kubectl").url(kubectlUrl))
);
```
function untar(archive: Uint8Array): TarEntry[]
Extract the entries from a tar archive — regular files, symlinks, and
directories. A path longer than the 100-byte `name` field is reconstructed
from whichever long-name form the archive uses: the POSIX `ustar` `prefix`
split, GNU tar's `@LongLink` pseudo-entries (typeflags `'L'` name / `'K'`
link target, whose data is the following member's value — Node's Linux
release tarballs use this), or pax extended headers (typeflag `'x'`, with
`path=`/`linkpath=` records — bsdtar/macOS use this). These metadata
pseudo-entries accumulate onto the next real member, matching GNU/bsdtar, so a
mixed archive is read correctly; a pax record wins over a GNU long name, which
wins over the header's own fields.
async function unzip(archive: Uint8Array): Promise<TarEntry[]>
Read the entries of a `.zip` archive, decompressing `stored` and `deflate`
members. The central directory is the source of truth. Directory entries (a
trailing `/`) are skipped. Encrypted, zip64, or otherwise-compressed entries
throw a friendly error naming the offending entry, and a header or data field
that runs past the archive is reported as a malformed zip (not a raw
out-of-bounds error). Every offset read from the archive is bounds-checked;
for integrity against a tampered download, pin a `.checksum(...)`, which is
verified before the archive is ever parsed.
function validateGraph(targets: Map<string, TargetBuilder>): void
Validate the whole graph: unknown references first, then cycles.
@throws {GraphError}
with a descriptive message including the cycle path.
const AnnounceTasks: AnnounceTasksApi
Announcement task functions for posting build status to chat platforms.
const BrowserTasks: BrowserTasksApi
Task functions for the user's browser.
const CHECKOUT_ACTION: "actions/checkout"
The action a {@link CiCheckout} is generated from when pins are resolved.
const CONFIG_FILE: "zuke.json"
The Zuke config file name; its presence marks a repository root.
const DEFAULT_POLL_INTERVAL_MS: 200
How often {@link ServiceBuilder.readyWhen} is polled while waiting.
const DEFAULT_READY_TIMEOUT_MS: 30000
The default time a service is given to become ready before it fails.
const DEFAULT_TOOLS_DIR: ".zuke/tools"
The default directory a {@link Toolchain} (and {@link ToolTasks}) installs into.
const FileTasks: FileTasksApi
Filesystem task functions for build scripts.
const HARDEN_RUNNER_ACTION: "step-security/harden-runner"
The action a {@link CiHardenRunner} is generated from when pins are resolved.
const INVALID_TOKEN: McpAuthReject
The refusal for a request that presented a token which did not hold up —
expired, wrong signature, wrong audience.
Distinct from {@link UNAUTHORIZED} on purpose: OAuth 2.1 §5.3.1 says a
challenge SHOULD NOT carry error information when the request had no
credentials at all, because there is nothing yet to have been wrong. Sending
`invalid_token` to a client that simply has not logged in tells it its stored
token was rejected, which is a different and misleading thing.
const REDACTED: "[redacted]"
The placeholder a {@link Redactor} substitutes for each secret value.
const RUN_LEASE_PREFIX: "zuke-run"
The lease name a run's own claim is taken under.
Named once because two places have to agree on it exactly: the process
claiming a run, and any sweep deciding whether that run still has an owner.
const RUN_LEASE_TTL_MS: 60000
How long a lease lives before a crashed holder's claim lapses.
The holder renews at half this interval, so a live process keeps its claim
indefinitely while a dead one becomes reclaimable within the TTL. Sixty
seconds trades promptness for tolerance: long enough that an ordinary pause —
a slow step, a busy host, a paused container — does not look like death, short
enough that a genuinely dead run is picked up on the next sweep rather than
hours later.
const ToolTasks: ToolTasksApi
Provision external CLIs from a build. `ToolTasks.install((s) => …)` fetches a
single release binary and `ToolTasks.npm(...)` a single npm package; group
several of either with {@link toolchain}.
const UNAUTHORIZED: McpAuthReject
The bare `401` challenge: the refusal an authenticator's own failure produces
(so a throw leaks nothing about why it threw), and the one the transport
answers an absent static bearer token with. A token that was presented
and rejected gets {@link INVALID_TOKEN} instead: the two are different facts
about the caller, and only the second one is about a credential.
const VERSION: string
The `@zuke/core` version. Kept in sync with deno.json by release-please.
Annotated `string` rather than left to inference on purpose. Without the
annotation the declared type is the version itself, as a string literal
type, and `deno doc` records a declared type verbatim — so the version
would be baked into `llms-full.txt` and the package README. release-please
rewrites the marked line below when it cuts a release but cannot run
`deno doc`, so those generated files would drift on every bump and
`apiDocsCheck` would fail on the release PR itself. Widening keeps a
version bump a self-contained edit.
const ZUKE_ACTION: "zuke-build/zuke"
The name a {@link CiPinResolver} is asked for the prelude action, so a
repository that pins its own actions can pin this one the same way.
const ZUKE_LOGO: `███████╗██╗ ██╗██╗ ██╗███████╗
╚══███╔╝██║ ██║██║ ██╔╝██╔════╝
███╔╝ ██║ ██║█████╔╝ █████╗
███╔╝ ██║ ██║██╔═██╗ ██╔══╝
███████╗╚██████╔╝██║ ██╗███████╗
╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝`
The Zuke wordmark in FIGlet's ANSI-shadow style. Solid `█` blocks form the
letters; box-drawing characters draw the shadow.
const cliReporter: Reporter
The sink every command-surface module writes through, neutralised for a
GitHub Actions runner.
These commands echo arguments the invoker chose — a run id, a target name, a
force reason — and read values another process wrote into the shared state
store. Under Actions those commonly come from a workflow-dispatch input or an
issue body rather than from a person at a terminal, and a thrown message
quotes them straight back.
A sink rather than a guard at each of the sixty-odd writes, because none of
them should be exempt: unlike the run's reporter, none of these modules emits
a workflow command of its own, so there is no half to carve out. The MCP
command writes only diagnostics here — its protocol never goes through the
console — so escaping cannot corrupt it.
The decision is made per line, not once when this module loads. A top-level
constant would read the environment at import time, before an embedder or a
test has set it, and the escaping would then silently not apply.
Public so a companion command surface — `@zuke/cli` is one — can write
through the same sink instead of composing a copy of it. It is the sink for
a command's own diagnostics and output, not the `reporter` handed to
{@link execute}: the run's renderer writes `::group::` and `::endgroup::` of
its own and wraps only third-party text in {@link escapingReporter} beside
them, and a sink that neutralises every leading `::` would break that
grouping.
const defaultRenderer: Renderer
The built-in renderer: Zuke's ruled headers and summary table.
const defaultStateHost: StateHost
The real, `Deno`-backed {@link StateHost}.
class AlreadyResumedError extends Error
Raised when a run has already been resumed by another process.
constructor(readonly runId: string, readonly by: string, readonly at: string)
Build the error from the run id and who is already running it.
override name: string
The error name.
class AnnounceError extends Error
Raised when an announcement is run before it is fully configured.
constructor(message: string)
Build the error with an explanatory message.
override name: string
The error name.
abstract class AnnouncementSettings
Fluent settings shared by every announcement: the message content (a body, an
optional title, a {@link AnnouncementLevel | level}, repeatable detail fields
and an action link), an optional display name, the webhook destination, and a
`fetch` seam for tests. All chainers return `this`. Subclasses add any
platform-specific configuration and render the payload.
protected text_: string
The main message body.
protected title_?: string
An optional heading shown above the body.
protected level_: AnnouncementLevel
The outcome level driving the accent colour and icon.
protected readonly fields_: AnnouncementField[]
Repeatable labelled detail fields.
protected link_?: AnnouncementLink
An optional action link rendered with the announcement.
protected username_?: string
An optional display name for the sender.
protected webhookUrl_?: string
The webhook destination URL.
protected fetch_?: typeof fetch
A `fetch` seam injected by tests.
protected token_?: string
An API/bot-mode token, when opted in with `.bot()`.
protected channel_?: string
The target channel in API/bot mode.
text(text: string): this
Set the main message body.
title(title: string): this
Set an optional heading shown above the body.
level(level: AnnouncementLevel): this
Set the outcome the message conveys (default `"info"`).
success(): this
Shorthand for `.level("success")`.
failure(): this
Shorthand for `.level("failure")`.
warning(): this
Shorthand for `.level("warning")`.
info(): this
Shorthand for `.level("info")`.
field(name: string, value: string): this
Add a labelled detail rendered beside the body. Repeatable.
link(text: string, url: string): this
Set an action link rendered with the message.
username(name: string): this
Override the display name the message is posted under. Honoured by Slack and
Discord; ignored by Teams, which has no equivalent field.
webhook(url: string): this
Set the incoming-webhook URL to post to. The URL embeds the secret, so
source it from a secret parameter.
fetch(impl: typeof fetch): this
The `fetch` implementation to use. Defaults to the global `fetch`; override
it to unit-test without network access.
bot(): this
Post through the platform's API with a bot/access token instead of an
incoming webhook. Pair with {@link token} and {@link channel}.
token(token: string): this
Set the bot/access token for {@link bot} mode (Slack `xoxb-…`, a Discord bot
token, or a Microsoft Graph bearer token). Source it from a secret
parameter; Zuke masks it in CI output. Implies {@link bot}.
channel(channel: string): this
Set the channel (id or name) to post to in {@link bot} mode.
protected announcement(): Announcement
The structured announcement assembled so far.
protected requireWebhook(): string
The webhook URL, or an {@link AnnounceError} if one was never set.
protected botRequested(): boolean
Whether the caller opted into bot mode via {@link bot} or {@link token}.
protected requireToken(): string
The bot/access token, or an {@link AnnounceError} if one was never set.
protected requireChannel(): string
The target channel, or an {@link AnnounceError} if one was never set.
abstract protected payload(): Record<string, unknown>
The platform-native JSON payload for a webhook post.
abstract protected sendBot(): Promise<void>
Post through the platform's API in {@link bot} mode.
send(): Promise<void>
Send the announcement: through the platform's API when {@link bot} mode was
requested, otherwise by posting the {@link payload} to the webhook.
class AssertionError extends Error
Raised by the assertion helpers when an expectation fails.
override name: string
The error name.
class BrowserOpenSettings extends ToolSettings
Settings for {@link BrowserTasksApi.open | BrowserTasks.open}. The binary and
argv are derived from the platform ({@link ToolSettings.os_}); the shared
chainers (`quiet`, `noThrow`, `toolPath`, …) apply as on any tool.
constructor(url: string)
Build settings that open `url` (must be `http:`/`https:`).
readonly url: string
The validated URL this invocation opens.
override protected defaultTool(): string
The platform opener: `open`, `xdg-open`, or `rundll32`.
override protected buildArgs(): string[]
The opener's argv: the URL, behind the protocol handler on Windows.
class Build
Base class for user-defined builds. Provides no targets of its own; subclasses
declare targets as properties. Optionally override the lifecycle hooks.
onStart(): void | Promise<void>
Called once before any target runs.
onFinish(_result: BuildResult): void | Promise<void>
Called once after the run completes (success or failure).
onTargetStart(_name: string): void | Promise<void>
Called just before a target's body executes (not for skipped/cached).
onTargetEnd(_name: string, _status: TargetStatus): void | Promise<void>
Called after each target settles, with its final status.
recoverWith(): Remediation[]
Remediations applied to every target, running after each target's own
{@link "./target.ts".TargetBuilder.recoverWith} when its body fails. Override
to attach a global AI fixer once instead of repeating it per target; the
default is none. Both styles compose — a target's own remediations run
first, then these.
```ts
class CI extends Build {
key = parameter("OpenAI API key").secret();
override recoverWith() {
return [aiFixer((f) => f.provider("openai").apiKey(this.key))];
}
lint = target().executes(() => DenoTasks.lint()); // healed globally
}
```
remoteCache(): RemoteCacheStore | undefined
The {@link "./remote_cache.ts".RemoteCacheStore} that shares target
{@link "./target.ts".TargetBuilder.outputs} across machines. Override to
declare one in code; the default is none, and — unless overridden — the
executor falls back to {@link "./remote_cache.ts".envCacheStore} (the
`ZUKE_REMOTE_CACHE_*` environment variables). Applies to targets that
declare both `inputs` and `outputs`.
```ts
class CI extends Build {
override remoteCache() {
return new HttpCacheStore({ url: this.cacheUrl.value, token: this.cacheToken.value });
}
build = target().inputs("src").outputs("dist").executes(...);
}
```
stateStore(): StateStore | undefined
The {@link "./state/store.ts".StateStore} that persists this build's run
records. Override to declare one in code; the default is none, and — unless
overridden — the executor falls back to the `ZUKE_STATE_URL` /
`ZUKE_STATE_DIR` environment variables, then (only when the run opts into
durable state) a filesystem store under `<root>/.zuke/runs`.
```ts
class CD extends Build {
override stateStore() {
return new HttpStateStore({ url: this.stateUrl.value, token: this.stateToken.value });
}
deploy = target().executes(async (ctx) => { await ctx.state.set({ at: "sit-7" }); });
}
```
deadline(): string | number | undefined
A wall-clock budget for a whole run, after which a reaping sweep settles it
`failed` — a duration like `"45m"` or milliseconds. No deadline by default.
It bounds running, not existing. A run parked at a `.waitsFor(...)` gate
is not spending it — the deadline is pushed forward on resume by however
long the run was parked, so a build with a 72-hour approval gate and a
45-minute deadline still has its 45 minutes when the approval arrives. The
waiting is bounded by the gate's own `.timeout()`.
A run with a live process working on it is never settled for time either:
the sweep asks whether anyone is still there before it looks at the
deadline. Nothing this is for escapes that — a hung process stops renewing
its lease, and a run killed over and over has no holder at all.
What it is really for is the run that stops making progress without failing
— a process that hangs, or one killed so hard that its work is repeatedly
picked up and abandoned again. Without a deadline such a run has no end
state at all; with one it reaches a terminal status, which is what anything
downstream is waiting for.
```ts
class Ci extends Build {
override deadline() {
return "45m";
}
}
```
extraEdges(_targets: Map<string, TargetBuilder>): OrderingEdge[]
Extra soft ordering edges to impose on the plan, beyond the `dependsOn`
/ `before` / `after` declared on targets. Override to feed an external graph
— e.g. a monorepo's `dependency-graph.json` — into scheduling without wiring
every edge by hand. Return `[before, after]` pairs from the passed
`targets` map (keyed by dotted name); each means `before` runs before
`after`. Edges whose endpoints are not both in a run's execution set are
ignored, and a cycle is reported with the usual friendly error.
These are execution-ordering edges. Like `.before()` / `.after()`, they
are not reflected in CI generated by `cicd()` — a fan-out job's `needs:`
mirrors hard `dependsOn` only — so an ordering that CI must also honour has
to be expressed as a `dependsOn`, not a soft edge.
```ts
class Monorepo extends Build {
web = target().executes(...);
api = target().executes(...);
override extraEdges(t: Map<string, Target>) {
// `api` must build before `web`, per the external dependency graph.
const edges: OrderingEdge[] = [];
const api = t.get("api"), web = t.get("web");
if (api && web) edges.push([api, web]);
return edges;
}
}
```
orderWith(_targets: Map<string, TargetBuilder>): OrderingEdge[] | Promise<OrderingEdge[]>
A lazy, per-run provider of soft ordering edges, merged with
{@link extraEdges}. Unlike `extraEdges` — synchronous, evaluated at
construction — this may be `async` and is evaluated when a run plans, so
it can read an external source (a monorepo's `dependency-graph.json`, an
API) to decide ordering at run time. The consumer keeps ownership of that
graph; Zuke only binds it in. Return `[before, after]` edges over the run's
targets; an edge whose endpoints are not both in the execution set is
ignored, and cycles are reported with the usual friendly error.
Like `extraEdges`, these are execution-ordering edges only: they are
honoured by a run and by `zuke cancel` (the compensation order), but not by
the static `graph`/`--list` views (which never run the provider) nor by
`cicd()`-generated CI (whose `needs:` mirrors hard `dependsOn`).
```ts
override async orderWith(t: Map<string, Target>): Promise<OrderingEdge[]> {
const graph = await loadDependencyGraph(); // e.g. dependency-graph.json
return graph.edges.flatMap(([before, after]) => {
const from = t.get(before), to = t.get(after);
return from && to ? [[from, to] as OrderingEdge] : [];
});
}
```
registry(): BuildRegistry | undefined
The {@link "./registry/registry.ts".BuildRegistry} this build registers
itself in (`zuke register`) and that a registry-backed `zuke mcp` server
discovers pipelines from. Override to declare one in code; the default is
none, and — unless overridden — the resolution falls back to the
`ZUKE_REGISTRY_URL` / `ZUKE_REGISTRY_DIR` environment variables, then (for
`zuke register`) a filesystem registry under `<root>/.zuke/builds`. Kept a
separate concern from {@link stateStore} (a run history and a build catalog
are different things), so a consumer can host a richer catalog as a plugin.
```ts
class CD extends Build {
override registry() {
return new HttpBuildRegistry({ url: this.registryUrl.value, token: this.registryToken.value });
}
}
```
mcpIdentity(): McpIdentityHook | undefined
A per-request identity hook for `zuke mcp` — resolve a trusted caller
from the request context (an authenticating reverse proxy's header) so a
shared, multi-user server attributes each call to the real engineer rather
than a client-self-reported label. When set, the resolved actor overrides
`--actor`, the environment, and the client label for that call, and flows to
the audit trail, run records, lock holders, and (for a registry-spawned
build) the child's `ZUKE_ACTOR`; a throwing hook rejects the request before
anything runs. Default: none — stdio/local use is unchanged.
```ts
class ControlPlane extends Build {
override mcpIdentity() {
return (ctx: McpRequestContext) => {
// The proxy strips any client copy of this header and injects its own.
const sub = ctx.headers.get("x-forwarded-user");
if (!sub) throw new Error("no identity from proxy");
return { actor: sub, via: "oauth-proxy" };
};
}
}
```
mcpAuth(): McpAuthenticator | undefined
An authenticator for `zuke mcp` — the general form of
{@link Build.mcpIdentity}, for a server callers reach directly rather than
through a proxy that has already identified them.
It runs before any dispatch, may be asynchronous (verifying a signature is),
and refuses by returning an {@link McpAuthReject} rather than by throwing —
so over HTTP the refusal is answered with its own status and
`WWW-Authenticate` challenge, which is how an MCP client discovers where to
authenticate. The identity it resolves overrides `--actor`, the environment,
and the client label for that call, and flows to the audit trail, run
records, lock holders, and (for a registry-spawned build) the child's
`ZUKE_ACTOR`, `ZUKE_ACTOR_KIND` and `ZUKE_ACTOR_ROLES`. Throwing still
refuses the request: the seam is fail-closed. Default: none.
Declare either this or {@link Build.mcpIdentity}; declaring both is
refused when the server starts, rather than letting one silently win.
```ts
class ControlPlane extends Build {
override mcpAuth(): McpAuthenticator {
return {
authenticate: async (ctx: McpRequestContext) => {
const claims = await verifyBearer(ctx.headers.get("authorization"));
if (claims === null) {
return { status: 401, error: "invalid_token", challenge: "Bearer" };
}
return { actor: claims.sub, kind: "human", roles: claims.roles };
},
};
}
}
```
mcpProtectedResource(): ProtectedResourceSettings | undefined
Declares this MCP endpoint an OAuth 2.0 protected resource, so a client
that has never authenticated can find out where to get a token.
`zuke mcp --http` then publishes the RFC 9728 metadata document and names
it in every `WWW-Authenticate` challenge, which is the whole of what
`claude mcp add --transport http <url>` needs to open a browser and
authenticate with nothing pasted. Zuke issues no tokens and hosts no
`/authorize`, `/token` or `/register` endpoint — those belong to the
identity provider named here, and {@link Build.mcpAuth} is where the tokens
it mints are verified. Default: none, and the server behaves exactly as
before.
The one thing to get right is that three strings must agree byte for
byte: the resource identifier below, the `resource` parameter the client
sends, and the audience the identity provider puts in the token. When they
differ every token fails validation, and nothing in the error says why.
```ts
class ControlPlane extends Build {
override mcpProtectedResource(): ProtectedResourceSettings {
return protectedResource("https://build.example.com/mcp")
.authorizationServer("https://acme.eu.auth0.com")
.scopes("zuke:run")
.name("Acme build server");
}
}
```
unforceable(): TargetBuilder[]
Targets an operator may not force with `zuke force` — the steps whose
body must actually run, whatever a live incident looks like.
Forcing settles a target without executing it: `skipped` takes a step off
the plan, `succeeded` records that a person did it by hand. That is the
right tool for a step that cannot succeed and the wrong one for a step
whose whole purpose is to be the thing that happened — a production apply,
a signing step, a migration. Naming those here refuses the force rather
than trusting an operator under pressure to remember which is which.
Returns target references, not names, so renaming a target keeps the
list correct instead of silently emptying it.
```ts
class CD extends Build {
applyProduction = target().executes(() => applyTerraform());
override unforceable() {
return [this.applyProduction];
}
}
```
mcpAuthorize(identity: McpIdentity, call: McpCall): McpAuthorization
Decide whether an authenticated MCP caller may make one call — the
build-level half of authorization (../../docs/mcp.md#authorization).
The default implementation is {@link defaultMcpAuthorize}: read tools need
`read`, running a target needs `run` (or whatever the target's
`requiresRole` asks for), and a run-scoped mutation needs the run's
initiator or `operator`. Override to express what the engine cannot know —
a change window, team ownership, a freeze.
Only consulted when the server authenticates its callers. With no
`mcpAuth()`/`mcpIdentity()` every caller is treated as holding every role,
so `--allow-run`, `--protect` and the operator token remain exactly the
gates they were; a local stdio server is unchanged.
Called after the allow-list and operator-token checks, so it can only
narrow what those already permit — an override cannot open a target the
server was not started to expose.
```ts
class ControlPlane extends Build {
override mcpAuthorize(identity: McpIdentity, call: McpCall) {
if (call.tool === "run:promote" && !inChangeWindow()) {
return { allow: false, reason: "outside the change window" };
}
return defaultMcpAuthorize(identity, call);
}
}
```
class CiFile
A declared CI file. Assign one (via {@link cicd}) to a build field and Zuke
keeps the file on disk in sync with the definition when the build runs.
constructor(spec: CiFileSpec)
Build the CI file from its spec, filling in the provider's default path.
readonly provider: CiProvider
The provider this file renders for.
readonly path: string
The output path, once resolved.
readonly explicitPath: boolean
Whether {@link path} came from the spec rather than a default.
readonly pipeline: CiPipeline
The base pipeline (pipeline-level fields, and the jobs unless fanning out).
readonly fanOut?: FanOutOptions
Fan-out options, when this file expands the build's targets into jobs.
readonly invokes?: readonly CiInvokes[]
The targets this file runs as jobs, when declared with `invokes`.
readonly pins?: CiPinResolver
Resolves pinned action references, so a SHA is stated once per repository.
get derived(): boolean
Whether this file's jobs are derived from the build rather than declared.
pipelineFor(targets: Map<string, TargetBuilder>): CiPipeline
The pipeline this file renders. Jobs come from the invoked targets, or from
a full fan-out of the graph, or — failing both — from the declared
{@link pipeline}.
at(path: string): CiFile
The same file bound to `path` — used to name a file from its field.
render(): string
Render the file's YAML content (the base pipeline; fan-out is resolved at discovery).
class DiscordAnnouncementSettings extends AnnouncementSettings
Fluent settings for {@link AnnounceTasksApi.discord}. Bot mode
(`.bot().token(t).channel(c)`) posts through the REST API with a bot token.
override protected payload(): Record<string, unknown>
Render the Discord webhook payload.
override protected sendBot(): Promise<void>
Post the announcement through the Discord REST API in bot mode.
class ExecSecretSettings
Fluent settings for {@link execSecret}: a command whose standard output is
the secret. Configure the binary with {@link ExecSecretSettings.command},
arguments with {@link ExecSecretSettings.arg}, and optionally the environment
and working directory. Output is trimmed of surrounding whitespace unless
{@link ExecSecretSettings.trim} is turned off (some values are
whitespace-sensitive).
command(binary: PathLike): this
The binary to run (e.g. `op`, `vault`, `gcloud`). Required.
arg(...values: Array<string | number | AbsolutePath>): this
Append one or more arguments to the command.
env(record: Record<string, string>): this
Merge additional environment variables for the process.
cwd(path: PathLike): this
Set the working directory for the process.
trim(on: boolean): this
Whether to trim surrounding whitespace from stdout (default `true`).
async resolve_(): Promise<string>
Run the command and return its captured stdout as the secret. Streaming is
suppressed (`quiet`) so the value is never echoed to the terminal, and a
non-zero exit throws a {@link SecretError} naming the command.
class FileSecretSettings
Fluent settings for {@link fileSecret}: read a secret from a file. Set the
path with {@link FileSecretSettings.path}; the content is trimmed of
surrounding whitespace unless {@link FileSecretSettings.trim} is turned off.
path(path: PathLike): this
The file to read the secret from. Required.
trim(on: boolean): this
Whether to trim surrounding whitespace from the content (default `true`).
async resolve_(): Promise<string>
Read the file and return its content as the secret. A missing or
unreadable file throws a {@link SecretError} naming the path.
class FileSystemBuildRegistry implements BuildRegistry
A {@link BuildRegistry} that writes one `<id>.json` file per build under a
directory.
Security. `dir` is trusted configuration — the location you choose to
store the build catalog (from `ZUKE_REGISTRY_DIR` or an explicit registry),
the same posture as {@link "../state/fs_store.ts".FileSystemStateStore}. The
only untrusted value that reaches a path is the build id, validated at
every point a path is built, so a traversal cannot be smuggled in via an id.
constructor(dir: string, host: StateHost)
Build the registry over `dir` (created on first write). Filesystem access
goes through `host`, which defaults to
{@link "../state/store.ts".defaultStateHost}.
async getBuild(id: string): Promise<{ descriptor: BuildDescriptor; version: string; } | null>
Fetch a build and the content-hash version of its stored file.
async register(descriptor: BuildDescriptor, expectedVersion: string | null): Promise<PutBuildResult>
Publish `descriptor` under an exclusive lock, guarding the expected version.
async deregister(id: string): Promise<void>
Remove a registered build under an exclusive lock; a missing file is a no-op.
async listBuilds(query: BuildQuery): Promise<BuildSummary[]>
List builds matching `query`, newest first. Unreadable files are skipped.
class FileSystemCacheStore implements RemoteCacheStore
A {@link RemoteCacheStore} backed by a shared or mounted directory.
constructor(dir: string)
Build the store over a directory.
@param dir
The directory archives are read from and written to.
get(key: string): Promise<Uint8Array | null>
Fetch the archived outputs stored under `key`, or `null` if there are none.
async put(key: string, artifact: Uint8Array): Promise<void>
Store `artifact` (a gzipped tar of a target's outputs) under `key`.
class FileSystemStateStore implements StateStore
A {@link StateStore} that writes one `<id>.json` file per run under a
directory.
Security. `dir` is trusted configuration — the location you choose to
store run state (from `ZUKE_STATE_DIR`, `--state`, or an explicit store), the
same posture as {@link "../remote_cache.ts".FileSystemCacheStore}. The only
untrusted value that reaches a path is the run id, which is validated at
every point a path is built, so a traversal cannot be smuggled in through an
id.
constructor(dir: string, host: StateHost)
Build the store over `dir` (created on first write). Filesystem access goes
through `host`, which defaults to {@link defaultStateHost}.
async getRun(id: string): Promise<{ record: RunRecord; version: string; } | null>
Fetch a run and the content-hash version of its stored file.
async putRun(record: RunRecord, expectedVersion: string | null): Promise<PutResult>
Publish `record` under an exclusive lock, guarding the expected version.
async listRuns(query: RunQuery): Promise<RunSummary[]>
List runs matching `query`, newest first. Unreadable files are skipped.
async deleteRun(id: string): Promise<void>
Delete a run's file (under its lock); a missing run is a no-op.
The run's lock records are deliberately left alone. It is tempting to take
them with the run — they are named after it, so once it is gone nothing can
look them up again — but "expired" does not mean "abandoned" in this store:
{@link renewLock} extends a lock whenever the token matches, whatever its
expiry, so a lapsed claim is still the holder's until somebody acquires
it. Deleting the record instead makes the next renewal answer `false`, which
the holder reads as the lease being lost, and a run that is merely slow —
the exact case the lease exists to tell apart from a dead one — stops.
Pruning must never be able to do that.
The litter is small and bounded in practice: {@link releaseLock} removes a
lock's file, and a run releases its lease whenever it settles, so only a
holder that dies without releasing leaves one behind. Clearing those safely
belongs to whoever can prove the holder is gone — a reaping sweep, which
proves it by acquiring — not to a command deleting old records.
async acquireLock(key: string, holder: LockHolder, ttlMs: number): Promise<LockResult>
Atomically acquire the lock `key` for `holder`, taking over if expired.
async renewLock(key: string, token: string, ttlMs: number): Promise<boolean>
Extend the lock `key` held under `token`; `false` if the token lost it.
async releaseLock(key: string, token: string): Promise<void>
Release the lock `key` if still held under `token`; a no-op otherwise.
async listLocks(): Promise<HeldLockEntry[]>
Every live lock in the `locks/` directory, ordered by key. `<key>.acq`
mutex markers and anything else in there are skipped; a record that fails
to parse is skipped too, since one corrupt file must not hide every other
lock from someone trying to see who holds what.
class ForEachSettings
Fluent configuration for {@link TargetBuilder.forEach}, in the settings-lambda
style: `.forEach(items, factory, (s) => s.concurrency(3).continueOnItemFailure())`.
Sets the {@link ForEachSettings.concurrency | concurrency} cap and whether one
item's failure isolates it or stops the whole batch.
concurrency_?: number
Max item pipelines in flight at once; set by {@link concurrency}.
continueOnItemFailure_: boolean
Isolate a failed item from its siblings; set by {@link continueOnItemFailure}.
concurrency(limit: number): this
Cap how many item pipelines run concurrently (default: the host CPU count).
Clamped to at least 1; `1` runs items one at a time.
continueOnItemFailure(on: boolean): this
Keep running the other items when one item's pipeline fails (the failed
item's later stages are still skipped). The fan-out target still fails at
the end if any item failed. Without this, the first item failure stops the
batch — the default.
class ForeignRunError extends Error
Thrown when a recovery path is handed a run that a different build owns:
the run's recorded origin and this process's disagree.
A sweep treats it as "not mine" and moves on rather than counting a failure,
the same way it treats a run another process has already resumed. A command
that named one run reports it, because the operator asked about a run that is
not this build's to touch.
constructor(readonly runId: string, readonly owner: string, readonly self: string)
Build the error from the run and the two disagreeing origins.
override name: string
The error name.
class GraphError extends Error
Raised when the build graph is invalid (cycle or unknown dependency).
override name: string
The error name.
class Group
A parallel batch of targets, created with {@link group}. Targets join it via
{@link TargetBuilder.partOf}; its members run concurrently with one another
(each still awaiting its own dependencies) regardless of the global parallel
setting. Passing a group to {@link TargetBuilder.dependsOn} depends on every
member at once.
readonly members_: TargetBuilder[]
Members that declared themselves part of this group, in declaration order.
name_?: string
Property name, assigned during discovery. Undefined until then.
class HttpBuildRegistry implements BuildRegistry
A {@link BuildRegistry} backed by HTTP.
Security. The `url` and `token` are trusted configuration — build
descriptors (structural CLI metadata plus a launch location) are sent to that
host, so point it only at a service you control and prefer a secret parameter
or environment variable over a hard-coded value.
constructor(options: HttpBuildRegistryOptions)
Build the registry from its URL, optional token, and `fetch` seam.
async getBuild(id: string): Promise<{ descriptor: BuildDescriptor; version: string; } | null>
`GET /builds/:id` → descriptor + `ETag`; a `404` is a miss.
async register(descriptor: BuildDescriptor, expectedVersion: string | null): Promise<PutBuildResult>
`PUT /builds/:id` guarded by `If-Match` / `If-None-Match`; `412` → conflict.
deregister(id: string): Promise<void>
`DELETE /builds/:id`; a missing build (`404`) is not an error.
async listBuilds(query: BuildQuery): Promise<BuildSummary[]>
`GET /builds?name=&since=` → an array of {@link BuildSummary}.
class HttpCacheStore implements RemoteCacheStore
A {@link RemoteCacheStore} backed by HTTP: `GET <url>/<key>` fetches an
artifact (a `404` means a miss) and `PUT <url>/<key>` stores one. Works with
any object store or cache server that speaks plain HTTP GET/PUT — an S3, GCS,
or R2 bucket behind a URL, or a self-hosted cache endpoint.
Security. The `url` (and `token`) are trusted configuration: outputs are
uploaded to that host and archives are extracted from it, so point it only at
a cache you control, and prefer a {@link "./params.ts" | secret parameter} or
an environment variable over a hard-coded value. On CI, restrict egress to
the cache host so a misconfigured or overridden URL can't exfiltrate
artifacts. A restored archive cannot name a path outside the workspace, cannot
carry a link or directory entry, and is refused if the path it would land on
passes through a symlink the workspace already holds — so a poisoned store
cannot write outside the workspace (see {@link restoreOutputs}). An artifact
larger than {@link HttpCacheStoreOptions.maxArtifactBytes} is refused before
it is buffered, and {@link restoreOutputs} separately bounds what it
decompresses to.
constructor(options: HttpCacheStoreOptions)
Build the store from its URL, optional token, size cap, and `fetch` seam.
async get(key: string): Promise<Uint8Array | null>
Fetch the archived outputs stored under `key`, or `null` if there are none.
async put(key: string, artifact: Uint8Array): Promise<void>
Store `artifact` (a gzipped tar of a target's outputs) under `key`.
class HttpError extends Error
Raised when an HTTP request returns a non-2xx status. The URL appears in the
message and on {@link url}, so it is passed through {@link redactUrl} first —
userinfo and credential query params never reach a log.
constructor(status: number, url: string)
Build the error from the failing response's status and URL.
override name: string
The error name.
readonly status: number
The HTTP status code of the failing response.
readonly url: string
The requested URL, with any credentials redacted.
class HttpStateStore implements StateStore
A {@link StateStore} backed by HTTP.
Security. The `url` and `token` are trusted configuration — run
records (which include resolved non-secret parameters and target metadata)
are sent to that host, so point it only at a service you control and prefer a
{@link "../params.ts" | secret parameter} or environment variable over a
hard-coded value.
constructor(options: HttpStateStoreOptions)
Build the store from its URL, optional token, and `fetch` seam.
async getRun(id: string): Promise<{ record: RunRecord; version: string; } | null>
`GET /runs/:id` → record + `ETag`; a `404` is a miss.
async putRun(record: RunRecord, expectedVersion: string | null): Promise<PutResult>
`PUT /runs/:id` guarded by `If-Match` / `If-None-Match`; `412` → conflict.
async listRuns(query: RunQuery): Promise<RunSummary[]>
`GET /runs?status=&target=&since=` → an array of {@link RunSummary}.
deleteRun(id: string): Promise<void>
`DELETE /runs/:id`; a missing run (`404`) is not an error.
async acquireLock(key: string, holder: LockHolder, ttlMs: number): Promise<LockResult>
`POST /locks/:key` → `201 { token }`, or `409` with the current holder.
async renewLock(key: string, token: string, ttlMs: number): Promise<boolean>
`PUT /locks/:key` renews; a `409`/`404` means the token lost the lock.
async listLocks(): Promise<HeldLockEntry[]>
`GET /locks` → the live locks the server holds. A server that has not
implemented the endpoint (`404`/`501`) is told apart from one that holds
nothing: an empty listing is an answer, and a missing endpoint is not.
async releaseLock(key: string, token: string): Promise<void>
`DELETE /locks/:key` releases; a missing lock (`404`) is not an error.
class LockConflictError extends Error
Raised when a target's lock is already held by another run. Its `message` is
the rendered guidance (from the target's `onConflict`, else a default), so it
surfaces verbatim in the CLI failure footer and the run record; `holder`
carries the structured identity for programmatic surfaces (e.g. MCP).
constructor(readonly holder: LockHolder, guidance: string)
Build the error from the current holder and the rendered guidance.
override name: string
The error name.
class LockSettings
Fluent configuration for {@link TargetBuilder.lock}, in the settings-lambda
style: `.lock((s) => s.lockKey("deploy", repo).withTtl("4h"))`. Set the key
(composed from sanitised parts with {@link LockSettings.lockKey}, or directly
with {@link LockSettings.key}), the {@link LockSettings.withTtl | TTL}, and an
optional {@link LockSettings.onConflict} message. The lambda runs after
parameters resolve, so the key may read `this.<param>.value`.
key_?: string
The resolved lock key; set by {@link key} or {@link lockKey}.
ttl_?: string | number
The TTL (a duration string or milliseconds); set by {@link withTtl}.
onConflict_?: (holder: LockHolder) => string
The conflict-guidance renderer; set by {@link onConflict}.
waitUpTo_?: string | number
How long to wait for a held lock; set by {@link waitUpTo}.
pollEvery_?: string | number
How often to retry while waiting; set by {@link pollEvery}.
lockKey(...parts: Array<string | number>): this
Set the lock key from parts, sanitised and joined via
{@link "./state/lock.ts".lockKey} — e.g. `s.lockKey("deploy", repo)`.
key(key: string): this
Set the lock key directly (must be filename-safe; prefer {@link lockKey}).
withTtl(ttl: string | number): this
How long the lock survives a killed holder — a duration string like `"4h"`
/ `"30m"` (see the duration parser) or raw milliseconds. A live holder
renews it while it runs, so it never expires under it.
waitUpTo(duration: string | number): this
Wait up to this long for a held lock instead of failing at once — a
duration string like `"30m"` or raw milliseconds. The target queues, and
takes the lock when the run holding it finishes; it fails with a
{@link "./state/lock.ts".LockConflictError} only once the wait is spent.
Set this for a shared resource a developer wants to use — one dev
environment, one database, one port — where failing fast just makes them
run the command again. Leave it off for a resource where a second run is a
mistake worth reporting immediately, which stays the default.
Waiting runs retry independently, so a queue of them is not served in
arrival order: a run that has waited longer has no claim over one that
arrived a moment ago.
pollEvery(duration: string | number): this
How often to retry while {@link waitUpTo} waits (default `"5s"`). Alone it
does nothing — without a wait there is no retry to pace.
onConflict(render: (holder: LockHolder) => string): this
Render the guidance shown to a run that loses the lock. Receives the
current {@link "./state/lock.ts".LockHolder}; the returned string becomes
the failure message. Defaults to a generic "held by … then retry" line.
class Parameter<K extends ParamValue = ParamValue, T extends K | K[] | undefined = K | undefined> implements AnyParameter
A typed build parameter. Declare one with {@link parameter} and configure it
with the fluent methods; each method returns a new parameter whose `value`
type reflects the configuration (`string`, `number`, `boolean`, and whether
it can be `undefined`).
`K` is the underlying value kind; `T` is the exposed `value` type, which is
`K` for required/defaulted parameters and `K | undefined` for optional ones.
constructor(spec: ParamSpec<K, T>)
Build a parameter from its resolved constructor spec.
name_?: string
Property name, assigned during discovery. Undefined until then.
readonly description_?: string
Human-readable description shown in `--help`/`--list`.
readonly kind_: ParamKind
The runtime value kind.
readonly required_: boolean
Whether a value must be supplied (no default).
readonly options_?: readonly string[]
The allowed string choices, if restricted with {@link Parameter.options}.
readonly envName_?: string
An explicit environment variable name override.
readonly flagName_?: string
An explicit CLI flag name override, without the leading dashes.
readonly hasFallback_: boolean
Whether the parameter has a declared default value.
readonly secret_: boolean
Whether the value is sensitive and should be masked in CI output.
readonly array_: boolean
Whether the value is a comma-separated / repeatable list (`.array()`).
readonly source_?: SecretSource
A provider that resolves the value when no flag/env supplied one.
readonly default_?: string
The declared default rendered as a string (an array default is joined with
commas), or `undefined` when the parameter has no default or an empty-list
one. For display in tool schemas and `--list`; never a secret value.
get value(): T
The resolved value. Throws if read before the build resolves parameters.
isSet_(): boolean
Whether the parameter resolved to a defined value (used by `.requires()`).
stringValue_(): string | undefined
The resolved value as a string, or `undefined` if unset (for masking).
secret(): Parameter<K, T>
Mark the value as sensitive: it is masked in CI output (`::add-mask::`) and
redacted from all of Zuke's reporter output. Pair with {@link Parameter.from}
to resolve the value from a secret manager rather than the environment.
from(source: SecretSource): Parameter<K, T>
Resolve the value from a {@link SecretSource} (see {@link execSecret} /
{@link fileSecret}) when neither a `--flag` nor an environment variable
supplied one — the source is a fallback provider, consulted before the
declared default. Typically paired with {@link Parameter.secret} so the
resolved value is redacted.
number(this: Parameter<string, string | undefined>): Parameter<number, number | undefined>
Parse the value as a number (e.g. `--workers 4`).
boolean(this: Parameter<string, string | undefined>): Parameter<boolean, boolean>
Treat the parameter as a boolean flag (e.g. `--verbose`); defaults to false.
options(this: Parameter<string, string | undefined>, ...values: string[]): Parameter<string, string | undefined>
Restrict a string parameter to a fixed set of choices.
default(this: Parameter<K, K | undefined>, value: K): Parameter<K, K>
Provide a default, making `value` non-optional (`K`).
required(this: Parameter<K, K | undefined>): Parameter<K, K>
Require a value, making `value` non-optional (`K`); errors if unsupplied.
env(name: string): Parameter<K, T>
Override the environment variable read as a fallback for this parameter.
flag(name: string): Parameter<K, T>
Override the CLI flag this parameter is set by. The leading `--` is
optional, so `.flag("--skip-e2e")` and `.flag("skip-e2e")` are the same.
The declared spelling replaces the derived one: only it is accepted on
the command line, and it is what `--help`, the JSON build surface, shell
completions and the registry descriptor all show. Reach for this when the
name-to-flag rule produces something you would not have chosen — a name
containing an initialism that ends in a digit is the usual case, since the
digit ends the run of capitals and `skipE2E` derives `--skip-e2-e`.
The environment variable is derived separately and is unaffected; override
it with {@link env}.
array(this: Parameter<E, E | undefined>): Parameter<E, E[]>
Accept a comma-separated list (or a repeated flag), exposing `value` as an
array. `--tags a,b` and `--tags a --tags b` both yield `["a", "b"]`; blank
entries are dropped, and an unsupplied optional list defaults to `[]`
(a required one is reported missing — see below).
Each element is parsed by this parameter's own element parser, so it
composes: `.options("a", "b").array()` validates every element against
the choices, and `.number().array()` yields a `number[]`, rejecting a
non-numeric entry. (Apply `.options()`/`.number()` before `.array()`.)
`.array()` composes last, after `.required()` too: a
`.required().array()` list stays required, so an unsupplied value is
reported as missing rather than silently resolving to the empty-list
default. An optional (non-required) list defaults to `[]`.
resolve_(raw: string | undefined): void
Resolve from a raw input (or `undefined` when none was supplied).
class ParameterError extends Error
Raised when a parameter value is invalid or read before resolution.
override name: string
The error name.
class ProtectedResourceError extends Error
Raised when a protected-resource declaration cannot produce a valid document.
constructor(message: string)
Construct the error with `message`.
override name: string
The error name.
class ProtectedResourceSettings
A build's protected-resource declaration, configured fluently.
The resource identifier is the single value everything else has to agree
with, so it is a direct argument to {@link protectedResource}; the rest are
setters. It must be the canonical URI of this MCP endpoint — the same
string the client sends as its RFC 8707 `resource` parameter, and the same
string the identity provider mints into the token's `aud`. Those three
agreeing byte for byte is the whole contract; when they disagree every token
fails validation, and the error says nothing about why.
constructor(resource: string)
Construct the settings for `resource`, the canonical endpoint URI.
resource_: string
The canonical resource identifier of this MCP endpoint.
authorizationServers_: string[]
Issuer identifiers of the authorization servers that issue for it.
scopes_: string[]
Scope values a caller may request for this resource.
resourceName_?: string
Human-readable name of the resource, for a consent screen.
documentation_?: string
URL of human-readable documentation for the resource.
authorizationServer(issuer: string): this
Add an authorization server by its issuer identifier — `https://acme.eu.auth0.com`,
not its metadata URL. It must string-match the `issuer` in that server's own
metadata document, or a client is required to reject it. At least one is
required: RFC 9728 marks the field optional, but MCP raises it to required.
scopes(...values: string[]): this
Declare the scope values a caller may request. These are advertised to
clients, which request them wholesale when a challenge names none, so keep
the list to what this server actually distinguishes rather than the whole
catalogue of a shared identity provider.
name(value: string): this
Set the human-readable resource name shown on a consent screen.
documentation(url: string): this
Set the URL of human-readable documentation for this resource.
class Redactor
Collects secret values and masks them in text. Register a value with
{@link Redactor.add} and rewrite a line with {@link Redactor.redact}; empty
strings are ignored (they would match everywhere) and duplicates are recorded
once. Longer secrets are applied first so a secret that contains another is
masked whole rather than partially.
add(value: string): void
Register a secret value to mask. Ignores empty strings and duplicates.
A multi-line value registers each of its lines as well as the whole
string, because redaction runs a line at a time and a whole-value pattern
can never match one line of it. Lines are trimmed, and a very short one is
skipped so it cannot mask ordinary text wherever it appears.
redact(line: string): string
Replace every registered secret in `line` with {@link REDACTED}.
get size(): number
The number of distinct patterns registered. A single-line secret
contributes one; a multi-line secret contributes the whole value plus each
of its qualifying lines.
class RunNotSuspendedError extends Error
Raised when a run is no longer `suspended` by the time a resume reaches it —
it has been settled, or a cancellation is in progress.
The counterpart to {@link AlreadyResumedError}, which covers a run another
process is currently driving. This covers one that already finished, and a
sweep treats it the same way: not its run to advance, and not a failure. Two
sweeps racing the same run is the normal case — one wins, and the loser
reading `succeeded` has discovered a success, not a fault. Counting it would
put a false alarm in the exit code a cron watches.
constructor(readonly runId: string, readonly status: RunStatus)
Build the error from the run id and the status found instead.
override name: string
The error name.
class SecretError extends Error
Raised when a {@link SecretSource} cannot produce a value.
override name: string
The error name.
class ServiceBuilder extends TargetBuilder
A long-lived {@link target}. Configure how it starts ({@link
ServiceBuilder.start}), how to tell it is ready ({@link
ServiceBuilder.readyWhen}), and — when the started handle is not
self-stopping — how it stops ({@link ServiceBuilder.stop}). It inherits the
ordering methods (`dependsOn`, `before`, `after`, `description`) from
{@link TargetBuilder}; a service has no `.executes` body.
override effect(name: string, fn: EffectFn): this
Refuse a crash-durable effect on a service.
A service target's whole job is launching a process; it runs no body, so
there is no point at which its effects would be driven and they would be
dropped without a word. Inherited from {@link TargetBuilder} only because
this is a subclass of it, so the refusal is stated here rather than left to
be discovered.
start(fn: () => ServiceHandle | Promise<ServiceHandle>): this
How to start the process. Return a {@link ServiceHandle} (e.g.
`$\`…`.spawn()`) so the service can be stopped on teardown; provide a
custom {@link ServiceBuilder.stop} if the handle is not self-stopping.
readyWhen(fn: () => boolean | Promise<boolean>): this
A readiness probe, polled until it returns `true` (or the timeout is hit).
Without one, the service is considered ready the moment it starts. See
{@link tcpReachable} for the common "is the port accepting connections?".
readyTimeout(ms: number): this
Override how long to wait for {@link ServiceBuilder.readyWhen} (default 30s).
stop(fn: (handle: ServiceHandle) => void | Promise<void>): this
Custom teardown, given the handle {@link ServiceBuilder.start} returned.
async launch_(name: string): Promise<RunningService>
INTERNAL: start the service and wait until it is ready, returning a handle
the executor stops on teardown. Throws {@link ServiceError} if no start was
configured, or if the service does not become ready in time (the
just-started process is stopped first so it is not leaked).
class ServiceError extends Error
Raised when a service cannot start or does not become ready in time.
override name: string
The error name.
class ServiceRegistry
Holds the services started during a run and stops them in reverse order on
teardown. Stopping never throws — a failure to stop one service is reported
and the rest are still stopped.
register(running: RunningService): void
Record a started service to stop later.
get size(): number
The number of services currently held.
async stopAll(report: (line: string) => void): Promise<void>
Stop every registered service, newest first, reporting each outcome.
class SlackAnnouncementSettings extends AnnouncementSettings
Fluent settings for {@link AnnounceTasksApi.slack}. Bot mode
(`.bot().token(t).channel(c)`) posts through the Web API (`chat.postMessage`).
override protected payload(): Record<string, unknown>
Render the Slack webhook payload.
override protected sendBot(): Promise<void>
Post the announcement through the Slack Web API in bot mode.
class SlackApiError extends Error
Raised when the Slack Web API accepts the request but reports a logical
failure (`{ ok: false }`), carrying Slack's machine-readable error code (e.g.
`channel_not_found`, `not_in_channel`, `invalid_auth`).
constructor(readonly error: string)
Build the error from Slack's machine-readable error code.
override name: string
The error name.
class TargetBuilder
The fluent builder returned by {@link target}. All configuration methods are
chainable and return `this`. A body (via {@link TargetBuilder.executes}) is
required before a target can be executed.
description_?: string
Human-readable summary shown in `--list`.
readonly dependsOn_: TargetBuilder[]
Hard prerequisites: these run (transitively) before this target.
readonly before_: TargetBuilder[]
Soft ordering: this runs before the listed targets if both are planned.
readonly after_: TargetBuilder[]
Soft ordering: this runs after the listed targets if both are planned.
fn_?: TargetFn
The target body.
readonly effects_: DeclaredEffect[]
Crash-durable effects, in declaration order (set by {@link effect}).
name_?: string
Property name, assigned during discovery. Undefined until then.
group_?: Group
The parallel batch this target belongs to, if any (set by {@link partOf}).
readonly inputs_: string[]
Input files/directories whose contents key the cache (set by {@link inputs}).
readonly outputs_: string[]
Output files/directories that must exist for a cache hit (set by {@link outputs}).
readonly onlyWhen_: Condition[]
Conditions gating execution; all must hold or the target is skipped.
readonly triggers_: TargetBuilder[]
Targets pulled in and run after this one (set by {@link triggers}).
readonly requires_: AnyParameter[]
Parameters that must be set for this target (set by {@link requires}).
proceedAfterFailure_: boolean
Continue the build if this target fails (set by {@link proceedAfterFailure}).
always_: boolean
Run even after the build has failed (set by {@link always}).
unlisted_: boolean
Hide this target from `--list`/`--help` (set by {@link unlisted}).
readOnly_: boolean
Advertise this target as query-only over MCP (set by {@link readOnly}).
requiresRole_?: string
The role an MCP caller needs to run this target (set by {@link requiresRole}).
dryRunnable_: boolean
Run this target's body under `--dry-run` with `$` echoed (set by {@link dryRunnable}).
readonly cacheKeys_: Array<() => string | Promise<string>>
Extra cache-key contributors beyond input files (set by {@link cacheKey}).
readonly produces_: string[]
Artifact paths this target produces (set by {@link produces}).
skipDependencies_: boolean
When skipped by a condition, also skip dependencies (set by {@link whenSkipped}).
timeout_?: number
Per-attempt timeout in milliseconds, if set by {@link timeout}.
retries_: number
Number of extra attempts on failure, set by {@link retry}.
retryDelay_: number
Delay between retry attempts in milliseconds.
readonly validateBefore_: Validation[]
Validations run before the body (set by {@link validateBefore}).
readonly validateAfter_: Validation[]
Validations run after the body (set by {@link validateAfter}).
readonly recoverWith_: Remediation[]
Remediations run after the body fails (set by {@link recoverWith}).
recoverAttempts_: number
Max fix-then-rerun cycles when the body fails (set by {@link recoverAttempts}).
lock_?: Configure<LockSettings>
Cross-run lock settings lambda, set by {@link lock} and run after params resolve.
waitsFor_?: Configure<WaitSettings>
External-event wait settings lambda, set by {@link waitsFor} and run when reached.
forEach_?: ForEachSpec
Fan-out spec, set by {@link forEach}: materialises per-item sub-target pipelines.
onCancel_?: () => TargetBuilder
Compensation thunk, set by {@link onCancel}: runs on cancel iff this target succeeded.
description(text: string): this
Set the human-readable description shown in `zuke --list`.
dependsOn(...targets: Array<TargetBuilder | Group>): this
Declare hard prerequisites. References sibling targets via `this.x`, or a
{@link group} (which expands to every member that has joined it).
partOf(group: Group): this
Join a parallel {@link group}. Members of the same group run concurrently
with one another (each still awaiting its own dependencies) even when the
build is otherwise sequential. Declare the group before the targets that
join it.
inputs(...paths: PathLike[]): this
Declare input files or directories (directories are hashed recursively).
A target with inputs is incremental: it is skipped (reported `cached`)
when its inputs are unchanged since the last successful run and all its
{@link outputs} still exist. Repeatable.
outputs(...paths: PathLike[]): this
Declare output files or directories. A cache hit also requires every output
to still exist, so deleting an output forces a rebuild. Repeatable.
onlyWhen(condition: Condition): this
Run only when `condition` holds; otherwise the target is skipped (and its
dependents still run). The predicate may be async and can read resolved
parameters or the environment. Repeatable — all conditions must hold.
```ts
deploy = target()
.onlyWhen(() => this.environment.value === "production")
.executes(...);
```
effect(name: string, fn: EffectFn): this
Declare a crash-durable effect: `fn` runs only after the intent to run
it has been written to the run record, so a process killed anywhere inside
it leaves evidence that it was owed.
That evidence is what a resume uses to drive it again. Note the precondition
as it stands today: a resume only picks up a run recorded `suspended`, and a
process killed outright leaves its run `running`, so an effect owed by a
killed process is re-driven once something moves that run back to
`suspended` — a reaping sweep, or an operator. An effect owed by a run that
suspended for any other reason is re-driven by the ordinary resume.
The guarantee is at-least-once, not exactly-once: a process that dies
after the side effect but before recording it will repeat the effect. Write bodies that tolerate that, either because repeating is
harmless or because the far side converges (an upsert rather than an
append).
```ts
gate = target().dependsOn(this.checks).always()
.effect("post-gate", async (ctx) => {
await postCheckRun(ctx.outcomeOf("checks")?.status === "succeeded");
});
```
Pin the inputs. A re-drive happens later, sometimes much later, so a
body that looks up "the current value" of anything acts on a world that has
moved on. Read what the effect acts on from durable state instead —
`ctx.state` or `ctx.stateOf(...)`, written by an earlier target — which is
replayed from the record and cannot be overridden from outside.
A parameter is nearly as good and not quite: the record seeds a resume, so
a parameter nobody re-supplies keeps the value the run started with, but a
resume that passes one explicitly overrides it (the only way to re-supply a
secret, since secrets are kept out of the record). For a value that must not
drift across a re-drive, prefer state.
Effects run after the body, in declaration order, and are repeatable. A
target may declare effects and no body at all. Requires a state store, which
is enabled automatically — an intent that cannot be recorded is a target
that fails before its effect runs, by design.
executes(fn: TargetFn): this
Set the target body. May be async.
before(...targets: TargetBuilder[]): this
Run before the listed targets if both are in the plan (soft ordering).
after(...targets: TargetBuilder[]): this
Run after the listed targets if both are in the plan (soft ordering).
triggers(...targets: TargetBuilder[]): this
Pull the listed targets into the plan and run them after this one. The
inverse of {@link dependsOn}: running this target triggers the others.
dependentFor(...targets: TargetBuilder[]): this
Declare this target as a prerequisite of the listed targets — the reverse
of {@link dependsOn}: each listed target gains this one as a dependency,
so this runs before them. Declare the listed targets above this one.
requires(...params: AnyParameter[]): this
Require that the given parameters resolve to a value before this target
runs; otherwise the target fails with a message naming the missing one.
Use it when a target needs a parameter that is optional build-wide.
proceedAfterFailure(): this
Keep running the rest of the build even if this target fails. The build
still reports failure, and this target's own dependents are skipped.
unlisted(): this
Hide this target from `--list` and `--help` (it can still be run by name).
readOnly(): this
Mark this target query-only for MCP: its `run:` tool advertises MCP's
`readOnlyHint` instead of the default `destructiveHint`, and it is exempt
from `--confirm-destructive`. A hint about intent only — the target still
runs its real body — so declare it on targets that inspect rather than
mutate (a status check, a report).
requiresRole(role: string): this
The role an MCP caller must hold to run this target — the per-target half
of authorization (../../docs/mcp.md#authorization).
The built-in roles are ordered `read` < `run` < `operator`, so an operator
satisfies a requirement for `run`; any other name is matched exactly, so an
identity provider's own group (`sre`, `release-manager`) works here without
being ranked into a hierarchy it never agreed to.
Only meaningful when the server authenticates its callers — a build with no
`mcpAuth()`/`mcpIdentity()` is gated by `--allow-run` and `--protect` as
before, and this is inert. It raises the bar for one target; it cannot
lower it, so a target requiring `read` still needs `run` to be executed.
```ts
promote = target().requiresRole("operator").executes(() => deployProd());
```
always(): this
Run this target even after the build has failed — for cleanup, teardown, or
an aggregate that has to report on the failure.
It still waits for its own dependencies, but waits for them to settle
rather than to succeed: a dependency that failed releases it, the same as
one that passed or was skipped. Anything else would make the modifier
unusable for the case it exists for, since a target that depends on the work
it is cleaning up would be held back by exactly the failure that should
trigger it. Use {@link TargetContext.outcomeOf} to see what actually
happened.
A dependency parked at a `.waitsFor(...)` gate is the exception: it has not
settled, so the target waits for the resume rather than reporting on a run
that is still in progress.
The build's overall result is unchanged — an `always` target that passes
does not rescue a failed build. Repeatable conditions/inputs apply.
dryRunnable(): this
Run this target's body under `--dry-run` instead of skipping it, with the
`$` shell in echo mode: each command (awaited or `.spawn()`ed) prints its
resolved argv and returns an empty success without starting a process.
Opt-in, because Zuke can only intercept `$`/{@link "./shell.ts".Command} —
any other side effect a body performs (writing a file, calling an API
directly) still happens under a dry run. Use it for bodies that are
shell-command orchestration, to preview the exact commands a real run would
execute. Without it, a dry run skips the body entirely (the default).
Because an echoed command returns empty stdout and exit code 0, a body
whose control flow or command arguments depend on a command's output
(`await $\`git rev-parse HEAD`.text()`, a `.code()`loop) should branch on the {@link "./executor.ts".TargetContext}`dryRun` flag rather than trust the
echoed result.
cacheKey(fn: () => string | Promise<string>): this
Contribute an extra value to this target's cache fingerprint, beyond its
input files — e.g. a parameter value, tool version, or git commit. The
target is up-to-date only when its inputs and every cache key are
unchanged. The function may be async. Repeatable.
```ts
compile = target()
.inputs("src")
.cacheKey(() => this.configuration.value)
.executes(...);
```
produces(...paths: PathLike[]): this
Declare artifact files/directories this target produces (metadata).
consumes(...targets: Array<TargetBuilder | Group>): this
Depend on the listed targets and consume their artifacts: equivalent to
{@link dependsOn} for ordering, expressing that this target uses what they
{@link produces}.
whenSkipped(behavior: "run-dependencies" | "skip-dependencies"): this
When this target is skipped by an {@link onlyWhen} condition, also skip its
dependencies that no other planned target needs. Because the dependencies
would otherwise run first, the condition is evaluated up front, so it must
not depend on state produced by other targets during the run.
timeout(ms: number): this
Fail the target if its body runs longer than `ms` milliseconds (per attempt).
retry(times: number, delayMs: number): this
Retry the target body up to `times` more attempts on failure, optionally
pausing `delayMs` between attempts. Combined with {@link timeout}, each
attempt is bounded by the timeout.
validateBefore(...validations: Validation[]): this
Run one or more {@link Validation}s before the target body. Each runs in
declaration order; the first to throw fails the target and the body never
runs. Repeatable. A cached/skipped target runs no validations.
```ts
deploy = target()
.validateBefore(this.securityReview) // gate before deploying
.executes(...);
```
validateAfter(...validations: Validation[]): this
Run one or more {@link Validation}s after the target body completes
successfully. Each runs in declaration order; the first to throw fails the
target. Repeatable.
recoverWith(...remediations: Remediation[]): this
Attach one or more {@link Remediation}s that run only if the body fails.
Each is given the failure; if any returns `{ retry: true }`, the executor
re-runs the body and, when it now passes, the target succeeds. This is the
hook the AI fixer in `@zuke/ai` uses for self-healing builds. Repeatable.
```ts
test = target()
.executes(() => DenoTasks.test((s) => s.allowAll()))
.recoverWith(aiFixer((f) => f.provider("claude").apiKey(this.key)));
```
recoverAttempts(times: number): this
The maximum number of fix-then-rerun cycles attempted when the body fails
and {@link recoverWith} remediations are configured (default 1). Each cycle
runs every remediation, then re-runs the body once; the count bounds how
many times that repeats before the failure is final. Clamped to at least 1.
lock(configure: Configure<LockSettings>): this
Hold a cross-run lock while this target runs: only one run may hold
`key` at a time, so a second run that tries to acquire it fails with a
{@link "./state/lock.ts".LockConflictError} naming the current holder. The
lock is released when the target settles — success, failure, or
cancellation — and expires after `options.ttl` as a backstop should the
holder be killed (a live holder renews it as it runs).
`key` may be a thunk, evaluated after parameters resolve, so it can depend
on `this.<param>.value`; compose composite keys with
{@link "./state/lock.ts".lockKey}. Requires a state store (a build that
uses `.lock()` gets a `.zuke/runs` filesystem store by default).
```ts
promote = target()
.lock((s) =>
s.lockKey("deploy", this.repo.value)
.withTtl("4h")
.onConflict((h) =>
`${this.repo.value} is being deployed by ${h.actor} (run ${h.runId}).`))
.executes(...);
```
waitsFor(configure: Configure<WaitSettings>): this
Suspend the run at this target until an external event occurs, then let the
run be resumed later (in a different process) — a settings lambda in the
same style as {@link lock}. The target is a gate (no body): when its
trigger is already satisfied it passes and dependents run; otherwise the
run's state is saved, the run is marked suspended, its independent branches
finish, and the process exits 0. Requires a state store.
```ts
awaitApproval = target()
.dependsOn(this.deploy)
.waitsFor((s) =>
s.on(externalSignal("testing-approved"))
.timeout("72h")
.onTimeout(() => this.rollback));
```
onCancel(compensation: OnCancel): this
Register a compensation target that undoes this target's effect when the
run is later cancelled (via `zuke cancel <run-id>`, an MCP `cancel_run`, or a
timed-out wait). The compensation runs iff this target succeeded — a
target that never ran, was skipped, or failed has nothing to undo. On
cancellation, compensations run in reverse order of the targets that
succeeded, so later work is unwound before the work it built on.
`compensation` is a sibling target, or a thunk returning one (use the thunk
form to reference a target declared below this one — class fields
initialise top-to-bottom). The compensation body receives a normal
{@link TargetContext} whose `state` exposes this target's persisted
metadata, so a deploy that recorded `{ slot: "sit-7" }` in `ctx.state` can be
rolled back from exactly that slot. Compensation failures are recorded but do
not stop the walk (cleanup is maximal). Requires a state store.
On a {@link forEach} sub-target, the compensation is per item: cancel
runs it for every item that had succeeded (or was still in-flight), each with
its own item-scoped context — see the fan-out section of
`docs/orchestration.md`.
```ts
deploy = target()
.executes((ctx) => ctx.state.set({ slot: "sit-7" }))
.onCancel(() => this.rollback);
rollback = target()
.executes((ctx) => tearDown(ctx.state.get().slot)); // reads deploy's meta
```
forEach(items: () => readonly Item[], factory: ForEachFactory<Item>, configure?: Configure<ForEachSettings>): this
Fan out over a runtime list: for each item, build an ordered pipeline of
sub-targets and run them with per-item failure isolation and bounded
concurrency. `items` is a thunk (evaluated when the target runs, so it can
read `this.<param>.value`); `factory` returns a record of sub-targets per
item, each implicitly depending on the one before it. Items run
concurrently, each item's stages sequentially — the pipeline model.
The sub-targets are materialised at run time (named
`parent[item].stage`) — `--list`/`graph` show only the one fan-out node —
and each is a first-class target with its own status in the summary and the
run record. The fan-out target fails if any item's pipeline fails.
A fan-out cannot contain a wait gate: neither the fan-out target itself
nor any stage may use {@link waitsFor} — a materialised sub-target has no
resume path, so the gate would be silently swallowed. Combining them fails
the target with guidance. Gate a fan-out by putting the wait on a separate
target that the fan-out `.dependsOn(...)`.
```ts
deployBatch = target()
.forEach(
() => this.repos.value, // string[]
(repo) => ({
checks: target().executes(() => checkDeployable(repo)),
deploy: target().executes((ctx) => applyToSit(repo, ctx)),
}),
(s) => s.concurrency(3).continueOnItemFailure(),
);
```
class TeamsAnnouncementSettings extends AnnouncementSettings
Fluent settings for {@link AnnounceTasksApi.teams}. Bot mode
(`.bot().token(t).team(id).channel(c)`) posts through Microsoft Graph with a
bearer token.
team(team: string): this
Set the Teams team (group) id to post to in bot mode (Microsoft Graph).
override protected payload(): Record<string, unknown>
Render the Teams webhook payload.
override protected sendBot(): Promise<void>
Post the announcement through Microsoft Graph in bot mode.
class ToolInstallSettings
Fluent settings for installing a release tool. Configure it in a
settings-lambda (`(s) => s.name(...).url(...)`), the same shape as Zuke's tool
wrappers. `name` and `url` are required; everything else is optional and
mirrors {@link InstallReleaseOptions}.
name_?: string
The tool name, and the installed filename. Set by {@link name}.
url_?: (platform: Platform) => string
Resolves the per-platform download URL. Set by {@link url}.
destDir_?: PathLike
Install directory (overrides the toolchain's). Set by {@link destDir}.
archive_?: DownloadFormat | ((platform: Platform) => DownloadFormat)
Download format (or a per-platform resolver). Set by {@link archive}.
binaryPath_?: string | ((platform: Platform) => string)
The binary's path within an archive (or a resolver). Set by {@link binaryPath}.
strip_?: number
Leading path components to strip on a tree install. Set by {@link strip}.
bins_?: string[]
Executable bins within a tree install. Set by {@link bins}.
checksum_?: string | ((platform: Platform) => string)
Expected SHA-256 (or a per-platform resolver). Set by {@link checksum}.
platform_?: InstallPlatform
The platform to resolve for. Set by {@link platform}.
download_?: DownloadFn
The download implementation. Set by {@link download}.
name(name: string): this
The tool name; also the installed binary's filename (`.exe` on Windows).
url(resolve: (platform: Platform) => string): this
Resolve the download URL for the target {@link Platform}.
destDir(dir: PathLike): this
The directory to install the binary into (created if missing).
archive(format: DownloadFormat | ((platform: Platform) => DownloadFormat)): this
Treat the download as a `"tar.gz"` or `"zip"` to unpack (default `"raw"`,
the bare binary). Pair with {@link binaryPath} for the binary inside. Pass a
`(platform) => format` resolver when the format is per-platform, as it is
for most Go and Rust releases — `.tar.gz` on Linux and macOS, `.zip` on
Windows (see {@link InstallReleaseOptions.archive}).
binaryPath(path: string | ((platform: Platform) => string)): this
For an archive, the binary's path within it (defaults to the name). Also
accepts a `(platform) => path` resolver, for the usual case of a `.exe`
inside the Windows archive only.
strip(components: number): this
For a tree install ({@link ToolTasksApi.installTree} / {@link Toolchain.tree}),
drop this many leading path components while unpacking — `1` unwraps a
release tarball's `tool-v1.2.3/` directory. Ignored by a single-binary install.
bins(...paths: string[]): this
For a tree install, the paths (relative to the stripped root) to mark
executable on POSIX — a runtime's `bin/node`, `bin/npm`, … Ignored by a
single-binary install.
checksum(sha256: string | ((platform: Platform) => string)): this
The expected SHA-256 (hex) of the downloaded artifact — verifies and caches
the install. Pass a `({ os, arch }) => string` resolver to pin it per
platform (see {@link InstallReleaseOptions.checksum}).
platform(platform: InstallPlatform): this
Resolve for a specific platform instead of the host (a foreign install).
download(fn: DownloadFn): this
Override the downloader (defaults to an HTTPS download; a test seam).
options_(fallbackDestDir: PathLike): InstallReleaseOptions
Build the {@link InstallReleaseOptions}, using `fallbackDestDir` when no
{@link destDir} was set. Throws if a required field is missing.
treeOptions_(fallbackDestDir: PathLike): InstallTreeOptions
Build the {@link InstallTreeOptions} for a tree install, using
`fallbackDestDir` when no {@link destDir} was set. A tree always ships
packed, so the archive defaults to `"tar.gz"` and `"raw"` is rejected. Throws
if a required field is missing.
class Toolchain
A declared set of external tools. Add tools with {@link Toolchain.tool} (a
{@link ToolInstallSettings} lambda) and fetch them all with
{@link Toolchain.install}. Build one with {@link toolchain}.
tool(configure: Configure<ToolInstallSettings>): this
Add a release tool, configured through a settings-lambda. Chainable.
tree(configure: Configure<ToolInstallSettings>): this
Add a multi-file runtime tree (see {@link ToolTasksApi.installTree}),
configured through a settings-lambda with `.strip(...)`/`.bins(...)`. In
{@link install}'s result its entry is the extracted tree's root — a callable
{@link AbsolutePath}, so `root("bin")` is the directory to put on `PATH`.
Chainable.
npm(spec: NpmToolSpec): this
Add an npm-registry package to provision as a version-pinned tool —
installed under `<destDir>/npm/<name>@<version>` and keyed in
{@link install}'s result by its {@link NpmToolSpec.name}. See
{@link installNpmTool}. Chainable.
get tools(): readonly ToolInstallSettings[]
The configured release tools, in declaration order.
get trees(): readonly ToolInstallSettings[]
The configured runtime trees, in declaration order.
get npmTools(): readonly NpmToolSpec[]
The configured npm-package tools, in declaration order.
async install(options: ToolchainInstallOptions): Promise<Map<string, AbsolutePath>>
Install every declared tool concurrently — reusing a cached copy where a
release tool's or tree's pinned checksum, or an npm tool's `name@version`
marker, matches — and return a map of tool name to installed
{@link AbsolutePath}. A {@link tree}'s entry is its extracted root directory.
class WaitSettings
Fluent configuration for {@link TargetBuilder.waitsFor}:
`.waitsFor((s) => s.on(externalSignal("approved")).timeout("72h"))`. Set the
{@link WaitSettings.on | trigger}, an optional {@link WaitSettings.timeout},
and an optional {@link WaitSettings.onTimeout} disposition. The lambda runs
when the target is reached, so the trigger may read `this.<param>.value`.
trigger_?: WaitTrigger
The trigger deciding when the wait is satisfied; set by {@link on}.
timeout_?: string | number
The deadline duration (string or ms); set by {@link timeout}.
onTimeout_?: OnTimeout
The timeout disposition thunk; set by {@link onTimeout}.
on(trigger: WaitTrigger): this
Set the {@link "./wait.ts".WaitTrigger} the wait is satisfied by.
timeout(duration: string | number): this
Give the wait a deadline (a duration like `"72h"` or milliseconds).
onTimeout(disposition: OnTimeout): this
What to do when the deadline passes: a thunk returning a sibling
compensation target (a thunk, so it can reference a target declared below
this one), or the string `"fail"` / `"cancel-run"`. Defaults to `"fail"`.
interface AbsolutePath
An immutable, absolute filesystem path with a fluent API.
Build one with {@link absolutePath}. The value itself is callable —
`path(...segments)` returns a new path with those segments appended — and the
equivalent {@link AbsolutePath.join} method does the same. `toString()`
yields the path string, so an `AbsolutePath` can be interpolated into the
`$` shell helper and passed straight to tool `args()`.
readonly path: string
The normalised path string (forward slashes, `.`/`..` resolved).
readonly name: string
The final segment, e.g. `"main.ts"` (or `""` for a root).
readonly stem: string
The final segment without its extension, e.g. `"main"` (`".gitignore"` has none).
readonly extension: string
The extension including the dot, e.g. `".ts"` (or `""` if none).
readonly isRoot: boolean
Whether this path is a filesystem root (`"/"`, `"C:/"`).
join(...segments: string[]): AbsolutePath
Append path segments, returning a new path.
parent(): AbsolutePath
The parent directory; a root is its own parent.
relativeTo(base: AbsolutePath | string): string
This path expressed relative to `base` (e.g. `"src/main.ts"`, `"../lib"`).
equals(other: AbsolutePath | string): boolean
Whether `other` resolves to the same normalised path.
toString(): string
The normalised path string.
interface AffectedOptions
Configure {@link ExecuteOptions.affected}: the base revision and diff seam.
base?: string
The git revision to diff against. Defaults to `HEAD` (uncommitted changes).
changedFiles?: ChangedFilesFn
How to list changed files. Defaults to {@link gitChangedFiles}.
interface AnnounceTasksApi
The shape of {@link AnnounceTasks}.
slack(configure?: Configure<SlackAnnouncementSettings>): Promise<void>
Announce to Slack. Configure a {@link SlackAnnouncementSettings}: set a
`.webhook(url)` (or `.bot().token(t).channel(c)` for the Web API) and the
message content.
teams(configure?: Configure<TeamsAnnouncementSettings>): Promise<void>
Announce to Microsoft Teams. Configure a {@link TeamsAnnouncementSettings}:
set a `.webhook(url)` (or `.bot().token(t).team(id).channel(c)` to post
through Microsoft Graph) and the message content.
discord(configure?: Configure<DiscordAnnouncementSettings>): Promise<void>
Announce to Discord. Configure a {@link DiscordAnnouncementSettings}: set a
`.webhook(url)` (or `.bot().token(t).channel(c)` to post through the REST
API with a bot token) and the message content.
interface Announcement
A structured announcement assembled by an {@link AnnouncementSettings}.
text: string
The main message body.
title?: string
An optional heading rendered above the message.
level: AnnouncementLevel
The outcome level driving the accent colour and icon.
fields?: AnnouncementField[]
Labelled details rendered beside the message.
link?: AnnouncementLink
A clickable action rendered with the announcement.
interface AnnouncementField
A labelled detail rendered beside the message (e.g. a version or environment).
name: string
The field's label.
value: string
The field's value.
interface AnnouncementLink
A clickable action rendered with the announcement (e.g. a link to a release).
text: string
The link's visible text.
url: string
The link's target URL.
interface AnyParameter
The non-generic view of a parameter, used by discovery and resolution.
name_?: string
Property name, assigned during discovery. Undefined until then.
readonly description_?: string
Human-readable description shown in `--help`/`--list`.
readonly kind_: ParamKind
The runtime value kind.
readonly required_: boolean
Whether a value must be supplied (no default).
readonly options_?: readonly string[]
The allowed string choices, if restricted with {@link Parameter.options}.
readonly envName_?: string
An explicit environment variable name override.
readonly flagName_?: string
An explicit CLI flag name override, without the leading dashes.
readonly hasFallback_: boolean
Whether the parameter has a declared default value.
readonly secret_: boolean
Whether the value is sensitive and should be masked in CI output.
readonly array_: boolean
Whether the value is a comma-separated / repeatable list (`.array()`).
readonly source_?: SecretSource
A provider that resolves the value when no flag/env supplied one.
readonly default_?: string
The declared default rendered as a string (an array default is joined with
commas), or `undefined` when the parameter has no default or an empty-list
one. For display in tool schemas and `--list`; never a secret value.
resolve_(raw: string | undefined): void
Resolve from a raw input (or `undefined` when none was supplied).
isSet_(): boolean
Whether the parameter resolved to a defined value (used by `.requires()`).
stringValue_(): string | undefined
The resolved value as a string, or `undefined` if unset (for masking).
interface BearerChallenge
The parts of a `WWW-Authenticate: Bearer` challenge Zuke emits.
metadataUrl?: string
Absolute URL of the protected resource metadata document (RFC 9728).
scopes?: readonly string[]
Scopes required for the attempted operation — all of them, in one go.
error?: ChallengeError
The failure, omitted for a request that presented no credentials.
description?: string
Developer-facing explanation; never shown to an end user.
interface BrowserTasksApi
The shape of {@link BrowserTasks}.
open(url: string, configure?: Configure<BrowserOpenSettings>): Promise<CommandOutput>
Open `url` in the default browser. Resolves when the opener process exits
(browsers detach, so this is launch, not page load).
```ts
await BrowserTasks.open("https://github.com/zuke-build/zuke");
```
interface BuildCache
The incremental cache used by the executor to skip up-to-date targets.
upToDate(target: TargetBuilder): Promise<boolean>
Whether `target` is up-to-date: it declares inputs, their fingerprint
matches the last successful run, and every declared output still exists.
record(target: TargetBuilder): Promise<void>
Record `target`'s current fingerprint after a successful run.
save(): Promise<void>
Persist the store if anything changed.
interface BuildDescriptor
A versioned snapshot of one registered build. Persisted as JSON; a registry's
opaque `version` (an ETag / content hash) drives compare-and-swap writes so
two registrations racing at the same version cannot both win.
id: string
Stable id of the build (its class name, unless overridden).
name: string
Human-facing build name (the build class name).
location: BuildLocation
Where the build lives, so a runner can launch it.
surface: CliDescription
The build's CLI surface, exactly as {@link "../describe.ts".describeCli} produces it.
actor: string
Who registered the build (a resolved actor; secrets never appear here).
createdAt: string
ISO-8601 timestamp when the build was first registered.
updatedAt: string
ISO-8601 timestamp of the last registration write.
interface BuildQuery
Filters for {@link "./registry.ts".BuildRegistry.listBuilds}; all fields optional.
name?: string
Keep only builds whose `name` equals this.
since?: string
Keep only builds registered at or after this ISO-8601 timestamp.
interface BuildRegistry
Pluggable persistence for {@link BuildDescriptor}s. `version` is an opaque
token (an ETag or content hash) used for optimistic concurrency: a write only
lands if the stored version still matches the one the writer last read, so two
registrations racing at the same version cannot both win.
getBuild(id: string): Promise<{ descriptor: BuildDescriptor; version: string; } | null>
Fetch a build and its current version, or `null` if it is not registered.
register(descriptor: BuildDescriptor, expectedVersion: string | null): Promise<PutBuildResult>
Write `descriptor` only if the stored version equals `expectedVersion`
(`null` meaning "must not exist yet"). Returns the new version, or a conflict
when the stored version has moved on — the caller re-reads and retries.
deregister(id: string): Promise<void>
Remove a registered build by id; a missing build is not an error.
listBuilds(query: BuildQuery): Promise<BuildSummary[]>
List registered builds matching `query`, newest first (by `createdAt`, then `id`).
interface BuildResult
Result passed to the {@link Build.onFinish} lifecycle hook.
ok: boolean
Whether every executed target succeeded (also `true` for a suspended run).
executed: string[]
Names of the targets that ran, in execution order.
error?: unknown
The error that aborted the run, if any.
suspended?: boolean
True when the run suspended at a `.waitsFor(...)` gate rather than
finishing — its state is saved and it can be resumed later. The process
still exits 0.
cancelled?: boolean
True when the run was cancelled (via `options.signal` / Ctrl-C, or by
another process running `zuke cancel`) rather than failing on its own.
Its compensations have run and the record is `cancelled`. `ok` is `false`.
runId?: string
The run's id, when a run identity was established (always, in practice —
every {@link "./executor.ts".execute} generates one). Lets the caller point
a follow-up (`zuke runs show`, `zuke cancel`) at this run.
interface BuildSummary
A compact registry listing row, returned by {@link "./registry.ts".BuildRegistry.listBuilds}.
id: string
The build id.
name: string
The build name.
actor: string
Who last registered the build.
createdAt: string
ISO-8601 first-registration timestamp.
updatedAt: string
ISO-8601 timestamp of the last registration write.
interface CancelOptions
Options for {@link cancelRun}.
runId: string
The id of the run to cancel.
stateStore?: StateStore | false
Durable store the run lives in. Defaults to the same resolution as a normal
run (explicit → `stateStore()` override → env → `.zuke/runs`); cancel always
needs one.
actor?: string
Who to attribute the cancellation to in the audit trail.
readEnv?: (name: string) => string | undefined
Reads an environment variable (secrets re-resolve from here for compensations).
silent?: boolean
Suppress progress output.
reporter?: Reporter
Custom reporter; overrides `silent`.
also?: string[]
Extra compensation target names to run first (a timed-out wait whose
`onTimeout` names a specific compensation target routes through here).
expiredWait?: { target: string; message: string; }
The wait whose expired deadline caused this cancellation, when one did:
the target's name and the message describing the miss. Recorded on that
target's row as it settles, so the terminal record still says why.
interface CancelResult
The outcome of {@link cancelRun}.
runId: string
The run that was cancelled.
status: RunStatus
The run's status after cancelling (`cancelled`, or the terminal status on a no-op).
noop: boolean
True when the run was already terminal and nothing was done.
compensated: string[]
Names of compensation targets whose bodies ran.
failures: CompensationFailure[]
Compensations that threw (recorded, non-fatal).
interface CiActionRef
A pinned action reference, and the version its commit corresponds to.
The version is emitted as a trailing `# v1.2.3` comment, which is not
decoration: Dependabot reads it to know which version a pinned SHA is, and
rewrites both together when it bumps. A generated workflow that dropped it
would leave automated bumps with no version to track.
ref: string
The pinned reference, `owner/repo@<sha>`.
version?: string
The version the SHA corresponds to, e.g. `v7.0.1`.
interface CiBootstrap
The single step every GitHub job starts with: the `zuke-build/zuke` composite
action, which hardens the runner, checks the repository out, and installs
Deno if asked — the three steps a Zuke job used to spell out separately.
It is the default because those three are not three decisions. They are one
prelude whose parts only work in one order, and writing them out three times
per workflow meant three pinned SHAs to keep current in every generated file
rather than one, inside an action that is itself versioned and tested.
The {@link CiHardenRunner} and {@link CiCheckout} options are still how a job
configures it — they become the action's inputs. What changes is how many
steps that renders, not what a build declares. A job that opts out of either
with `harden: false` or `checkout: false` falls back to the separate steps,
since one action cannot do half of itself.
action?: CiUses
The pinned action reference. Defaults to the release this version of Zuke
was built against, or to whatever a `pins` resolver returns for
{@link ZUKE_ACTION} — which is the form to prefer, because a pin baked into
a published package goes stale between releases and a bot's bump to the
generated file would be reverted by the next regeneration.
denoVersion?: string
Install this Deno version, for a job that runs `deno` directly rather than
through the `./zuke` launcher (which bootstraps its own).
name?: string
The step name. Defaults to `"Harden and check out with Zuke"`.
interface CiCheckout
The repository checkout, emitted as an `actions/checkout` step after any
{@link CiHardenRunner} and before the job's own steps. Like hardening, the
pinned {@link action} reference is required.
action?: CiUses
The pinned action reference. Omit it when the file supplies a `pins` resolver.
persistCredentials?: boolean
Keep the token in git config so a later step can push. Defaults to `false`:
a job that does not push should not leave a credential behind.
ref?: string
The ref to check out. Defaults to the one that triggered the run.
fetchDepth?: number
How much history to fetch. `0` means the full history — needed by anything
that walks past commits, such as a secret scan.
name?: string
The step name. Defaults to `"Checkout"`.
interface CiConcurrency
A concurrency group: at most one run per group, optionally cancelling the prior one.
group: string
The group key (often interpolated, e.g. `ci-${{ github.ref }}`).
cancelInProgress?: boolean
Cancel an in-progress run in the same group when a new one starts.
interface CiFileSpec
A CI configuration file declared on a build: a pipeline bound to a path.
provider?: CiProvider
The provider to render for. Defaults to `"github"`, which is what the
`.github/workflows` default path assumes anyway.
pins?: CiPinResolver
Resolves each action's pinned reference by name, so hardening and checkout
can be requested without restating a SHA.
With a resolver, every job is hardened and checked out by default — the
prelude nearly every job needs — and a job opts out with `harden: false` or
adjusts the policy without naming the action again.
path?: string
The output path (relative to the working directory).
Defaults to the field name the file is declared on, in the provider's
conventional directory: `releaseWorkflow = cicd({...})` writes
`.github/workflows/release.yml`. A trailing `Workflow`/`Ci`/`Yaml` is
dropped, and camelCase becomes kebab-case. Recovering the name from the
field is how `target()` works too, so a workflow needs no more ceremony
than a target.
Falls back to the provider's single conventional file
(`.github/workflows/ci.yml`, `.gitlab-ci.yml`, …) when the name is not
available — a file built outside a build class.
pipeline?: CiPipeline
The pipeline to render. Defaults to a single `build` job that runs the build.
fanOut?: boolean | FanOutOptions
Fan the build's targets out into one CI job per target, wired by their
dependencies (see {@link fanOutPipeline}). `true` uses the defaults; pass
{@link FanOutOptions} to customise. When set, {@link pipeline} supplies the
pipeline-level fields (name, triggers, …) and its `jobs` are ignored.
invokes?: readonly CiInvokes[]
The targets this workflow runs — one job each, in place of hand-written
{@link CiPipeline.jobs}.
This is the intended way to declare a workflow. A job is almost entirely
implied by its target, so naming the targets is usually the whole
declaration: the id, the display name, the `./zuke <target>` command, and
the `needs:` edges between jobs all come from the build graph. Pass a
{@link CiInvocation} instead of a bare target only for what the runner
decides rather than the build — a matrix, token scopes, an egress policy.
Each job runs its target's whole subgraph in one process, exactly as
`./zuke <target>` does locally — so dependencies inside a target run
in-process and need no cache to share their output. Use {@link fanOut}
instead to give every target in the graph its own job, which does need a
remote cache.
Targets are passed as references (`this.ci`), not names, so a rename is a
compile error rather than a workflow that silently runs nothing. As with
`dependsOn`, that means the declaration must appear below the targets it
invokes — class fields initialise top-to-bottom, so a forward reference is
`undefined`. Declaring workflows last is the simplest way to satisfy it.
interface CiHardenRunner
Runner hardening, emitted as a `step-security/harden-runner` step before
anything else in the job.
This cannot move into the build: the point of the step is to install an egress
control before build code runs, so a build that set it up itself would be
the very code it is meant to contain. Generating it is the next best thing —
the policy is declared in one place, in code, next to the job it protects.
The pinned {@link action} reference is required rather than defaulted. A
default would mean either a floating tag (which supply-chain scanners reject
as an unpinned use) or a commit SHA baked into `@zuke/core` that goes stale
between releases. Passing it makes the pin the caller's — and lets a build
source it from wherever its bumps are automated.
action?: CiUses
The pinned action reference, e.g. `step-security/harden-runner@<sha>`.
Omit it when the file supplies a {@link CiFileSpec.pins} resolver, which is
the better arrangement: the SHA is then stated once for the repository
rather than at every use.
egress?: "audit" | "block"
`"audit"` records outbound connections; `"block"` drops everything outside
{@link allowedEndpoints}. Defaults to `"audit"` — the safe choice for a job
with no secrets, where a false block would be worse than an unrecorded call.
allowedEndpoints?: string[]
The hosts a `"block"` policy permits, as `host:port`. Ignored when auditing.
Every entry should be traceable to something the build actually reaches.
name?: string
The step name. Defaults to `"Harden the runner"`.
interface CiInvocation
One job's worth of a workflow, derived from a target.
A job's shape is almost entirely implied by the target it runs: the id and
display name come from the target, the command is `./zuke <target>`, and the
`needs:` edges come from the target's `dependsOn`. So an invoked target
usually needs nothing said about it at all — pass the target and the job is
generated.
The fields here are the residue that genuinely cannot be inferred, because
they are properties of the runner rather than of the work: which OS matrix
to fan out over, which token scopes the job needs, how much egress to permit,
how long to allow. Set one only when the default is wrong.
target: TargetBuilder
The target this job runs.
id?: string
Override the job id (defaults to the target's name, CI-sanitised).
name?: string
Override the display name (defaults to the target's description, else its name).
runsOn?: string
The runner, when it differs from the pipeline default.
matrix?: Record<string, Array<string | number>>
A build matrix — fanning one target out over several OSes, say.
failFast?: boolean
Let the other matrix legs finish when one fails.
permissions?: Record<string, string>
The token permissions this job needs (see {@link CiJob.permissions}).
timeoutMinutes?: number
Fail the job after this many minutes.
harden?: CiHardenRunner | false
Harden this job's runner, overriding the pipeline default.
checkout?: CiCheckout | false
Check out in this job, overriding the pipeline default.
bootstrap?: CiBootstrap | false
The prelude action for this job, overriding the pipeline default.
if?: string
A condition gating the job.
env?: Record<string, string>
Environment variables for the target's own step — where a secret is mapped
in, e.g. `{ GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" }`.
after?: readonly TargetBuilder[]
Extra `needs:` edges beyond those implied by the target's `dependsOn`. Use
it to order two invoked targets that are independent in the build graph but
must not run concurrently in CI.
before?: CiStep[]
Steps to run before the target, for something no target can do (see below).
then?: CiStep[]
Steps to run after the target.
steps?: CiStep[]
Replace the generated `./zuke <target>` step entirely. The escape hatch of
last resort — prefer {@link before}/{@link then}, and prefer moving the work
into the target over either.
interface CiJob
A job: a named unit of work with steps, optionally fanned out by a matrix.
id?: string
Stable identifier, used as the job key and as a dependency target. Defaults to `"build"`.
name?: string
Human-readable job name.
runsOn?: string
The runner. Interpreted per provider: a GitHub runner label and Azure
`vmImage` (default `ubuntu-latest`), or a GitLab Docker image (runner
default when omitted). Ignored when a matrix defines `os` on GitHub.
needs?: string[]
Other jobs (by {@link id}) that must finish before this one.
matrix?: Record<string, Array<string | number>>
A build matrix: each key fans out over its values.
failFast?: boolean
Let the other matrix legs finish when one fails (`fail-fast: false`). Default
GitHub behaviour cancels them, which hides whether a failure is
platform-specific — the thing a cross-OS matrix exists to answer.
permissions?: Record<string, string>
The token permissions this job's `GITHUB_TOKEN` carries. Set it per job
rather than pipeline-wide so a job holds only what it needs — the isolation
that lets one job push commits while another only reads. GitHub only.
harden?: CiHardenRunner | false
Harden the runner before this job's steps. Overrides
{@link CiPipeline.harden}; pass `false` to opt this job out of a
pipeline-wide default.
checkout?: CiCheckout | false
Check the repository out before this job's steps. Overrides
{@link CiPipeline.checkout}; pass `false` to opt out.
bootstrap?: CiBootstrap | false
The prelude action for this job. Overrides {@link CiPipeline.bootstrap};
pass `false` to render hardening and checkout as separate steps instead.
env?: Record<string, string>
Environment variables for the job.
if?: string
A condition gating the job. A raw provider expression: GitHub `if:`, Azure
`condition:`. Ignored on GitLab. Use it to e.g. skip forked pull requests.
timeoutMinutes?: number
Fail the job if it runs longer than this many minutes.
concurrency?: CiConcurrency
A concurrency group for this job alone (GitHub only), as opposed to the
pipeline-level {@link CiPipeline.concurrency}. A job the `if:` skips never
enters its group, whereas a run whose every job is skipped still enters the
pipeline's — and, with `cancelInProgress`, cancels the run it shares the
group with. Use it when a trigger can fire runs that do no work.
steps?: CiStep[]
The steps to run, in order. Defaults to a single step that runs the build.
interface CiPipeline
A complete, provider-agnostic CI pipeline.
name?: string
The pipeline name. Defaults to `"CI"`.
triggers?: CiTriggers
When it runs. Defaults to push and pull request on `main`; pass an empty
object (`{}`) for a pipeline triggered only by external means.
permissions?: Record<string, string>
Workflow-level token permissions (GitHub only), e.g.
`{ contents: "read", "pull-requests": "write" }`. Ignored elsewhere.
Defaults to `{ contents: "read" }` — least privilege, and what a workflow
that only reads the repository needs. A job that needs more declares it, so
the wider scope sits next to the job that justifies it. Pass `{}` for no
permissions at all, which is stricter than the default rather than absent.
concurrency?: CiConcurrency
Limit concurrent runs (GitHub only). Ignored elsewhere.
harden?: CiHardenRunner
Harden every job's runner, unless a job overrides it or opts out with
`harden: false`. Declared once here rather than repeated per job, since the
policy is usually uniform across a workflow. GitHub only.
checkout?: CiCheckout
Check the repository out in every job, unless a job overrides it or opts out
with `checkout: false`. GitHub only.
bootstrap?: CiBootstrap | false
The prelude action every job starts with, unless a job says otherwise with
`bootstrap: false`. Defaults to the `zuke-build/zuke` action; see
{@link CiBootstrap}. GitHub only.
jobs?: CiJob[]
The jobs to run. Defaults to a single `build` job that runs the build.
interface CiStep
A single step in a job.
name?: string
Human-readable step name.
id?: string
A stable identifier for the step, so later steps can read its outputs
(`${{ steps.<id>.outputs.x }}`). GitHub only.
if?: string
A condition gating this step — a raw provider expression, e.g.
`runner.os == 'Windows'` or `always()`. GitHub only.
shell?: string
The shell to run `run` with (`bash`, `pwsh`, `sh`, …). Omit for the runner's
default, which differs per OS. GitHub only.
continueOnError?: boolean
Continue the job even when this step fails (`continue-on-error`). GitHub only.
run?: string
A shell command to run. Portable across all providers.
uses?: CiUses
A GitHub Action reference (e.g. `actions/checkout@v4`). Rendered only for
GitHub; skipped for GitLab and Azure.
with?: Record<string, string>
Inputs for a {@link uses} Action (GitHub only).
env?: Record<string, string>
Environment variables for this step. Rendered as `env:` on GitHub Actions
and on Azure Pipelines `script` steps; ignored on GitLab (which sources
variables from project settings, not the job YAML).
interface CiSyncOptions
Filesystem seams for {@link syncCiFiles} (overridable for tests).
check?: boolean
Verify instead of write: report an out-of-date file as `stale` rather than
overwriting it. Intended for CI, where committed config must match the build.
read?: (path: string) => Promise<string | null>
Read a file's contents, or `null` when it does not exist.
write?: (path: string, content: string) => Promise<void>
Write a file, creating parent directories as needed.
interface CiSyncResult
The outcome of syncing one {@link CiFile}.
path: string
The file's path.
status: CiSyncStatus
Whether it was written, already current, or (in check mode) out of date.
interface CiTriggers
When the pipeline runs.
push?: string[]
Branches whose pushes trigger the pipeline. An empty array means every
branch (no filter); omit the field to disable the push trigger.
pullRequest?: string[]
Branches whose pull/merge requests trigger the pipeline. An empty array
means every branch (no filter); omit the field to disable the trigger.
pullRequestTypes?: string[]
Which pull-request activity types fire the pipeline, on top of the branch
filter — GitHub's default is `opened`, `synchronize`, `reopened`. Add
`edited` when a gate reads the pull request's own description, since editing
it changes what a check should see without pushing a commit. GitHub only.
manual?: boolean
Allow manual runs (workflow dispatch / web).
issueComment?: string[]
Run when a comment is created, edited, or deleted on an issue or a pull
request (`issue_comment`), filtered to these activity types — an empty
array means every type. A comment on a pull request arrives as an issue
comment too, which is what lets a maintainer's comment start a job; the job
runs on the default branch, so its `if:` must decide who may start it.
GitHub only.
branchProtectionRule?: boolean
Run when a branch protection rule is created, edited, or deleted
(`branch_protection_rule`) — a supply-chain scan wants to re-score when the
repository's own protections change. GitHub only.
schedule?: ScheduleEntry[]
Timezone-aware scheduled runs. Each entry is a 5-field cron in an optional
IANA timezone (`{ cron: "30 9 * * 1-5", tz: "Europe/Sofia" }`). Fully
supported on GitHub (compiled to UTC crons, with a generated guard step
for daylight-saving zones) and Azure (native `schedules:`, UTC/fixed
offset only); ignored on GitLab and Bitbucket, whose schedules are
configured in the provider UI, not in-file. See {@link "./ci_schedule.ts"}.
interface CliCommandInfo
A reserved command (`graph`, `generate-ci`, `completions`).
readonly name: string
The command word.
readonly description: string
One-line summary.
interface CliDescription
A build's full CLI surface, suitable for JSON serialization.
readonly commands: CliCommandInfo[]
The reserved positional commands.
readonly flags: CliFlagInfo[]
The built-in option flags.
readonly targets: CliTargetInfo[]
The build's targets, in declaration order.
readonly parameters: CliParameterInfo[]
The build's declared parameters, in declaration order.
interface CliFlagInfo
A built-in option flag.
readonly name: string
The flag, with leading dashes.
readonly description: string
One-line summary.
interface CliParameterInfo
A parameter declared on the build.
readonly name: string
The parameter's property name — the key an MCP tool call and `execute`'s
`params` map use (e.g. `skipE2e`). Distinct from {@link flag}, which is its
kebab-case form.
readonly flag: string
The CLI flag (without leading dashes), e.g. `skip-e2e`.
readonly description: string
The parameter's description, or `""` when none was set.
readonly required: boolean
Whether a value is required.
readonly kind: "string" | "number" | "boolean"
The parameter's value kind.
readonly boolean: boolean
Whether the flag is a value-less boolean.
readonly array: boolean
Whether repeated flags accumulate into a list.
readonly options: string[]
The allowed values, when the parameter is constrained to a set.
readonly default?: string
The declared default rendered as a string, when the parameter has one.
interface CliTargetInfo
A target declared on the build.
readonly name: string
The target's name (its field name on the build).
readonly description: string
The target's description, or `""` when none was set.
readonly dependsOn: string[]
The names of its direct dependencies, in declaration order.
readonly default: boolean
Whether this is the conventional `default` target.
readonly unlisted: boolean
Whether the target is hidden from `--list` (still runnable by name).
interface CompensationFailure
A compensation that threw during the cancel walk (recorded, non-fatal).
target: string
The compensation target that failed.
forTarget: string
The original target whose compensation this was.
error: string
The failure message.
interface ConditionContext
The context a condition receives.
Deliberately narrower than a {@link TargetContext}. A condition on a
`.whenSkipped("skip-dependencies")` target is evaluated before the run
starts, to decide what the run prunes — at which point the run's identity,
its durable state handles, and its cancellation signal do not exist yet. This
type carries only what is available at every moment a condition can be
called.
readonly target: string
Dotted name of the target this condition gates.
plan(): RunPlan
The resolved shape of this run — which targets it plans, and how they
relate. Lets a condition gate on the graph ("only when `deploy` was asked
for") rather than only on the environment.
Reports the plan, not the outcome, which matters most here: a condition can
be one of the things deciding what runs, so the plan deliberately does not
claim to know what will execute. See {@link "./run_plan.ts".RunPlan}.
interface CopyOptions
Options for {@link FileTasksApi.copy}.
overwrite?: boolean
Overwrite an existing destination file (default `true`).
interface CreateDirectoryOptions
Options for {@link FileTasksApi.createDirectory}.
recursive?: boolean
Create parent directories as needed (default `true`).
interface DeclaredEffect
One effect declared on a target: its name and its body.
name: string
The effect's name, unique within the target and stable across runs.
fn: EffectFn
The body run once the intent is durably recorded.
interface DescribeCliOptions
Options for {@link describeCli}.
omitSecrets?: boolean
Drop `.secret()` parameters from the surface (used for registry descriptors).
interface EffectContext extends TargetContext
The context an effect body receives: the target's own context, plus which
effect this is and whether it has been driven before.
readonly effect: string
The effect's declared name.
readonly redriven: boolean
True when a previous attempt at this effect already committed its intent —
so its side effect may have happened, wholly or partly, and this run is
repeating it.
Effects are at-least-once. A body that can tell the difference should say
so in what it writes, rather than assume it is the first to get here.
interface EffectState
The durable intent-and-completion row for one of a target's effects.
There is no idempotency key here. An effect is identified by where it sits —
the run, the target, and its declared name — which the record already spells
out structurally, so a key would be a second spelling of the same fact and a
place for a secret to end up.
status: EffectStatus
How far the effect got.
intentAt: string
ISO-8601 time the intent was committed — always before the body ran.
settledAt?: string
ISO-8601 time it settled, if it has.
error?: string
The failure message when `status` is `failed`.
attempts: number
How many times the body has been driven. Above one means it was re-driven.
interface ExecuteOptions
Options for {@link execute}.
silent?: boolean
Suppress all banner/summary output (used by tests).
banner?: boolean
Print the opening banner — the wordmark (off CI), the framework, runtime
and platform versions, and this run's id and directory. Defaults to on;
`false` is the CLI's `--no-banner`, and `ZUKE_NO_BANNER` turns it off from
the environment.
Only ever printed when the run writes to the real console. A `silent` run
or one given its own {@link ExecuteOptions.reporter} is embedding the
executor, and gets no banner whatever this says.
reporter?: Reporter
Custom reporter; overrides `silent`.
plugins?: Plugin[]
Lifecycle observers invoked alongside the build's own hooks, in order.
Lets third-party packages report/time/notify without subclassing the build.
skip?: string[]
Target names to skip even if they appear in the plan (CLI `--skip`).
parallel?: boolean | number
Run independent targets concurrently. `false`/omitted runs sequentially in
deterministic order; `true` uses the host's CPU count; a number sets the
maximum concurrency. Dependencies still complete before their dependents.
cache?: boolean | BuildCache
Incremental caching: skip targets whose declared {@link TargetBuilder.inputs}
are unchanged since the last successful run (and whose outputs still exist).
Defaults to on; pass `false` to disable (CLI `--no-cache`). A {@link
BuildCache} may be supplied directly (used in tests).
remoteCache?: RemoteCacheStore | false
A {@link RemoteCacheStore} that shares target {@link TargetBuilder.outputs}
across machines: a local cache miss restores outputs from it, and a
successful run uploads them. `false` disables it (CLI `--no-remote-cache`).
When omitted, the build's `remoteCache()` override is used, falling back to
the `ZUKE_REMOTE_CACHE_*` environment variables. Ignored when `cache` is a
supplied {@link BuildCache} or is `false`.
params?: Record<string, string>
Raw parameter values from the command line, keyed by parameter (property)
name. Each declared {@link Parameter} is resolved from this map, then the
environment, then its declared default before any target runs.
readEnv?: (name: string) => string | undefined
Reads an environment variable as a parameter fallback. Defaults to
`Deno.env.get` (returning `undefined` when env access is unavailable);
overridable so parameter resolution can be tested hermetically.
prompt?: (flag: string, description: string | undefined) => string | undefined
Prompt for a missing required parameter, returning the entered value (or
`undefined` to leave it unset). Defaults to an interactive terminal prompt
when stdin is a TTY and the build is not on CI; overridable for testing.
dryRun?: boolean
Plan only: resolve and print every target that would run (honouring
`--skip` and `onlyWhen` conditions) without executing any body or touching
the cache (CLI `--dry-run`).
affected?: AffectedOptions
Restrict the run to the targets affected by files changed since a base git
revision (CLI `--affected[=<base>]`). A target is affected when a changed
file falls inside its declared {@link TargetBuilder.inputs} or a dependency
is affected; a target that declares no inputs is always considered affected.
Unaffected targets are skipped. The base revision defaults to `HEAD`; supply
`changedFiles` to inject the diff (used in tests).
github?: boolean
Force GitHub Actions output formatting on or off. Auto-detected from the
`GITHUB_ACTIONS` environment variable when omitted.
color?: boolean
Force ANSI colour on or off. Auto-detected (a TTY with `NO_COLOR` unset,
outside GitHub Actions) when omitted; off by default with a custom reporter.
renderer?: Renderer
Renderer for the per-target banners and the end-of-build summary. Defaults
to Zuke's built-in {@link "./renderer.ts".defaultRenderer}; `@zuke/console`
exports an alternative a build can inject to restyle its output.
signal?: AbortSignal
Cancel the run when this signal aborts (wired to Ctrl-C/SIGTERM by the CLI,
or fired by another process running `zuke cancel`). Every target body's
{@link "./target.ts".TargetContext} `signal` mirrors it, and it is applied
as the shell's ambient default so an in-flight `$` command is terminated
(SIGTERM) on cancellation. When the run is cancelled, the compensations of
every target that had succeeded run in reverse order (see
{@link "./target.ts".TargetBuilder.onCancel}) and the result is a non-ok
`cancelled` outcome. A body that ignores its signal still runs to
completion, so promptly-cancellable work should pass `ctx.signal` to its
shell commands.
stateStore?: StateStore | false
Durable run state (see {@link "./state/store.ts".StateStore}). A supplied
store is used directly; `false` disables state entirely. When omitted, the
build's `stateStore()` override is used, falling back to `ZUKE_STATE_URL` /
`ZUKE_STATE_DIR`, and finally — only when {@link state} is set — a
filesystem store under `<root>/.zuke/runs`.
state?: boolean
Opt a plain build into durable state (CLI `--state`): fall back to a
`.zuke/runs` filesystem store when nothing else is configured. Ignored when
a store is resolved from {@link stateStore}, the build, or the environment.
actor?: string
Who to attribute the run to in its state record (CLI `--actor`). Falls back
to `ZUKE_ACTOR`, then the CI actor, then `"anonymous"`.
actorKind?: ActorKind
Whether a person or a machine asked for the run (CLI `--actor-kind`). Falls
back to `ZUKE_ACTOR_KIND`, else `"human"`. Recorded on the run's immutable
initiator, never inferred from the actor's name.
resume?: ResumeState
Continue a suspended run instead of starting a fresh one. Set by
{@link "./resume.ts".resumeRun} after it has transitioned the run to
`running`; carries the existing record, its store version, and the targets
already succeeded (which are not re-run). Not for direct use — call
`resumeRun`.
interface ExtractOptions
Options common to {@link extractTarGzip} and {@link extractZip}.
strip?: number
Drop this many leading path components from every entry (like tar's
`--strip-components`). An entry left with no path — e.g. the archive's
single top-level directory — is skipped. Defaults to `0`. Use `1` to unpack
a release tarball that wraps everything in a `tool-v1.2.3/` directory.
interface FanOutOptions
Options for {@link fanOutPipeline}: how a build's targets become parallel CI
jobs.
command?: (target: string) => string
The command a job runs for its target, given the target name. Defaults to
the `./zuke <target>` launcher (which bootstraps Deno). Each job runs only
its own target; its dependencies run in their own jobs and are shared via
the {@link "./remote_cache.ts" | remote cache}, so pair fan-out with one.
setupSteps?: CiStep[]
Steps prepended to every job — tool setup, cache restore. None by default:
the prelude action every job starts with already hardens the runner and
checks the repository out (and GitLab and Azure check out on their own), so
a job's steps are just `Run <target>` unless you add to them. Provide `env`
for `ZUKE_REMOTE_CACHE_*` here or via {@link env}.
runsOn?: string
The runner for every job (see {@link CiJob.runsOn}).
includeUnlisted?: boolean
Include targets hidden from `--list` via `.unlisted()`. Defaults to false.
env?: Record<string, string>
Environment variables set on every job (e.g. the remote-cache config).
interface FileTasksApi
The shape of {@link FileTasks}.
exists(path: PathLike): Promise<boolean>
Whether `path` exists.
homeDirectory(): string
The current user's home directory, read from `$HOME` (falling back to
`$USERPROFILE` on Windows). Throws a clear error when neither is set or
environment access is unavailable, so callers get a path or a useful
failure — never an `undefined` to thread through.
createDirectory(path: PathLike, options?: CreateDirectoryOptions): Promise<void>
Create the directory at `path`. Creates parents by default
({@link CreateDirectoryOptions.recursive}); a recursive create is a no-op
when the directory already exists.
cleanDirectory(path: PathLike): Promise<void>
Remove everything inside the directory at `path`, leaving an empty
directory. A no-op if `path` does not exist (it is not created).
remove(path: PathLike, options?: RemoveOptions): Promise<boolean>
Remove `path`, tolerating a missing target the way `rm -f` does: a
`NotFound` resolves to `false` instead of throwing. Any other error (e.g. a
non-empty directory removed without {@link RemoveOptions.recursive}) is
rethrown.
@return
`true` if something was removed, `false` if `path` did not exist.
copy(source: PathLike, destination: PathLike, options?: CopyOptions): Promise<void>
Copy a file or directory tree from `source` to `destination` (directories
are copied recursively).
move(source: PathLike, destination: PathLike): Promise<void>
Move (rename) `source` to `destination`.
symlink(target: PathLike, path: PathLike, options?: SymlinkOptions): Promise<void>
Create a symbolic link at `path` pointing to `target`.
`target` is stored in the link verbatim, so a relative one resolves
against the link's own directory — which is what makes a link between two
sibling checkouts survive both being moved together.
With {@link SymlinkOptions.force} an entry already at `path` is replaced
atomically, which is the `ln -sfn` case a re-run of an idempotent target
needs; without it an existing entry is an `AlreadyExists` error. A
directory at `path` is never replaced.
readLink(path: PathLike): Promise<string>
The target of the symbolic link at `path`, exactly as stored in the link —
relative if it was created relative, and not checked for existence.
Throws if `path` is not a symbolic link, which is the same answer
`Deno.readLink` gives.
readText(path: PathLike): Promise<string>
Read the UTF-8 text content of the file at `path`.
writeText(path: PathLike, content: string): Promise<void>
Write `content` to the file at `path`, creating or truncating it.
readJson(path: PathLike): Promise<T>
Read and parse the JSON file at `path`.
interface ForEachItem
One materialised fan-out item: a unique label plus its pipeline stages.
key: string
A label unique within the fan-out, used to name the item's sub-targets.
stages: Record<string, TargetBuilder>
The item's ordered pipeline stages, keyed by stage name.
interface ForEachSpec
The internal fan-out spec stored by {@link TargetBuilder.forEach}. Its
{@link ForEachSpec.materialize} closure captures the item type, so the runtime
list and factory are erased to concrete {@link ForEachItem}s the executor can
run without knowing the item type.
materialize: () => ForEachItem[]
Produce the per-item sub-target pipelines from the runtime list.
configure?: Configure<ForEachSettings>
Optional fan-out settings (concurrency, per-item failure isolation).
interface ForceOptions
Options for {@link forceTarget}.
runId: string
The run to act on.
target: string
The dotted target name to force.
outcome: ForcedOutcome
What the target should settle to without running.
reason?: string
Why — recorded on the override and shown by `zuke runs show`.
actor?: string
Who to attribute the decision to (`--actor`); resolved as for a run.
stateStore?: StateStore | false
The durable store the run lives in; resolved as for a run when absent.
readEnv?: (name: string) => string | undefined
Reads an environment variable (injectable for tests).
interface ForceResult
The result of a {@link forceTarget} call.
ok: boolean
Whether the override was recorded.
denial?: ForceDenial
Why it was refused, when it was.
message: string
A message naming the target and the rule, suitable for an operator.
override?: TargetOverride
The override as recorded, when it was.
interface GlobOptions
Options for {@link glob}.
cwd?: string
Directory to resolve the pattern against (default: `Deno.cwd()`). Ignored
for an absolute pattern, which names its own root.
interface HeldLease
A held lease. Release it when the work it covers is over.
readonly lost: AbortSignal
Aborts if the lease is lost — the store reports the claim is no longer this
holder's, which means something else has taken the work over.
A signal rather than a callback because a holder is not always ready to
receive one at the moment it acquires: a resume takes the lease before the
run it will drive exists. A signal can be read late and still be true.
release(): Promise<void>
Stop the heartbeat and release the claim (best-effort).
interface HeldLockEntry
One live lock, as reported by {@link "./store.ts".StateStore.listLocks} — the
key, who holds it, and when it lapses if the holder disappears.
Deliberately not the stored record: the acquisition token is the holder's
proof of ownership, and a read-only listing has no business handing it out.
key: string
The lock key, as it was acquired.
holder: LockHolder
Who holds it.
expiresAt: number
Epoch-millisecond expiry: when it frees itself if the holder is gone.
interface HttpBuildRegistryOptions
Configuration for an {@link HttpBuildRegistry}.
url: string
The base URL build endpoints are built under (any trailing slash is ignored).
token?: string
A bearer token sent as `Authorization: Bearer <token>`, if set.
fetch?: typeof fetch
The `fetch` implementation; defaults to the global. Overridable for tests.
interface HttpCacheStoreOptions
Configuration for an {@link HttpCacheStore}.
url: string
The base URL keys are appended to (any trailing slash is ignored).
token?: string
A bearer token sent as `Authorization: Bearer <token>`, if set.
fetch?: typeof fetch
The `fetch` implementation; defaults to the global. Overridable for tests.
maxArtifactBytes?: number
The most a fetched artifact may weigh on the wire, in bytes, before it is
refused. Defaults to 512 MiB. Raise it for a target whose compressed
outputs are genuinely larger; a refusal is a warned rebuild, not a build
failure. What the bytes decompress to is bounded separately, by
{@link restoreOutputs}.
interface HttpOptions
Options shared by the HTTP helpers.
headers?: Record<string, string>
Extra request headers (e.g. an `Authorization` token).
fetch?: typeof fetch
The `fetch` implementation to use. Defaults to the global `fetch`;
override it to unit-test without network access.
interface HttpStateStoreOptions
Configuration for an {@link HttpStateStore}.
url: string
The base URL run endpoints are built under (any trailing slash is ignored).
token?: string
A bearer token sent as `Authorization: Bearer <token>`, if set.
fetch?: typeof fetch
The `fetch` implementation; defaults to the global. Overridable for tests.
interface InstallNpmToolOptions
Options for {@link installNpmTool}.
destDir?: PathLike
The root tools directory; the package installs under
`<destDir>/npm/<name>@<version>`. Defaults to
{@link "./tool.ts".DEFAULT_TOOLS_DIR} (`.zuke/tools`).
run?: NpmRunner
The npm-install runner. Defaults to the ambient `npm`; a test seam.
os?: OperatingSystem
The OS whose bin-shim filename to return (`.cmd` on Windows). Defaults to
the host; a test seam for the Windows shim path.
interface InstallPlatform
The host identity: a Zuke {@link OperatingSystem} and {@link Architecture}.
os: OperatingSystem
The operating system (normalised: `macos`, not `darwin`).
arch: Architecture
The CPU architecture.
interface InstallReleaseOptions
Options for {@link installRelease}.
name: string
The tool name; also the installed binary's filename (`.exe` on Windows).
url: (platform: Platform) => string
Resolve the download URL for the target {@link Platform}.
destDir: PathLike
The directory to install the binary into (created if missing).
archive?: DownloadFormat | ((platform: Platform) => DownloadFormat)
The download format. `"raw"` (default) treats the download as the binary
itself; `"tar.gz"` and `"zip"` unpack it and take {@link binaryPath} from
inside. Many release assets ship one or the other.
Like {@link url} and {@link checksum} this accepts a resolver, because the
format is routinely per-platform: a Go or Rust project typically publishes
`.tar.gz` for Linux and macOS and `.zip` for Windows. Pass
`(p) => p.os === "windows" ? "zip" : "tar.gz"` rather than declaring one
format that is wrong on a third of the platforms.
binaryPath?: string | ((platform: Platform) => string)
For a `"tar.gz"` or `"zip"` archive, the binary's path within the archive.
Defaults to {@link name}.
Also resolver-friendly, for the same reason: the same release usually names
the binary `tool` inside its Unix archive and `tool.exe` inside its Windows
one, so `(p) => p.os === "windows" ? "tool.exe" : "tool"` is the common
shape. (The installed filename gets its `.exe` automatically — this is the
path to copy out of the archive.)
platform?: InstallPlatform
The platform to resolve the URL for. Defaults to {@link hostPlatform}.
Override it to install a foreign binary or to unit-test URL resolution.
download?: DownloadFn
The download implementation. Defaults to {@link httpDownload}; override it
to unit-test without network access.
checksum?: string | ((platform: Platform) => string)
The expected SHA-256 (hex) of the downloaded artifact — the `.tar.gz`
for an archive, or the binary itself for a `"raw"` download; this is what
release pages publish as the checksum. When set, the download is verified
against it (a mismatch throws and nothing is installed) and the checksum
doubles as a cache key: a prior install whose recorded checksum matches
is reused without downloading again. Omit it and the tool is downloaded
every time and not verified.
Because {@link url} resolves a different artifact per platform, each has its
own hash — so pass a resolver `(platform) => string` (like `url`) to pin a
checksum per platform, or a plain string when a single artifact is installed.
interface InstallTreeOptions
Options for {@link installTree}.
name: string
The tool name; the extracted tree lands in `<destDir>/<name>`.
url: (platform: Platform) => string
Resolve the download URL for the target {@link Platform}.
destDir: PathLike
The directory the tree is installed under (created if missing).
archive: ArchiveFormat | ((platform: Platform) => ArchiveFormat)
The archive format — a multi-file runtime always ships packed. Accepts a
per-platform resolver for the usual `.tar.gz` on Unix / `.zip` on Windows
split (see {@link InstallReleaseOptions.archive}).
strip?: number
Leading path components to drop while unpacking (tar's `--strip-components`).
A release tarball wraps everything in a `tool-v1.2.3/` directory, so `1`
unwraps it; {@link bins} and the returned tree root are then relative to the
stripped tree. Defaults to `0`.
bins?: readonly string[]
Paths (relative to the stripped tree root) to mark executable on POSIX — the
tar reader does not preserve mode bits, so a runtime's `bin/node`,
`bin/npm`, … need it. Chmod follows a symlink to its real target, so listing
a symlinked bin makes the script it points at executable too.
platform?: InstallPlatform
The platform to resolve for. Defaults to {@link hostPlatform}.
download?: DownloadFn
The download implementation. Defaults to {@link httpDownload}; a test seam.
checksum?: string | ((platform: Platform) => string)
The expected SHA-256 (hex) of the downloaded archive — verified before
anything is unpacked, and used as the cache key (see
{@link InstallReleaseOptions.checksum}). Omit it and the tree is downloaded
every time and not verified.
interface LockHolder
Who holds a lock — surfaced to the loser of a conflict so it can act.
actor: string
The actor that acquired the lock.
runId: string
The run that holds it (`zuke cancel <runId>` releases it).
since: string
ISO-8601 timestamp when it was acquired.
runUrl?: string
A link to the holding run (e.g. its CI job), when known.
interface LogoOptions
Options for {@link logoLines} and `ConsoleTasks.logo`.
tagline?: string
A line printed dimmed under the art (e.g. a version or strapline).
letterStyle?: readonly StyleName[]
Styles for the solid letter blocks. Defaults to `["cyan", "bold"]`.
shadowStyle?: readonly StyleName[]
Styles for the shadow characters. Defaults to `["dim"]`.
interface McpAuthReject
Why a request was refused, and how the transport should say so.
The status and challenge are what make OAuth discovery work: an MCP client
learns where to authenticate from a `401` carrying `WWW-Authenticate`, which a
JSON-RPC error inside a `200` can never tell it.
status: number
The HTTP status to answer with — a client error, `401` or `403` in practice.
error: string
A short reason, machine-readable where there is a standard code for it (an
OAuth authenticator's `"invalid_token"`, say). It becomes the JSON-RPC
error message on the refusal, so it is read by people too.
detail?: string
A short human-readable detail. Never a secret: it is returned to the caller.
challenge?: string
The `WWW-Authenticate` header value to challenge with, when one applies. A
value a header cannot carry (a newline, a NUL, a character outside Latin-1)
is dropped rather than sent, so it cannot turn the refusal into a fault.
interface McpAuthenticator
Authenticates one request for the MCP server.
Invoked once per message, before any dispatch: a rejection stops the request
outright, so nothing executes and nothing is written to state. Returning an
{@link McpIdentity} accepts the caller; returning an {@link McpAuthReject}
refuses it. Throwing also refuses it — the seam is fail-closed, so a bug in an
authenticator denies rather than admits.
Configure one with `override mcpAuth()` on the build.
authenticate(ctx: McpRequestContext): Promise<McpIdentity | McpAuthReject> | McpIdentity | McpAuthReject
Resolve the caller's identity from the request, or refuse the request.
interface McpCall
What is being authorized — one MCP tool call, described for a policy.
tool: string
The tool name, e.g. `run:deploy`, `cancel_run`, `list_runs`.
target?: string
The target a `run:` call names, when it names one.
requiredRoles?: readonly string[]
Every role declared with `.requiresRole(...)` anywhere in the plan this
call would execute — not just on the target it names.
Invoking a target runs its dependencies, so a requirement that only guarded
the entry point would be bypassed by invoking anything that depends on it.
This mirrors `--protect`, which is enforced across the whole plan for the
same reason. The caller must satisfy all of them.
run?: RunSummary
The run a run-scoped call acts on, when it acts on one. Absent for a sweep
over every run, which no single run's owner can authorize.
interface McpIdentity
A trusted caller identity, resolved per request by an
{@link McpAuthenticator}. Its {@link McpIdentity.actor} is the
highest-precedence attribution — it overrides `--actor`, the environment, and
the client's self-reported label for the call.
actor: string
The authenticated actor — an OAuth subject, a GitHub login, a service name.
kind?: "human" | "service"
Whether a person or a machine is calling. Absent is read as `"human"`, the
conservative default: a policy that treats service callers differently must
see the claim stated rather than inferred.
roles?: readonly string[]
The roles this caller holds.
Omitting it means this authenticator does not speak roles: the
role policy (../../docs/mcp.md#authorization) then does not constrain the
caller, and the server's allow-list, `--protect` globs and operator token
remain the only gates — what a server did before roles existed. An empty
list is the opposite claim: the question was considered and nothing was
granted, so the policy denies.
Role names are passed through as given; the comma-separated environment
variable a registry-spawned child reads escapes them rather than this
dropping them, so sanitisation cannot turn a granted set into an empty one.
via?: string
How the identity was established (e.g. `"oauth-proxy"`); informational.
interface McpRequestContext
The per-request context a transport hands the message handler, and the only
thing an authenticator sees of the request. Empty on the stdio transport,
which has no request to describe.
readonly headers: Headers
The request headers; an empty {@link Headers} on stdio.
readonly request?: Request
The HTTP request the caller arrived on, when it did — so an authenticator
can read its method and URL, not just its headers. Absent on the stdio
transport, which has no request.
Its body is deliberately empty: the body belongs to the transport, which
reads it once to parse the JSON-RPC message, and a credential never lives
there. Everything else — method, URL, headers — is the real request's.
interface NpmToolSpec
A specification of an npm-registry package to provision as a tool.
name: string
The npm package to install, e.g. `"vitest"` or `"@nestjs/cli"`.
version: string
The exact version to pin, e.g. `"4.1.9"` — installed as `name@version`.
bin?: string
The bin to resolve, when it differs from the package name — `@nestjs/cli`
publishes the `nest` bin, so `{ name: "@nestjs/cli", bin: "nest" }`.
Defaults to {@link name}.
interface OpenCacheOptions
Optional extras for {@link openCache}: a remote store and a warning sink.
remote?: RemoteCacheStore
A {@link RemoteCacheStore} to restore outputs from (on a local miss) and
upload them to (after a successful run). Applies only to targets that
declare {@link TargetBuilder.outputs}.
warn?: (message: string) => void
Report a non-fatal remote-cache error (a get/put failure never fails the build).
interface OutputHost
Filesystem effects used to archive and restore a target's outputs.
readFile(path: string): Promise<Uint8Array | null>
File contents, or `null` if the path does not exist.
stat(path: string): Promise<{ isDirectory: boolean; } | null>
Whether a path exists and is a directory, or `null` if it is missing.
lstat(path: string): Promise<{ isSymlink: boolean; isDirectory: boolean; } | null>
Describe a path without following a final symlink, or `null` if it is
missing. Distinct from {@link OutputHost.stat}, which resolves a link and so
cannot see one: {@link restoreOutputs} refuses to write through a link,
which is a question only an `lstat` can answer.
readDir(path: string): Promise<string[]>
The entry names within a directory.
writeFile(path: string, bytes: Uint8Array): Promise<void>
Write a file, creating parent directories as needed.
interface Platform extends InstallPlatform
A platform with helpers to name it the way a tool's downloads do. `osLabel`
and `archLabel` map the `os`/`arch` to a tool's own naming, falling back to
the value itself for anything not in the alias map — so a `url` callback reads
`p.osLabel({ macos: "darwin" })` (for a tool that spells macOS "darwin")
instead of a hand-written `os === …` ternary. This is what the
{@link InstallReleaseOptions.url} and {@link InstallReleaseOptions.checksum}
callbacks receive.
osLabel(aliases?: Partial<Record<OperatingSystem, string>>): string
The OS named for downloads: `aliases[os]`, else the {@link InstallPlatform.os} itself.
archLabel(aliases?: Partial<Record<Architecture, string>>): string
The arch named for downloads: `aliases[arch]`, else the {@link InstallPlatform.arch} itself.
interface Plugin
A lifecycle observer. Every hook is optional; implement only the ones you
need. Hooks may be async — the executor awaits each before continuing.
name?: string
A name for diagnostics (optional).
onStart?(run: RunInfo): void | Promise<void>
Called once before any target runs, with the run's {@link RunInfo}.
onTargetStart?(target: string, run: RunInfo): void | Promise<void>
Called just before a target's body executes (not for skipped/cached), with
the target name and the run's {@link RunInfo}.
onTargetEnd?(target: string, status: TargetStatus, timing: TargetTiming): void | Promise<void>
Called after each target settles, with its final status and its
{@link TargetTiming} (run id + duration).
onFinish?(result: BuildResult, run: RunInfo): void | Promise<void>
Called once after the run completes (success or failure), with the result
and the run's {@link RunInfo}.
onRunStateChange?(record: RunRecord): void | Promise<void>
Called on each run-level durable status change — the run going
`running`, `suspended`, `succeeded`, `failed`, `cancelling`, or `cancelled`
— with the current {@link "./state/types.ts".RunRecord}. It carries the full
record (per-target timings, waits, the audit trail), so a metrics exporter
can derive spans, wait durations, and counters from a single source.
Only fires when a state store is configured (the record's home); a plain
build with no store never produces one, and this hook stays silent.
The record is the secret-free projection: `secret()` parameters are
omitted, and `ctx.state` metadata, target errors, and audit arguments are
run through the redactor before they reach it — the same data already
persisted to the store and shown by `zuke runs show`. It is safe to export.
A run cancelled in-process (Ctrl-C / its `signal`) is observed as
`running` → `cancelling` → `cancelled`. When another process cancels the
run (`zuke cancel`), this process observes it through `cancelling` and stops
— the canceller's process owns the final `cancelled` — so treat `cancelling`
as run-ended for the owning process.
interface Remediation
A recovery step plugged into a target with {@link TargetBuilder.recoverWith}.
It runs only after the target body fails, receives the failure, and may
attempt to repair it — returning `{ retry: true }` to ask the executor to
re-run the body (the real build command is the verifier). Implemented, for
example, by the AI fixer in `@zuke/ai`, but any object with a `remediate`
method qualifies.
name?: string
A name for diagnostics (optional).
remediate(context: RemediationContext): RemediationResult | Promise<RemediationResult>
Inspect (and optionally repair) the failure; report whether to retry.
interface RemediationContext
Context passed to a {@link Remediation} after a target body fails.
target: string
The name of the failed target.
attempt: number
The 1-based recovery attempt (the body has already failed `attempt` times).
error: unknown
The failure being remediated. When a target fails through the shell this is
a `CommandError` carrying the failed command and its captured `stderr`.
redact(text: string): string
Mask every resolved `secret` parameter in `text`.
A remediation that publishes anywhere — a pull-request comment, a job
summary, a file it writes — has to run its output through this first. What
it is publishing is typically a model's response to a prompt built from the
failure, and that prompt carries the failed command and its output, so a
secret the build holds can come back in the reply.
Zuke's own reporter and run record redact what they emit, but a
remediation that posts over the network is not going through either of them:
this is the only thing between such a value and a comment that cannot be
taken back.
Masks the same values {@link "./params.ts".parameter} marked secret, so a
credential the build never declared is not covered — declare it.
interface RemediationResult
The outcome of one {@link Remediation} attempt.
retry: boolean
Re-run the target body after this remediation? `true` asks the executor to
retry (the remediation changed something — e.g. applied a fix); `false`
leaves the failure standing (e.g. a diagnose-only remediation that only
explained the failure).
summary?: string
A one-line description of what was diagnosed or done, for diagnostics.
interface RemoteCacheStore
A content-addressed store for archived target outputs, keyed by
{@link remoteCacheKey}. Both operations are best-effort from the build's
point of view: the executor never fails a build because the store is
unreachable — it just rebuilds and, where it can, re-uploads.
get(key: string): Promise<Uint8Array | null>
Fetch the archived outputs stored under `key`, or `null` if there are none.
put(key: string, artifact: Uint8Array): Promise<void>
Store `artifact` (a gzipped tar of a target's outputs) under `key`.
interface RemoveOptions
Options for {@link FileTasksApi.remove}.
recursive?: boolean
Remove a directory and its contents recursively, like `rm -r`.
interface Renderer
How the executor renders a build's output. Each method is pure — it returns
the lines to print rather than writing them — so a custom renderer stays
unit-testable and the executor keeps control of the output streams.
targetHeader(style: Style, name: string): string[]
The banner that opens a target's section (a `::group::` under Actions).
targetPassFooter(style: Style, name: string, ms: number): string[]
The footer printed after a target body succeeds.
targetFailFooter(style: Style, name: string, ms: number, error: unknown): { info: string[]; error: string[]; }
The footer printed after a target body fails, split into `info` (stdout)
and `error` (stderr) so the caller can fan the lines out correctly.
targetDryRunFooter(style: Style, name: string): string[]
The footer printed for a dry-run target that was never executed.
summaryBlock(style: Style, reports: TargetReport[], totalMs: number, ok: boolean): string[]
The end-of-build summary block: the aligned table and closing verdict.
jobSummaryMarkdown(reports: TargetReport[], totalMs: number, ok: boolean): string
The GitHub Actions job-summary Markdown mirroring the terminal summary.
interface Reporter
Sink for executor output, defaulting to the console. Overridable in tests.
info(line: string): void
Write an informational line.
error(line: string): void
Write an error line.
interface ResolveRegistryOptions
Inputs {@link resolveBuildRegistry} needs to build the default filesystem registry.
readEnv: (name: string) => string | undefined
Reads an environment variable (injectable for tests).
host: StateHost
Filesystem effects for the default/env filesystem registry.
defaultDir: string
Directory the default filesystem registry writes to (`<root>/.zuke/builds`).
enableDefault: boolean
Fall back to the default filesystem registry when nothing else is
configured. `zuke register` sets this so the command works out of the box.
interface ResolveStateOptions
Inputs {@link resolveStateStore} needs to build the default filesystem store.
readEnv: (name: string) => string | undefined
Reads an environment variable (injectable for tests).
host: StateHost
Filesystem effects for the default/env filesystem store.
defaultDir: string
Directory the default filesystem store writes to (`<root>/.zuke/runs`).
enableDefault: boolean
Fall back to the default filesystem store when nothing else is configured.
Set when the run opts into durable state (`--state`, or — from a later
milestone — a durable feature like a lock or a wait).
interface ResumeOptions
Options for {@link resumeRun}.
runId: string
The id of the suspended run to resume.
stateStore?: StateStore | false
Durable store the run lives in. Defaults to the same resolution as a normal
run (explicit → `stateStore()` override → env → `.zuke/runs`); resume always
needs one.
signal?: string
Deliver a signal by this name before resuming (satisfies `externalSignal`).
data?: JsonValue
The signal's JSON payload (defaults to `{}`); ignored without {@link signal}.
params?: Record<string, string>
Non-secret parameter overrides; the rest come from the record.
banner?: boolean
Print the opening banner (see {@link "./executor.ts".ExecuteOptions.banner}).
readEnv?: (name: string) => string | undefined
Reads an environment variable (secrets re-resolve from here).
actor?: string
Who to attribute the resumption to (stamped on the run).
forceGraph?: boolean
Continue even if the build graph changed since the run was suspended.
resumeDegraded?: boolean
Resume even though the record is {@link "./state/types.ts".RunRecord.degraded}
— a state write was permanently lost, so a target that succeeded may still
be recorded `running` or `pending`. The resume trusts the record as written,
which means such a target runs again; passing this accepts that risk,
on the grounds that the operator — not Zuke — knows whether the target is
safe to repeat.
silent?: boolean
Suppress banner/summary output.
reporter?: Reporter
Custom reporter; overrides `silent`.
plugins?: Plugin[]
Lifecycle observers for the resumed run. Because a resume keeps the original
run id, a plugin sees the continuation under the same identity — so an
exporter's spans join one trace across the suspend/resume boundary.
interface ResumeState
The continuation state {@link resumeRun} hands to {@link execute} on a resume.
record: RunRecord
The run being continued (already transitioned to `running`).
version: string
Its current store version, for the writer to continue from.
done: ReadonlySet<string>
Names of targets recorded `succeeded` — seeded as done, never re-run.
lease?: HeldLease
The lease the resumer took before moving the record out of `suspended`.
Held by the resumer rather than acquired here, because the record must never
read `running` in the store without its lease already held — that pairing is
what tells a sweep the difference between a live run and an abandoned one.
interface ResumeWhenOptions
Options for {@link resumeWhen}.
interval?: string | number
How often `zuke resume --check` should re-evaluate the predicate.
interface RunEvent
One entry in a run's audit trail: an MCP tool call, who made it, and how it
ended. Appended (never mutated) so the trail is a chronological record. The
MCP server records a {@link RunEvent} for every mutating or denied tool call;
`zuke runs show` prints them.
at: string
ISO-8601 time the call was recorded.
tool: string
The tool called (e.g. `run:deploy`, `signal_run`).
actor: string
Who made the call (a resolved actor; see {@link "./record.ts".resolveActor}).
outcome: RunEventOutcome
Whether the call ran, was denied by authorization, or errored.
args: Record<string, string>
The call's arguments, redacted — secret values masked, tokens dropped.
detail?: string
A short, redacted human detail (e.g. a denial reason), when present.
roles?: readonly string[]
The roles the caller held, when the server authenticated them. What makes a
denial answerable afterwards: the actor and the reason say who was refused
and by which rule, and this says what they were carrying at the time.
Absent on a server with no authenticator, which knows of no roles.
interface RunGraphNode
One entry of a run's graph-shape snapshot.
name: string
The target's dotted name.
dependsOn: string[]
The dotted names of its direct dependencies.
interface RunInfo
Run identity passed to a plugin's lifecycle hooks, so an observer can group a
run's events (e.g. under one trace id) — stable across a suspend/resume
boundary, since a resumed run keeps the original id.
readonly runId: string
The run id, stable for every target in the run (and across a resume).
readonly dryRun: boolean
True when the run is a dry run (no target body executes).
interface RunInitiator
Who asked for a run, stamped once when it is created and never rewritten.
Distinct from {@link RunRecord.actor}, which every resume overwrites with
whoever picked the run up — so on a run that suspended and was resumed by a
sweep, `actor` is the sweep's service account and this is still the engineer
who started it. That is the subject a run-scoped authorization rule means by
"whoever started this run", and the difference a notification needs to tell a
person's deploy from a scheduler's.
actor: string
Who asked for the run, resolved once at creation.
kind: ActorKind
Whether a person or a machine asked. Stated, never inferred from the actor.
at: string
ISO-8601 time this attribution was fixed. The run's `createdAt` for a run
stamped at creation, and the original run's `createdAt` for one backfilled
when a resume was about to overwrite the evidence — so it dates the
attribution, not the write that recorded it.
interface RunOptions
Options for {@link run}.
args?: string[]
Command-line arguments. Defaults to `Deno.args`.
plugins?: Plugin[]
Lifecycle observers to run alongside the build's own hooks.
renderer?: Renderer
Renderer for the per-target banners and end-of-build summary. Defaults to
Zuke's built-in look; inject `consoleRenderer` from `@zuke/console` (or a
custom {@link Renderer}) to restyle a build's output.
interface RunPlan
The resolved shape of this run: the class-field targets it plans, in order,
and the dependencies between them.
This describes the plan, not the outcome. A target is in the plan because
the graph put it there; it may still be skipped by a condition, by
`--affected`, or by an operator's forced outcome, and a run that fails early
never reaches its later targets at all. Ask {@link RunPlan.includes} what the
run set out to do, and `ctx.outcomeOf(...)` what actually became of a target
once it settled.
Two things the plan is not.
It is not every target that will appear in the run record. A `.forEach()`
fan-out expands into its sub-targets while the run executes, long after the
graph is planned, so `fan[us].prep` has an outcome and a summary row but is
absent from the plan — only the `fan` target that produced it is in
there. For a fan-out, `outcomeOf` sees more than `includes` does.
It is not a promise that holds across processes. The plan is the graph as
this process resolved it, and it is fixed for the whole of this process: two
bodies, and both evaluations of a condition, always agree. A second process —
a resume, or a `zuke cancel` running compensations — re-resolves the graph
from the build class it was given, so it can legitimately differ: the class
may have changed since the run was suspended (which is what `--force-graph`
is for), and a lazy `orderWith` provider may answer differently or, if it is
unreachable, be degraded to the base topological order.
readonly targets: readonly string[]
Every planned target's dotted name, in the run's deterministic execution
order.
The build summary can list more rows than this: a `.forEach()` fan-out's
sub-targets are created during execution and get their own rows, but are
not part of the planned graph.
includes(target: string): boolean
Whether `target` is part of this run's planned set.
The question a body asks to decide whether its work is needed by something
else in the run — "am I building for a `deploy` that was actually asked
for?". An unknown name is `false`, never an error, so probing for an
optional target does not need a guard.
dependenciesOf(target: string): readonly string[]
The targets that must complete before `target` may start: everything the
planner treats as a predecessor — declared `dependsOn` dependencies, the
targets this one `triggers`, and the soft `before`/`after`, `extraEdges`
and `orderWith` edges that apply within this run's set.
Empty for a target with no predecessors and for a name that is not in
the plan — use {@link RunPlan.includes} to tell those apart.
interface RunQuery
Filters for {@link "./store.ts".StateStore.listRuns}; all fields are optional.
status?: RunStatus
Keep only runs with this status.
target?: string
Keep only runs whose graph contains a target with this dotted name.
since?: string
Keep only runs created at or after this ISO-8601 timestamp.
limit?: number
Return at most this many runs (the newest, since listing is newest-first).
Applied server-side so a large store stays listable; `0` returns none.
interface RunRecord
A versioned snapshot of one run. Persisted as JSON; a store's opaque
`version` (an ETag / content hash) drives compare-and-swap writes.
id: string
Unique run ID (matches {@link "../target.ts".TargetContext} `runId`).
build: string
The build class name.
buildId?: string
Which build instance this run belongs to — `ZUKE_BUILD_ID`, else
`GITHUB_REPOSITORY`, resolved once at creation. Absent when neither was set
(and on every record written before this field existed).
The class name above cannot identify a build: a `zuke.ts` templated across
a dozen services shares its name, its target names and its graph shape, so
every shape-based check passes and one service's recovery sweep would drive
another's runs with its own target bodies. This is what a recovery path
compares; see {@link "../ownership.ts"}.
rootTarget: string
The dotted name of the requested (root) target.
status: RunStatus
The run's lifecycle status.
actor: string
The run's last writer (resolved from `--actor`, `ZUKE_ACTOR`, or CI
env). Every resume overwrites it with whoever picked the run up, so it
answers "who touched this most recently", not "whose run is this" — see
{@link RunRecord.initiator} for that.
initiator?: RunInitiator
Who asked for the run, stamped once at creation and immutable thereafter.
Absent on a record written before this field existed; such a record's
{@link RunRecord.actor} is the closest answer available, and is exactly the
right one on a run that was never resumed.
overrides?: Record<string, TargetOverride>
Operator-forced target outcomes, keyed by dotted target name (see
{@link TargetOverride}). Absent until something is forced.
Read when the executor reaches the target, so an override lands for any
target the run has not started yet — in practice on the next resume, since
that is the process that loads the record after the force was written.
createdAt: string
ISO-8601 timestamp when the run was created.
updatedAt: string
ISO-8601 timestamp of the last write.
graph: RunGraphNode[]
The graph shape the run planned, in declaration order.
params: Record<string, string>
Resolved parameter values, keyed by name. Secrets are always omitted.
The values the run was launched with, and they are not rewritten. A
resume may supply different ones, in which case the run executes under two
sets and this field cannot hold both — so it keeps the launch's, which is
what the targets that ran before the suspension used, and what a
cancellation resolves each compensation body from. A resume that changed
anything records it in {@link RunRecord.events} instead.
targets: Record<string, TargetRunState>
Per-target progress, keyed by dotted target name.
signals: Record<string, SignalRecord>
External signals received so far, keyed by name (see `.waitsFor(...)`).
events: RunEvent[]
Append-only audit trail of MCP tool calls against this run (see {@link RunEvent}).
degraded?: boolean
True when at least one state write for this run was permanently lost —
a conflicting write from another process could not be re-applied within the
writer's retry budget. Writes are best-effort, so the run itself carried on;
the flag is how a later reader learns that a transition which really
happened may be missing from the record. In particular a target that
succeeded can still be recorded `running` or `pending`, so a resume would
re-run it — which is why a resume refuses a degraded record unless
`--resume-degraded` overrides it (see
{@link "../resume.ts".ResumeOptions.resumeDegraded}) — and why a
cancellation compensates every target whose success the record cannot rule
out, rather than only those recorded `succeeded` (see
{@link "../cancel.ts".runCompensations}).
It is set by the writer when it loses a write and persisted by the next
write that lands — the failing one, by definition, could not carry it. A
drop that leaves the mutation in memory for a later write to re-persist does
not set it. Absent (or `false`) means no write is known to be missing.
deadlineAt?: string
ISO-8601 wall-clock deadline for the whole run, stamped once at creation
from `Build.deadline()`. Absent when the build sets none.
A budget for running, not for existing. A run parked at an approval gate
is not spending it — its budget there is the wait's own timeout — so only a
sweep over `running` runs consults this.
intendedTerminal?: RunStatus
The terminal status the process that moved this run to `cancelling` means
to leave it in. Absent means `cancelled`, which is what an ordinary
`zuke cancel` intends and what every record written before this field
existed meant.
Recorded rather than inferred, because the settlement can be finished by a
different process than the one that began it: a canceller that crashes
leaves the run `cancelling`, and whoever recovers it has no other way to
know whether an operator was cancelling the run or a sweep was failing an
abandoned one.
interface RunSummary
A compact run listing row, returned by {@link "./store.ts".StateStore.listRuns}.
id: string
The run ID.
build: string
The build class name.
rootTarget: string
The dotted name of the requested (root) target.
status: RunStatus
The run's lifecycle status.
actor: string
The run's last writer (see {@link RunRecord.actor}).
initiator?: RunInitiator
Who asked for the run (see {@link RunRecord.initiator}). Absent on a record
written before the field existed, and from a store that does not project
it — {@link RunSummary.actor} is the fallback in both cases.
createdAt: string
ISO-8601 creation timestamp.
updatedAt: string
ISO-8601 timestamp of the last write.
interface RunningService
A started service the executor holds until it tears it down.
readonly name: string
The service's target name, for diagnostics.
stop(): Promise<void>
Stop the service; never rejects (failures are the registry's concern).
interface ScheduleEntry
A scheduled trigger: a 5-field cron expression in an optional IANA timezone.
cron: string
A standard 5-field cron expression (`minute hour day-of-month month day-of-week`).
tz?: string
An IANA timezone (e.g. `Europe/Sofia`) the `cron` is expressed in. Omitted
(or `UTC`) means the cron is already UTC and is emitted verbatim.
interface SecretSource
A provider that resolves a secret's value on demand. Built by
{@link execSecret} or {@link fileSecret} and attached to a parameter with
`.from(source)`; the framework calls {@link SecretSource.resolve} during
parameter resolution.
resolve(): Promise<string>
Produce the secret value, or throw {@link SecretError} on failure.
interface ServiceHandle
A running service — whatever {@link ServiceBuilder.start} returns. Its
{@link ServiceHandle.stop} tears it down; a {@link
https://jsr.io/@zuke/core SpawnedProcess} is one, so
`.start(() => $\`…`.spawn())` needs no explicit stop.
stop(): void | Promise<void>
Terminate the service. Called on teardown unless `.stop()` overrides it.
interface SignalRecord
A payload received for an external signal (see {@link RunRecord.signals}).
data: JsonValue
The signal's JSON payload (`{}` when none was sent).
receivedAt: string
ISO-8601 timestamp when the signal was recorded.
interface StateHost
Injected filesystem effects for {@link "./fs_store.ts".FileSystemStateStore},
so it stays unit-testable. The default implementation is
{@link defaultStateHost}.
readText(path: string): Promise<string | null>
File contents, or `null` when the file does not exist.
writeText(path: string, content: string): Promise<void>
Write a file's contents, creating parent directories as needed.
rename(from: string, to: string): Promise<void>
Rename a file (used to publish a temp file atomically).
createExclusive(path: string): Promise<boolean>
Create `path` exclusively: resolve `true` if it was created, `false` if it
already existed. Used as an atomic lock marker.
remove(path: string): Promise<void>
Remove a file; a missing file is not an error.
listDir(path: string): Promise<string[]>
The entry names in a directory, or `[]` when the directory is absent.
mkdirp(path: string): Promise<void>
Create a directory and any missing parents.
now(): number
The current time in epoch milliseconds — the clock for lock expiry (injectable for tests).
interface StateStore
Pluggable persistence for run records. `version` is an opaque token (an ETag
or content hash) used for optimistic concurrency: a write only lands if the
stored version still matches the one the writer last read, so two writers
racing at the same version cannot both win.
getRun(id: string): Promise<{ record: RunRecord; version: string; } | null>
Fetch a run and its current version, or `null` if it does not exist.
putRun(record: RunRecord, expectedVersion: string | null): Promise<PutResult>
Write `record` only if the stored version equals `expectedVersion` (`null`
meaning "must not exist yet"). Returns the new version, or a conflict when
the stored version has moved on — the caller re-reads and retries.
listRuns(query: RunQuery): Promise<RunSummary[]>
List runs matching `query`, newest first (by `createdAt`, then `id`).
deleteRun(id: string): Promise<void>
Delete a run permanently. A missing run is not an error (delete is
idempotent). Backs `zuke runs prune`; on the HTTP backend this maps to a
`DELETE /runs/:id` a server may leave unimplemented (retention there is the
server's job — see `docs/state-api.md`).
acquireLock(key: string, holder: LockHolder, ttlMs: number): Promise<LockResult>
Atomically acquire the lock `key` for `holder`, expiring after `ttlMs`. An
expired lock is taken over. Returns a `token` on success, or the current
holder when the lock is live.
renewLock(key: string, token: string, ttlMs: number): Promise<boolean>
Extend the lock `key` held under `token` by another `ttlMs`. Returns `false`
if the token no longer owns it (expired and taken over), so a heartbeat can
detect a lost lock.
releaseLock(key: string, token: string): Promise<void>
Release the lock `key` if still held under `token`; a no-op otherwise.
listLocks?(): Promise<HeldLockEntry[]>
Every lock currently held, keyed and ordered by key — the read-only answer
to "who holds this, and until when?". Expired records are not held locks
and are left out: reporting one as held is the failure this exists to
prevent.
Optional, so a store implemented outside this repository does not break by
not having one. A caller that needs the listing rather than an empty result
should go through {@link listStoreLocks}, which fails with a message naming
the backend instead of pretending the store holds nothing.
interface SummaryEntry
One rendered `key: value` note on a target's summary row.
readonly key: string
The note's label, as reported (whitespace collapsed to one line).
readonly value: string
The note's value, rendered as text (whitespace collapsed to one line).
interface SymlinkOptions
Options for {@link FileTasksApi.symlink}.
force?: boolean
Replace an existing entry at the link path, the way `ln -sfn` does
(default `false`, which throws an `AlreadyExists` as `Deno.symlink` does).
The replacement is atomic: the new link is created under a sibling temp
name and renamed over the path, so a concurrent reader sees either the old
entry or the new link and never a missing path. A directory at the
path is never replaced — the rename refuses it, empty or not — so forcing
a link cannot cost a caller a directory.
type?: "file" | "dir"
What the link points at: `"file"` or `"dir"`. Windows needs the
distinction and ignores nothing else; POSIX ignores the option entirely.
Pass `"dir"` when linking a directory, or the link is unusable on Windows.
interface TarEntry
A single entry within a tar archive — a regular file or a symbolic link.
name: string
The entry's path inside the archive (≤ 100 bytes).
data: Uint8Array
The file contents (empty for a symlink entry).
linkname?: string
For a symbolic-link entry, its target (≤ 100 bytes); absent for a regular
file. Node's release tarballs, for one, ship `bin/npm`/`bin/npx` as symlinks
into `lib/node_modules`, so extracting a runtime tree must preserve them.
interface TargetContext
The context passed to every target body. Optional to receive — an existing
zero-argument `.executes(() => …)` stays valid, since a zero-argument
function is assignable to this one-parameter type — but a body that wants the
run's identity, a cancellation signal, or durable per-target state reads them
here.
readonly runId: string
Unique ID of this run, stable for every target in the run.
readonly initiator?: RunInitiator
Who asked for this run — stamped once when the run was created, and
unchanged by any later resume, so it still names the engineer who started a
deploy that a sweep has since picked up several times.
Absent when the run has no durable record to have stamped one (no state
store), and on a record written before the field existed.
readonly target: string
Dotted name of the executing target.
readonly signal: AbortSignal
Aborted when the run is cancelled (see {@link "./executor.ts".ExecuteOptions}
`signal`). Pass it to a shell command's `.signal()` to have that command
terminated on cancellation; the executor also applies it as the shell's
ambient default, so a plain `$` in the body is terminated too.
readonly state: TargetStateHandle
Durable per-target metadata. Persisted to the run's state store when one is
configured (see {@link "./state/store.ts".StateStore}), and an in-memory
no-op otherwise. The carrier for state that must survive across a
suspend/resume boundary — do not put secrets in it.
readonly signals: ReadonlyMap<string, SignalRecord>
Payloads of the external signals received so far, keyed by name (see
`.waitsFor(...)` and {@link "./wait.ts".externalSignal}). Empty until a
signal is delivered by `zuke resume <id> --signal <name>`.
readonly dryRun: boolean
True when the run is a dry run (bodies do not execute under a dry run).
stateOf(target: string): TargetStateHandle
The durable state handle of another target in this run — the seam a body
reads a dependency's published metadata through (e.g. the result a
`.waitsFor(githubWorkflow(...))` gate recorded to its state). `stateOf(this target)` is equivalent to {@link state}. It reads the run's current
record, so it sees writes a dependency made earlier in the run — including
across a suspend/resume, since the record is durable.
outcomeOf(target: string): TargetOutcomeView | undefined
What another target in this run did, or `undefined` if it has no outcome
yet — it has not run, or is running now.
The seam for a target that must decide on the run's results rather than
merely follow them: an aggregate gate reporting one verdict for a fan of
checks, some of which are allowed to fail. `.dependsOn(...)` alone cannot
express that, because a failed dependency never lets its dependents run;
pair this with `.always()` and `.proceedAfterFailure()` on the checks.
It reads what this run has settled so far, whichever process settled it:
outcomes from a previous process come back after a resume, because they
are in the durable record. A sibling running concurrently has no
outcome yet — depend on what you intend to read.
outcomes(): ReadonlyMap<string, TargetOutcomeView>
Every outcome this run has settled so far, keyed by dotted target name — a
snapshot, not a live view. Targets that have not settled are absent rather
than present with a placeholder status.
plan(): RunPlan
The resolved shape of this run — which targets it plans, and how they
relate. The seam for a body whose work depends on what else was asked
for: a build that signs its artifact only when `deploy` is in the run.
Reports the plan, not the outcome — see {@link "./run_plan.ts".RunPlan}.
reportSummary(pairs: SummaryPairs): void
Report `key: value` notes into this target's row of the end-of-build
summary — where a count or a version belongs once the body is done:
```text
test Succeeded 8.1s // Tests: 837 · Passed: 837 · Failed: 0
```
Notes accumulate across calls, and reporting a key again replaces its
value in place. Each key and value is rendered on one line (whitespace
collapsed, control sequences removed). Library code with no context in
hand — a tool wrapper reporting the counts its tool printed — reports
through the ambient {@link "./summary_note.ts".reportSummary}, and those
notes land in the same row. A failed target keeps its notes:
a red `test` row still says how many failed. A compensation (see
{@link TargetBuilder.onCancel}) has no row, so its calls are dropped.
interface TargetOutcomeView
What another target in this run did, as {@link TargetContext.outcomeOf}
reports it.
The status is the record's vocabulary, not the summary's: a target whose
body ran and one served from the cache both read `"succeeded"`, because that
is the distinction the durable record keeps. A body branching on this wants
"did it work", which both answer the same way.
readonly status: TargetRunStatus
The target's status: `succeeded`, `failed`, `skipped`, `waiting`, …
readonly error?: string
The failure's message, when it failed. Redacted like every stored string.
readonly startedAt?: string
When it started, ISO-8601, if it did.
readonly endedAt?: string
When it settled, ISO-8601, if it has.
readonly summary?: readonly SummaryEntry[]
The notes it reported into its row of the build summary (see
{@link TargetContext.reportSummary}), when it reported any — so an
aggregating target can read a dependency's test counts, not only its
verdict. Durable: present after a resume too.
interface TargetOverride
An operator's decision to settle a target without running it — recorded on
the run so the executor honours it and the trail says who decided.
Two shapes of intervention, both of which a build cannot express itself:
`skipped` takes a step off the plan that cannot succeed, and `succeeded`
marks one that a person completed by hand. Dependents proceed either way; the
difference is what a later cancellation compensates, since only the second
asserts that the target's effects exist.
outcome: ForcedOutcome
What the target settles to when the executor reaches it.
actor: string
Who forced it (a resolved actor).
at: string
ISO-8601 time the override was recorded.
reason?: string
Why, when the operator gave a reason.
interface TargetReport
One row of the end-of-build summary.
name: string
The target's name.
status: TargetStatus
The target's terminal status.
ms: number
The target's wall-clock duration in milliseconds.
summary?: SummaryEntry[]
The notes the target reported into its row (see
{@link "./target.ts".TargetContext.reportSummary}) — present only when it
reported at least one, so a note-less row stays `{ name, status, ms }`.
interface TargetRunState
The recorded progress of a single target.
status: TargetRunStatus
The target's current status within the run.
meta: Record<string, JsonValue>
Durable metadata written via {@link "../target.ts".TargetStateHandle}.
startedAt?: string
ISO-8601 timestamp when the body started, if it has.
endedAt?: string
ISO-8601 timestamp when the target settled, if it has.
error?: string
The failure message when `status` is `failed`.
waitingFor?: WaitState
The pending wait when `status` is `waiting` (set by `.waitsFor(...)`).
effects?: Record<string, EffectState>
The declared effects of this target, keyed by effect name — present only
once at least one has been armed.
summary?: SummaryEntry[]
The notes the target reported into its row of the build summary (see
{@link "../target.ts".TargetContext.reportSummary}), in the order they
were first reported — present only when it reported at least one, and
redacted like every other stored string. What lets `zuke runs show` and
`ctx.outcomeOf` say a target ran 4094 tests, not only that it succeeded.
interface TargetStateHandle
A target's durable, per-target metadata, surfaced on {@link TargetContext} as
`state`. Writes are persisted to the run's state store (see
{@link "./state/store.ts".StateStore}) and are visible to later runs — e.g. a
resuming process reading what a suspended target recorded. When no store is
configured, the handle is an in-memory no-op scoped to the current run.
Never store a secret here — state is persisted in plain JSON and read
back by later runs and by `zuke runs show`.
set(patch: Record<string, JsonValue>): Promise<void>
Merge a JSON patch into this target's persisted metadata (awaits the write).
trySet(patch: Record<string, JsonValue>): Promise<boolean>
{@link set}, reporting whether the patch was recorded: `true` when it
reached the store, `false` when the write was dropped.
A write can be dropped — conflicted away for good, or refused by a store
that errored — and {@link set} resolves the same either way, so a body
that needs the value to be durable cannot tell. This is the seam for the
cases that do need to know: before an irreversible step that depends on
the value, or before handing a compensation something it will have to read
back.
Do not read `false` as "it may still land": a dropped write is sometimes
re-persisted by a later one, but nothing guarantees it, so treat `false`
as not recorded. A dropped write also warns, and one that is definitely
unrecoverable marks the run {@link "./state/types.ts".RunRecord.degraded}
so a later resume knows the record is missing something.
A handle with nothing durable behind it always answers `true` — a build
with no state store, and a compensation, whose state is in-memory by
design. Nothing is persisted, but nothing is dropped either.
get(): Record<string, JsonValue>
Read this target's persisted metadata (from prior attempts/runs too).
interface TargetTiming
Timing for a settled target, passed to {@link Plugin.onTargetEnd}.
readonly runId: string
The run id (see {@link RunInfo}).
readonly durationMs: number
The target's wall-clock duration in milliseconds (0 for skipped/cached).
interface TestCounts
The counts a test run produced — the one shape every test-runner wrapper
maps its runner's own summary line onto, so `DenoTasks.test`,
`VitestTasks.run`, `JestTasks.run` and the rest all put the same labels on
their rows. `passed` and `failed` are always known; the rest are the
optional categories a runner may or may not have, left out when it has none.
readonly passed: number
Tests that passed.
readonly failed: number
Tests that failed.
readonly skipped?: number
Tests the run selected but did not execute: skipped, ignored, pending.
readonly todo?: number
Tests marked as still to be written.
readonly flaky?: number
Tests that failed and then passed on a retry (Playwright's "flaky").
interface ToolTasksApi
The task surface of {@link ToolTasks}.
install(configure: Configure<ToolInstallSettings>): Promise<AbsolutePath>
Install a single release tool, configured through a
{@link ToolInstallSettings} lambda, and resolve to its installed path.
Defaults the install directory to `.zuke/tools`.
installTree(configure: Configure<ToolInstallSettings>): Promise<AbsolutePath>
Install a multi-file runtime tree (Node.js, a JDK, …) from one archive,
configured through a {@link ToolInstallSettings} lambda, and resolve to the
extracted tree's root {@link AbsolutePath}. Because that path is callable,
`root("bin", "node")` is a binary and `root("bin")` a directory to put on
`PATH`. Use `.strip(...)` and `.bins(...)`; defaults the install directory to
`.zuke/tools`. See {@link installTree}; group several with
{@link Toolchain.tree}.
npm(spec: NpmToolSpec, options?: InstallNpmToolOptions): Promise<AbsolutePath>
Provision a single npm-registry package as a version-pinned, cached tool and
resolve to its installed bin path. Defaults the install root to
`.zuke/tools`. See {@link installNpmTool}; group several with
{@link Toolchain.npm}.
interface ToolchainInstallOptions
Options for {@link Toolchain.install}.
destDir?: PathLike
Where tools without their own `destDir` install. Defaults to `.zuke/tools`.
download?: DownloadFn
The download implementation for every release tool (defaults per {@link installRelease}).
npmRun?: NpmRunner
The npm-install runner for npm-package tools (defaults to the ambient `npm`; a test seam).
interface Validation
A check plugged into a target with {@link TargetBuilder.validateBefore} or
{@link TargetBuilder.validateAfter}. The target decides when it runs; the
validation decides what it checks. Throw from {@link Validation.validate} to
fail the target (and break the build). Implemented, for example, by the AI
reviewers in `@zuke/ai`, but any object with a `validate` method qualifies.
name?: string
A name for diagnostics (optional).
validate(context: ValidationContext): void | Promise<void>
Run the check; throw to fail the target. May be async.
interface ValidationContext
Context passed to a {@link Validation} when it runs.
target: string
The name of the target the validation is attached to.
redact(text: string): string
Mask every resolved `secret` parameter in `text`.
A validation that publishes anywhere — a pull-request comment, a review
thread, a job summary, a file it writes — has to run its output through
this first. What it is publishing is typically a model's assessment of a
diff, or of a failure, and the prompt behind it carries the command output
that a secret the build holds can appear in; the secret then comes back in
the reply.
Handed over rather than left for the validation to find, for the same
reason {@link RemediationContext.redact} is: the validation that most needs
it lives in another package and cannot reach core's internals. Zuke's own
reporter and run record redact what they emit, but a validation that
posts over the network goes through neither.
Masks the same values {@link "./params.ts".parameter} marked secret, so a
credential the build never declared is not covered — declare it.
interface WaitContext
The durable context a {@link WaitTrigger} may use while deciding whether its
event has occurred. Its {@link WaitContext.state} handle is the awaiting
target's persisted metadata — it survives a suspend/resume, even across
processes — so a stateful trigger (e.g. "dispatch a GitHub workflow, then poll
it") can remember what it started and hand a result to the target's body. The
built-in triggers ignore it.
readonly state: TargetStateHandle
The awaiting target's durable state handle (the same one its body receives
as `ctx.state`). Reads and writes here persist with the run and are visible
to a later resume in another process.
readonly runId: string
The run id — stable across a resume, so a natural correlation key.
readonly target: string
The awaiting target's dotted name.
interface WaitState
The pending wait recorded on a suspended target (see {@link TargetRunState.waitingFor}).
trigger: string
A human-readable descriptor of what is awaited (e.g. `signal:approved`).
deadline?: string
ISO-8601 deadline after which {@link onTimeout} applies, if a timeout was set.
onTimeout: WaitDisposition
What happens when the deadline passes.
interface WaitTrigger
Decides whether the event a target waits for has occurred. `descriptor` is a
short, JSON-safe label recorded on the suspended target; `isSatisfied` is
evaluated against the run's received signals (and a durable {@link
WaitContext}) when the target is reached and again on each resume attempt.
readonly descriptor: string
A short label recorded on the wait (e.g. `signal:approved`).
readonly pollIntervalMs?: number
Poll interval hint (ms) for predicate triggers driven by `zuke resume --check`.
isSatisfied(signals: ReadonlyMap<string, SignalRecord>, context: WaitContext): boolean | Promise<boolean>
Whether the awaited event has occurred, given the run's received signals
and a durable {@link WaitContext}. The context lets a trigger persist
correlation state across a suspend/resume; a trigger that only inspects
signals may ignore it (fewer parameters stay assignable).
type ActorKind = "human" | "service"
Whether a person or a machine asked for a run (see {@link RunInitiator}).
type AnnouncementLevel = "success" | "failure" | "warning" | "info"
The outcome an announcement conveys. It drives the accent colour and the icon
prepended to the message; defaults to `"info"`.
type Architecture = "x86_64" | "aarch64"
The CPU architectures Zuke recognises.
type ArchiveFormat = "tar.gz" | "zip"
A packed download format, unpacked after the checksum is verified.
type BuildLocation = { kind: "module"; module: string; cwd: string; repo?: string; } | { kind: "command"; command: string[]; cwd: string; repo?: string; }
Where a registered build lives, so a runner can launch it. Two forms: a
`module` (the entry file `deno run` executes — the form `zuke register`
writes) or an explicit `command` (a launch argv, for a build fronted by a
wrapper script). Both carry the working directory and, in CI, the repository.
type ChallengeError = "invalid_token" | "insufficient_scope"
How a bearer challenge names the failure, when there is one to name.
type ChangedFilesFn = (base: string) => Promise<string[]>
Lists the files changed since `base` (a git revision), each path relative to
the repository root. The seam behind {@link ExecuteOptions.affected}; defaults
to {@link gitChangedFiles} and is overridable so the affected plan can be
tested without a real git repository.
type CiHost = "github" | "gitlab" | "azure" | "bitbucket" | "local"
The CI host a build is running on, or `"local"` when not on CI. The names
match {@link CiProvider} so they compose with CI generation and per-host
integrations (e.g. posting a review to the right pull-request API).
type CiInvokes = TargetBuilder | CiInvocation
A target to invoke, bare when the derived job needs no adjustment.
type CiPinResolver = (action: string) => CiUses
Resolves an action's pinned reference by name, e.g. `"actions/checkout"`.
Supplying one is what lets a workflow declare hardening and checkout by
intent rather than by repeating a SHA at every use. Without it each
{@link CiHardenRunner} and {@link CiCheckout} must carry its own `action`.
type CiProvider = "github" | "gitlab" | "azure" | "bitbucket"
The CI providers {@link generateCi} can target.
type CiSyncStatus = "written" | "unchanged" | "stale"
What {@link syncCiFiles} did to a file.
type CiUses = string | CiActionRef
A step's `uses:` value — a bare reference, or one carrying its version.
type Condition = (ctx: ConditionContext) => boolean | Promise<boolean>
A predicate gating whether a target runs; may be synchronous or async.
Receiving the context is optional — a zero-argument
`.onlyWhen(() => …)` stays valid, since a zero-argument function is
assignable to this one-parameter type.
type DownloadFn = (url: string, dest: PathLike) => Promise<void>
A download function: fetch `url` into the file at `dest`.
type DownloadFormat = "raw" | ArchiveFormat
How a downloaded artifact is treated: `"raw"` is the binary itself, an
{@link ArchiveFormat} is unpacked and one path taken from inside.
type EffectFn = (ctx: EffectContext) => unknown | Promise<unknown>
The body of a declared effect (see {@link TargetBuilder.effect}).
type EffectStatus = "pending" | "done" | "failed"
Where one declared effect has got to (see `.effect(...)`).
`pending` is the load-bearing one: it means the intent was committed and the
body may or may not have run. A process that dies mid-effect leaves exactly
that, which is what tells a later resume to drive it again.
type ForEachFactory<Item> = (item: Item, index: number) => Record<string, TargetBuilder>
Builds one item's ordered pipeline of sub-targets for {@link TargetBuilder.forEach}.
The returned record's keys are stage names and its values are targets; each
stage implicitly depends on the one declared before it, so an item's stages
run in insertion order.
type ForceDenial = "unknown_run" | "run_terminal" | "unknown_target" | "already_settled" | "unforceable" | "has_effects" | "foreign_run" | "write_failed"
Why a force was refused. Each maps to a message naming the target, so an
operator learns which rule stopped them rather than that "it failed".
type ForcedOutcome = "skipped" | "succeeded"
What an operator may force a target to, without running it.
type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue; }
A JSON-serialisable value — the only thing that may be persisted in a
target's {@link TargetStateHandle}, since run state is stored as JSON.
type LockResult = { ok: true; token: string; } | { ok: false; holder: LockHolder; }
The result of {@link StateStore.acquireLock}: a `token` proving ownership, or
the current `holder` when the lock is already held.
type McpAuthorization = { readonly allow: true; } | { readonly allow: false; readonly reason: string; }
A policy's verdict on one call.
type McpIdentityHook = (ctx: McpRequestContext) => McpIdentity
Resolve a trusted {@link McpIdentity} from a request's context. The original,
synchronous identity seam, kept as sugar for the common case of trusting a
header an authenticating reverse proxy injected: throwing rejects the whole
request. {@link authenticatorFromHook} adapts one onto
{@link McpAuthenticator}, which is what the server actually runs.
type NpmRunner = (args: string[]) => Promise<void>
Runs `npm install <args>` — the injectable subprocess seam. Defaults to
spawning the ambient `npm`; a test injects a fake that records the argv and
plants the expected bin, so provisioning stays hermetic and network-free.
type OnCancel = TargetBuilder | (() => TargetBuilder)
A compensation registered with {@link TargetBuilder.onCancel}: either a
sibling target directly, or a thunk returning one. The thunk form defers
evaluation so a compensation declared below the target it cleans up (class
fields initialise top-to-bottom) can still be referenced.
type OnTimeout = () => TargetBuilder | "fail" | "cancel-run"
What a timed-out wait does — resolved from {@link WaitSettings.onTimeout}.
type OperatingSystem = "linux" | "macos" | "windows"
The operating systems Zuke recognises — Deno's raw `Deno.build.os` values
normalised to a friendly set (notably `darwin` → `macos`). Used across the
ecosystem so builds branch on `"macos"` rather than the surprising `"darwin"`.
type OrderingEdge = readonly [TargetBuilder, TargetBuilder]
A soft ordering edge `[before, after]`: `before` must run before `after`,
with no data dependency. Returned by {@link "./build.ts".Build.extraEdges} to
feed a consumer's dependency graph (e.g. a monorepo's `dependency-graph.json`)
into planning; an edge whose endpoints are not both in the run's execution set
is ignored, and cycles are reported like any other.
type ParamKind = "string" | "number" | "boolean"
A parameter's runtime kind tag.
type ParamValue = string | number | boolean
The value kinds a parameter can hold.
type PathLike = string | AbsolutePath
A filesystem path accepted by Zuke APIs: either a plain string or an
{@link AbsolutePath}. Anywhere a tool wrapper or build helper takes a path,
it accepts a `PathLike` and coerces it to a string.
type PutBuildResult = { ok: true; version: string; } | { ok: false; conflict: true; }
The result of a {@link BuildRegistry.register} compare-and-swap write.
type PutResult = { ok: true; version: string; } | { ok: false; conflict: true; }
The result of a {@link StateStore.putRun} compare-and-swap write.
type RunEventOutcome = "ok" | "denied" | "error"
The outcome recorded for an audited MCP tool call (see {@link RunEvent}).
type RunStatus = "running" | "suspended" | "cancelling" | "succeeded" | "failed" | "cancelled"
The lifecycle status of a whole run. `cancelling` is the transient state a
cancellation moves through — the run has been asked to stop and its
compensations are running — before it settles as `cancelled`.
type SummaryPairs = Readonly<Record<string, SummaryValue>>
The notes a target reports, keyed by their label — `{ Passed: 837, Failed: 0 }`.
Keys render in the order they are first reported.
type SummaryValue = string | number
A value a summary note may carry; a number is rendered as written.
type Target = TargetBuilder
A configured target. Alias of {@link TargetBuilder} — the same object both
builds and represents the target. Exposed as `Target` for use in signatures.
type TargetFn = (ctx: TargetContext) => unknown | Promise<unknown>
The executable body of a target. May be synchronous or asynchronous, and any
returned value is ignored — so a body can return a tool-wrapper call directly
(`.executes(() => DenoTasks.lint())`, which resolves to a `CommandOutput`)
without wrapping it in an `async` block just to discard the result. A single
returned promise is awaited before dependents run; a returned array of
promises is not (it is not a thenable), so `await Promise.all([...])` inside
the body when you fan work out, rather than returning the array.
type TargetRunStatus = "pending" | "running" | "waiting" | "succeeded" | "failed" | "skipped"
The status of one target within a run record. `waiting` (a suspended
external-event wait) is produced only from a later milestone; the executor
records the others.
type TargetStatus = "passed" | "failed" | "skipped" | "cached" | "waiting"
The outcome of a single target, reported in the summary and lifecycle hooks.
`waiting` marks a `.waitsFor(...)` gate whose event has not occurred — the run
suspends there.
type WaitDisposition = "fail" | "cancel-run" | { target: string; }
What a timed-out wait does: fail, cancel the run, or run a compensation target.
Ergonomic process execution built on `Deno.Command`, exposed as the `$`
tagged template.
```ts
await $`deno test -A`; // throws on non-zero exit
const out = await $`git rev-parse HEAD`.text(); // trimmed stdout
const code = await $`flaky-cmd`.noThrow().code(); // exit code, no throw
await $`build`.env({ NODE_ENV: "prod" }).cwd("./app");
```
Interpolated values become discrete argv entries — they are never spliced
into a shell string — so there is no shell-injection surface. Arrays expand
to multiple arguments.
@module
function $(strings: TemplateStringsArray, ...values: Interpolatable[]): Command
Run an external command, ergonomically.
@example
`await $\`deno test -A``
function splitShellArgs(input: string): string[]
Split `input` into argv the way a POSIX shell would, honouring the quoting
rules only:
- Unquoted runs of whitespace separate arguments; leading, trailing, and
repeated whitespace produce no empty arguments.
- Single quotes are fully literal — no escape sequences at all, so `'a\b'`
yields `a\b`.
- Inside double quotes a backslash escapes only `"`, `\`, ```, `$`, and a
newline; before anything else it stays literal, so `"\d+"` yields `\d+`
rather than silently losing the backslash.
- Outside quotes a backslash escapes the following character, so `a\ b` is one
argument.
- A backslash-newline pair is a line continuation and is removed, both
unquoted and inside double quotes; a backslash at the very end of the input
is a dangling continuation and is dropped.
- Adjacent segments concatenate (`a"b c"d` → `ab cd`) and a quoted empty
string is a real, empty argument (`''` → `[""]`).
Non-goals, deliberately not implemented — the input is turned into argv,
never interpreted: no variable expansion (`"$HOME"` stays `$HOME`), no
globbing, no tilde expansion, no command substitution, and no operator
handling of any kind (`|`, `&&`, `;`, `>` are ordinary characters). A caller
that needs those must split on them itself, or run a real shell.
`\r` is treated as a separator alongside space, tab, and newline so a command
line read from a CRLF file cannot smuggle an invisible carriage return into an
argument.
@param input
The command string to split.
@return
The argv entries, in order; an empty array for blank input.
@throws {ShellArgsError}
If a single or double quote is never closed.
function tokenize(strings: ReadonlyArray<string>, values: ReadonlyArray<Interpolatable>): string[]
Tokenise a tagged-template invocation into an argv array.
Literal whitespace separates arguments; interpolated values are appended as
atomic tokens (so `--flag=${x}` and `pre${x}` work), and arrays expand to one
argument per element. Interpolated values are never re-split on whitespace,
which is what keeps command construction injection-free.
class Command implements PromiseLike<CommandOutput>
A lazily-executed command. Built by the `$` tagged template. The process does
not start until the command is awaited or a terminal method (`text`, `lines`,
`code`) is called; the result is memoised so repeated reads are cheap.
constructor(argv: string[])
Build a command from a discrete argv array (binary first).
env(record: Record<string, string>): this
Merge additional environment variables.
cwd(path: PathLike): this
Set the working directory for the process.
noThrow(): this
Do not throw on a non-zero exit; combine with {@link code}.
quiet(): this
Suppress live stdout/stderr streaming to the terminal.
killAfter(ms: number): this
Kill the process if it runs longer than `ms` milliseconds, raising a
{@link CommandTimeoutError}. Fires even under {@link noThrow}.
stdin(text: string): this
Write `text` to the child's standard input, then close it.
The reason this exists is credentials. A tool that takes a password or token
as an argument puts it in the process table, where any local user reading
`/proc` or running `ps` can see it, and in whatever transcript the command
line is echoed into. Tools that care offer a stdin form instead — `docker login --password-stdin` is the canonical one — and without this there was no
way to use it: the secret had nowhere to go but argv.
The text never reaches {@link Command.commandLine}, because it is not part
of the command line. That is the point, not a side effect.
Only affects the run paths. A {@link Command.spawn}ed long-running process
keeps its inherited stdin, since a service that reads from the terminal is a
different thing from a one-shot that takes a secret.
maxCapturedBytes(bytes: number): this
Cap how much of each captured stream is kept in memory, in bytes
(default 8 MiB). Capture keeps the newest bytes: once the cap is reached the
oldest are dropped, {@link CommandOutput.truncated} is set, and
{@link CommandOutput.text} prefixes a notice. Raise it for a command whose
whole output you must parse; lower it to bound a chatty one. Live streaming
to the terminal is never capped — every byte still reaches it.
@throws {RangeError}
If `bytes` is not a positive whole number.
signal(signal: AbortSignal): this
Terminate the process (via `SIGTERM`) when `signal` aborts — for example
when the enclosing run is cancelled. Overrides the executor's ambient
run signal for this command. Composes with {@link killAfter}: either the
timeout or the abort kills the process, whichever fires first.
get commandLine(): string
The command line, for diagnostics — argv joined by spaces, with the resolved
value of every `secret` parameter of the enclosing run masked. This is the
only rendered form of the command (the echo under `--dry-run`, a
{@link CommandError} message), so a secret passed as an argv token cannot
leak through one. The argv given to the operating system is unchanged.
then(onfulfilled?: ((value: CommandOutput) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>
Await support: run the command and resolve to a {@link CommandOutput}.
async text(): Promise<string>
Run and resolve to trimmed stdout — prefixed with a truncation notice if the
capture cap was hit. Throws on non-zero unless `noThrow`.
async lines(): Promise<string[]>
Run and resolve to stdout split into lines (trailing blank dropped).
async code(): Promise<number>
Run and resolve to the numeric exit code. Never throws on non-zero.
spawn(): SpawnedProcess
Start the command as a long-lived process without waiting for it to
exit, returning a {@link SpawnedProcess} handle. Use this for a service —
a dev server, a database, `docker compose up` — that must keep running
while other targets execute; stop it with {@link SpawnedProcess.stop}.
stdout/stderr are inherited so the process's output is visible.
class CommandError extends Error
Raised when a command exits non-zero and throwing was not suppressed.
constructor(readonly command: string, readonly code: number, readonly stderr: string)
Build the error from the failed command line, exit code, and stderr.
override name: string
The error name.
class CommandOutput
The resolved result of a command, available when awaiting a {@link Command}.
constructor(readonly code: number, readonly stdout: string, readonly stderr: string, readonly truncated: boolean, readonly maxCapturedBytes: number)
Build the output from the process exit code and captured streams.
text(): string
Trimmed stdout, prefixed with a one-line notice when {@link truncated} — so
a caller reading the output cannot mistake a tail for the whole of it.
class CommandTimeoutError extends Error
Raised when a command is killed for exceeding its {@link Command.killAfter}
budget. Thrown regardless of {@link Command.noThrow}, since a timeout is a
distinct, exceptional outcome from a normal non-zero exit.
constructor(readonly command: string, readonly timeoutMs: number)
Build the error from the command line and the elapsed-time budget.
override name: string
The error name.
class ShellArgsError extends Error
Raised when {@link splitShellArgs} reaches the end of the input with a quote
still open. Names the offending quote character and the offset at which it was
opened so the bad spot in a long command line is findable.
constructor(readonly quote: string, readonly offset: number)
Build the error from the unclosed quote character and its offset.
override name: string
The error name.
class SpawnedProcess
A long-lived process started with {@link Command.spawn} — the handle a
{@link https://jsr.io/@zuke/core service} keeps alive. Unlike awaiting a
{@link Command}, spawning does not wait for the process to exit; call
{@link SpawnedProcess.stop} to terminate it (which is also the default
service teardown). Its stdout/stderr are inherited so the process's own
output is visible.
constructor(child: Deno.ChildProcess | undefined, readonly commandLine: string)
Wrap a spawned child process (or none, for a stub) and its command line.
get pid(): number
The operating-system process id (`-1` for a dry-run stub).
get status(): Promise<Deno.CommandStatus>
Resolves when the process exits (immediate success for a dry-run stub).
stop(signal: Deno.Signal, graceMs: number): Promise<void>
Terminate the process and wait for it to exit. Sends `signal` (default
`SIGTERM`); if the process has not exited within `graceMs` (default 5s), it
escalates to `SIGKILL` so a process that ignores `SIGTERM` cannot hang
teardown. A process that has already exited is treated as stopped. A
dry-run stub (no child) is a no-op.
type Interpolatable = string | number | AbsolutePath | Array<string | number | AbsolutePath>
A value that may be interpolated into a `$` template.
Foundations for typed tool wrappers (settings-lambda task functions).
A tool package (e.g. `@zuke/deno`, `@zuke/npm`) defines one settings class
per subcommand by extending {@link ToolSettings}: `buildArgs()` assembles
the subcommand argv purely (no I/O), while the base contributes the common
fluent chainers (`env`, `cwd`, `noThrow`, `quiet`, `toolPath`, `args`) and
the execution logic, which reuses {@link Command} so argv stays an array
end-to-end — there is no shell string and no injection surface.
```ts
class MyToolSettings extends ToolSettings {
protected defaultTool() { return "mytool"; }
protected buildArgs() { return ["build", "--fast"]; }
}
await runSettings(new MyToolSettings(), (s) => s.cwd("app"));
```
@module
function defineTool(tool: string, options: DefineToolOptions): ToolTask
Define a fluent task for a CLI that has no dedicated `@zuke` wrapper. Returns
a task that runs the tool, configured through a {@link DynamicToolSettings}
lambda — the same settings-lambda style as the built-in wrappers, with
`arg`/`flag`/`option` for argv and the shared `cwd`/`env`/`noThrow`/… chainers.
```ts
import { defineTool } from "@zuke/core/tooling";
const terraform = defineTool("terraform");
await terraform((s) => s.arg("plan").option("out", "plan.tfplan"));
// → terraform plan --out plan.tfplan
const helmUpgrade = defineTool("helm", { subcommand: "upgrade" });
await helmUpgrade((s) => s.arg("api", "./chart").flag("install"));
// → helm upgrade api ./chart --install
```
function runSettings<S extends ToolSettings>(settings: S, configure?: Configure<S>): Promise<CommandOutput>
Construct-configure-run: the shared shape of every task function.
```ts
export const MyTasks = {
build: (configure?: Configure<MyBuildSettings>) =>
runSettings(new MyBuildSettings(), configure),
};
```
function shimFallbackArgv(argv: ReadonlyArray<string>, os: typeof Deno.build.os): string[] | null
On Windows, wrap an argv in a `cmd /c` invocation so `.cmd`/`.bat` shims
(such as npm's) become spawnable; returns `null` on other platforms.
@deprecated
Unsafe, and no longer used. The wrapped argv is handed to
`cmd.exe` as one command line quoted by C-runtime rules, which quote on
spaces but not on the metacharacters cmd.exe acts on, so an operand
containing `&`, `|` or `^` and no space is re-parsed as further commands.
Nothing needs the wrapper: `Deno.Command` spawns a `.cmd`/`.bat` directly,
escaping for the command processor as it does. Scheduled for removal in the
next major.
function windowsCmdShim(argv: ReadonlyArray<string>, os: typeof Deno.build.os): string[]
On Windows, wrap a resolved `.cmd`/`.bat` shim in `cmd /c`. Returns `argv`
unchanged on other platforms or when the binary is not a batch shim.
@deprecated
Unsafe, and no longer used. Its premise — that `Deno.Command`
cannot launch a batch shim — does not hold: Deno spawns one through the
command processor itself, quoting each argument for it. Wrapping instead
makes `cmd.exe` the direct child, whose command line is quoted by C-runtime
rules that leave `&`, `|` and `^` bare, so a caller operand carrying one is
re-parsed as a command. Scheduled for removal in the next major.
class DynamicToolSettings extends ToolSettings
Fluent settings for a {@link defineTool} tool: build the argv with
{@link DynamicToolSettings.arg}/{@link DynamicToolSettings.flag}/{@link
DynamicToolSettings.option} (in call order), plus all the shared chainers
(`cwd`, `env`, `noThrow`, `quiet`, `toolPath`, `args`).
constructor(tool: string, initial: string[])
Build settings for `tool`, seeded with any `initial` subcommand tokens.
override protected defaultTool(): string
The configured tool binary.
arg(...values: Array<string | number>): this
Append raw positional/argument tokens.
flag(name: string): this
Append a boolean flag, e.g. `flag("verbose")` → `--verbose` (or `-v`).
option(name: string, value: string | number): this
Append a flag and its value as two tokens, e.g. `--output dist`.
override protected buildArgs(): string[]
The argv assembled from the `arg`/`flag`/`option` calls, in order.
abstract class SubcommandSettings extends ToolSettings
Base for a wrapper over a CLI organised into subcommand groups — a command
path built with {@link command} plus repeatable `--flag [value]` options built
with {@link flag}. The agent and cloud wrappers (`gh`, `gcloud`, `claude`,
`gemini`, `codex`) share this shape; each sets its binary via
{@link ToolSettings.defaultTool} and, when needed, a fixed prefix via
{@link leadingTokens} or between-command global flags via {@link middleTokens}.
The argv is assembled as
`[...leadingTokens(), ...command, ...middleTokens(), ...flags]`, keeping every
token a discrete argv entry so command construction stays injection-free.
command(...parts: Array<string | number>): this
Append command-path tokens — the group, verb, and operands — in order.
flag(name: string, value?: string | number): this
Add an arbitrary flag. With a value it renders `--name value`; without one
the bare `--name`. Repeatable.
protected leadingTokens(): string[]
Fixed token(s) placed before the command path (e.g. a subcommand-group
name). Empty by default; override to prepend a constant prefix.
protected middleTokens(): string[]
Token(s) placed between the command path and the trailing flags (e.g. a
wrapper's common global flags). Empty by default; override to insert them.
override protected buildArgs(): string[]
Assemble the argv: leading tokens, command path, middle tokens, then flags.
class ToolNotFoundError extends Error
Raised when a tool's binary cannot be found on the system.
constructor(readonly tool: string, sawNodeModules: boolean)
Build the error naming the tool binary that could not be found.
override name: string
The error name.
abstract class ToolSettings
Abstract fluent base for tool settings. Subclasses provide the binary
({@link defaultTool}) and the pure subcommand argv ({@link buildArgs});
the base provides the shared chainers and {@link run}.
os_: typeof Deno.build.os
The platform identifier used when resolving a tool from `node_modules`,
which on Windows means looking for the `.cmd` shim rather than the bare
name.
In production this is always `Deno.build.os`. It is exposed as a public
field — rather than read from `Deno.build.os` inline — so that tests can
pin a specific platform without spawning a subprocess or touching the
environment:
```ts
const s = new MyToolSettings();
s.os_ = "windows"; // resolve as Windows would, on any host
```
The trailing underscore signals an internal test seam: do not rely on this
field in production code.
protected markSecret(value: string): void
Register `value` with the run's redactor, so every rendered form of this
command masks it.
For the credential a tool will only take as an argument. Prefer
{@link "./shell.ts".Command.stdin} wherever the tool offers a stdin form:
this masks the rendering, not the argv, so the value is still visible in
the process table to anyone on the same host. It is what to reach for when
there is no better option, not instead of the better option.
Protected because it is for a wrapper to call while building its own
settings — the wrapper is what knows which of its fields is a credential.
A build marks its own values by declaring them with
{@link "./params.ts".parameter} and `.secret()`, which is the same redactor.
Does nothing outside a run, where there is no redactor to register with —
a wrapper used standalone in a script still works, it simply has nothing
masking its output.
abstract protected defaultTool(): string
The binary to spawn when {@link toolPath} is not set.
abstract protected buildArgs(): string[]
The subcommand argv. Must be pure — no I/O, no environment reads.
protected onOutput(_output: CommandOutput): void
Called by {@link run} with the process's output once it has exited —
before a non-zero exit becomes a `CommandError`, so a wrapper sees the
output of a failed run too. The base does nothing. A wrapper overrides it
to read a result its tool prints and report it, e.g. a test runner's
pass/fail line into the build summary via `reportSummary`; it must not
throw. Not called when the process times out or could not be spawned.
protected stdinInput(): string | undefined
Hook: the text to write to the tool's standard input, or `undefined` to
leave it inherited. The base supplies none.
The reason it exists is credentials. A secret in argv is readable by every
local user through `/proc` or `ps`, and tools that care offer a stdin form
instead — `docker login --password-stdin` is the canonical one. Without a
hook here a wrapper could not use that form: {@link
"./shell.ts".Command.stdin} exists, but {@link run} builds its `Command`
privately, so a subclass had no way to reach it and had to put the secret
on the command line regardless.
Not named after that flag, because it is not docker's alone — other tools
read secrets from stdin too, and what this returns is simply what the tool
reads.
The guarantee {@link "./shell.ts".Command.stdin} makes carries through: the
text is not part of the command line, so it reaches neither
{@link "./shell.ts".Command.commandLine}, a dry-run echo, nor a
{@link CommandError} message. Prefer it over {@link markSecret}, which
masks only how the argv is rendered and leaves the value in the process
table.
protected defaultResolution(): ToolResolution
The wrapper's default binary-resolution strategy. The base returns
`"path"` (bare name on `PATH`); a JS-ecosystem wrapper whose binary is
almost always installed under `node_modules` overrides this to
`"node_modules"`. A per-call {@link fromNodeModules}/{@link fromPath} and
the ambient `ZUKE_TOOL_RESOLUTION` both take precedence over this default.
env(record: Record<string, string>): this
Merge additional environment variables for the process.
cwd(path: PathLike): this
Set the working directory for the process.
noThrow(): this
Do not throw on a non-zero exit; inspect `code` on the output instead.
get throwsOnError(): boolean
Whether a failure should throw — the default, or `false` after
{@link noThrow}. A task that layers its own validation on top of the
subprocess (e.g. a coverage-threshold gate) reads this to decide whether a
gate failure throws or is merely reported.
quiet(): this
Suppress live stdout/stderr streaming to the terminal.
killAfter(ms: number): this
Kill the tool if it runs longer than `ms` milliseconds, raising a
`CommandTimeoutError`. Fires even under {@link noThrow}.
maxCapturedBytes(bytes: number): this
Cap how much of each captured stream the run keeps in memory, in bytes
(default 8 MiB). Once the cap is reached the oldest bytes are dropped,
`CommandOutput.truncated` is set, and `CommandOutput.text` prefixes a
notice. Raise it for a tool whose whole output must be parsed — a coverage
report, a `--json` dump — and lower it to bound a chatty one. Live
streaming to the terminal is never capped.
@throws {RangeError}
If `bytes` is not a positive whole number.
toolPath(path: PathLike): this
Override the binary to run (e.g. an absolute path to the tool).
fromNodeModules(): this
Resolve the binary npx-style: walk up from the working directory looking
for `node_modules/.bin/<tool>`, falling back to `PATH` on a miss. Overrides
both the wrapper default and the ambient `ZUKE_TOOL_RESOLUTION`. Has no
effect once {@link toolPath} is set (an explicit path always wins).
fromPath(): this
Resolve the binary from `PATH` only, ignoring any `node_modules/.bin`.
args(...extra: Array<string | number | AbsolutePath>): this
Escape hatch: append raw arguments after all typed options.
argv(): string[]
The full argv (binary first). Pure — useful for tests and diagnostics.
resolvedArgv(): string[]
The argv {@link run} will actually spawn — like {@link argv}, but with the
`node_modules/.bin` resolution applied (so it performs I/O). Useful for
tests and diagnostics: it reveals whether a wrapper resolved to a local
shim or fell back to the bare name on `PATH`.
async run(): Promise<CommandOutput>
Run the configured tool, raising a {@link ToolNotFoundError} naming it when
the binary is missing.
The argv is spawned as it was resolved, on every platform. Windows batch
shims used to be wrapped in `cmd /c` here, which silently gave up the
argv-boundary guarantee every wrapper relies on: `Deno.Command` hands
`cmd.exe` a single command line built with C-runtime quoting, which quotes
on spaces but not on `&`, `|` or `^`, so cmd.exe re-parsed an operand like
`A=1&whoami` as a second command. Spawning the shim itself instead keeps
that decision where it belongs: Deno resolves a bare name through `PATHEXT`
and launches a `.cmd`/`.bat` through the command processor with quoting
hardened for it, so the operand stays one argument.
interface DefineToolOptions
Options for {@link defineTool}.
subcommand?: string | string[]
Leading subcommand token(s) prepended to every invocation.
type Configure<S> = (settings: S) => S
A lambda that configures a settings instance and returns it.
type ToolResolution = "node_modules" | "path"
How {@link ToolSettings.run} locates a wrapper's binary when no explicit
{@link ToolSettings.toolPath} is set:
- `"path"` — spawn the bare tool name and let the OS resolve it on `PATH`
(the default, matching a native/global install);
- `"node_modules"` — npx-style: walk up from the working directory looking
for `node_modules/.bin/<tool>`, falling back to `PATH` on a miss (so a
package hoisted to a monorepo root runs with no `.toolPath()`).
type ToolTask = (configure?: Configure<DynamicToolSettings>) => Promise<CommandOutput>
A ready-to-run task for a {@link defineTool} tool.
A conformance kit for tool-wrapper tests.
Every `@zuke/*` wrapper package owes its unit test the same three checks: the
settings class spawns the binary it claims to, it resolves that binary the way
the wrapper intends (bare on `PATH`, or npx-style from `node_modules/.bin`),
and a missing binary surfaces as a
{@link "./tooling.ts".ToolNotFoundError} rather than some raw
`Deno.errors.NotFound`. Hand-written per package, that is a temp-directory /
`ZUKE_TOOL_RESOLUTION` save-and-restore dance copied dozens of times — and a
wrapper that quietly forgets the resolution check keeps passing.
{@link assertWrapperConformance} runs all three, hermetically (nothing real is
ever spawned), and takes the expected resolution mode as a required
argument so each wrapper asserts its default instead of remembering it:
```ts
Deno.test("biome conforms", async () => {
await assertWrapperConformance(() => new BiomeCheckSettings(), "biome", {
resolution: "node_modules",
});
});
```
@module
async function assertWrapperConformance(makeSettings: () => ToolSettings, tool: string, options: WrapperConformanceOptions): Promise<void>
Assert that a tool wrapper conforms: `makeSettings()` spawns `tool`, resolves
it per `options.resolution`, and reports a missing binary as a
{@link "./tooling.ts".ToolNotFoundError}.
`makeSettings` is called once per check, so each check gets a pristine
instance. The resolution check runs against a throwaway temp directory holding
a fake `node_modules/.bin/<tool>` shim, with `ZUKE_TOOL_RESOLUTION` unset for
the duration and restored afterwards; no real subprocess is ever launched.
A wrapper whose `run()` resolves something at run time must have that pinned
inside `makeSettings` — `() => new DockerComposeUpSettings().usePlugin()`,
say — or the missing-binary check would probe the ambient host. It reports a
`ToolNotFoundError` raised for any binary other than the planted one as a
failure, so such a wrapper cannot pass by accident on a host that lacks the
real tool.
@throws {Error}
naming the wrapper and the fix, on the first failed check.
function missingTool<S extends ToolSettings>(settings: S): S
Point `settings` at a binary that cannot exist, so running it raises a
{@link "./tooling.ts".ToolNotFoundError} without ever launching a real
process — the way a wrapper test proves each of its task functions reaches
execution.
The platform is pinned to `linux` so the assertion reads the same on every
runner, rather than depending on how the host reports a missing binary:
```ts
await assertRejects(() => BiomeTasks.check(missingTool), ToolNotFoundError);
```
interface WrapperConformanceOptions
Options for {@link assertWrapperConformance}.
resolution: ToolResolution
The resolution strategy the wrapper must use when nothing overrides it:
`"node_modules"` for a JS-ecosystem tool installed under `node_modules`,
`"path"` for a natively installed one. Required, with no default: an
npm-distributed wrapper that forgot to override `defaultResolution()` is
exactly the bug this kit exists to catch, and a default would let that
wrapper's test pass by saying nothing.
Primitive terminal rendering, shared by the executor's build reporting
(`./report.ts`) and the `@zuke/console` package: ANSI styling, terminal-width
detection, duration formatting, and the reusable `line`/`box`/`table`
primitives that draw a build's output.
Everything here is pure — no I/O, no process state — so argv-free output can
be unit-tested and reused without duplicating escape codes. Cells may already
carry ANSI codes; width is measured on the visible text ({@link visibleWidth})
so painted content still aligns.
@module
function box(style: Style, content: string | readonly string[], options: BoxOptions): string[]
A bordered panel around `content` (a string, split on newlines, or an array
of lines). Content may carry ANSI codes; padding is measured on the visible
text so the border stays flush.
function detectWidth(): number
Read the terminal width if available, clamped to a sane range.
function escapeData(value: string): string
Escape a value interpolated into the body of a GitHub Actions workflow
command (`::error::<data>`).
A workflow command is terminated by the end of its line, so a value carrying
a newline continues into what the runner parses as a fresh command. A
target's failure message embeds a subprocess's stderr verbatim, which is not
ours to trust: a tool that writes `::stop-commands::` on a line of its own
would otherwise suspend the runner's command processing, and one that writes
`::error::` would forge an annotation. Percent-encoding is the escape the
Actions spec defines for exactly this, and `%` is encoded first so the
encoding cannot be spoofed by a literal `%0A` in the input.
function escapeLine(text: string): string
Neutralise workflow commands in text that is printed as itself on a stream
the GitHub Actions runner parses — a failure message, a target name, a
summary row — rather than interpolated into a command's body.
{@link escapeData} is the wrong tool there. It answers the same threat, but
by encoding every newline, which would fold a multi-line compiler dump into
one unreadable `%0A`-joined line: correct, and useless to the person reading
the log. This keeps the text as it was written and disarms only the two
sequences the runner acts on.
Both forms are covered, because the runner accepts both. A line whose first
non-blank characters are `::` opens a command, and leading whitespace is
trimmed before that test, so indenting the text defends nothing. The legacy
`##[command]` form is recognised anywhere in a line, so it needs no newline
to reach at all.
"Blank" is the runner's idea of it, not this language's. The two sets differ
by exactly one character in the direction that matters: NEXT LINE (U+0085),
which the runner trims and `\s` does not match. It is not a line terminator
for the reader the runner uses, so it travels inside a line and disappears
only when the command is parsed, which would let `::` reach the front of a
line that looked indented here. It is matched explicitly for that reason.
Ordinary output is returned unchanged; only text that would have been
executed as a command comes back visibly encoded.
function escapeLineIf(github: boolean, text: string): string
{@link escapeLine}, applied only when something is parsing this process's
output for workflow commands.
The condition is not a security gate — the runner parses every line a step
writes, whatever this process believes about its own style. It is a
readability one: `escapeLine` leaves ordinary text alone, but a compiler
dump that legitimately begins a line with `::` would come back encoded, and
on a developer's terminal that is noise protecting against nothing.
It exists so the decision has one implementation. It was written out at more
than a dozen call sites, in three shapes that had already drifted apart — two
asking the style, one asking the environment — which is how a site gets added
without it.
function escapeProperty(value: string): string
Escape a value interpolated into a workflow command's property list
(`::error title=<property>::`). Properties are comma-separated and
colon-terminated, so those two characters need encoding on top of what
{@link escapeData} handles.
function formatDuration(ms: number): string
Format a duration in milliseconds as `1.2s`.
function isStyleName(name: string): name is StyleName
Whether a string names one of the {@link SGR} styles.
function line(style: Style, options: LineOptions): string
A horizontal rule spanning the style's width (dimmed by default).
function pad(text: string, width: number, align: "left" | "right"): string
Pad `text` to `width` visible columns, aligning left (default) or right.
function paint(color: boolean, codes: string, text: string): string
Wrap text in ANSI codes when colour is enabled, otherwise return it as-is.
function sgrCodes(names: readonly StyleName[]): string
Concatenate the escape codes for `names` (an unknown name contributes none).
function stripAnsi(text: string): string
Strip ANSI escape sequences, leaving the visible text.
function stylize(color: boolean, names: readonly StyleName[], text: string): string
Paint `text` in the named styles when `color` is enabled.
function table(style: Style, columns: readonly TableColumn[], rows: readonly (readonly string[])[], options: TableOptions): string[]
An aligned text table: a styled header row, an optional dividing rule, then
one line per row. Column widths fit the widest visible cell; cells may already
carry ANSI colour. Rows shorter than the columns are padded with empty cells.
function visibleWidth(text: string): number
The printable width of `text`, ignoring any ANSI colour codes it carries.
const SGR: { reset: string; bold: string; dim: string; italic: string; underline: string; black: string; red: string; green: string; yellow: string; blue: string; magenta: string; cyan: string; white: string; gray: string; }
ANSI select-graphic-rendition codes, keyed by style name.
interface BoxOptions
Options for {@link box}.
title?: string
A title embedded in the top border.
padding?: number
Horizontal padding inside the border, in spaces. Defaults to `1`.
width?: number
Force an inner width; widened automatically to fit content and title.
border?: readonly StyleName[]
Styles for the border characters. Defaults to `["dim"]`.
titleStyle?: readonly StyleName[]
Styles for the title text. Defaults to `["bold"]`.
interface LineOptions
Options for {@link line}.
char?: string
The character to repeat. Defaults to `═`.
width?: number
The rule width. Defaults to the style's width.
style?: readonly StyleName[]
Styles applied to the whole rule. Defaults to `["dim"]`.
interface Style
How a run renders its output.
github: boolean
Wrap target output in `::group::`/`::endgroup::` and emit `::error::`.
color: boolean
Emit ANSI colour codes (off when piped, under `NO_COLOR`, or in CI).
width: number
Width of horizontal rules and boxes, in characters.
interface TableColumn
One column of a {@link table}.
header: string
The column header.
align?: "left" | "right"
Cell alignment. Defaults to `left`.
interface TableOptions
Options for {@link table}.
separator?: string
Column separator. Defaults to two spaces.
divider?: boolean
Draw a dividing rule under the header. Defaults to `true`.
headerStyle?: readonly StyleName[]
Styles for the header row. Defaults to `["bold"]`.
dividerStyle?: readonly StyleName[]
Styles for the divider rule. Defaults to `["dim"]`.
type StyleName = keyof typeof SGR
A style name understood by {@link sgrCodes}, {@link paint}, and markup.
A backend conformance kit for the state-api (`docs/state-api.md`).
A hosted {@link "./state/store.ts".StateStore} / {@link
"./registry/registry.ts".BuildRegistry} backend must implement the same
compare-and-swap, listing, and TTL-lock semantics the filesystem backend
does — the exactly-once resume, lock takeover, and one-writer-wins guarantees
the core relies on ride on them. This module extracts those semantics into
store-agnostic scenarios you can point at any implementation: Zuke's own test
lane runs them against the filesystem store, and a backend author runs them
against a live service:
```sh
ZUKE_STATE_TOKEN=… ZUKE_REGISTRY_TOKEN=… \
deno run -A jsr:@zuke/core/conformance --url http://localhost:8080
```
Both variables are optional — set them only when the backend requires a
bearer token. There is no `--token` argument: passing one is refused, because
a credential in argv is readable by every local process and lands in the
transcript of whatever invoked the kit.
Every scenario uses freshly-generated ids, so it is safe to run against a
shared, persistent service; the lock-takeover scenario uses a short real TTL
and a brief sleep, so it takes a beat of wall-clock time. A backend that
passes is compatible with
{@link "./state/http_store.ts".HttpStateStore} /
{@link "./registry/http_registry.ts".HttpBuildRegistry}; one that violates
CAS fails loudly.
@module
async function checkBuildRegistry(make: BuildRegistryFactory): Promise<ConformanceResult[]>
Run the build-registry conformance scenarios against the registry `make` builds.
async function checkStateStore(make: StateStoreFactory, options: ConformanceOptions): Promise<ConformanceResult[]>
Run the state-store conformance scenarios against the store `make` builds.
async function runConformanceCli(args: string[], deps: ConformanceCliDeps): Promise<number>
Run the conformance kit as a CLI: `--url <base>` (required) names the backend
and both suites run against it. Prints a `PASS`/`FAIL` line per scenario and
resolves to a process exit code — `0` when every scenario passes, `1` when any
fails, `--url` is missing, or a credential was passed as an argument.
The bearer token comes from the environment, `ZUKE_STATE_TOKEN` and
`ZUKE_REGISTRY_TOKEN`, exactly as every other consumer of these stores reads
it. It used to be a `--token` argument, which put a credential that can forge
run records, the audit trail and lock exclusivity into the process table for
any local user to read, and into the transcript of whatever invoked it.
`--token` is now refused rather than ignored: an invocation that still
passes one would otherwise authenticate as anonymous and fail somewhere less
obvious, and the credential would already have been exposed by the time it
did.
interface ConformanceCliDeps
Injectable dependencies for {@link runConformanceCli} (tests override them).
makeStateStore?: (url: string, token?: string) => StateStore
Build the {@link StateStore} for a url/token (default {@link HttpStateStore}).
makeBuildRegistry?: (url: string, token?: string) => BuildRegistry
Build the {@link BuildRegistry} for a url/token (default {@link HttpBuildRegistry}).
log?: (line: string) => void
Emit a line of output (default `console.log`).
readEnv?: (name: string) => string | undefined
Read an environment variable (default the process environment).
interface ConformanceOptions
Tuning options for the conformance scenarios.
lockTtlMs?: number
The lock TTL (ms) the takeover scenario acquires with; it then waits a bit
longer than this for the lock to expire. Raise it for a slow backend.
Default 200.
interface ConformanceResult
The outcome of one conformance scenario.
readonly name: string
The scenario's name.
readonly ok: boolean
Whether the backend satisfied it.
readonly error?: string
The failure detail when `ok` is false.
type BuildRegistryFactory = () => BuildRegistry | Promise<BuildRegistry>
A `() =>` factory the kit calls once to obtain the registry under test.
type StateStoreFactory = () => StateStore | Promise<StateStore>
A `() =>` factory the kit calls once to obtain the store under test.
========================================================================
# @zuke/deno
========================================================================
`@zuke/deno` — typed `DenoTasks` wrappers for the `deno` CLI, for use in
Zuke build targets.
```ts
import { DenoTasks } from "@zuke/deno";
await DenoTasks.check((s) => s.paths("mod.ts"));
await DenoTasks.test((s) => s.allowAll().coverage("cov_profile"));
await DenoTasks.fmt((s) => s.check());
```
@module
function parseCacheInfo(stdout: string): DenoCacheInfo
Parse the cache report from `deno info --json` stdout.
function parseModuleGraph(stdout: string): DenoModuleGraph
Parse the module graph from `deno info --json <file>` stdout.
Entries without a specifier are skipped rather than guessed at: a module
deno could not name is not a module a build can act on.
const DenoTasks: DenoTasksApi
Typed task functions for the `deno` CLI.
class CoverageThresholdError extends Error
Raised when measured coverage falls below a configured threshold.
constructor(readonly failures: string[])
Construct the error from one message per metric that fell short.
override name: string
The error name, `"CoverageThresholdError"`.
class DenoAddSettings extends DenoLockSettings
Settings for `deno add`.
packages(...specs: string[]): this
The packages to add, e.g. `jsr:@std/assert` or `npm:express` (required).
dev(): this
Add as a dev dependency (`--dev`). Deno only distinguishes the two in a
`package.json`; against a `deno.json` the flag has nothing to record.
jsr(): this
Read unprefixed package names as JSR packages (`--jsr`).
npm(): this
Read unprefixed package names as npm packages (`--npm`), deno's default.
saveExact(): this
Record the exact version, without a caret range (`--save-exact`).
lockfileOnly(): this
Update the lockfile without installing (`--lockfile-only`).
packageJson(): this
Record the dependency in `package.json` rather than `deno.json` (`--package-json`).
override protected buildArgs(): string[]
Assemble the `deno add` argv.
class DenoApproveScriptsSettings extends DenoSettings
Settings for `deno approve-scripts`.
packages(...specs: string[]): this
The npm specifiers whose lifecycle scripts to approve (required).
lockfileOnly(): this
Record the approval in the lockfile without installing (`--lockfile-only`).
override protected buildArgs(): string[]
Assemble the `deno approve-scripts` argv.
class DenoBenchSettings extends DenoPermissionSettings
Settings for `deno bench`.
paths(...paths: PathLike[]): this
Restrict the run to specific benchmark files or directories.
filter(pattern: string): this
Only run benchmarks whose name matches (`--filter`).
json(): this
Report results as JSON (`--json`) rather than the table. Deno marks the
flag unstable, so treat the shape as subject to change between releases.
noRun(): this
Cache the benchmark modules without running them (`--no-run`) — a cheap
way to prove the benchmarks still compile without paying to run them.
permitNoFiles(): this
Succeed when no benchmark files matched (`--permit-no-files`) instead of
failing the target.
ignore(...patterns: string[]): this
Skip files matching these patterns (`--ignore`).
config(path: PathLike): this
Use an explicit config file (`--config`).
override protected buildArgs(): string[]
Assemble the `deno bench` argv.
class DenoBumpVersionSettings extends DenoSettings
Settings for `deno bump-version`.
The subcommand is experimental — deno itself prints a notice saying so on
every run — so treat its output as subject to change between releases.
increment(kind: DenoVersionIncrement): this
The increment to apply. Omit it to derive the increment from the
conventional commits since {@link start}.
dryRun(): this
Print the planned changes without writing any files (`--dry-run`).
workspace(): this
Bump every package in the workspace (`--workspace`).
noWorkspace(): this
Bump only the manifest in the current directory (`--no-workspace`).
config(path: PathLike): this
The manifest to bump (`--config`).
importMap(path: PathLike): this
The import map whose `jsr:` constraints to rewrite (`--import-map`).
base(ref: string): this
Git ref to compare against in conventional-commits mode (`--base`).
start(ref: string): this
Git ref to start from in conventional-commits mode (`--start`).
releaseNotes(path: PathLike): this
Release notes file to prepend to in conventional-commits mode (`--release-notes`).
override protected buildArgs(): string[]
Assemble the `deno bump-version` argv.
class DenoCacheSettings extends DenoSettings
Settings for `deno cache`.
reload(): this
Reload remote modules instead of using the cache (`--reload`).
frozen(): this
Error out if the lockfile is out of date (`--frozen`). See
{@link DenoPermissionSettings.frozen} for why the name mirrors the real
Deno flag rather than `PnpmSettings.frozenLockfile()`'s naming.
paths(...paths: PathLike[]): this
The entry points to cache (at least one is required).
override protected buildArgs(): string[]
Assemble the `deno cache` argv.
class DenoCheckSettings extends DenoSettings
Settings for `deno check`.
paths(...paths: PathLike[]): this
The files to type-check (at least one is required).
all(): this
Type-check remote modules and npm packages too (`--all`), not just the
local code. Slower, and the only way to catch a dependency whose published
types do not actually compile.
doc(): this
Type-check the code blocks in JSDoc and Markdown as well (`--doc`).
docOnly(): this
Type-check only the code blocks in JSDoc and Markdown (`--doc-only`).
checkJs(): this
Type-check JavaScript files too (`--check-js`).
config(path: PathLike): this
Type-check against a specific configuration file (`--config`) instead of the
one Deno would discover by walking up from the checked files.
The discovered config decides how bare specifiers resolve, so pointing at
another one type-checks the same sources against a different dependency
set — for example checking a workspace member against the published
version of a sibling it declares, rather than the local member that
workspace resolution would substitute.
noConfig(): this
Discover no configuration file at all (`--no-config`).
noLock(): this
Ignore the lockfile entirely (`--no-lock`), neither reading nor writing it.
Use it for a check whose resolutions are deliberately not the project's:
writing them into the committed lock would corrupt it, and reading it would
pin the very versions the check is trying to vary.
frozen(): this
Error out if the lockfile is out of date (`--frozen`). See
{@link DenoPermissionSettings.frozen} for why the name mirrors the real
Deno flag rather than `PnpmSettings.frozenLockfile()`'s naming.
lock(path: PathLike): this
Use an explicit lockfile (`--lock`) instead of the discovered `deno.lock`.
importMap(path: PathLike): this
Load an import map from a file or URL (`--import-map`).
noNpm(): this
Do not resolve npm modules (`--no-npm`).
noRemote(): this
Do not resolve remote modules (`--no-remote`).
reload(...specifiers: string[]): this
Reload the module cache (`--reload`), optionally only these specifiers.
nodeModulesDir(mode: NodeModulesMode): this
Set the node-modules management mode (`--node-modules-dir`).
nodeModulesLinker(mode: NodeModulesLinker): this
Set the npm linker mode (`--node-modules-linker`).
vendor(enabled: boolean): this
Toggle the local vendor folder (`--vendor`).
watch(): this
Re-check when a watched file changes (`--watch`).
watchExclude(...paths: PathLike[]): this
Exclude paths from the watcher (`--watch-exclude`).
noClearScreen(): this
Keep previous output when re-running under `--watch` (`--no-clear-screen`).
override protected onOutput(output: CommandOutput): void
Report `Errors`, the diagnostics printed, onto the build summary.
override protected buildArgs(): string[]
Assemble the `deno check` argv.
class DenoCiSettings extends DenoSettings
Settings for `deno ci`.
prod(): this
Install production dependencies only, excluding dev ones (`--prod`).
skipTypes(): this
Exclude `@types/*` packages (`--skip-types`). Deno selects them by name,
so a package that ships runtime code under a `@types/` name is skipped
too.
envFile(path: PathLike): this
Load environment variables from a file (`--env-file`).
override protected buildArgs(): string[]
Assemble the `deno ci` argv.
class DenoCleanSettings extends DenoSettings
Settings for `deno clean`.
dryRun(): this
Report what would be removed without removing it (`--dry-run`).
except(...paths: PathLike[]): this
Keep the cache entries these files need (`--except`), clearing everything
else. Use it to drop stale dependencies without forcing the next build to
re-download the ones it still uses.
override protected buildArgs(): string[]
Assemble the `deno clean` argv.
class DenoCompileSettings extends DenoPermissionSettings
Settings for `deno compile`.
script(path: PathLike): this
The entrypoint to compile (required).
scriptArgs(...args: Array<string | number>): this
Arguments baked into the executable, passed after the entrypoint.
output(path: PathLike): this
Output file (`--output`); defaults to a name inferred from the entrypoint.
target(triple: DenoCompileTarget): this
Cross-compile for another platform (`--target`).
include(...paths: PathLike[]): this
Embed an extra module, file or directory (`--include`, repeatable).
Needed for anything the module graph cannot see statically — a
dynamically imported module, a worker entrypoint, or a data file the
program reads at runtime.
exclude(...paths: PathLike[]): this
Exclude a file or directory from what {@link include} embedded (`--exclude`).
excludeUnusedNpm(): this
Embed only the npm packages the module graph actually reaches
(`--exclude-unused-npm`), instead of the whole lockfile snapshot.
Packages reached only through a dynamic import are not statically
traceable, so pass those to {@link include} explicitly.
icon(path: PathLike): this
Set the executable's Windows icon from a `.ico` file (`--icon`).
noTerminal(): this
Hide the console window on Windows (`--no-terminal`).
selfExtracting(): this
Produce a self-extracting binary (`--self-extracting`) that unpacks its
embedded file system to disk on first run and executes from there.
bundle(): this
Bundle the entrypoint before embedding it (`--bundle`), rather than
shipping the whole `node_modules` tree. Experimental: it produces a
smaller, faster-starting binary but drops dynamic `require`/`import`
patterns that cannot be traced statically.
minify(): this
Minify the bundled output (`--minify`). Requires {@link bundle} — the CLI
rejects `--minify` on its own, and so does this wrapper, so the mistake
surfaces while the argv is being built rather than after the compile
starts.
config(path: PathLike): this
Use an explicit config file (`--config`).
noCheck(): this
Skip type-checking before compiling (`--no-check`).
override protected buildArgs(): string[]
Assemble the `deno compile` argv.
class DenoCoverageSettings extends DenoSettings
Settings for `deno coverage`.
dir(path: PathLike): this
The coverage profile directory to report on.
lcov(): this
Emit lcov instead of the table report (`--lcov`).
output(path: PathLike): this
Write the report to a file (`--output=`).
exclude(pattern: string): this
Exclude files matching the pattern (`--exclude=`).
include(pattern: string): this
Report only on files matching the pattern (`--include=`).
ignore(...patterns: string[]): this
Skip files matching these patterns (`--ignore=`).
html(): this
Write an HTML report into the profile directory (`--html`).
Mutually exclusive with {@link lcov} and with any threshold: given both,
deno emits the lcov and silently produces no HTML, so asking for both is
refused rather than quietly honoured in half.
detailed(): this
Report per-line detail alongside the summary table (`--detailed`).
linesThreshold(percent: number): this
Fail the gate if line coverage is below `percent`. `deno coverage` has no
fail-under flag, so {@link DenoTasks.coverage} enforces this after parsing
the lcov report (and forces `--lcov` so a report exists to parse).
branchesThreshold(percent: number): this
Fail the gate if branch coverage is below `percent` (see {@link linesThreshold}).
threshold(percent: number): this
Fail the gate if either line or branch coverage is below `percent`.
perFileThreshold(percent: number): this
Fail the gate if any single instrumented file's line coverage is below
`percent` — a per-file floor, so an under-tested file can't hide inside a
healthy aggregate (see {@link CoverageThresholds.perFile}, which notes the
`deno coverage` limit for files no test loads).
get thresholds(): CoverageThresholds
The configured thresholds; read by {@link DenoTasks.coverage}.
get outputPath(): string | undefined
The `--output` file path, if {@link output} was set; read by the task.
override protected buildArgs(): string[]
Assemble the `deno coverage` argv.
class DenoDocSettings extends DenoSettings
Settings for `deno doc`.
paths(...paths: PathLike[]): this
The source files (entry points) to document.
json(): this
Output the documentation as JSON (`--json`).
html(): this
Generate static HTML documentation (`--html`).
lint(): this
Report documentation diagnostics rather than rendering docs (`--lint`).
private(): this
Include private and internal symbols (`--private`).
stripTrailingHtml(): this
Drop the trailing `.html` from generated links (`--strip-trailing-html`).
name(title: string): this
Title for the generated HTML documentation (`--name`).
output(dir: PathLike): this
Output directory for HTML documentation (`--output`).
filter(symbol: string): this
Document only the symbol at this dot-separated path (`--filter`).
categoryDocs(path: PathLike): this
JSON file of per-category Markdown docs (`--category-docs`).
symbolRedirectMap(path: PathLike): this
JSON file redirecting symbols to external links (`--symbol-redirect-map`).
defaultSymbolMap(path: PathLike): this
Mapping of default export names to the names usage blocks show (`--default-symbol-map`).
frozen(): this
Error out if the lockfile is out of date (`--frozen`). See
{@link DenoPermissionSettings.frozen} for why the name mirrors the real
Deno flag rather than `PnpmSettings.frozenLockfile()`'s naming.
noLock(): this
Ignore the lockfile entirely (`--no-lock`).
lock(path: PathLike): this
Use an explicit lockfile (`--lock`).
importMap(path: PathLike): this
Load an import map from a file or URL (`--import-map`).
noNpm(): this
Do not resolve npm modules (`--no-npm`).
noRemote(): this
Do not resolve remote modules (`--no-remote`).
reload(...specifiers: string[]): this
Reload the module cache (`--reload`), optionally only these specifiers.
override protected buildArgs(): string[]
Assemble the `deno doc` argv.
class DenoEvalSettings extends DenoPermissionSettings
Settings for `deno eval`.
The code is passed as a single argv entry by the shell layer, never
interpolated into a command string, so a value built from build parameters
cannot break out of it.
code(source: string): this
The source to evaluate (required).
print(): this
Print the expression's result to stdout (`--print`).
ext(value: DenoSourceExt): this
Treat the source as this content type (`--ext`), rather than TypeScript.
config(path: PathLike): this
Use an explicit config file (`--config`).
override protected buildArgs(): string[]
Assemble the `deno eval` argv.
class DenoFmtSettings extends DenoSettings
Settings for `deno fmt`.
check(): this
Verify formatting without writing changes (`--check`).
failFast(): this
Stop at the first badly formatted file (`--fail-fast`).
lineWidth(columns: number): this
Maximum line width (`--line-width`), 80 by default.
indentWidth(columns: number): this
Indentation width (`--indent-width`), 2 by default.
useTabs(enabled: boolean): this
Indent with tabs rather than spaces (`--use-tabs`).
singleQuote(enabled: boolean): this
Quote strings with single quotes (`--single-quote`).
noSemicolons(enabled: boolean): this
Omit semicolons except where they are required (`--no-semicolons`).
proseWrap(mode: DenoProseWrap): this
How to wrap prose in Markdown (`--prose-wrap`).
unstableComponent(): this
Format Svelte, Vue, Astro and Angular files (`--unstable-component`).
unstableSql(): this
Format SQL files (`--unstable-sql`).
ext(value: string): this
Treat the inputs as this content type (`--ext`). `deno fmt` accepts far
more than the script extensions — Markdown, JSON, CSS, HTML, YAML and the
component formats among them — so this takes a string rather than the
narrower script-only union the runtime subcommands use.
ignore(...patterns: string[]): this
Skip files matching these patterns (`--ignore`).
permitNoFiles(): this
Succeed when no files matched (`--permit-no-files`).
config(path: PathLike): this
Use an explicit config file (`--config`).
noConfig(): this
Discover no configuration file at all (`--no-config`).
watch(): this
Re-format when a watched file changes (`--watch`).
watchExclude(...paths: PathLike[]): this
Exclude paths from the watcher (`--watch-exclude`).
noClearScreen(): this
Keep previous output when re-running under `--watch` (`--no-clear-screen`).
paths(...paths: PathLike[]): this
Restrict formatting to specific files or directories.
override protected onOutput(output: CommandOutput): void
Report `Files` (and `Unformatted` under `--check`) onto the build summary.
override protected buildArgs(): string[]
Assemble the `deno fmt` argv.
class DenoInfoSettings extends DenoSettings
Settings for `deno info`.
path(file: PathLike): this
The module to report on — a path, or any specifier `deno info` accepts,
including a `file://` URL. Prefer the URL form when the specifier is
built rather than typed: it is the same string on every OS, where a
constructed path is not.
Omit it to report on the caches themselves — `deno info` with no module
prints the cache directories rather than a module graph, which is why
{@link DenoTasks.cacheInfo} and {@link DenoTasks.moduleGraph} are separate
readers.
json(): this
Emit the report as JSON (`--json`).
config(path: PathLike): this
Use an explicit config file (`--config`).
importMap(path: PathLike): this
Load an import map from a file or URL (`--import-map`).
reload(): this
Reload the module cache before reporting (`--reload`).
frozen(): this
Error out if the lockfile is out of date (`--frozen`). See
{@link DenoPermissionSettings.frozen} for why the name mirrors the real
Deno flag.
noLock(): this
Ignore the lockfile entirely (`--no-lock`).
noNpm(): this
Do not resolve npm modules (`--no-npm`).
noRemote(): this
Do not resolve remote modules (`--no-remote`).
get modulePath(): string | undefined
The module {@link path} was set to, if any; read by
{@link DenoTasks.moduleGraph} and {@link DenoTasks.cacheInfo} to tell the
two reports apart. Reading the flag off the built argv would not do it:
`--import-map` and `--config` also leave a non-flag token at the end.
override protected buildArgs(): string[]
Assemble the `deno info` argv.
class DenoInitSettings extends DenoSettings
Settings for `deno init`.
directory(name: string): this
The directory to create, or the package to scaffold from.
lib(): this
Scaffold an example library project (`--lib`).
serve(): this
Scaffold an example `deno serve` project (`--serve`).
empty(): this
Scaffold a minimal project — just `main.ts` and `deno.json` (`--empty`).
jsr(): this
Scaffold from a JSR package (`--jsr`).
npm(): this
Scaffold from an npm `create-*` package (`--npm`).
yes(): this
Answer the scaffolding prompts affirmatively and grant full permissions
(`--yes`). Required for an unattended run: without it `deno init` can stop
on a prompt no build target is there to answer.
override protected buildArgs(): string[]
Assemble the `deno init` argv.
class DenoInstallSettings extends DenoPermissionSettings
Settings for `deno install`.
global(): this
Install a global executable (`--global`/`-g`) instead of project deps.
force(): this
Overwrite an existing installation (`--force`/`-f`).
root(path: PathLike): this
Install root; the binary lands in `<root>/bin` (`--root`).
name(value: string): this
Name the installed executable (`--name`/`-n`).
module(spec: string): this
The module to install, e.g. `npm:cspell@9` (required for a global install).
moduleArgs(...args: Array<string | number>): this
Arguments baked into the generated launcher, emitted after the `--`
separator deno requires for them.
dev(): this
Install dev dependencies only alongside the rest (`--dev`).
prod(): this
Install production dependencies only (`--prod`).
saveExact(): this
Record exact versions, without a caret range (`--save-exact`).
lockfileOnly(): this
Update the lockfile without installing (`--lockfile-only`).
skipTypes(): this
Exclude `@types/*` packages (`--skip-types`).
jsr(): this
Read unprefixed package names as JSR packages (`--jsr`).
npm(): this
Read unprefixed package names as npm packages (`--npm`).
packageJson(): this
Install into `package.json` rather than `deno.json` (`--package-json`).
entrypoint(path: PathLike): this
Name the entrypoint the launcher runs (`--entrypoint`).
compile(): this
Build a compiled launcher (`--compile`) rather than a script shim, and
cross-compile it with {@link os} and {@link arch}.
os(value: string): this
Target operating system for a compiled launcher (`--os`).
arch(value: string): this
Target architecture for a compiled launcher (`--arch`).
config(path: PathLike): this
Use an explicit config file (`--config`).
noConfig(): this
Discover no configuration file at all (`--no-config`).
lock(path: PathLike): this
Use an explicit lockfile (`--lock`).
noLock(): this
Ignore the lockfile entirely (`--no-lock`).
importMap(path: PathLike): this
Load an import map from a file or URL (`--import-map`).
cachedOnly(): this
Resolve only from the cache (`--cached-only`).
noNpm(): this
Do not resolve npm modules (`--no-npm`).
noRemote(): this
Do not resolve remote modules (`--no-remote`).
reload(...specifiers: string[]): this
Reload the module cache (`--reload`), optionally only these specifiers.
nodeModulesDir(mode: NodeModulesMode): this
Set the node-modules management mode (`--node-modules-dir`).
nodeModulesLinker(mode: NodeModulesLinker): this
Set the npm linker mode (`--node-modules-linker`).
vendor(enabled: boolean): this
Toggle the local vendor folder (`--vendor`).
envFile(path: PathLike): this
Load environment variables from a file (`--env-file`).
allowScripts(...packages: string[]): this
Permit npm lifecycle scripts, optionally only for these packages (`--allow-scripts`).
conditions(...values: string[]): this
Resolve npm package exports with these conditions (`--conditions`).
preload(...paths: PathLike[]): this
Execute these modules before the main one (`--preload`).
require(...paths: PathLike[]): this
Execute these CommonJS modules before the main one (`--require`).
typeCheck(scope?: "all" | "remote"): this
Type-check before installing (`--check`).
noCheck(scope?: "all" | "remote"): this
Skip type-checking (`--no-check`).
inspect(hostPort?: string): this
Activate the inspector (`--inspect`).
inspectBrk(hostPort?: string): this
Activate the inspector and break at the start (`--inspect-brk`).
inspectWait(hostPort?: string): this
Activate the inspector and wait for a debugger (`--inspect-wait`).
override protected buildArgs(): string[]
Assemble the `deno install` argv.
class DenoLintSettings extends DenoSettings
Settings for `deno lint`.
fix(): this
Apply automatic fixes (`--fix`).
listRules(): this
List the available rules and exit (`--rules`) rather than linting. Pair it
with {@link json} to get the catalogue in machine-readable form.
json(): this
Report diagnostics as JSON (`--json`).
compact(): this
Report diagnostics one per line (`--compact`).
rulesTags(...tags: string[]): this
Enable the rule sets carrying these tags (`--rules-tags`).
rulesInclude(...rules: string[]): this
Enable these rules on top of the configured set (`--rules-include`).
rulesExclude(...rules: string[]): this
Disable these rules (`--rules-exclude`).
ext(value: string): this
Treat the inputs as this content type (`--ext`).
ignore(...patterns: string[]): this
Skip files matching these patterns (`--ignore`).
permitNoFiles(): this
Succeed when no files matched (`--permit-no-files`).
config(path: PathLike): this
Use an explicit config file (`--config`).
noConfig(): this
Discover no configuration file at all (`--no-config`).
watch(): this
Re-lint when a watched file changes (`--watch`).
watchExclude(...paths: PathLike[]): this
Exclude paths from the watcher (`--watch-exclude`).
noClearScreen(): this
Keep previous output when re-running under `--watch` (`--no-clear-screen`).
paths(...paths: PathLike[]): this
Restrict linting to specific files or directories.
override protected onOutput(output: CommandOutput): void
Report `Files` and `Problems` onto the build summary.
override protected buildArgs(): string[]
Assemble the `deno lint` argv.
abstract class DenoLockSettings extends DenoSettings
Base for the `deno` subcommands that read and write the lockfile.
The lockfile flags are a section the CLI repeats across every one of them,
so they live here once rather than being restated per subcommand.
frozen(): this
Error out if the lockfile is out of date (`--frozen`). See
{@link DenoPermissionSettings.frozen} for why the name mirrors the real
Deno flag.
noLock(): this
Ignore the lockfile entirely (`--no-lock`), neither reading nor writing it.
lock(path: PathLike): this
Use an explicit lockfile (`--lock`) instead of the discovered `deno.lock`.
protected get lockArgs(): string[]
The shared lockfile flags; read by subclasses assembling their argv.
class DenoOutdatedSettings extends DenoLockSettings
Settings for `deno outdated`.
filters(...patterns: string[]): this
Restrict the report to dependencies matching these filters, which may
include `*` wildcards. Filters match the alias a dependency is declared
under, not the package it resolves to.
compatible(): this
Only consider versions satisfying the declared semver range (`--compatible`).
latest(): this
Consider the latest version regardless of the declared range (`--latest`).
recursive(): this
Include every workspace member (`--recursive`).
update(): this
Write the newer versions back into the manifest (`--update`) instead of
only reporting them. Without it `deno outdated` reports and changes
nothing, which is what a freshness gate wants.
lockfileOnly(): this
Update the lockfile without installing (`--lockfile-only`).
override protected buildArgs(): string[]
Assemble the `deno outdated` argv.
class DenoPackSettings extends DenoSettings
Settings for `deno pack`.
files(...patterns: string[]): this
File patterns to include in the tarball; defaults to the package's own.
allowDirty(): this
Pack even with an uncommitted working tree (`--allow-dirty`).
allowSlowTypes(): this
Skip fast-check type extraction (`--allow-slow-types`). The tarball then
ships without `.d.ts` files, so consumers get no types from it.
dryRun(): this
Report what would be packed without writing the tarball (`--dry-run`).
noSourceMaps(): this
Omit source maps from the tarball (`--no-source-maps`).
ignore(...patterns: string[]): this
Exclude files matching these patterns (`--ignore`).
output(path: PathLike): this
Write the tarball here (`--output`) instead of `<name>-<version>.tgz`.
setVersion(version: string): this
Override the version recorded in the tarball (`--set-version`).
config(path: PathLike): this
Use an explicit config file (`--config`).
override protected buildArgs(): string[]
Assemble the `deno pack` argv.
abstract class DenoPermissionSettings extends DenoSettings
Base for subcommands that accept `--allow-*` permission flags.
allowAll(): this
Grant all permissions (`--allow-all`).
allow(permission: DenoPermission, ...values: string[]): this
Grant one permission, optionally scoped to values (`--allow-read=a,b`).
frozen(): this
Error out if the lockfile is out of date instead of silently updating it
(`--frozen`). Use it whenever the module graph must match the committed
`deno.lock` exactly — running an `npm:` tool in CI, say, so its transitive
tree stays pinned to the audited integrity hashes rather than being
resolved afresh. Named `frozen` — not `frozenLockfile` — to mirror the real
Deno CLI flag exactly. This is a deliberate divergence from
`PnpmSettings.frozenLockfile()` in `@zuke/pnpm`, which follows pnpm's own
flag name instead: guideline 7 (mirror the real CLI) takes priority over
cross-package naming symmetry.
protected get permissionArgs(): string[]
The accumulated permission flags, in declaration order.
protected get frozenArgs(): string[]
The `--frozen` flag, if set; read by subclasses assembling their argv.
class DenoPublishSettings extends DenoSettings
Settings for `deno publish`.
allowDirty(): this
Publish even with an uncommitted working tree (`--allow-dirty`).
allowSlowTypes(): this
Permit slow types in the published package (`--allow-slow-types`).
noCheck(): this
Skip type-checking before publishing (`--no-check`).
dryRun(): this
Validate without publishing (`--dry-run`).
config(path: PathLike): this
Use an explicit config file (`--config`).
token(value: string): this
Authenticate with a token instead of interactive/OIDC auth (`--token`).
Registered with the run's redactor, so every rendering of the command
masks it — a dry-run echo, a failure message. That is as far as it goes:
`deno publish` accepts the token only as a flag, so the value still
reaches the child's argv, where `ps` and `/proc/<pid>/cmdline` expose it to
every other user on the host and to every process the build starts.
There is no environment variable to move it to. `DENO_AUTH_TOKENS` is not
one — it authenticates module fetches, not publishing.
Prefer passing no token at all. On GitHub Actions `deno publish` uses OIDC,
so the publish is authenticated by the workflow's identity and there is no
credential to expose; this repository's own publish does exactly that.
setVersion(version: string): this
Publish under an overridden version (`--set-version`) instead of the one
in the manifest — how a release job publishes a version it computed
without first committing it.
deno accepts it only when publishing a single package: it is rejected in
a workspace, where the versions have to come from each member's own
manifest. Whether the working directory is a workspace is not visible
while the argv is being built, so this is a note rather than a refusal.
noProvenance(): this
Disable provenance attestation (`--no-provenance`).
Provenance is what lets a consumer verify the published artifact came from
this repository's CI, so turning it off is a deliberate weakening rather
than a default worth reaching for. It stays on unless this is called.
deno produces it by default only on GitHub Actions, so elsewhere there is
nothing to disable. The case it exists for is the trade it makes: the
attestation publicly links the package to the repository, workflow and
commit it was built from, which a package published from a private
repository may not want disclosed. Weigh that against consumers losing
the ability to verify the artifact's origin, and prefer keeping it.
typeCheck(scope?: "all" | "remote"): this
Type-check before publishing (`--check`), optionally including remote code.
noConfig(): this
Discover no configuration file at all (`--no-config`).
envFile(path: PathLike): this
Load environment variables from a file (`--env-file`).
override protected buildArgs(): string[]
Assemble the `deno publish` argv.
class DenoRemoveSettings extends DenoLockSettings
Settings for `deno remove`.
packages(...names: string[]): this
The packages to remove, by the name they are recorded under (required).
lockfileOnly(): this
Update the lockfile without touching `node_modules` (`--lockfile-only`).
packageJson(): this
Remove from `package.json` rather than `deno.json` (`--package-json`).
override protected buildArgs(): string[]
Assemble the `deno remove` argv.
class DenoRunSettings extends DenoPermissionSettings
Settings for `deno run`.
script(path: PathLike): this
The script to run (required).
scriptArgs(...args: Array<string | number>): this
Arguments passed to the script (after the script path).
config(path: PathLike): this
Use an explicit config file (`--config`).
noConfig(): this
Discover no configuration file at all (`--no-config`).
reload(...specifiers: string[]): this
Reload the module cache (`--reload`), optionally only these specifiers.
lock(path: PathLike): this
Use an explicit lockfile (`--lock`).
noLock(): this
Ignore the lockfile entirely (`--no-lock`).
importMap(path: PathLike): this
Load an import map from a file or URL (`--import-map`).
cachedOnly(): this
Resolve only from the cache (`--cached-only`), fetching nothing.
noNpm(): this
Do not resolve npm modules (`--no-npm`).
noRemote(): this
Do not resolve remote modules (`--no-remote`).
nodeModulesDir(mode: NodeModulesMode): this
Set the node-modules management mode (`--node-modules-dir`).
nodeModulesLinker(mode: NodeModulesLinker): this
Set the npm linker mode (`--node-modules-linker`).
vendor(enabled: boolean): this
Toggle the local vendor folder (`--vendor`).
envFile(path: PathLike): this
Load environment variables from a file (`--env-file`).
cert(path: PathLike): this
Load a certificate authority from a PEM file (`--cert`).
location(href: string): this
Set `globalThis.location` (`--location`).
seed(value: number): this
Seed the random number generator (`--seed`).
v8Flags(...flags: string[]): this
Pass flags through to V8 (`--v8-flags`).
conditions(...values: string[]): this
Resolve npm package exports with these conditions (`--conditions`).
preload(...paths: PathLike[]): this
Execute these modules before the main one (`--preload`).
require(...paths: PathLike[]): this
Execute these CommonJS modules before the main one (`--require`).
noCodeCache(): this
Disable the V8 code cache (`--no-code-cache`).
allowScripts(...packages: string[]): this
Permit npm lifecycle scripts, optionally only for these packages (`--allow-scripts`).
typeCheck(scope?: "all" | "remote"): this
Type-check before running (`--check`), optionally including remote code.
noCheck(scope?: "all" | "remote"): this
Skip type-checking (`--no-check`), optionally only for remote code.
inspect(hostPort?: string): this
Activate the inspector (`--inspect`).
inspectBrk(hostPort?: string): this
Activate the inspector and break at the start (`--inspect-brk`).
inspectWait(hostPort?: string): this
Activate the inspector and wait for a debugger (`--inspect-wait`).
watch(): this
Restart when a watched file changes (`--watch`).
watchHmr(): this
Watch with hot-module replacement (`--watch-hmr`), which only `deno run`
offers. It implies watching, so it replaces `--watch` rather than joining
it.
watchExclude(...paths: PathLike[]): this
Exclude paths from the watcher (`--watch-exclude`).
noClearScreen(): this
Keep previous output when re-running under `--watch` (`--no-clear-screen`).
override protected buildArgs(): string[]
Assemble the `deno run` argv.
class DenoServeSettings extends DenoPermissionSettings
Settings for `deno serve`.
A server runs until it is stopped, so a build target that awaits this task
blocks forever. Give it a bound with `.killAfter(ms)` — for a smoke test
that the server starts — or run it from a target the build is not waiting
on.
script(path: PathLike): this
The module exporting the server's default handler (required).
scriptArgs(...args: Array<string | number>): this
Arguments passed to the server module (after the module path).
port(value: number): this
The TCP port to serve on (`--port`); `0` picks a free one.
host(value: string): this
The TCP address to serve on (`--host`), defaulting to all interfaces.
parallel(): this
Run one server worker per available CPU (`--parallel`), or as many as
`DENO_JOBS` allows.
open(): this
Open a browser on the served address (`--open`).
watch(): this
Restart the server when a watched file changes (`--watch`).
config(path: PathLike): this
Use an explicit config file (`--config`).
override protected buildArgs(): string[]
Assemble the `deno serve` argv.
abstract class DenoSettings extends ToolSettings
Base for all `deno` subcommand settings: binary is the deno running here.
override protected defaultTool(): string
Default the tool binary to the `deno` running this build, so a project
whose Deno came from the `./zuke` launcher rather than from `PATH` still
works and the subprocess is the same version as its parent.
{@link denoExecutable} is what answers that: a build compiled with
`deno compile` is not Deno, and spawning its own executable would run the
build again with `test` or `fmt` as a target rather than running Deno, so
that case resolves a real one on `PATH` instead.
class DenoTaskSettings extends DenoSettings
Settings for `deno task`.
name(value: string): this
The task name from deno.json (required).
taskArgs(...args: Array<string | number>): this
Arguments forwarded to the task.
recursive(): this
Run the task in every workspace member (`--recursive`).
filter(pattern: string): this
Select the workspace members to run the task in (`--filter`). It selects
on its own — {@link recursive} is not a prerequisite.
noPrefix(): this
Drop the per-member name prefix from output (`--no-prefix`), which a
recursive run adds. Useful when the output is being parsed rather than
read.
evalShell(): this
Treat the task name as a shell command to evaluate (`--eval`) instead of
a task defined in the configuration file.
taskCwd(path: PathLike): this
Run the task from this directory (`--cwd`).
Distinct from the inherited `cwd`, which sets the directory the `deno`
process itself is spawned in: this one moves only the task, leaving
configuration discovery anchored where deno started.
config(path: PathLike): this
Use an explicit config file (`--config`).
frozen(): this
Error out if the lockfile is out of date (`--frozen`). See
{@link DenoPermissionSettings.frozen} for why the name mirrors the real
Deno flag rather than `PnpmSettings.frozenLockfile()`'s naming.
lock(path: PathLike): this
Use an explicit lockfile (`--lock`).
noLock(): this
Ignore the lockfile entirely (`--no-lock`).
nodeModulesDir(mode: NodeModulesMode): this
Set the node-modules management mode (`--node-modules-dir`).
nodeModulesLinker(mode: NodeModulesLinker): this
Set the npm linker mode (`--node-modules-linker`).
envFile(path: PathLike): this
Load environment variables from a file (`--env-file`).
override protected buildArgs(): string[]
Assemble the `deno task` argv.
class DenoTestSettings extends DenoPermissionSettings
Settings for `deno test`.
A run reports its counts into the running target's row of the build
summary — `// Tests: 837 · Passed: 837 · Failed: 0` — read from the result
line the pretty and dot reporters print (ignored tests count as `Skipped`);
a failed run reports too, so a red row says how many failed. The JUnit and TAP reporters print no such line,
so a run under `.reporter("junit")`/`.reporter("tap")` reports nothing.
paths(...paths: PathLike[]): this
Restrict the run to specific test files or directories.
coverage(dir: PathLike): this
Collect coverage into the given profile directory (`--coverage=`).
coverageRawDataOnly(): this
Collect raw coverage data without generating a report (`--coverage-raw-data-only`).
clean(): this
Empty the coverage profile directory before running (`--clean`), so a run
reports on its own tests rather than on whatever a previous run left.
The directory need not come from {@link coverage}: `DENO_COVERAGE_DIR`
sets it too, which is why this is not tied to that setter.
filter(pattern: string): this
Only run tests whose name matches (`--filter`).
parallel(): this
Run test files in parallel (`--parallel`).
failFast(count?: number): this
Stop after `count` failures (`--fail-fast`), or after the first.
doc(): this
Evaluate the code blocks in JSDoc and Markdown as tests (`--doc`).
noRun(): this
Cache the test modules without running them (`--no-run`).
shuffle(seed?: number): this
Randomise test order (`--shuffle`), optionally with a fixed seed so a
failing order can be replayed.
traceLeaks(): this
Trace the ops a test leaks (`--trace-leaks`). It costs run time and is
the only practical way to find which test leaked a pending op — the
flakiest class of failure a suite has.
sanitizeOps(): this
Require every async op started in a test to finish in it (`--sanitize-ops`).
sanitizeResources(): this
Require every resource opened in a test to be closed in it (`--sanitize-resources`).
hideStacktraces(): this
Omit stack traces from failure output (`--hide-stacktraces`).
reporter(kind: DenoTestReporter): this
Select the console reporter (`--reporter`).
junitPath(path: PathLike): this
Also write a JUnit XML report to `path` (`--junit-path`), whatever the
console reporter is — this is the file a CI test-report UI ingests.
ext(value: string): this
Treat the inputs as this content type (`--ext`).
ignore(...patterns: string[]): this
Skip files matching these patterns (`--ignore`).
permitNoFiles(): this
Succeed when no test files matched (`--permit-no-files`).
config(path: PathLike): this
Use an explicit config file (`--config`).
noConfig(): this
Discover no configuration file at all (`--no-config`).
typeCheck(scope?: "all" | "remote"): this
Type-check before running (`--check`), optionally including remote code.
noCheck(scope?: "all" | "remote"): this
Skip type-checking (`--no-check`), optionally only for remote code.
inspect(hostPort?: string): this
Activate the inspector (`--inspect`).
inspectBrk(hostPort?: string): this
Activate the inspector and break at the start (`--inspect-brk`).
inspectWait(hostPort?: string): this
Activate the inspector and wait for a debugger (`--inspect-wait`).
envFile(path: PathLike): this
Load environment variables from a file (`--env-file`).
cert(path: PathLike): this
Load a certificate authority from a PEM file (`--cert`).
location(href: string): this
Set `globalThis.location` (`--location`).
seed(value: number): this
Seed the random number generator (`--seed`), making a run reproducible.
v8Flags(...flags: string[]): this
Pass flags through to V8 (`--v8-flags`).
conditions(...values: string[]): this
Resolve npm package exports with these conditions (`--conditions`).
preload(...paths: PathLike[]): this
Execute these modules before the main one (`--preload`).
require(...paths: PathLike[]): this
Execute these CommonJS modules before the main one (`--require`).
allowScripts(...packages: string[]): this
Permit npm lifecycle scripts, optionally only for these packages (`--allow-scripts`).
lock(path: PathLike): this
Use an explicit lockfile (`--lock`).
noLock(): this
Ignore the lockfile entirely (`--no-lock`).
importMap(path: PathLike): this
Load an import map from a file or URL (`--import-map`).
cachedOnly(): this
Resolve only from the cache (`--cached-only`).
noNpm(): this
Do not resolve npm modules (`--no-npm`).
noRemote(): this
Do not resolve remote modules (`--no-remote`).
reload(...specifiers: string[]): this
Reload the module cache (`--reload`), optionally only these specifiers.
nodeModulesDir(mode: NodeModulesMode): this
Set the node-modules management mode (`--node-modules-dir`).
nodeModulesLinker(mode: NodeModulesLinker): this
Set the npm linker mode (`--node-modules-linker`).
vendor(enabled: boolean): this
Toggle the local vendor folder (`--vendor`).
watch(): this
Re-run when a watched file changes (`--watch`).
watchExclude(...paths: PathLike[]): this
Exclude paths from the watcher (`--watch-exclude`).
noClearScreen(): this
Keep previous output when re-running under `--watch` (`--no-clear-screen`).
override protected onOutput(output: CommandOutput): void
Report the run's counts into the build summary (see the class docs).
override protected buildArgs(): string[]
Assemble the `deno test` argv.
class DenoUninstallSettings extends DenoLockSettings
Settings for `deno uninstall`.
packages(...names: string[]): this
The dependency names, or the global executable name, to remove (required).
global(): this
Remove a globally installed executable (`--global`) rather than a project dependency.
root(path: PathLike): this
The installation root the executable lives under (`--root`).
lockfileOnly(): this
Update the lockfile without touching `node_modules` (`--lockfile-only`).
packageJson(): this
Remove from `package.json` rather than `deno.json` (`--package-json`).
override protected buildArgs(): string[]
Assemble the `deno uninstall` argv.
class DenoUpgradeSettings extends DenoSettings
Settings for `deno upgrade`.
version(value: string): this
The version, channel (`alpha`, `beta`, `rc`, `canary`) or commit hash to
install. Omit it to move to the latest stable release.
dryRun(): this
Run every check without replacing the executable (`--dry-run`).
force(): this
Replace the executable even when it is already up to date (`--force`).
noDelta(): this
Download the full archive instead of a delta update (`--no-delta`).
output(path: PathLike): this
Write the upgraded executable somewhere else (`--output`), leaving the
running one in place. This is what makes `upgrade` usable from a build:
a target can fetch a second Deno without replacing the one executing it.
checksum(sha256: string): this
Verify the downloaded archive against a SHA-256 checksum (`--checksum`).
override protected buildArgs(): string[]
Assemble the `deno upgrade` argv.
class DenoWhySettings extends DenoLockSettings
Settings for `deno why`.
packageName(value: string): this
The package to explain, optionally with a version (`express@4.18.2`)
(required).
override protected buildArgs(): string[]
Assemble the `deno why` argv.
interface CoverageThresholds
Line and branch percentage floors; an omitted metric is not enforced.
lines?: number
Minimum line-coverage percentage (0–100).
branches?: number
Minimum branch-coverage percentage (0–100).
perFile?: number
Minimum per-file line-coverage percentage (0–100). Unlike {@link lines}
(an aggregate over the whole report), this fails the gate when any single
instrumented file falls below the floor — so an under-tested file can't
hide inside a healthy repo-wide average. Files with no measurable lines are
skipped. Note the coverage tool's limit: `deno coverage` only reports files
that were loaded, so a source file no test imports at all is invisible to
this check (as it is to every coverage metric).
interface DenoCacheInfo
The cache locations `deno info --json` reports when given no module.
denoVersion?: string
The version of deno that produced the report.
denoDir?: string
The root cache directory, i.e. `DENO_DIR`.
modulesCache?: string
Where fetched remote modules are stored.
npmCache?: string
Where npm packages are stored.
typescriptCache?: string
Where emitted TypeScript is stored.
registryCache?: string
Where registry metadata is stored.
originStorage?: string
Where origin-bound storage (`localStorage`) is kept.
interface DenoModule
One module in the graph {@link parseModuleGraph} returns.
specifier: string
The module's fully qualified specifier, e.g. `file:///…/mod.ts`.
kind?: string
How deno classified the module, e.g. `esm` or `npm`; absent on an error entry.
local?: string
The module's path in the local cache, when it has been fetched.
size?: number
The module's size in bytes, when known.
mediaType?: string
The media type deno resolved, e.g. `TypeScript`.
error?: string
Why the module could not be loaded, when it could not be.
dependencies: DenoModuleDependency[]
The specifiers this module imports, in source order.
interface DenoModuleDependency
One import edge out of a {@link DenoModule}.
specifier: string
The specifier exactly as written in the source.
error?: string
Why the dependency could not be resolved, when it could not be.
interface DenoModuleGraph
The module graph `deno info --json <file>` reports.
roots: string[]
The entry points the graph was built from.
modules: DenoModule[]
Every module reachable from {@link roots}, deno's order preserved.
redirects: Record<string, string>
Specifier redirects deno followed, from requested to resolved.
interface DenoTasksApi
The shape of {@link DenoTasks}.
run(configure?: Configure<DenoRunSettings>): Promise<CommandOutput>
Run a script: `deno run`.
test(configure?: Configure<DenoTestSettings>): Promise<CommandOutput>
Run tests: `deno test`. Reports the run's counts into the running
target's row of the build summary (`// Tests: 837 · Passed: 837 · Failed: 0`), read from deno's own result line — see
{@link DenoTestSettings}.
check(configure?: Configure<DenoCheckSettings>): Promise<CommandOutput>
Type-check files: `deno check`.
fmt(configure?: Configure<DenoFmtSettings>): Promise<CommandOutput>
Format files: `deno fmt`.
lint(configure?: Configure<DenoLintSettings>): Promise<CommandOutput>
Lint files: `deno lint`.
doc(configure?: Configure<DenoDocSettings>): Promise<CommandOutput>
Generate documentation: `deno doc`.
cache(configure?: Configure<DenoCacheSettings>): Promise<CommandOutput>
Warm the module cache: `deno cache`.
coverage(configure?: Configure<DenoCoverageSettings>): Promise<CommandOutput>
Report coverage: `deno coverage`.
install(configure?: Configure<DenoInstallSettings>): Promise<CommandOutput>
Install a script or executable: `deno install`.
publish(configure?: Configure<DenoPublishSettings>): Promise<CommandOutput>
Publish a package to JSR: `deno publish`.
task(configure?: Configure<DenoTaskSettings>): Promise<CommandOutput>
Run a deno.json task: `deno task`.
serve(configure?: Configure<DenoServeSettings>): Promise<CommandOutput>
Run a server: `deno serve`.
eval(configure?: Configure<DenoEvalSettings>): Promise<CommandOutput>
Evaluate a snippet: `deno eval`.
bench(configure?: Configure<DenoBenchSettings>): Promise<CommandOutput>
Run benchmarks: `deno bench`.
compile(configure?: Configure<DenoCompileSettings>): Promise<CommandOutput>
Build a self-contained executable: `deno compile`.
clean(configure?: Configure<DenoCleanSettings>): Promise<CommandOutput>
Remove the cache directory: `deno clean`.
info(configure?: Configure<DenoInfoSettings>): Promise<CommandOutput>
Report on a module or the caches: `deno info`.
init(configure?: Configure<DenoInitSettings>): Promise<CommandOutput>
Scaffold a new project: `deno init`.
upgrade(configure?: Configure<DenoUpgradeSettings>): Promise<CommandOutput>
Upgrade the deno executable: `deno upgrade`.
add(configure?: Configure<DenoAddSettings>): Promise<CommandOutput>
Add dependencies: `deno add`.
remove(configure?: Configure<DenoRemoveSettings>): Promise<CommandOutput>
Remove dependencies: `deno remove`.
uninstall(configure?: Configure<DenoUninstallSettings>): Promise<CommandOutput>
Uninstall a dependency or global executable: `deno uninstall`.
outdated(configure?: Configure<DenoOutdatedSettings>): Promise<CommandOutput>
Report outdated dependencies: `deno outdated`.
why(configure?: Configure<DenoWhySettings>): Promise<CommandOutput>
Explain why a package is installed: `deno why`.
ci(configure?: Configure<DenoCiSettings>): Promise<CommandOutput>
Install strictly from the lockfile: `deno ci`.
approveScripts(configure?: Configure<DenoApproveScriptsSettings>): Promise<CommandOutput>
Approve npm lifecycle scripts: `deno approve-scripts`.
bumpVersion(configure?: Configure<DenoBumpVersionSettings>): Promise<CommandOutput>
Bump the version in the manifest: `deno bump-version`.
pack(configure?: Configure<DenoPackSettings>): Promise<CommandOutput>
Build an npm-compatible tarball: `deno pack`.
moduleGraph(configure?: Configure<DenoInfoSettings>): Promise<DenoModuleGraph>
The module graph rooted at a file, parsed from `deno info --json`.
A reader, not a gate: it returns the graph rather than printing it, so a
build can assert on what its entry point actually pulls in.
cacheInfo(configure?: Configure<DenoInfoSettings>): Promise<DenoCacheInfo>
The toolchain's cache locations, parsed from `deno info --json`.
type DenoCompileTarget = "x86_64-unknown-linux-gnu" | "aarch64-unknown-linux-gnu" | "x86_64-pc-windows-msvc" | "x86_64-apple-darwin" | "aarch64-apple-darwin"
A target triple `deno compile` can cross-compile to, as listed by
`deno compile --help`. Typed as a union so a typo in a release matrix is a
compile-time error rather than a build that fails minutes into CI.
type DenoPermission = "read" | "write" | "net" | "env" | "run" | "sys" | "ffi" | "import"
A Deno permission domain, as used by `--allow-*` flags.
type DenoProseWrap = "always" | "never" | "preserve"
How `deno fmt` wraps prose in Markdown (`--prose-wrap`).
type DenoSourceExt = "ts" | "tsx" | "js" | "jsx" | "mts" | "mjs" | "cts" | "cjs"
A content type `deno eval` and `deno bench` accept for `--ext`.
type DenoTestReporter = "pretty" | "dot" | "junit" | "tap"
The report formats `deno test --reporter` accepts.
type DenoVersionIncrement = "major" | "minor" | "patch" | "premajor" | "preminor" | "prepatch" | "prerelease"
A version increment `deno bump-version` understands.
type NodeModulesLinker = "isolated" | "hoisted"
The linker modes `--node-modules-linker` accepts.
type NodeModulesMode = "auto" | "manual" | "none"
The node-modules management modes `--node-modules-dir` accepts.
========================================================================
# @zuke/docs
========================================================================
`@zuke/docs` — typed tasks that turn already-generated API documentation into
agent-friendly artifacts, so neither humans nor agents have to guess an API.
You supply each package's documentation text (for a Deno workspace, the
output of `deno doc`); this package renders it into three things:
- an `llms.txt` index (the llmstxt.org convention),
- a complete `llms-full.txt` reference (the whole surface in one file),
- a generated `## API` block in every package README.
It runs no subprocess and depends only on `@zuke/core`, so it works without
`deno` on `PATH` and without the `@zuke/deno` package — pair it with whatever
produces your doc text (`@zuke/deno`'s `DenoTasks.doc`, a checked-in file, …).
```ts
import { DocsTasks } from "@zuke/docs";
const docs = [{ name: "@acme/core", dir: "core", doc: denoDocText }];
await DocsTasks.apiDocs(docs, { project: { title: "Acme", summary: "…" } });
// In the CI gate:
const stale = await DocsTasks.checkApiDocs(docs);
if (stale.length > 0) throw new Error(`Stale docs: ${stale.join(", ")}`);
```
@module
const DocsTasks: DocsTasksApi
Typed tasks for generating and verifying API documentation.
interface ApiDocsOptions
Options accepted by {@link DocsTasks.apiDocs} and {@link DocsTasks.checkApiDocs}.
packagesDir?: string
Directory holding the package subdirectories. Default `"packages"`.
jsrBaseUrl?: string
Base URL for package documentation links. Default `"https://jsr.io"`.
index?: string
Output path for the short index. Default `"llms.txt"`.
full?: string
Output path for the full reference. Default `"llms-full.txt"`.
readmes?: boolean
Inject a generated `## API` block into each package README. Default `true`.
project?: ProjectInfo
Project framing for the index. Falls back to a generic blurb.
regenerateCommand?: string
Command shown in "regenerate with …" notes. Default `"deno task docs"`.
interface DocLintReport
One package's `deno doc --lint` output plus the type names it imports from
other `@zuke/*` packages, fed into {@link DocsTasksApi.checkDocLint}. The
caller runs the linter and scans the package's imports, so `@zuke/docs` never
runs `deno`.
pkg: string
The package identifier, surfaced in violations (e.g. `@zuke/kubectl`).
output: string
The raw `deno doc --lint` output for the package's entrypoints.
crossPackageTypes: string[]
The local names the package imports from another `@zuke/*` package. A
`private-type-ref` to one of these is the accepted residual (guideline 4);
a ref to any other type is a defect (the type is first-party and must be
exported).
interface DocLintViolation
A documentation-lint defect, tied to the package it was found in.
pkg: string
The package the defect is in.
kind: string
The lint rule, e.g. `"missing-jsdoc"` or `"private-type-ref"`.
message: string
The diagnostic's headline message.
interface DocsTasksApi
The shape of {@link DocsTasks}.
apiDocs(docs: PackageDoc[], options?: ApiDocsOptions): Promise<string[]>
From the supplied per-package docs, generate the index, the full reference,
and (unless disabled) each package README's API block, writing only the
files whose content changed. Returns the paths written.
checkApiDocs(docs: PackageDoc[], options?: ApiDocsOptions): Promise<string[]>
Recompute every artifact and return the paths that are out of date on disk
(empty when everything is current). Writes nothing.
checkDocLint(reports: DocLintReport[]): DocLintViolation[]
Classify `deno doc --lint` output across packages into real defects: every
`missing-jsdoc`, plus every `private-type-ref` whose referenced type is not
an accepted cross-package import (in the report's `crossPackageTypes`).
Fails safe — any other referenced type is treated as a first-party leak.
Pure: the caller runs the linter; this classifies. Empty when clean.
interface PackageDoc
One package's already-generated documentation, fed into the tasks.
name: string
The published name, e.g. `@zuke/deno`.
dir: string
The directory under `packagesDir` whose README receives the API block.
doc: string
The package's API documentation text — typically the output of
`deno doc <entry>` (machine-specific `Defined in …` lines are stripped for
you). Produced by the caller, so this package never has to run `deno`.
interface ProjectInfo
Project framing rendered into the `llms.txt` index.
title: string
Heading for the index, e.g. `"Zuke"`.
summary: string
One-paragraph summary, rendered as the index's blockquote.
example?: string
An optional canonical code example, fenced under an "Example" heading.
install?: string
An optional install/scaffold command, shown in the "do not guess" list.
guidance?: string[]
Extra bullet lines appended to the "do not guess" list.
cli?: string
An optional pre-rendered markdown block describing the `zuke` command
surface, rendered under a `## CLI` heading in the index. The caller builds
it (e.g. from the build's command/flag registry) so this package stays
agnostic about CLI specifics.
========================================================================
# @zuke/npm
========================================================================
`@zuke/npm` — typed `NpmTasks` wrappers for the `npm` CLI, for use in Zuke
build targets (including builds that drive Node projects).
```ts
import { NpmTasks } from "@zuke/npm";
await NpmTasks.ci();
await NpmTasks.run((s) => s.script("build"));
const stale = await NpmTasks.outdatedEntries();
```
Typed tasks cover the everyday npm surface — installing, running scripts,
publishing, registry administration, inspection, and the project's own
files. A handful hand back parsed values rather than raw output:
`outdatedEntries`, `auditSummary`, `pkgGet`, and `whoamiName`.
@module
function parsePkgField(stdout: string, key: string): string | undefined
The scalar `npm pkg get <key>` reported, or `undefined` when the field is
unset or is not a scalar.
npm answers with JSON, so a string field arrives quoted, a missing one
arrives as `{}`, and asking within a workspace (or for several keys) yields
an object keyed by what was asked for — this reads all three. An object or
array field yields `undefined`, because there is no single string to hand
back.
Not part of the package's public surface — exported for its unit test.
async function readPkgField(key: string, configure?: Configure<NpmPkgSettings>): Promise<string | undefined>
Run `npm pkg get <key> --json` and read the field out of it. Backs
{@link "./npm.ts".NpmTasks.pkgGet}.
async function readWhoami(configure?: Configure<NpmWhoamiSettings>): Promise<string | undefined>
Read the authenticated user's name, or `undefined` when the registry does
not recognise this machine. Backs {@link "./npm.ts".NpmTasks.whoamiName}.
Being logged out is an answer, not a failure — a release target asks so it
can report the missing credential itself, rather than dying on npm's exit
code partway through.
const NpmTasks: NpmTasksApi
Typed task functions for the `npm` CLI.
class NpmAccessSettings extends NpmSettings
Settings for `npm access`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
listPackages(owner?: string, pkg?: string): this
List the packages a user, scope, or team can reach (`access list packages`).
listCollaborators(pkg?: string, user?: string): this
List a package's collaborators (`access list collaborators`).
getStatus(pkg?: string): this
Read whether a package is public or private (`access get status`), the default.
setStatus(level: "public" | "private", pkg?: string): this
Set a package public or private (`access set status=<level>`).
setMfa(mode: "none" | "publish" | "automation", pkg?: string): this
Require two-factor auth for publishing (`access set mfa=<mode>`).
grant(permission: "read-only" | "read-write", team: string, pkg?: string): this
Give a team access (`access grant <permission> <scope:team>`).
revoke(team: string, pkg?: string): this
Take a team's access away (`access revoke <scope:team>`).
otp(code: string): this
Provide a one-time password.
Carried in `npm_config_otp` rather than on the command line — see
{@link NpmSettings.applyOtp} for why.
override protected subcommandArgs(): string[]
Assemble the `npm access` argv.
class NpmAuditSettings extends NpmWorkspaceSettings
Settings for `npm audit`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
fix(): this
Install compatible updates for what it finds (`npm audit fix`).
signatures(): this
Verify the registry signatures of what is installed (`npm audit signatures`).
auditLevel(level: "info" | "low" | "moderate" | "high" | "critical" | "none"): this
The severity at which the command fails
(`--audit-level=<info|low|moderate|high|critical|none>`).
omit(...types: NpmOmitType[]): this
Skip a dependency group (`--omit=<group>`); repeatable.
include(...types: NpmIncludeType[]): this
Keep a dependency group npm would otherwise omit (`--include=<group>`); repeatable.
packageLockOnly(): this
Audit the lockfile without touching `node_modules` (`--package-lock-only`).
dryRun(): this
Report what a fix would change without changing it (`--dry-run`).
override protected subcommandArgs(): string[]
Assemble the `npm audit` argv.
class NpmCacheSettings extends NpmSettings
Settings for `npm cache`. Pick the operation with {@link add},
{@link clean}, {@link ls}, or {@link verify}.
override protected readonly taskName: string
The `NpmTasks` method this backs.
add(...specs: string[]): this
Add a package to the cache (`cache add <spec>`).
clean(key?: string): this
Empty the cache (`cache clean`). npm refuses this without `--force`, so
pair it with {@link force} — see the error this reports otherwise.
ls(...specs: string[]): this
List what the cache holds (`cache ls`).
verify(): this
Check and compact the cache (`cache verify`), the default.
cache(path: PathLike): this
Use a specific cache directory (`--cache=<path>`).
force(): this
Confirm a clean npm would otherwise refuse (`--force`).
override protected subcommandArgs(): string[]
Assemble the `npm cache` argv.
class NpmCiSettings extends NpmDependencySettings
Settings for `npm ci`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
noAudit(): this
Skip the audit npm runs after installing (`--no-audit`).
noFund(): this
Skip the funding message (`--no-fund`).
override protected subcommandArgs(): string[]
Assemble the `npm ci` argv.
class NpmConfigSettings extends NpmSettings
Settings for `npm config`. Pick the operation with {@link get}, {@link set},
{@link deleteKeys}, {@link list}, or {@link fix}.
override protected readonly taskName: string
The `NpmTasks` method this backs.
get(...keys: string[]): this
Read config keys (`config get <key>...`).
set(...assignments: string[]): this
Write config keys (`config set <key>=<value>...`).
deleteKeys(...keys: string[]): this
Remove config keys (`config delete <key>...`).
list(): this
List the effective configuration (`config list`).
fix(): this
Repair invalid config entries (`config fix`).
location(where: "global" | "user" | "project"): this
Which file to read or write (`--location=<global|user|project>`).
long(): this
Include defaults in a listing (`--long`).
override protected subcommandArgs(): string[]
Assemble the `npm config` argv.
class NpmDedupeSettings extends NpmDependencySettings
Settings for `npm dedupe`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
dryRun(): this
Report what would move without changing the tree (`--dry-run`).
override protected subcommandArgs(): string[]
Assemble the `npm dedupe` argv.
abstract class NpmDependencySettings extends NpmWorkspaceSettings
Shared base for the install-shaped commands: the `--omit`/`--include`
dependency-group selectors npm accepts on all of them, plus the package
specs most of them take.
packages(...specs: string[]): this
Package specs the command operates on (positional); repeatable.
omit(...types: NpmOmitType[]): this
Skip a dependency group (`--omit=<group>`); repeatable.
include(...types: NpmIncludeType[]): this
Keep a dependency group npm would otherwise omit (`--include=<group>`); repeatable.
ignoreScripts(): this
Do not run lifecycle scripts (`--ignore-scripts`).
foregroundScripts(): this
Show lifecycle-script output as it runs (`--foreground-scripts`).
protected get packageSpecs(): readonly string[]
The package specs given, for the subclasses that must require them.
protected dependencyArgs(): string[]
The dependency-group and lifecycle-script flags these commands share.
override protected onOutput(output: CommandOutput): void
Report `Added`, `Removed`, `Changed` (and `Vulnerabilities` when audited) onto the build summary.
class NpmDeprecateSettings extends NpmSettings
Settings for `npm deprecate`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
spec(value: string): this
The package spec to deprecate, e.g. `app@<2` (required).
message(text: string): this
The warning installers will see (required). An empty message is how npm
un-deprecates a version, so it must be given deliberately rather than
by omission.
otp(code: string): this
Provide a one-time password.
Carried in `npm_config_otp` rather than on the command line — see
{@link NpmSettings.applyOtp} for why.
override protected subcommandArgs(): string[]
Assemble the `npm deprecate` argv.
class NpmDistTagSettings extends NpmWorkspaceSettings
Settings for `npm dist-tag`. Pick the subcommand with {@link add},
{@link rm}, or {@link ls}.
override protected readonly taskName: string
The `NpmTasks` method this backs.
add(spec: string, tag?: string): this
Point a tag at a published version (`dist-tag add <pkg@version> [<tag>]`).
The spec must carry the version; a tag cannot point at a range. With no
tag npm uses `latest`, as it does on the command line.
rm(spec: string, tag: string): this
Remove a tag (`dist-tag rm <pkg> <tag>`).
ls(spec?: string): this
List a package's tags (`dist-tag ls [<pkg>]`), the default.
override protected subcommandArgs(): string[]
Assemble the `npm dist-tag` argv.
class NpmExecSettings extends NpmWorkspaceSettings
Settings for `npm exec`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
command(name: string): this
The command to execute (required).
package(spec: string): this
The package providing the command (`--package=`).
yes(): this
Skip the install prompt (`--yes`).
no(): this
Refuse to install anything (`--no`), so the command runs only if it is
already present — what a hermetic CI step wants instead of a silent fetch.
execArgs(...args: Array<string | number>): this
Arguments forwarded to the command (after `--`).
override protected subcommandArgs(): string[]
Assemble the `npm exec` argv.
class NpmInitSettings extends NpmWorkspaceSettings
Settings for `npm init`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
initializer(spec: string): this
The initializer package to run, e.g. `vite` for `npm init vite`
(positional). With none, npm writes a `package.json` itself.
yes(): this
Accept the defaults instead of prompting (`--yes`).
scope(name: string): this
Scope the created package (`--scope=<@scope>`).
initArgs(...args: Array<string | number>): this
Arguments forwarded to the initializer (after `--`).
override protected subcommandArgs(): string[]
Assemble the `npm init` argv.
class NpmInstallSettings extends NpmDependencySettings
Settings for `npm install`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
saveDev(): this
Save to devDependencies (`--save-dev`).
saveOptional(): this
Save to optionalDependencies (`--save-optional`).
savePeer(): this
Save to peerDependencies (`--save-peer`).
saveExact(): this
Pin exact versions (`--save-exact`).
noSave(): this
Install without recording the dependency (`--no-save`).
installStrategy(strategy: "hoisted" | "nested" | "shallow" | "linked"): this
How npm lays out the tree (`--install-strategy=<strategy>`).
noAudit(): this
Skip the audit npm runs after installing (`--no-audit`).
noFund(): this
Skip the funding message (`--no-fund`).
override protected subcommandArgs(): string[]
Assemble the `npm install` argv.
class NpmLinkSettings extends NpmDependencySettings
Settings for `npm link`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
saveDev(): this
Record the linked package in devDependencies (`--save-dev`).
override protected subcommandArgs(): string[]
Assemble the `npm link` argv.
class NpmLsSettings extends NpmWorkspaceSettings
Settings for `npm ls`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
spec(value: string): this
Limit the listing to one package spec (positional).
depth(levels: number): this
How deep to walk the tree (`--depth=<n>`); `0` lists direct dependencies.
all(): this
Show every dependency, not just the top level (`--all`).
long(): this
Include extended information (`--long`).
parseable(): this
Emit one line per package, tab-separated (`--parseable`).
omit(...types: NpmOmitType[]): this
Skip a dependency group (`--omit=<group>`); repeatable.
override protected subcommandArgs(): string[]
Assemble the `npm ls` argv.
class NpmOutdatedSettings extends NpmWorkspaceSettings
Settings for `npm outdated`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
packages(...specs: string[]): this
Limit the report to these package specs (positional); repeatable.
all(): this
Report transitive dependencies too (`--all`).
long(): this
Include the package type and homepage (`--long`).
override protected subcommandArgs(): string[]
Assemble the `npm outdated` argv.
class NpmOwnerSettings extends NpmWorkspaceSettings
Settings for `npm owner`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
add(user: string, pkg: string): this
Add a maintainer (`owner add <user> <pkg>`).
rm(user: string, pkg: string): this
Remove a maintainer (`owner rm <user> <pkg>`).
ls(pkg: string): this
List a package's maintainers (`owner ls <pkg>`).
otp(code: string): this
Provide a one-time password.
Carried in `npm_config_otp` rather than on the command line — see
{@link NpmSettings.applyOtp} for why.
override protected subcommandArgs(): string[]
Assemble the `npm owner` argv.
class NpmPackSettings extends NpmWorkspaceSettings
Settings for `npm pack`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
packages(...specs: string[]): this
Package specs to pack (positional); defaults to the current project.
packDestination(dir: PathLike): this
Where to write the tarball (`--pack-destination=<dir>`).
dryRun(): this
Report what would be packed without writing a tarball (`--dry-run`).
override protected subcommandArgs(): string[]
Assemble the `npm pack` argv.
class NpmPingSettings extends NpmSettings
Settings for `npm ping`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
override protected subcommandArgs(): string[]
Assemble the `npm ping` argv.
class NpmPkgSettings extends NpmWorkspaceSettings
Settings for `npm pkg`. Pick the operation with {@link get}, {@link set},
{@link deleteKeys}, or {@link fix}.
override protected readonly taskName: string
The `NpmTasks` method this backs.
get(...keys: string[]): this
Read one or more `package.json` fields (`pkg get <key>...`).
set(...assignments: string[]): this
Write fields (`pkg set <key>=<value>...`). Each argument is npm's own
`key=value` form, which is also how it addresses arrays and nested keys.
deleteKeys(...keys: string[]): this
Remove fields (`pkg delete <key>...`).
fix(): this
Repair what npm can correct automatically (`pkg fix`).
force(): this
Skip npm's confirmation for a destructive edit (`--force`).
override protected subcommandArgs(): string[]
Assemble the `npm pkg` argv.
class NpmPruneSettings extends NpmDependencySettings
Settings for `npm prune`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
dryRun(): this
Report what would be removed without removing it (`--dry-run`).
override protected subcommandArgs(): string[]
Assemble the `npm prune` argv.
class NpmPublishSettings extends NpmWorkspaceSettings
Settings for `npm publish`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
tag(name: string): this
Publish under a dist-tag (`--tag=`).
access(level: NpmAccess): this
Set the package access level (`--access=`).
dryRun(): this
Report what would be published without uploading (`--dry-run`).
otp(code: string): this
Provide a one-time password.
Carried in `npm_config_otp` rather than on the command line — see
{@link NpmSettings.applyOtp} for why.
provenance(): this
Publish with a provenance attestation (`--provenance`), which npm can
generate from a trusted CI run — the supply-chain signal a consumer can
verify against the workflow that built the tarball.
override protected subcommandArgs(): string[]
Assemble the `npm publish` argv.
class NpmRebuildSettings extends NpmDependencySettings
Settings for `npm rebuild`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
noBinLinks(): this
Do not create the `.bin` symlinks (`--no-bin-links`).
override protected subcommandArgs(): string[]
Assemble the `npm rebuild` argv.
class NpmRunSettings extends NpmWorkspaceSettings
Settings for `npm run`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
script(name: string): this
The package.json script to run (required).
ifPresent(): this
Do not fail when the script is missing (`--if-present`).
scriptArgs(...args: Array<string | number>): this
Arguments forwarded to the script (after `--`).
override protected subcommandArgs(): string[]
Assemble the `npm run` argv.
class NpmSbomSettings extends NpmWorkspaceSettings
Settings for `npm sbom`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
sbomFormat(format: "cyclonedx" | "spdx"): this
Which document to emit (`--sbom-format=<cyclonedx|spdx>`), required by npm.
sbomType(type: "library" | "application" | "framework"): this
What the project is (`--sbom-type=<library|application|framework>`).
omit(...types: NpmOmitType[]): this
Skip a dependency group (`--omit=<group>`); repeatable.
packageLockOnly(): this
Build the document from the lockfile alone (`--package-lock-only`).
override protected subcommandArgs(): string[]
Assemble the `npm sbom` argv.
abstract class NpmSettings extends ToolSettings
Shared base for every `npm` subcommand: the binary, and the flags npm treats
as configuration rather than as a command's own — it accepts these on any
command, which is why they live here instead of being repeated.
abstract protected readonly taskName: string
The `NpmTasks` method this settings class backs, for the errors it
reports — so a failure names the task a build called, not the class. A
field rather than a method: it is the class's identity, not a
computation.
override protected defaultTool(): string
The default binary: `npm` resolved from PATH.
abstract protected subcommandArgs(): string[]
The subcommand argv, before the shared config flags are appended.
protected applyOtp(code: string): void
Carry a one-time password to npm through `npm_config_otp` rather than
`--otp=`.
npm maps every config key to `npm_config_<key>`, so the flag and the
variable are the same setting by two routes — and only one of them is
world-readable. A child's argv shows in `ps` and `/proc/<pid>/cmdline` to
every other user on the host and to every process the build starts; its
environment does not.
Protected, and deliberately not a chainer on this base: only the
subcommands npm actually accepts `--otp` for expose one, so the wrapper
keeps mirroring the real CLI. This is the single implementation those
chainers share.
The value is registered with the run's redactor as well, for the renderings
the environment route does not cover.
registry(url: string): this
Use a specific registry (`--registry=<url>`).
json(): this
Emit JSON (`--json`). The value-returning tasks set this themselves; a
caller reaches for it to parse output the wrapper does not yet model.
logLevel(level: NpmLogLevel): this
How much npm prints (`--loglevel=<level>`).
global(): this
Operate on the global install rather than the project (`--global`).
prefix(path: PathLike): this
Run as if npm were started in this directory (`--prefix=<path>`).
userconfig(path: PathLike): this
Read this user config file rather than `~/.npmrc` (`--userconfig=<path>`).
protected configArgs(): string[]
The config flags, rendered after the subcommand's own arguments.
override protected buildArgs(): string[]
Assemble the `npm` argv: the subcommand, then the shared config flags —
but before any `--`, because everything after that separator belongs to
the script or the executed command rather than to npm. Appending blindly
would hand `--json` to the script and leave npm's own output unchanged.
class NpmTestSettings extends NpmWorkspaceSettings
Settings for `npm test`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
testArgs(...args: Array<string | number>): this
Arguments forwarded to the test script (after `--`).
override protected subcommandArgs(): string[]
Assemble the `npm test` argv.
class NpmTokenSettings extends NpmSettings
Settings for `npm token`. Pick the subcommand with {@link list},
{@link create}, or {@link revoke}.
override protected readonly taskName: string
The `NpmTasks` method this backs.
list(): this
List this account's tokens (`token list`), the default.
create(): this
Create a token (`token create`).
revoke(idOrToken: string): this
Revoke a token by id or value (`token revoke <id|token>`).
npm takes this positionally, with no environment route, so the value
reaches the child's argv either way. It is registered with the run's
redactor so every rendering of the command masks it; the process table
is not something the wrapper can do anything about here.
Masked whichever it is, because the two are indistinguishable from here —
npm accepts the id or the token itself, and nothing in the string says
which. Masking an id costs a `[redacted]` in this run's output; not
masking a token puts a live credential in it.
readOnly(): this
Create a token that cannot publish (`--read-only`).
cidr(...ranges: string[]): this
Restrict a created token to these ranges (`--cidr=<range>`); repeatable.
otp(code: string): this
Provide a one-time password.
Carried in `npm_config_otp` rather than on the command line — see
{@link NpmSettings.applyOtp} for why.
override protected subcommandArgs(): string[]
Assemble the `npm token` argv.
class NpmUninstallSettings extends NpmDependencySettings
Settings for `npm uninstall`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
noSave(): this
Remove the package without updating `package.json` (`--no-save`).
override protected subcommandArgs(): string[]
Assemble the `npm uninstall` argv.
class NpmUnpublishSettings extends NpmWorkspaceSettings
Settings for `npm unpublish`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
spec(value: string): this
The package spec to remove, e.g. `app@1.2.3` (positional).
force(): this
Confirm an unpublish npm would otherwise refuse (`--force`) — removing a
whole package, or a version outside the 72-hour window.
dryRun(): this
Report what would be removed without removing it (`--dry-run`).
override protected subcommandArgs(): string[]
Assemble the `npm unpublish` argv.
class NpmUpdateSettings extends NpmDependencySettings
Settings for `npm update`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
save(): this
Write the updated ranges back to `package.json` (`--save`).
override protected subcommandArgs(): string[]
Assemble the `npm update` argv.
class NpmVersionSettings extends NpmWorkspaceSettings
Settings for `npm version`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
bump(value: string): this
The bump: `patch` | `minor` | `major` or an explicit semver (required).
message(text: string): this
Commit message; `%s` expands to the new version (`--message`).
noGitTagVersion(): this
Do not create a git commit and tag (`--no-git-tag-version`).
preid(id: string): this
The prerelease identifier for a `pre*` bump (`--preid=<id>`), e.g. `rc`.
allowSameVersion(): this
Accept a bump to the version already set (`--allow-same-version`).
override protected subcommandArgs(): string[]
Assemble the `npm version` argv.
class NpmViewSettings extends NpmWorkspaceSettings
Settings for `npm view`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
spec(value: string): this
The package spec to read, e.g. `react@18` (positional).
field(...names: string[]): this
A field of the registry metadata to print, e.g. `version` or
`dist-tags.latest` (positional); repeatable. With none, npm prints the
whole record.
override protected subcommandArgs(): string[]
Assemble the `npm view` argv.
class NpmWhoamiSettings extends NpmSettings
Settings for `npm whoami`.
override protected readonly taskName: string
The `NpmTasks` method this backs.
override protected subcommandArgs(): string[]
Assemble the `npm whoami` argv.
abstract class NpmWorkspaceSettings extends NpmSettings
Base for the npm commands that accept workspace selection. `--workspace`
names one (repeatable) and `--workspaces` means all of them; npm takes one
or the other, and this refuses the combination rather than passing on a
command whose meaning is ambiguous.
workspace(...names: string[]): this
Run in this workspace (`--workspace=<name>`); repeatable.
workspaces(): this
Run in every workspace (`--workspaces`). Mutually exclusive with
{@link workspace} — setting both is a build error.
includeWorkspaceRoot(): this
Include the root project alongside the workspaces (`--include-workspace-root`).
protected workspaceArgs(): string[]
The workspace flags, after refusing a selection that names both one
workspace and all of them.
interface NpmAuditSummary
How many vulnerabilities `npm audit` found, by severity.
info: number
Informational findings.
low: number
Low-severity findings.
moderate: number
Moderate-severity findings.
high: number
High-severity findings.
critical: number
Critical-severity findings.
total: number
Every finding, whatever its severity.
interface NpmOutdatedEntry
One dependency `npm outdated` reports as behind.
name: string
The package name.
current?: string
The version installed now, absent when the package is missing entirely.
wanted?: string
The newest version the range in `package.json` allows.
latest?: string
The newest version published.
location?: string
Where in the tree it is installed.
dependent?: string
The package that depends on it.
interface NpmTasksApi
The shape of {@link NpmTasks}.
install(configure?: Configure<NpmInstallSettings>): Promise<CommandOutput>
Install dependencies: `npm install`.
ci(configure?: Configure<NpmCiSettings>): Promise<CommandOutput>
Clean install from the lockfile: `npm ci`.
uninstall(configure?: Configure<NpmUninstallSettings>): Promise<CommandOutput>
Remove dependencies: `npm uninstall`.
update(configure?: Configure<NpmUpdateSettings>): Promise<CommandOutput>
Update dependencies within their ranges: `npm update`.
dedupe(configure?: Configure<NpmDedupeSettings>): Promise<CommandOutput>
Flatten duplicated packages: `npm dedupe`.
prune(configure?: Configure<NpmPruneSettings>): Promise<CommandOutput>
Remove packages nothing depends on: `npm prune`.
rebuild(configure?: Configure<NpmRebuildSettings>): Promise<CommandOutput>
Rebuild native packages: `npm rebuild`.
link(configure?: Configure<NpmLinkSettings>): Promise<CommandOutput>
Symlink a package for local development: `npm link`.
run(configure?: Configure<NpmRunSettings>): Promise<CommandOutput>
Run a package.json script: `npm run`.
test(configure?: Configure<NpmTestSettings>): Promise<CommandOutput>
Run the project's test script: `npm test`.
exec(configure?: Configure<NpmExecSettings>): Promise<CommandOutput>
Execute a package binary: `npm exec`.
publish(configure?: Configure<NpmPublishSettings>): Promise<CommandOutput>
Publish the package: `npm publish`.
pack(configure?: Configure<NpmPackSettings>): Promise<CommandOutput>
Build a tarball without publishing it: `npm pack`.
version(configure?: Configure<NpmVersionSettings>): Promise<CommandOutput>
Bump the package version: `npm version`.
unpublish(configure?: Configure<NpmUnpublishSettings>): Promise<CommandOutput>
Remove a published version: `npm unpublish`.
deprecate(configure?: Configure<NpmDeprecateSettings>): Promise<CommandOutput>
Warn installers off a version: `npm deprecate`.
distTag(configure?: Configure<NpmDistTagSettings>): Promise<CommandOutput>
Manage dist-tags: `npm dist-tag add|rm|ls`.
view(configure?: Configure<NpmViewSettings>): Promise<CommandOutput>
Read registry metadata: `npm view`.
ping(configure?: Configure<NpmPingSettings>): Promise<CommandOutput>
Check the registry is reachable: `npm ping`.
whoami(configure?: Configure<NpmWhoamiSettings>): Promise<CommandOutput>
Print the authenticated user: `npm whoami`.
whoamiName(configure?: Configure<NpmWhoamiSettings>): Promise<string | undefined>
The authenticated user's name, or `undefined` when this machine is not
logged in — an answer a release target can act on, rather than the
non-zero exit npm reports.
access(configure?: Configure<NpmAccessSettings>): Promise<CommandOutput>
Manage package access: `npm access`.
owner(configure?: Configure<NpmOwnerSettings>): Promise<CommandOutput>
Manage package maintainers: `npm owner add|rm|ls`.
token(configure?: Configure<NpmTokenSettings>): Promise<CommandOutput>
Manage registry tokens: `npm token list|create|revoke`.
ls(configure?: Configure<NpmLsSettings>): Promise<CommandOutput>
List the installed tree: `npm ls`.
outdated(configure?: Configure<NpmOutdatedSettings>): Promise<CommandOutput>
Report dependencies behind their latest: `npm outdated`.
outdatedEntries(configure?: Configure<NpmOutdatedSettings>): Promise<NpmOutdatedEntry[]>
The outdated dependencies as parsed {@link NpmOutdatedEntry} values. npm
exits non-zero because something is outdated, so this reads that as the
answer rather than as a failure; an empty array means everything is
current.
audit(configure?: Configure<NpmAuditSettings>): Promise<CommandOutput>
Audit dependencies for vulnerabilities: `npm audit`.
auditSummary(configure?: Configure<NpmAuditSettings>): Promise<NpmAuditSummary>
The audit's vulnerability counts by severity, so a target decides for
itself what is worth failing on. npm's non-zero exit is the finding, not
an error.
sbom(configure?: Configure<NpmSbomSettings>): Promise<CommandOutput>
Emit a software bill of materials: `npm sbom`.
init(configure?: Configure<NpmInitSettings>): Promise<CommandOutput>
Create a package or run an initializer: `npm init`.
pkg(configure?: Configure<NpmPkgSettings>): Promise<CommandOutput>
Read or write package.json fields: `npm pkg get|set|delete|fix`.
pkgGet(key: string, configure?: Configure<NpmPkgSettings>): Promise<string | undefined>
One `package.json` field as a string, or `undefined` when it is unset or
is not a scalar — how a build reads its own version without parsing the
manifest or guessing where it lives.
config(configure?: Configure<NpmConfigSettings>): Promise<CommandOutput>
Read or write npm configuration: `npm config get|set|delete|list|fix`.
cache(configure?: Configure<NpmCacheSettings>): Promise<CommandOutput>
Maintain the package cache: `npm cache add|clean|ls|verify`.
type NpmAccess = "public" | "restricted"
An access level accepted by npm's `--access` flag.
type NpmIncludeType = "prod" | "dev" | "optional" | "peer"
A dependency group accepted by npm's `--include` flag.
type NpmLogLevel = "silent" | "error" | "warn" | "notice" | "http" | "info" | "verbose" | "silly"
How verbose npm should be (`--loglevel`).
type NpmOmitType = "dev" | "optional" | "peer"
A dependency group accepted by npm's `--omit` flag.
========================================================================
# @zuke/npx
========================================================================
`@zuke/npx` — typed `NpxTasks` wrappers for the `npx` package runner, for use
in Zuke build targets (including builds that drive Node projects).
```ts
import { NpxTasks } from "@zuke/npx";
await NpxTasks.npx((s) => s.command("cowsay").yes().execArgs("hello"));
```
@module
const NpxTasks: NpxTasksApi
Typed task functions for the `npx` package runner.
class NpxSettings extends ToolSettings
Settings for the `npx` package runner.
override protected defaultTool(): string
The executable this settings object drives: `npx`.
command(name: string): this
The package binary to execute (required unless {@link call} is set).
package(...specs: string[]): this
Packages to load before running (`--package=`); repeatable.
call(script: string): this
Execute a string as if inside `npm run-script` (`--call`).
yes(): this
Auto-install a missing package without prompting (`--yes`).
no(): this
Never auto-install; fail if the package is missing (`--no`).
ignoreExisting(): this
Ignore binaries already present in `$PATH` (`--ignore-existing`).
execArgs(...args: Array<string | number>): this
Arguments forwarded to the command.
override protected buildArgs(): string[]
Assemble the `npx <command>` argv from the configured settings.
interface NpxTasksApi
The shape of {@link NpxTasks}.
npx(configure?: Configure<NpxSettings>): Promise<CommandOutput>
Download and execute a package binary: `npx <command>`.
========================================================================
# @zuke/bun
========================================================================
`@zuke/bun` — typed `BunTasks` wrappers for the `bun` CLI, for use in Zuke
build targets (package management, scripts, and the built-in test runner).
```ts
import { BunTasks } from "@zuke/bun";
await BunTasks.install((s) => s.frozenLockfile());
await BunTasks.run((s) => s.script("build"));
```
@module
const BunTasks: BunTasksApi
Typed task functions for the `bun` CLI.
class BunAddSettings extends BunSettings
Settings for `bun add`.
packages(...specs: string[]): this
Package specs to add (required).
dev(): this
Add to devDependencies (`--dev`).
optional(): this
Add to optionalDependencies (`--optional`).
exact(): this
Pin the exact version (`--exact`).
global(): this
Install globally (`--global`).
override protected onOutput(output: CommandOutput): void
Report `Installed` and `Removed` onto the build summary.
override protected buildArgs(): string[]
Assemble the `bun add` argv.
class BunInstallSettings extends BunSettings
Settings for `bun install`.
production(): this
Install without devDependencies (`--production`).
frozenLockfile(): this
Fail if the lockfile is out of date (`--frozen-lockfile`).
override protected onOutput(output: CommandOutput): void
Report `Installed` and `Removed` onto the build summary.
override protected buildArgs(): string[]
Assemble the `bun install` argv.
class BunRemoveSettings extends BunSettings
Settings for `bun remove`.
packages(...names: string[]): this
Package names to remove (required).
override protected onOutput(output: CommandOutput): void
Report `Installed` and `Removed` onto the build summary.
override protected buildArgs(): string[]
Assemble the `bun remove` argv.
class BunRunSettings extends BunSettings
Settings for `bun run`.
script(name: string): this
The package.json script to run (required).
scriptArgs(...args: Array<string | number>): this
Arguments forwarded to the script.
override protected buildArgs(): string[]
Assemble the `bun run` argv.
abstract class BunSettings extends ToolSettings
Base for all `bun` subcommand settings: binary is `bun` from PATH.
override protected defaultTool(): string
The tool binary: `bun` on PATH.
class BunTestSettings extends BunSettings
Settings for `bun test`.
paths(...patterns: string[]): this
Test file or directory patterns to run; omit to run all tests.
coverage(): this
Collect coverage (`--coverage`).
bail(): this
Stop after the first failure (`--bail`).
override protected onOutput(output: CommandOutput): void
Report the run's counts onto the build summary (see the module docs).
override protected buildArgs(): string[]
Assemble the `bun test` argv.
class BunXSettings extends BunSettings
Settings for `bun x` (the `bunx` package runner).
command(name: string): this
The package binary to execute (required).
execArgs(...args: Array<string | number>): this
Arguments forwarded to the command.
override protected buildArgs(): string[]
Assemble the `bun x` argv.
interface BunTasksApi
The shape of {@link BunTasks}.
install(configure?: Configure<BunInstallSettings>): Promise<CommandOutput>
Install dependencies: `bun install`.
add(configure?: Configure<BunAddSettings>): Promise<CommandOutput>
Add dependencies: `bun add`.
remove(configure?: Configure<BunRemoveSettings>): Promise<CommandOutput>
Remove dependencies: `bun remove`.
run(configure?: Configure<BunRunSettings>): Promise<CommandOutput>
Run a package.json script: `bun run`.
x(configure?: Configure<BunXSettings>): Promise<CommandOutput>
Execute a package binary: `bun x` (bunx).
test(configure?: Configure<BunTestSettings>): Promise<CommandOutput>
Run the test suite: `bun test`.
========================================================================
# @zuke/pnpm
========================================================================
`@zuke/pnpm` — typed `PnpmTasks` wrappers for the `pnpm` CLI, for use in Zuke
build targets (including builds that drive Node/workspace projects).
```ts
import { PnpmTasks } from "@zuke/pnpm";
await PnpmTasks.install((s) => s.frozenLockfile());
await PnpmTasks.run((s) => s.script("build").filter("app"));
```
@module
const PnpmTasks: PnpmTasksApi
Typed task functions for the `pnpm` CLI.
class PnpmAddSettings extends PnpmSettings
Settings for `pnpm add`.
packages(...specs: string[]): this
Package specs to add (required).
saveDev(): this
Save to devDependencies (`--save-dev`).
saveExact(): this
Pin the exact version (`--save-exact`).
global(): this
Install globally (`--global`).
override protected buildArgs(): string[]
Assemble the `pnpm add` argv.
class PnpmDlxSettings extends PnpmSettings
Settings for `pnpm dlx`.
command(name: string): this
The command to execute (required).
package(spec: string): this
The package providing the command (`--package=`).
execArgs(...args: Array<string | number>): this
Arguments forwarded to the command.
override protected buildArgs(): string[]
Assemble the `pnpm dlx` argv.
class PnpmInstallSettings extends PnpmSettings
Settings for `pnpm install`.
frozenLockfile(): this
Fail if the lockfile is out of date (`--frozen-lockfile`).
prod(): this
Install without devDependencies (`--prod`).
override protected buildArgs(): string[]
Assemble the `pnpm install` argv.
class PnpmPublishSettings extends PnpmSettings
Settings for `pnpm publish`.
tag(name: string): this
Publish under a dist-tag (`--tag=`).
access(level: PnpmAccess): this
Set the package access level (`--access=`).
noGitChecks(): this
Skip the clean-working-tree checks (`--no-git-checks`).
dryRun(): this
Report what would be published without uploading (`--dry-run`).
override protected buildArgs(): string[]
Assemble the `pnpm publish` argv.
class PnpmRemoveSettings extends PnpmSettings
Settings for `pnpm remove`.
packages(...names: string[]): this
Package names to remove (required).
override protected buildArgs(): string[]
Assemble the `pnpm remove` argv.
class PnpmRunSettings extends PnpmSettings
Settings for `pnpm run`.
script(name: string): this
The package.json script to run (required).
filter(pattern: string): this
Restrict to matching workspace packages (`--filter`).
ifPresent(): this
Do not fail when the script is missing (`--if-present`).
scriptArgs(...args: Array<string | number>): this
Arguments forwarded to the script.
override protected buildArgs(): string[]
Assemble the `pnpm run` argv.
abstract class PnpmSettings extends ToolSettings
Base for all `pnpm` subcommand settings: binary is `pnpm` from PATH.
override protected defaultTool(): string
The default binary: `pnpm` resolved from PATH.
override protected onOutput(output: CommandOutput): void
Report `Added`, `Downloaded` and `Reused` onto the build summary.
interface PnpmTasksApi
The shape of {@link PnpmTasks}.
install(configure?: Configure<PnpmInstallSettings>): Promise<CommandOutput>
Install dependencies: `pnpm install`.
add(configure?: Configure<PnpmAddSettings>): Promise<CommandOutput>
Add dependencies: `pnpm add`.
remove(configure?: Configure<PnpmRemoveSettings>): Promise<CommandOutput>
Remove dependencies: `pnpm remove`.
run(configure?: Configure<PnpmRunSettings>): Promise<CommandOutput>
Run a package.json script: `pnpm run`.
dlx(configure?: Configure<PnpmDlxSettings>): Promise<CommandOutput>
Download and execute a package binary: `pnpm dlx`.
publish(configure?: Configure<PnpmPublishSettings>): Promise<CommandOutput>
Publish the package: `pnpm publish`.
type PnpmAccess = "public" | "restricted"
An access level accepted by pnpm's `--access` flag.
========================================================================
# @zuke/yarn
========================================================================
`@zuke/yarn` — typed `YarnTasks` wrappers for the `yarn` CLI, for use in Zuke
build targets (Yarn Classic v1 and Berry v2+; version-specific options are
documented on each method).
```ts
import { YarnTasks } from "@zuke/yarn";
await YarnTasks.install((s) => s.immutable());
await YarnTasks.run((s) => s.script("build"));
```
@module
const YarnTasks: YarnTasksApi
Typed task functions for the `yarn` CLI.
class YarnAddSettings extends YarnSettings
Settings for `yarn add`.
packages(...specs: string[]): this
Package specs to add (required).
dev(): this
Add to devDependencies (`--dev`).
exact(): this
Pin the exact version (`--exact`).
override protected buildArgs(): string[]
Assemble the `yarn add` argv.
class YarnDlxSettings extends YarnSettings
Settings for `yarn dlx` (Yarn Berry's one-off package runner).
command(name: string): this
The command to execute (required).
package(spec: string): this
An extra package to make available (`--package`).
execArgs(...args: Array<string | number>): this
Arguments forwarded to the command.
override protected buildArgs(): string[]
Assemble the `yarn dlx` argv.
class YarnInstallSettings extends YarnSettings
Settings for `yarn install`.
immutable(): this
Fail if the lockfile would change — `--immutable` (Yarn Berry).
frozenLockfile(): this
Fail if the lockfile would change — `--frozen-lockfile` (Yarn Classic).
override protected buildArgs(): string[]
Assemble the `yarn install` argv.
class YarnRemoveSettings extends YarnSettings
Settings for `yarn remove`.
packages(...names: string[]): this
Package names to remove (required).
override protected buildArgs(): string[]
Assemble the `yarn remove` argv.
class YarnRunSettings extends YarnSettings
Settings for `yarn run`.
script(name: string): this
The package.json script to run (required).
scriptArgs(...args: Array<string | number>): this
Arguments forwarded to the script.
override protected buildArgs(): string[]
Assemble the `yarn run` argv.
abstract class YarnSettings extends ToolSettings
Base for all `yarn` subcommand settings: binary is `yarn` from PATH.
override protected defaultTool(): string
The default binary: `yarn` resolved from PATH.
override protected onOutput(output: CommandOutput): void
Report `Added` and `Removed` (Yarn Berry) onto the build summary.
interface YarnTasksApi
The shape of {@link YarnTasks}.
install(configure?: Configure<YarnInstallSettings>): Promise<CommandOutput>
Install dependencies: `yarn install`.
add(configure?: Configure<YarnAddSettings>): Promise<CommandOutput>
Add dependencies: `yarn add`.
remove(configure?: Configure<YarnRemoveSettings>): Promise<CommandOutput>
Remove dependencies: `yarn remove`.
run(configure?: Configure<YarnRunSettings>): Promise<CommandOutput>
Run a package.json script: `yarn run`.
dlx(configure?: Configure<YarnDlxSettings>): Promise<CommandOutput>
Download and execute a package binary: `yarn dlx` (Berry).
========================================================================
# @zuke/cmd
========================================================================
`@zuke/cmd` — generic command execution for Zuke builds: the fallback for
tools that have no dedicated wrapper package.
Check the package catalogue in `llms.txt` before reaching for it. A tool with
a `@zuke/<tool>` wrapper should be driven through that wrapper — running it
here instead gives up typed flags and the wrapper's tool resolution, so the
example below deliberately uses a tool Zuke does not wrap.
```ts
import { CmdTasks } from "@zuke/cmd";
await CmdTasks.exec("shellcheck", (s) => s.args("--severity", "warning"));
```
@module
const CmdTasks: CmdTasksApi
Task functions for running arbitrary tools.
class CmdSettings extends ToolSettings
Settings for a generic command: the tool name plus raw arguments.
constructor(tool: PathLike)
Create settings for `tool`; the tool name is required.
override protected defaultTool(): string
The command to run — the tool name passed to the constructor.
override protected buildArgs(): string[]
No implicit arguments; the caller supplies them via `.args(...)`.
interface CmdTasksApi
The shape of {@link CmdTasks}.
exec(tool: PathLike, configure?: Configure<CmdSettings>): Promise<CommandOutput>
Run `tool` with the configured settings.
========================================================================
# @zuke/console
========================================================================
`@zuke/console` — task-shaped console output for Zuke builds, so a build never
reaches for `console.log`. A levelled logger (NUKE-style), Spectre.Console-style
markup and a semantic theme, and the primitives Zuke draws its own output with
(`line`, `rule`, `box`, `table`, target `header`/`summary`).
```ts
import { ConsoleTasks as Log } from "@zuke/console";
Log.rule("Deploy");
Log.info("pushing [bold]core@1.2.0[/]");
Log.success("published 4 packages");
```
A build can also route the executor's own banners through this package:
```ts
import { run } from "@zuke/core";
import { consoleRenderer } from "@zuke/console";
await run(MyBuild, { renderer: consoleRenderer });
```
@module
function createConsoleRenderer(theme: Theme): Renderer
Build a {@link Renderer} that draws target headers with `theme`'s palette.
function logoLines(color: boolean, options: LogoOptions): string[]
The logo as printable lines: the {@link ZUKE_LOGO} art painted two-tone when
`color` is on (letters bright, shadow dimmed), plus the optional tagline.
Pure — no I/O and no environment reads — so callers that manage their own
output (like the `zuke` CLI) can route the lines through any sink.
const ConsoleTasks: ConsoleTasksApi
Task-shaped console output. A single namespaced object (like `FileTasks`)
rather than loose helpers: logging methods, structural primitives, and
configuration all hang off `ConsoleTasks`.
const ZUKE_LOGO: `███████╗██╗ ██╗██╗ ██╗███████╗
╚══███╔╝██║ ██║██║ ██╔╝██╔════╝
███╔╝ ██║ ██║█████╔╝ █████╗
███╔╝ ██║ ██║██╔═██╗ ██╔══╝
███████╗╚██████╔╝██║ ██╗███████╗
╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝`
The Zuke wordmark in FIGlet's ANSI-shadow style. Solid `█` blocks form the
letters; box-drawing characters draw the shadow.
const consoleRenderer: Renderer
The default console renderer, using {@link defaultTheme}.
const defaultTheme: Theme
The default palette — a conventional terminal colour scheme.
interface ConsoleOptions
Options accepted when reconfiguring {@link ConsoleTasks}.
level?: LogLevel
The minimum severity to print.
sink?: Sink
Where rendered lines go (default: stdout/stderr).
theme?: Theme
A custom colour palette.
color?: boolean
Force ANSI colour on or off (default: auto-detected).
width?: number
Force the rule/box width (default: the terminal width).
github?: boolean
Force GitHub Actions output formatting (default: auto-detected).
interface ConsoleTasksApi
The shape of {@link ConsoleTasks}.
info(message: string): void
Log an informational message (markup-aware).
log(message: string): void
Alias for {@link ConsoleTasksApi.info}.
success(message: string): void
Log a success/completion message.
warn(message: string): void
Log a warning (a `::warning::` annotation under GitHub Actions).
error(message: string, options?: ErrorOptions): void
Log an error, optionally appending a thrown value's message.
debug(message: string): void
Log a debug diagnostic (shown only at `debug`/`trace` level).
trace(message: string): void
Log the most verbose trace output (shown only at `trace` level).
escape(text: string): string
Escape `[`/`]` in `text` so it renders literally rather than as markup —
for embedding arbitrary or untrusted strings in a message.
line(options?: LineOptions): void
Print a horizontal rule spanning the width.
rule(title?: string, options?: RuleOptions): void
Print a rule, optionally with a centred title.
box(content: string | string[], options?: BoxOptions): void
Print a bordered panel around `content` (markup-aware).
table(columns: TableColumn[], rows: string[][], options?: TableOptions): void
Print an aligned table; header and cell text may contain markup.
header(name: string): void
Print the ruled banner Zuke opens a target's section with.
logo(options?: LogoOptions): void
Print the Zuke ASCII logo, two-tone when colour is on.
summary(reports: TargetReport[], totalMs: number, ok: boolean): void
Print the end-of-build summary table and closing verdict.
group(name: string): void
Open a collapsible group; close it with {@link ConsoleTasksApi.endGroup}.
endGroup(): void
Close the group opened by {@link ConsoleTasksApi.group}.
configure(options: ConsoleOptions): void
Reconfigure logging (level, sink, theme, colour, width, Actions mode).
level(): LogLevel
The active minimum severity.
reset(): void
Reset all configuration to defaults (level re-seeded from the env).
interface ErrorOptions
Options for {@link ConsoleTasks.error}.
error?: unknown
An error whose message is appended as a dimmed detail line.
interface LogoOptions
Options for {@link logoLines} and `ConsoleTasks.logo`.
tagline?: string
A line printed dimmed under the art (e.g. a version or strapline).
letterStyle?: readonly StyleName[]
Styles for the solid letter blocks. Defaults to `["cyan", "bold"]`.
shadowStyle?: readonly StyleName[]
Styles for the shadow characters. Defaults to `["dim"]`.
interface RuleOptions extends LineOptions
Options for {@link ConsoleTasks.rule}.
interface Sink
A destination for rendered lines. Overridable to capture output in tests.
out(line: string): void
Write a line to standard output.
err(line: string): void
Write a line to standard error.
interface Theme
The colour palette. Each semantic token maps to the ANSI styles applied to
text (or markup) tagged with that name.
info: StyleName[]
Informational messages.
success: StyleName[]
Success/completion messages.
warn: StyleName[]
Warnings.
error: StyleName[]
Errors and failures.
debug: StyleName[]
Debug diagnostics.
trace: StyleName[]
The most verbose trace output.
muted: StyleName[]
De-emphasised, secondary text.
type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "silent"
A severity threshold. Messages below the active level are suppressed.
========================================================================
# @zuke/cli
========================================================================
`@zuke/cli` — the `zuke` command. Install it globally with
```sh
deno install -A -g -n zuke jsr:@zuke/cli
```
scaffold Zuke into any project with `zuke setup`, then run its build from
anywhere inside the project with `zuke <target>`: every command that is not
the CLI's own (`setup`, `import`, `doc`) is forwarded to the nearest
`zuke.ts`, exactly as the `./zuke` launcher would run it.
@module
async function main(args: string[], host: SetupHost, prompter: Prompter, docRunner: DocRunner, starActions: StarActions, buildRunner: BuildRunner, buildProbe: BuildProbe): Promise<number>
The CLI entry point. Returns a process exit code; `host`, `prompter`,
`docRunner`, `starActions`, `buildRunner`, and `buildProbe` are injectable
for testing.
function parseImportFlags(args: string[]): ImportFlags
Parse the argument list following `zuke import`.
function parseSetupFlags(args: string[]): SetupFlags
Parse the argument list following `zuke setup`.
const defaultPrompter: Prompter
The real {@link Prompter}, backed by Deno's `prompt`/`confirm`.
interface BuildLocation
Where a forwarded command runs, and how.
root: string
The absolute repository root: the directory holding `zuke.json`.
frozen: boolean
Whether a `deno.lock` sits at the root, so the run passes `--frozen`.
interface BuildProbe
The filesystem questions discovery asks, injectable so the walk and its
trust gate are testable without a filesystem or a second user.
exists(path: string): Promise<boolean>
Whether a path exists (the link itself, not its target).
ownership(path: string): Promise<Ownership | null>
The owner and mode of what a path resolves to, or `null` when nothing is
there.
uid(): number | null
The current user's numeric id, or `null` where the platform has none.
interface ImportFlags extends SetupFlags
Flags accepted by `zuke import` — the setup flags plus `--from`.
from?: ImportSource
Force a source (`package.json` or `makefile`); auto-detected when unset.
interface Ownership
What the trust gate needs to know about a filesystem entry.
uid: number | null
The numeric owner, or `null` where the platform reports none (Windows).
mode: number | null
The permission bits, or `null` where the platform reports none.
interface Prompter
The interactive surface, injectable so the wizard is testable without a TTY.
interactive(): boolean
Whether prompts should be shown (i.e. stdin is a terminal).
ask(question: string, fallback: string): string
Ask a free-text question, returning `fallback` if unanswered.
confirm(question: string): boolean
Ask a yes/no question.
interface SetupFlags
Flags accepted by `zuke setup`.
force: boolean
Overwrite existing files.
yes: boolean
Skip prompts and accept defaults.
name?: string
Build class name for the starter `zuke.ts`.
dir?: string
Directory to scaffold into (defaults to the current directory).
launcherName?: string
Base name for the launcher scripts, when `zuke` is taken by a directory.
mcp: boolean
Also write `.mcp.json`, registering the build's MCP server for agent clients.
allowRun: boolean
Register that server with `--allow-run`, so the agent may execute targets. Implies `mcp`.
bootstrapDeno?: boolean
Which launchers to scaffold: `true` (`--bootstrap-deno`) for ones that
install a pinned, checksum-verified Deno when none is on `PATH`, `false`
(`--no-bootstrap-deno`) for ones that require it and fail closed. Unset:
ask when interactive, else take the default (bootstrap).
interface SetupHost
Injected side effects, so {@link runSetup} is unit-testable.
exists(path: string): Promise<boolean>
Whether a path exists.
isDirectory(path: string): Promise<boolean>
Whether a path exists and is a directory (a reserved-name collision).
isSymlink(path: string): Promise<boolean>
Whether a path is a symbolic link, without following it. Scaffolding
refuses to write through one: the writes below follow links, so a link
planted at a scaffold name by the very repository being set up would
redirect them outside the target directory.
The guard covers the names scaffolding chooses, which is where the hazard
is — the caller never asked for `.gitignore` to be written, so a repository
redirecting it is a decision nobody made. It reports what is there when it
runs, and a link planted after it would escape it; that is why
{@link SetupHost.writeText} does not write through a link either, so the
refusal is the friendly answer rather than the only defence.
Two things stay out of scope. The directory the caller names with `--dir`
is the caller's to name, symlink or not. And a hard link is
indistinguishable from the file it shares, so no probe can see one; git
cannot check one out either, which is what keeps it out of the threat this
guards.
readText(path: string): Promise<string>
Read a file as UTF-8 text.
writeText(path: string, content: string): Promise<void>
Write UTF-8 text to a file, creating or replacing it.
An implementation must not write through a symbolic link standing at
`path`: the scaffolder's confinement to its target directory rests on this,
and the pre-write {@link SetupHost.isSymlink} check alone cannot carry it,
since a link can appear after the check.
chmod(path: string, mode: number): Promise<void>
Set a file's permission bits (may be unsupported on some platforms).
log(message: string): void
Emit a line of progress output.
interface StarActions
The side effects behind {@link promptStar}, injectable so tests never spawn
`gh` or a browser.
ghAuthenticated(): Promise<boolean>
Whether the `gh` CLI is installed and holds a login.
starWithGh(): Promise<void>
Star the Zuke repository through `gh api`.
openBrowser(url: string): Promise<boolean>
Open `url` in the default browser; `false` when it could not launch.
type BuildRunner = (root: string, denoArgs: string[]) => Promise<number>
Runs `deno <denoArgs>` from `root` and resolves to its exit code — the
injectable subprocess seam, so the forwarding is testable without a build.
type DocRunner = (denoArgs: string[]) => Promise<number>
Runs `deno doc <args>` — the injectable subprocess seam for
{@link commandDoc}, so the command is testable without spawning `deno`.
type ImportSource = "package.json" | "Makefile"
The kinds of project `zuke import` can read.
========================================================================
# @zuke/docker
========================================================================
`@zuke/docker` — typed `DockerTasks` wrappers for the `docker` CLI.
```ts
import { DockerTasks } from "@zuke/docker";
await DockerTasks.build((s) => s.tag("app:latest"));
await DockerTasks.run((s) => s.rm().image("app:latest"));
const running = await DockerTasks.psEntries();
```
Typed tasks cover the everyday docker surface — building, running and
inspecting containers, moving images, the registry, and the `volume`,
`network`, `system`, and `context` groups. Four hand back parsed values
rather than raw output: `psEntries`, `imageEntries`, `volumeNames`, and
`networkNames`. The swarm commands (`service`, `stack`, `node`, `secret`)
and `compose` are out of scope; `compose` has its own package,
`@zuke/docker-compose`.
@module
const DockerTasks: DockerTasksApi
Typed task functions for the `docker` CLI.
class DockerBuildSettings extends DockerSettings
Settings for `docker build`.
tag(reference: string): this
Add an image tag (`-t`); repeatable.
file(path: PathLike): this
Use an explicit Dockerfile (`-f`).
target(stage: string): this
Build a specific stage (`--target`).
platform(value: string): this
Set the target platform(s) (`--platform`).
buildArg(key: string, value: string): this
Pass a build-time variable (`--build-arg KEY=value`); repeatable.
noCache(): this
Do not use the layer cache (`--no-cache`).
pull(): this
Always attempt to pull newer base images (`--pull`).
push(): this
Push the result to the registry after building (`--push`).
context(path: PathLike): this
The build context path or URL (default `.`).
override protected subcommandArgs(): string[]
Assemble the `docker build` argv.
class DockerCommitSettings extends DockerSettings
Settings for `docker commit`.
container(name: string): this
The container to snapshot (required).
reference(name: string): this
The image name to give the snapshot (positional).
message(text: string): this
A commit message (`-m`/`--message`).
author(value: string): this
The author to record (`-a`/`--author`).
change(...instructions: string[]): this
Apply a Dockerfile instruction to the result (`-c`/`--change`); repeatable.
noPause(): this
Leave the container running while it is committed (`--pause=false`).
docker pauses it by default, which is what makes the snapshot consistent.
override protected subcommandArgs(): string[]
Assemble the `docker commit` argv.
abstract class DockerContainerListSettings extends DockerSettings
Base for the commands that act on one or more existing containers. The
empty-list check is here so every one of them reports the same thing rather
than letting docker print its usage.
containers(...names: string[]): this
Container names or ids to act on (required); repeatable.
protected containerList(task: string): string[]
The container list, after refusing an empty one.
abstract class DockerContainerSettings extends DockerProcessSettings
Base for `run` and `create`, which configure a new container identically —
docker's own `run` is `create` followed by `start`.
image(reference: string): this
The image to run (required).
name(value: string): this
Name the container (`--name`).
rm(): this
Remove the container when it exits (`--rm`).
detach(): this
Run in the background (`-d`).
publish(host: string | number, container: string | number): this
Publish a container port to the host (`-p`); repeatable.
volume(source: PathLike, target: PathLike): this
Mount a host path into the container (`-v`); repeatable.
network(value: string): this
Attach the container to a network (`--network`).
entrypoint(command: string): this
Override the image's entrypoint (`--entrypoint`).
platform(value: string): this
Run the image for a specific platform (`--platform`).
pull(policy: "always" | "missing" | "never"): this
When to pull the image (`--pull=<always|missing|never>`).
restart(policy: string): this
The restart policy (`--restart`), e.g. `unless-stopped`.
label(key: string, value: string): this
Attach metadata to the container (`--label`); repeatable.
protected containerArgs(task: string): string[]
Assemble everything after the subcommand: the flags, the image, and the
command. `run` and `create` differ only in the token in front of this.
class DockerContextSettings extends DockerSettings
Settings for `docker context`. Pick the subcommand with {@link create},
{@link ls}, {@link use}, {@link inspect}, {@link remove}, or {@link show}.
create(name: string): this
Create a context (`context create <name>`).
ls(): this
List contexts (`context ls`), the default.
use(name: string): this
Make a context the default for later commands (`context use <name>`).
inspect(...names: string[]): this
Describe contexts (`context inspect [<name>...]`).
remove(...names: string[]): this
Remove contexts (`context rm <name>...`).
show(): this
Print the context in use (`context show`).
dockerHost(address: string): this
The daemon a created context points at (`--docker host=<address>`).
description(text: string): this
A human description for a created context (`--description`).
from(name: string): this
Copy an existing context (`--from <name>`).
format(template: string): this
Render each context through a Go template (`--format`).
quietOutput(): this
Only print context names (`-q`/`--quiet`).
force(): this
Remove even the context in use (`-f`/`--force`).
override protected subcommandArgs(): string[]
Assemble the `docker context` argv.
class DockerCpSettings extends DockerSettings
Settings for `docker cp`. Either end may be a container path
(`<container>:<path>`) — which is what makes this the way a build gets an
artifact out of a container that has already stopped.
from(path: PathLike): this
The source, `<container>:<path>` or a host path (required).
to(path: PathLike): this
The destination, `<container>:<path>` or a host path (required).
archive(): this
Keep uid/gid rather than mapping to the current user (`-a`/`--archive`).
followLink(): this
Follow a symlink in the source (`-L`/`--follow-link`).
override protected subcommandArgs(): string[]
Assemble the `docker cp` argv.
class DockerCreateSettings extends DockerContainerSettings
Settings for `docker create`.
override protected subcommandArgs(): string[]
Assemble the `docker create` argv.
class DockerDiffSettings extends DockerSettings
Settings for `docker diff`.
container(name: string): this
The container whose filesystem changes to show (required).
override protected subcommandArgs(): string[]
Assemble the `docker diff` argv.
class DockerExecSettings extends DockerProcessSettings
Settings for `docker exec`.
container(name: string): this
The container to run the command in (required).
detach(): this
Run the command in the background (`-d`).
privileged(): this
Give the command extended privileges (`--privileged`).
override protected subcommandArgs(): string[]
Assemble the `docker exec` argv.
class DockerExportSettings extends DockerSettings
Settings for `docker export`.
container(name: string): this
The container whose filesystem to export (required).
output(path: PathLike): this
Write to a file rather than stdout (`-o`/`--output`).
override protected subcommandArgs(): string[]
Assemble the `docker export` argv.
class DockerHistorySettings extends DockerSettings
Settings for `docker history`.
image(reference: string): this
The image whose layers to show (required).
noTrunc(): this
Print the full commands rather than eliding them (`--no-trunc`).
quietOutput(): this
Only show layer ids (`-q`/`--quiet`).
format(template: string): this
Render each layer through a Go template (`--format`).
override protected subcommandArgs(): string[]
Assemble the `docker history` argv.
class DockerImagePruneSettings extends DockerSettings
Settings for `docker image prune`.
all(): this
Remove every unused image, not only the dangling ones (`--all`). This is
the difference between reclaiming a little space and reclaiming a lot.
force(): this
Do not prompt for confirmation (`--force`), which a build always needs.
filter(...expressions: string[]): this
Limit what is pruned (`--filter`), e.g. `until=24h`; repeatable.
override protected subcommandArgs(): string[]
Assemble the `docker image prune` argv.
class DockerImagesSettings extends DockerSettings
Settings for `docker images`.
all(): this
Show all images, including intermediate layers (`-a`).
quietOutput(): this
Only show image IDs (`-q`).
filter(expression: string): this
Filter the listing (`--filter`); repeatable.
repository(name: string): this
Restrict to a repository (positional argument).
format(template: string): this
Render each image through a Go template (`--format`), e.g.
`{{json .}}` for one JSON object per line.
{@link "./docker.ts".DockerTasks.imageEntries} pins that form.
digests(): this
Also show each image's digest (`--digests`).
override protected subcommandArgs(): string[]
Assemble the `docker images` argv.
class DockerImportSettings extends DockerSettings
Settings for `docker import`.
source(path: PathLike): this
The tarball to import (required); `-` reads it from stdin, as docker's own
`import` does.
reference(name: string): this
The image name to give the result (positional).
message(text: string): this
A commit message for the imported image (`-m`/`--message`).
change(...instructions: string[]): this
Apply a Dockerfile instruction to the result (`-c`/`--change`); repeatable.
platform(value: string): this
The platform to import for (`--platform`).
override protected subcommandArgs(): string[]
Assemble the `docker import` argv.
class DockerInfoSettings extends DockerSettings
Settings for `docker info`.
format(template: string): this
Render through a Go template (`--format`), e.g. `{{json .}}`.
override protected subcommandArgs(): string[]
Assemble the `docker info` argv.
class DockerInspectSettings extends DockerSettings
Settings for `docker inspect`.
targets(...names: string[]): this
The objects to inspect — containers, images, volumes (required).
format(template: string): this
Render through a Go template (`--format`), e.g. `{{.State.Status}}` for
one field, or `{{json .}}` for the whole record as JSON.
type(kind: string): this
Only look for this kind of object (`--type`), e.g. `container`.
size(): this
Include the disk usage of a container (`-s`/`--size`).
override protected subcommandArgs(): string[]
Assemble the `docker inspect` argv.
class DockerKillSettings extends DockerContainerListSettings
Settings for `docker kill`.
signal(name: string): this
The signal to send (`-s`/`--signal`); docker defaults to `SIGKILL`.
override protected subcommandArgs(): string[]
Assemble the `docker kill` argv.
class DockerLoadSettings extends DockerSettings
Settings for `docker load`.
input(path: PathLike): this
Read from a tar archive instead of STDIN (`-i`).
quietOutput(): this
Suppress the load output (`-q`).
override protected subcommandArgs(): string[]
Assemble the `docker load` argv.
class DockerLoginSettings extends DockerSettings
Settings for `docker login`.
username(value: string): this
The username (`-u`).
password(value: string): this
The password (`-p`), which docker accepts and which therefore stays on the
wrapper.
Prefer {@link passwordStdin}. This flag puts the password in the child's
argv, and a child's argv is world-readable on a default Linux host —
through `ps` or `/proc/<pid>/cmdline` — to every other user on the machine
and every process the build starts. On a shared or self-hosted runner that
is every co-tenant.
The value is registered with the run's redactor, so Zuke's own renderings
of the command mask it. That does not cover the process table: the argv
handed to the operating system is not redacted, and docker itself warns
about `-p` for the same reason. Masking is what can be done here, not a
fix for the exposure.
passwordStdin(token: string): this
Pipe the password to docker through STDIN (`--password-stdin`), which
keeps it off the command line entirely.
This is the route docker documents for exactly this reason, and the one to
use in CI. The token goes to the child's standard input, which — unlike its
argv — no other process on the host can read.
The password is required, because the flag on its own means nothing: it
tells docker to read standard input, and this wrapper is what has to put
something there. A `--password-stdin` with no writer leaves docker reading
a stream that is never written.
registry(server: string): this
The registry server (defaults to Docker Hub).
override protected stdinInput(): string | undefined
The password given to {@link passwordStdin}, handed to the child on its
standard input. `undefined` when that setter was never called, which is
what leaves stdin alone for every other login.
override protected subcommandArgs(): string[]
Assemble the `docker login` argv.
class DockerLogoutSettings extends DockerSettings
Settings for `docker logout`.
registry(server: string): this
The registry to forget (positional); defaults to Docker Hub.
override protected subcommandArgs(): string[]
Assemble the `docker logout` argv.
class DockerLogsSettings extends DockerSettings
Settings for `docker logs`.
container(name: string): this
The container whose logs to read (required).
follow(): this
Keep streaming (`-f`/`--follow`). A target that follows logs never
returns on its own — pair it with `.killAfter(...)` from the tooling base,
or with a container that exits.
tail(lines: number | "all"): this
Show only the last N lines (`--tail`), or `all`.
since(when: string): this
Only logs since this timestamp or relative time (`--since`), e.g. `10m`.
until(when: string): this
Only logs before this timestamp or relative time (`--until`).
timestamps(): this
Prefix each line with its timestamp (`-t`/`--timestamps`).
details(): this
Include the extra attributes docker records (`--details`).
override protected subcommandArgs(): string[]
Assemble the `docker logs` argv.
class DockerNetworkSettings extends DockerSettings
Settings for `docker network`. Pick the subcommand with {@link create},
{@link ls}, {@link remove}, {@link inspect}, {@link connect},
{@link disconnect}, or {@link prune}.
create(name: string): this
Create a network (`network create <name>`).
ls(): this
List networks (`network ls`), the default.
remove(...names: string[]): this
Remove networks (`network rm <name>...`).
inspect(...names: string[]): this
Describe networks (`network inspect <name>...`).
connect(network: string, container: string): this
Attach a container to a network (`network connect <net> <container>`).
disconnect(network: string, container: string): this
Detach a container (`network disconnect <net> <container>`).
prune(): this
Remove the networks nothing uses (`network prune`).
driver(name: string): this
The network driver (`--driver`), e.g. `bridge`.
subnet(cidr: string): this
The subnet in CIDR form (`--subnet`).
gateway(address: string): this
The gateway address (`--gateway`).
label(key: string, value: string): this
Attach metadata (`--label`); repeatable.
alias(name: string): this
An extra name the container answers to on this network (`--alias`).
filter(...expressions: string[]): this
Filter a listing or a prune (`--filter`); repeatable.
format(template: string): this
Render each network through a Go template (`--format`).
{@link "./docker.ts".DockerTasks.networkNames} pins `{{.Name}}`.
quietOutput(): this
Only print network ids (`-q`/`--quiet`).
force(): this
Do not prompt for confirmation (`--force`).
override protected subcommandArgs(): string[]
Assemble the `docker network` argv.
class DockerPauseSettings extends DockerContainerListSettings
Settings for `docker pause`.
override protected subcommandArgs(): string[]
Assemble the `docker pause` argv.
class DockerPortSettings extends DockerSettings
Settings for `docker port`.
container(name: string): this
The container whose port mappings to show (required).
port(value: string | number): this
A single private port to resolve, e.g. `8080/tcp`.
override protected subcommandArgs(): string[]
Assemble the `docker port` argv.
abstract class DockerProcessSettings extends DockerSettings
Base for the commands that run a process — `run`, `create`, and `exec` —
carrying the flags all three accept.
interactive(): this
Keep stdin open (`-i`).
tty(): this
Allocate a pseudo-TTY (`-t`).
envVar(key: string, value: string): this
Set an environment variable (`-e`); repeatable.
envFile(...paths: PathLike[]): this
Read environment variables from a file (`--env-file`); repeatable.
workdir(path: PathLike): this
Set the working directory inside the container (`-w`).
user(value: string): this
Run as this user or `uid:gid` (`-u`/`--user`).
commandArgs(...args: Array<string | number>): this
The command and arguments to run inside the container.
protected ttyArgs(): string[]
The `-i`/`-t` flags, which every one of these commands renders first.
protected processArgs(): string[]
The environment, working directory, and user flags.
protected trailingCommand(): string[]
The trailing command, after the container or image it runs in.
class DockerPsSettings extends DockerSettings
Settings for `docker ps`.
all(): this
Show stopped containers too (`-a`).
quietOutput(): this
Only show container IDs (`-q`).
filter(expression: string): this
Filter the listing (`--filter`); repeatable.
format(template: string): this
Render each container through a Go template (`--format`), e.g.
`{{json .}}` for one JSON object per line.
{@link "./docker.ts".DockerTasks.psEntries} pins that form.
latest(): this
Show only the most recently created container (`-l`/`--latest`).
noTrunc(): this
Print ids and commands in full (`--no-trunc`).
size(): this
Include each container's disk usage (`-s`/`--size`).
override protected subcommandArgs(): string[]
Assemble the `docker ps` argv.
class DockerPullSettings extends DockerSettings
Settings for `docker pull`.
image(reference: string): this
The image reference to pull (required).
platform(value: string): this
Pull a specific platform (`--platform`).
quietOutput(): this
Suppress verbose output (`-q`).
override protected subcommandArgs(): string[]
Assemble the `docker pull` argv.
class DockerPushSettings extends DockerSettings
Settings for `docker push`.
image(reference: string): this
The image reference to push (required).
allTags(): this
Push every tag of the repository (`--all-tags`).
override protected subcommandArgs(): string[]
Assemble the `docker push` argv.
class DockerRenameSettings extends DockerSettings
Settings for `docker rename`.
container(name: string): this
The container to rename (required).
newName(name: string): this
Its new name (required).
override protected subcommandArgs(): string[]
Assemble the `docker rename` argv.
class DockerRestartSettings extends DockerContainerListSettings
Settings for `docker restart`.
timeout(seconds: number): this
Seconds to wait before killing the container (`-t`/`--time`).
signal(name: string): this
The signal to send first (`-s`/`--signal`).
override protected subcommandArgs(): string[]
Assemble the `docker restart` argv.
class DockerRmSettings extends DockerContainerListSettings
Settings for `docker rm`.
force(): this
Force removal of a running container (`-f`).
volumes(): this
Also remove anonymous volumes (`-v`).
override protected subcommandArgs(): string[]
Assemble the `docker rm` argv.
class DockerRmiSettings extends DockerSettings
Settings for `docker rmi`.
images(...references: string[]): this
The images to remove (at least one is required).
force(): this
Force removal (`-f`).
override protected subcommandArgs(): string[]
Assemble the `docker rmi` argv.
class DockerRunSettings extends DockerContainerSettings
Settings for `docker run`.
override protected subcommandArgs(): string[]
Assemble the `docker run` argv.
class DockerSaveSettings extends DockerSettings
Settings for `docker save`.
images(...references: string[]): this
The images to save (at least one is required).
output(path: PathLike): this
Write to a file instead of STDOUT (`-o`).
override protected subcommandArgs(): string[]
Assemble the `docker save` argv.
class DockerSearchSettings extends DockerSettings
Settings for `docker search`.
term(value: string): this
What to search Docker Hub for (required).
limit(count: number): this
Cap the number of results (`--limit`).
filter(...expressions: string[]): this
Filter the results (`--filter`), e.g. `is-official=true`; repeatable.
format(template: string): this
Render each result through a Go template (`--format`).
noTrunc(): this
Print descriptions in full (`--no-trunc`).
override protected subcommandArgs(): string[]
Assemble the `docker search` argv.
abstract class DockerSettings extends ToolSettings
Shared base for every `docker` subcommand: the binary and global options.
override protected defaultTool(): string
The invoked binary is `docker`.
abstract protected subcommandArgs(): string[]
The subcommand argv, after the global options.
dockerContext(name: string): this
Use a named docker context (`--context`), which is how a build talks to a
remote daemon or a second local one without exporting `DOCKER_HOST`.
Named `dockerContext` rather than `context` because `docker build`'s
trailing `PATH` is also called a context, and
{@link "./build.ts".DockerBuildSettings.context} already means that one.
host(address: string): this
The daemon socket to connect to (`--host`), e.g. `ssh://build@host`.
logLevel(level: DockerLogLevel): this
How much the client logs (`--log-level`).
config(path: PathLike): this
Where the client config lives (`--config`).
debug(): this
Enable client debug output (`--debug`).
override protected buildArgs(): string[]
Assemble the `docker` argv: the global options, then the subcommand.
docker reads these before the subcommand, so the order is not cosmetic —
`docker ps --context x` is an error, `docker --context x ps` is not.
class DockerStartSettings extends DockerContainerListSettings
Settings for `docker start`.
attach(): this
Attach STDOUT/STDERR and forward signals (`-a`).
override protected subcommandArgs(): string[]
Assemble the `docker start` argv.
class DockerStatsSettings extends DockerSettings
Settings for `docker stats`.
containers(...names: string[]): this
Limit the report to these containers; omit for all running ones.
all(): this
Include stopped containers (`-a`/`--all`).
format(template: string): this
Render through a Go template (`--format`).
override protected subcommandArgs(): string[]
Assemble the `docker stats` argv. `--no-stream` is always set: without it
docker streams forever, and a build target that never returns is a hang,
not a measurement.
class DockerStopSettings extends DockerContainerListSettings
Settings for `docker stop`.
time(seconds: number): this
Seconds to wait before killing (`-t`).
override protected subcommandArgs(): string[]
Assemble the `docker stop` argv.
class DockerSystemSettings extends DockerSettings
Settings for `docker system`. Pick the subcommand with {@link prune},
{@link df}, or {@link info}.
prune(): this
Reclaim space (`system prune`).
df(): this
Report what is using disk (`system df`).
info(): this
Describe the daemon (`system info`).
all(): this
Prune every unused image, not only the dangling ones (`--all`) — the
difference between reclaiming a little space and reclaiming a lot.
force(): this
Do not prompt for confirmation (`--force`), which a build always needs.
volumes(): this
Also remove unused volumes (`--volumes`), which a prune otherwise keeps.
filter(...expressions: string[]): this
Limit what is pruned (`--filter`), e.g. `until=24h`; repeatable.
verbose(): this
Break the `df` report down per object (`-v`/`--verbose`).
format(template: string): this
Render through a Go template (`--format`).
override protected subcommandArgs(): string[]
Assemble the `docker system` argv.
class DockerTagSettings extends DockerSettings
Settings for `docker tag`.
source(reference: string): this
The existing image reference (required).
target(reference: string): this
The new image reference (required).
override protected subcommandArgs(): string[]
Assemble the `docker tag` argv.
class DockerTopSettings extends DockerSettings
Settings for `docker top`.
container(name: string): this
The container whose processes to list (required).
psArgs(...args: string[]): this
Arguments passed through to `ps` inside the container.
override protected subcommandArgs(): string[]
Assemble the `docker top` argv.
class DockerUnpauseSettings extends DockerContainerListSettings
Settings for `docker unpause`.
override protected subcommandArgs(): string[]
Assemble the `docker unpause` argv.
class DockerUpdateSettings extends DockerContainerListSettings
Settings for `docker update`.
memory(limit: string): this
The memory limit (`--memory`), e.g. `512m`.
cpus(count: string): this
How many CPUs the container may use (`--cpus`).
restart(policy: string): this
The restart policy (`--restart`).
override protected subcommandArgs(): string[]
Assemble the `docker update` argv.
class DockerVersionSettings extends DockerSettings
Settings for `docker version`.
format(template: string): this
Render through a Go template (`--format`), e.g. `{{.Server.Version}}` to
read just the daemon's version.
override protected subcommandArgs(): string[]
Assemble the `docker version` argv.
class DockerVolumeSettings extends DockerSettings
Settings for `docker volume`. Pick the subcommand with {@link create},
{@link ls}, {@link remove}, {@link inspect}, or {@link prune}.
create(name?: string): this
Create a volume (`volume create [<name>]`).
ls(): this
List volumes (`volume ls`), the default.
remove(...names: string[]): this
Remove volumes (`volume rm <name>...`).
inspect(...names: string[]): this
Describe volumes (`volume inspect <name>...`).
prune(): this
Remove the volumes nothing uses (`volume prune`).
driver(name: string): this
The volume driver (`--driver`), for a created volume.
label(key: string, value: string): this
Attach metadata (`--label`); repeatable.
opt(key: string, value: string): this
A driver-specific option (`--opt`); repeatable.
filter(...expressions: string[]): this
Filter a listing or a prune (`--filter`); repeatable.
format(template: string): this
Render each volume through a Go template (`--format`).
{@link "./docker.ts".DockerTasks.volumeNames} pins `{{.Name}}`.
quietOutput(): this
Only print volume names (`-q`/`--quiet`).
force(): this
Do not prompt, and remove even a volume in use where docker allows it (`--force`).
all(): this
Prune anonymous and named volumes (`--all`), not only anonymous ones.
override protected subcommandArgs(): string[]
Assemble the `docker volume` argv.
class DockerWaitSettings extends DockerContainerListSettings
Settings for `docker wait` — blocking until the containers stop, then
printing their exit codes, which is how a build gets a test container's
result rather than the runner's.
override protected subcommandArgs(): string[]
Assemble the `docker wait` argv.
interface DockerContainerEntry
One container of `docker ps --format '{{json .}}'`.
id?: string
The container id, as docker abbreviates it in a listing.
image?: string
The image it was created from.
names?: string
Its names, comma-separated as docker reports them.
command?: string
The command it runs.
status?: string
A human description of its state, e.g. `Up 3 minutes`.
state?: string
The bare state, e.g. `running` or `exited`.
ports?: string
The published ports, as docker formats them.
interface DockerImageEntry
One image of `docker images --format '{{json .}}'`.
id?: string
The image id, as docker abbreviates it in a listing.
repository?: string
The repository, or `<none>` for an untagged image.
tag?: string
The tag, or `<none>` when the image carries none.
createdSince?: string
How docker describes the image's age, e.g. `2 days ago`.
size?: string
The on-disk size, as docker formats it.
digest?: string
The digest, when the listing was asked for one.
interface DockerTasksApi
The shape of {@link DockerTasks}.
build(configure?: Configure<DockerBuildSettings>): Promise<CommandOutput>
Build an image: `docker build`.
run(configure?: Configure<DockerRunSettings>): Promise<CommandOutput>
Run a container: `docker run`.
create(configure?: Configure<DockerCreateSettings>): Promise<CommandOutput>
Create a container without starting it: `docker create`.
exec(configure?: Configure<DockerExecSettings>): Promise<CommandOutput>
Run a command in a container: `docker exec`.
start(configure?: Configure<DockerStartSettings>): Promise<CommandOutput>
Start containers: `docker start`.
stop(configure?: Configure<DockerStopSettings>): Promise<CommandOutput>
Stop containers: `docker stop`.
restart(configure?: Configure<DockerRestartSettings>): Promise<CommandOutput>
Restart containers: `docker restart`.
kill(configure?: Configure<DockerKillSettings>): Promise<CommandOutput>
Signal containers: `docker kill`.
pause(configure?: Configure<DockerPauseSettings>): Promise<CommandOutput>
Suspend a container's processes: `docker pause`.
unpause(configure?: Configure<DockerUnpauseSettings>): Promise<CommandOutput>
Resume them: `docker unpause`.
rm(configure?: Configure<DockerRmSettings>): Promise<CommandOutput>
Remove containers: `docker rm`.
wait(configure?: Configure<DockerWaitSettings>): Promise<CommandOutput>
Block until containers stop, then print their exit codes: `docker wait` —
how a build gets a test container's result rather than the runner's.
rename(configure?: Configure<DockerRenameSettings>): Promise<CommandOutput>
Rename a container: `docker rename`.
update(configure?: Configure<DockerUpdateSettings>): Promise<CommandOutput>
Change a container's resource limits: `docker update`.
ps(configure?: Configure<DockerPsSettings>): Promise<CommandOutput>
List containers: `docker ps`.
psEntries(configure?: Configure<DockerPsSettings>): Promise<DockerContainerEntry[]>
The containers as parsed {@link DockerContainerEntry} values, from
`docker ps --format '{{json .}}'`.
logs(configure?: Configure<DockerLogsSettings>): Promise<CommandOutput>
Read a container's logs: `docker logs`.
inspect(configure?: Configure<DockerInspectSettings>): Promise<CommandOutput>
Describe docker objects: `docker inspect`.
top(configure?: Configure<DockerTopSettings>): Promise<CommandOutput>
List a container's processes: `docker top`.
stats(configure?: Configure<DockerStatsSettings>): Promise<CommandOutput>
Sample resource usage once: `docker stats --no-stream`.
port(configure?: Configure<DockerPortSettings>): Promise<CommandOutput>
Show a container's port mappings: `docker port`.
diff(configure?: Configure<DockerDiffSettings>): Promise<CommandOutput>
Show a container's filesystem changes: `docker diff`.
cp(configure?: Configure<DockerCpSettings>): Promise<CommandOutput>
Copy files between a container and the host: `docker cp`.
commit(configure?: Configure<DockerCommitSettings>): Promise<CommandOutput>
Turn a container into an image: `docker commit`.
export(configure?: Configure<DockerExportSettings>): Promise<CommandOutput>
Export a container's filesystem: `docker export`.
images(configure?: Configure<DockerImagesSettings>): Promise<CommandOutput>
List images: `docker images`.
imageEntries(configure?: Configure<DockerImagesSettings>): Promise<DockerImageEntry[]>
The images as parsed {@link DockerImageEntry} values, from
`docker images --format '{{json .}}'`.
pull(configure?: Configure<DockerPullSettings>): Promise<CommandOutput>
Pull an image: `docker pull`.
push(configure?: Configure<DockerPushSettings>): Promise<CommandOutput>
Push an image: `docker push`.
tag(configure?: Configure<DockerTagSettings>): Promise<CommandOutput>
Tag an image: `docker tag`.
rmi(configure?: Configure<DockerRmiSettings>): Promise<CommandOutput>
Remove images: `docker rmi`.
save(configure?: Configure<DockerSaveSettings>): Promise<CommandOutput>
Save images to a tar archive: `docker save`.
load(configure?: Configure<DockerLoadSettings>): Promise<CommandOutput>
Load images from a tar archive: `docker load`.
history(configure?: Configure<DockerHistorySettings>): Promise<CommandOutput>
Show an image's layers: `docker history`.
import(configure?: Configure<DockerImportSettings>): Promise<CommandOutput>
Create an image from a tarball: `docker import`.
imagePrune(configure?: Configure<DockerImagePruneSettings>): Promise<CommandOutput>
Remove unused images: `docker image prune`.
login(configure?: Configure<DockerLoginSettings>): Promise<CommandOutput>
Authenticate to a registry: `docker login`.
logout(configure?: Configure<DockerLogoutSettings>): Promise<CommandOutput>
Forget a registry's credentials: `docker logout`.
search(configure?: Configure<DockerSearchSettings>): Promise<CommandOutput>
Search Docker Hub: `docker search`.
info(configure?: Configure<DockerInfoSettings>): Promise<CommandOutput>
Describe the daemon: `docker info`.
version(configure?: Configure<DockerVersionSettings>): Promise<CommandOutput>
Report client and daemon versions: `docker version`.
system(configure?: Configure<DockerSystemSettings>): Promise<CommandOutput>
Reclaim space or report usage: `docker system prune|df|info`.
volume(configure?: Configure<DockerVolumeSettings>): Promise<CommandOutput>
Manage volumes: `docker volume create|ls|rm|inspect|prune`.
volumeNames(configure?: Configure<DockerVolumeSettings>): Promise<string[]>
The volume names, from `docker volume ls --format '{{.Name}}'`.
network(configure?: Configure<DockerNetworkSettings>): Promise<CommandOutput>
Manage networks: `docker network create|ls|rm|inspect|connect|…`.
networkNames(configure?: Configure<DockerNetworkSettings>): Promise<string[]>
The network names, from `docker network ls --format '{{.Name}}'`.
context(configure?: Configure<DockerContextSettings>): Promise<CommandOutput>
Manage the daemons to talk to: `docker context create|ls|use|…`.
type DockerLogLevel = "debug" | "info" | "warn" | "error" | "fatal"
How verbose the docker client is (`--log-level`).
========================================================================
# @zuke/docker-compose
========================================================================
`@zuke/docker-compose` — typed Docker Compose task wrappers for Zuke builds.
Configure a fluent settings object in a lambda; the task builds the argv and
runs it. The wrapper detects whether Compose is installed as the v2 plugin
(`docker compose`) or the v1 standalone binary (`docker-compose`) at run
time, so the same build works on either host.
```ts
import { DockerComposeTasks } from "@zuke/docker-compose";
await DockerComposeTasks.up((s) => s.file("compose.yml").detach().build());
await DockerComposeTasks.logs((s) => s.follow().tail(100));
await DockerComposeTasks.down((s) => s.volumes());
```
@module
async function defaultComposeProbe(argv: readonly string[]): Promise<boolean>
The default {@link ComposeProbe}: run the candidate's `version` subcommand
quietly and treat a zero exit as success. A missing binary resolves to
`false` rather than throwing, so detection can fall through to the next
candidate.
function resetComposeInvocationCache_(): void
Clear the cached Compose invocation so the next
{@link resolveComposeInvocation} re-detects. Internal test seam — the
trailing underscore signals it is not part of the stable public API.
function resolveComposeInvocation(probe: ComposeProbe): Promise<string[]>
Resolve how Docker Compose is invoked on this host: `["docker", "compose"]`
for the v2 plugin or `["docker-compose"]` for the v1 standalone binary. The
v2 plugin is preferred; if neither is runnable a {@link ToolNotFoundError} is
raised. The result is cached after the first successful detection (a failed
detection is not cached, so a later call retries). Pass a custom
{@link ComposeProbe} to override how candidates are tested.
const DockerComposeTasks: DockerComposeTasksApi
Typed task functions for Docker Compose (`docker compose`/`docker-compose`).
class DockerComposeBuildSettings extends DockerComposeSettings
Settings for `compose build`.
noCache(): this
Do not use the layer cache (`--no-cache`).
pull(): this
Always attempt to pull newer base images (`--pull`).
buildArg(key: string, value: string): this
Pass a build-time variable (`--build-arg KEY=value`); repeatable.
services(...names: string[]): this
Restrict to specific services (positional); optional.
override protected composeArgs(): string[]
Assemble the `compose build` argv.
class DockerComposeCommitSettings extends DockerComposeSettings
Settings for `compose commit`.
service(name: string): this
The service whose container to commit (required).
reference(value: string): this
The image reference to create, e.g. `my-app:test`.
author(value: string): this
Image author (`--author`).
message(value: string): this
Commit message (`--message`).
change(...instructions: string[]): this
Apply a Dockerfile instruction to the created image (`--change`).
index(value: number): this
Pick the replica to commit when the service has several (`--index`).
noPause(): this
Leave the container running during the commit (`--pause=false`). Compose
pauses it by default so the filesystem cannot change mid-capture; turning
that off trades a consistent image for uninterrupted service.
override protected composeArgs(): string[]
Assemble the `compose commit` argv.
class DockerComposeConfigSettings extends DockerComposeSettings
Settings for `compose config`.
quietOutput(): this
Only validate, printing nothing (`-q`).
servicesOnly(): this
Print the service names only (`--services`).
volumesOnly(): this
Print the volume names only (`--volumes`).
format(value: string): this
Output format (`--format`), e.g. `yaml` or `json`.
override protected composeArgs(): string[]
Assemble the `compose config` argv.
class DockerComposeCpSettings extends DockerComposeSettings
Settings for `compose cp`.
Compose copies between a service container and the local filesystem, so
exactly one side names a service. Naming both or neither is refused rather
than handed to Compose as a path it cannot resolve.
fromService(service: string, path: PathLike): this
Copy out of `service` at `path` (`SERVICE:PATH`).
fromLocal(path: PathLike): this
Copy out of a local path.
toService(service: string, path: PathLike): this
Copy into `service` at `path` (`SERVICE:PATH`).
toLocal(path: PathLike): this
Copy into a local path.
index(value: number): this
Pick the replica to copy from when the service has several (`--index`).
all(): this
Include containers created by `compose run` (`--all`).
archive(): this
Preserve uid/gid information (`--archive`).
followLink(): this
Follow symbolic links in the source path (`--follow-link`).
override protected composeArgs(): string[]
Assemble the `compose cp` argv.
class DockerComposeCreateSettings extends DockerComposeSettings
Settings for `compose create`.
services(...names: string[]): this
Restrict creation to these services.
build(): this
Build images before creating containers (`--build`).
noBuild(): this
Never build, whatever the policy says (`--no-build`).
forceRecreate(): this
Recreate containers even when their configuration has not changed (`--force-recreate`).
noRecreate(): this
Leave existing containers in place (`--no-recreate`).
removeOrphans(): this
Remove containers for services no longer in the file (`--remove-orphans`).
quietPull(): this
Pull without progress output (`--quiet-pull`).
pull(policy: DockerComposePullPolicy): this
When to pull images before creating (`--pull`).
scale(service: string, replicas: number): this
Create `replicas` containers for `service` (`--scale`).
yes(): this
Answer every prompt affirmatively (`--yes`), so an unattended run cannot stall.
override protected composeArgs(): string[]
Assemble the `compose create` argv.
class DockerComposeDownSettings extends DockerComposeSettings
Settings for `compose down`.
volumes(): this
Also remove named and anonymous volumes (`-v`).
removeOrphans(): this
Remove containers for services no longer defined (`--remove-orphans`).
rmi(type: string): this
Remove images of the given type (`--rmi`), e.g. `all` or `local`.
timeout(seconds: number): this
Shutdown timeout in seconds (`-t`).
override protected composeArgs(): string[]
Assemble the `compose down` argv.
class DockerComposeEventsSettings extends DockerComposeSettings
Settings for `compose events`.
services(...names: string[]): this
Restrict the stream to these services.
json(): this
Emit each event as a JSON object (`--json`).
since(timestamp: string): this
Include events since a timestamp (`--since`).
until(timestamp: string): this
Stop streaming at a timestamp (`--until`).
Without it the command streams until interrupted, so a build target that
awaits it blocks — bound the run with this or with `.killAfter(ms)`.
override protected composeArgs(): string[]
Assemble the `compose events` argv.
class DockerComposeExecSettings extends DockerComposeSettings
Settings for `compose exec`.
service(name: string): this
The service whose container to exec into (required).
detach(): this
Run in the background (`-d`).
noTty(): this
Disable pseudo-TTY allocation (`-T`).
workdir(path: PathLike): this
Working directory inside the container (`-w`).
envVar(key: string, value: string): this
Set an environment variable (`-e KEY=value`); repeatable.
commandArgs(...args: Array<string | number>): this
The command and arguments to execute.
override protected composeArgs(): string[]
Assemble the `compose exec` argv.
class DockerComposeExportSettings extends DockerComposeSettings
Settings for `compose export`.
service(name: string): this
The service whose container filesystem to export (required).
output(path: PathLike): this
Write the tar archive to a file (`--output`) instead of stdout. Prefer it:
a tar stream captured as the command's stdout goes through Zuke's output
buffer, which is text-shaped and size-capped.
index(value: number): this
Pick the replica to export when the service has several (`--index`).
override protected composeArgs(): string[]
Assemble the `compose export` argv.
class DockerComposeImagesSettings extends DockerComposeListingSettings
Settings for `compose images`.
services(...names: string[]): this
Restrict the listing to these services.
override protected composeArgs(): string[]
Assemble the `compose images` argv.
class DockerComposeKillSettings extends DockerComposeSettings
Settings for `compose kill`.
services(...names: string[]): this
Restrict the kill to these services.
signal(name: string): this
The signal to send (`--signal`), `SIGKILL` by default. Send `SIGTERM` to
let a service run its shutdown path — `kill` skips the grace period `stop`
gives it.
removeOrphans(): this
Remove containers for services no longer in the file (`--remove-orphans`).
override protected composeArgs(): string[]
Assemble the `compose kill` argv.
abstract class DockerComposeListingSettings extends DockerComposeSettings
Shared by the listing subcommands that accept `--format` and `--quiet`.
`--format json` is what makes these readable by a build rather than by a
person, so the convenience {@link json} spells it rather than leaving the
caller to remember the value.
format(value: string): this
Format the output (`--format`), e.g. `table` or `json`.
json(): this
Emit JSON (`--format json`).
quietOutput(): this
Print only identifiers or names (`--quiet`).
protected listingFlags(): string[]
The shared listing flags, in the CLI's own order.
class DockerComposeLogsSettings extends DockerComposeSettings
Settings for `compose logs`.
follow(): this
Stream new log output (`-f`).
timestamps(): this
Prefix each line with a timestamp (`-t`).
tail(lines: number | "all"): this
Show only the last N lines, or `all` (`--tail`).
services(...names: string[]): this
Restrict to specific services (positional); optional.
override protected composeArgs(): string[]
Assemble the `compose logs` argv.
class DockerComposeLsSettings extends DockerComposeListingSettings
Settings for `compose ls`, which lists Compose projects rather than services.
all(): this
Include stopped projects (`--all`).
filter(expression: string): this
Filter the listing (`--filter`), e.g. `name=my-project`.
override protected composeArgs(): string[]
Assemble the `compose ls` argv.
class DockerComposePauseSettings extends DockerComposeServiceListSettings
Settings for `compose pause`.
override protected get subcommand(): string
The subcommand this class renders.
class DockerComposePortSettings extends DockerComposeSettings
Settings for `compose port`, which prints the host address a service's
container port was published on.
service(name: string): this
The service to ask about (required).
privatePort(port: number): this
The container-side port to look up (required).
protocol(value: "tcp" | "udp"): this
The protocol of the binding (`--protocol`), `tcp` by default.
index(value: number): this
Pick the replica to ask when the service has several (`--index`).
override protected composeArgs(): string[]
Assemble the `compose port` argv.
class DockerComposePsSettings extends DockerComposeSettings
Settings for `compose ps`.
all(): this
Show stopped containers too (`-a`).
quietOutput(): this
Only show container IDs (`-q`).
servicesOnly(): this
Display services instead of containers (`--services`).
services(...names: string[]): this
Restrict to specific services (positional); optional.
override protected composeArgs(): string[]
Assemble the `compose ps` argv.
class DockerComposePullSettings extends DockerComposeSettings
Settings for `compose pull`.
ignorePullFailures(): this
Continue past services whose pull fails (`--ignore-pull-failures`).
quietOutput(): this
Pull without printing progress (`-q`).
services(...names: string[]): this
Restrict to specific services (positional); optional.
override protected composeArgs(): string[]
Assemble the `compose pull` argv.
class DockerComposePushSettings extends DockerComposeSettings
Settings for `compose push`.
ignorePushFailures(): this
Continue past services whose push fails (`--ignore-push-failures`).
services(...names: string[]): this
Restrict to specific services (positional); optional.
override protected composeArgs(): string[]
Assemble the `compose push` argv.
class DockerComposeRestartSettings extends DockerComposeSettings
Settings for `compose restart`.
timeout(seconds: number): this
Restart timeout in seconds (`-t`).
services(...names: string[]): this
Restrict to specific services (positional); optional.
override protected composeArgs(): string[]
Assemble the `compose restart` argv.
class DockerComposeRmSettings extends DockerComposeSettings
Settings for `compose rm`.
force(): this
Do not prompt for confirmation (`-f`).
stop(): this
Stop the containers first if needed (`-s`).
volumes(): this
Also remove anonymous volumes (`-v`).
services(...names: string[]): this
Restrict to specific services (positional); optional.
override protected composeArgs(): string[]
Assemble the `compose rm` argv.
class DockerComposeRunSettings extends DockerComposeSettings
Settings for `compose run`.
service(name: string): this
The service to run (required).
rm(): this
Remove the container after it exits (`--rm`).
detach(): this
Run in the background (`-d`).
noDeps(): this
Do not start linked services (`--no-deps`).
name(value: string): this
Assign a container name (`--name`).
envVar(key: string, value: string): this
Set an environment variable (`-e KEY=value`); repeatable.
commandArgs(...args: Array<string | number>): this
The command and arguments to run inside the container.
override protected composeArgs(): string[]
Assemble the `compose run` argv.
class DockerComposeScaleSettings extends DockerComposeSettings
Settings for `compose scale`.
scale(service: string, replicas: number): this
Scale `service` to `replicas` instances; repeatable (required).
noDeps(): this
Do not start linked services (`--no-deps`).
override protected composeArgs(): string[]
Assemble the `compose scale` argv.
abstract class DockerComposeServiceListSettings extends DockerComposeSettings
Settings shared by `compose pause` and `compose unpause`, which take only a
service list.
services(...names: string[]): this
Restrict the command to these services.
abstract protected get subcommand(): string
The subcommand this class renders.
override protected composeArgs(): string[]
Assemble the subcommand argv.
abstract class DockerComposeSettings extends ToolSettings
Base for all Compose subcommand settings. Holds the invocation prefix
(`docker compose` vs `docker-compose`) and the global options that precede
every subcommand (`-f`, `-p`, `--profile`, …), and resolves the prefix at
run time unless it was pinned with {@link usePlugin}/{@link useStandalone}.
override protected defaultTool(): string
The resolved binary (`docker` or `docker-compose`) for error messages.
file(path: PathLike): this
Add a Compose file (`-f`); repeatable, order-significant.
projectName(name: string): this
Set the project name (`-p`).
profile(name: string): this
Enable a service profile (`--profile`); repeatable.
projectDirectory(path: PathLike): this
Set the project working directory (`--project-directory`).
envFile(path: PathLike): this
Load environment from a file (`--env-file`).
usePlugin(): this
Force the v2 plugin form (`docker compose`) and skip detection.
useStandalone(): this
Force the v1 standalone form (`docker-compose`) and skip detection.
abstract protected composeArgs(): string[]
The subcommand argv (without global options). Must be pure — no I/O.
override protected buildArgs(): string[]
Assemble the global options followed by the subcommand argv.
override async run(): Promise<CommandOutput>
Resolve the invocation prefix (unless pinned) and run, so the same build
works against either the v2 plugin or the v1 standalone binary.
class DockerComposeStartSettings extends DockerComposeSettings
Settings for `compose start`.
services(...names: string[]): this
Restrict to specific services (positional); optional.
override protected composeArgs(): string[]
Assemble the `compose start` argv.
class DockerComposeStopSettings extends DockerComposeSettings
Settings for `compose stop`.
timeout(seconds: number): this
Shutdown timeout in seconds (`-t`).
services(...names: string[]): this
Restrict to specific services (positional); optional.
override protected composeArgs(): string[]
Assemble the `compose stop` argv.
class DockerComposeTopSettings extends DockerComposeSettings
Settings for `compose top`.
services(...names: string[]): this
Restrict the report to these services.
override protected composeArgs(): string[]
Assemble the `compose top` argv.
class DockerComposeUnpauseSettings extends DockerComposeServiceListSettings
Settings for `compose unpause`.
override protected get subcommand(): string
The subcommand this class renders.
class DockerComposeUpSettings extends DockerComposeSettings
Settings for `compose up`.
detach(): this
Run in the background (`-d`).
build(): this
Build images before starting (`--build`).
forceRecreate(): this
Recreate containers even if unchanged (`--force-recreate`).
removeOrphans(): this
Remove containers for services no longer defined (`--remove-orphans`).
wait(): this
Wait until services are running/healthy (`--wait`).
abortOnContainerExit(): this
Stop all containers if any container stops (`--abort-on-container-exit`).
noDeps(): this
Start only the named services, leaving their dependencies alone
(`--no-deps`).
Without it compose starts or recreates a dependency that is stopped or
whose configuration changed. With an already-healthy stack the two agree,
so the difference shows up only on the runs where a dependency was not
ready — which is where a target that meant "just this service" wants to be
explicit.
pull(policy: DockerComposePullPolicy): this
When to fetch images before starting (`--pull`). `always` keeps a stack on
the current published images rather than whatever was pulled last;
`missing` fetches only what is absent locally; `never` uses what is there.
Distinct from `DockerComposeBuildSettings.pull`, which is `build --pull`,
and from the `pull` task, which is the subcommand — each mirrors its own
command.
exitCodeFrom(service: string): this
Exit with this service's container's exit code (`--exit-code-from`).
scale(service: string, instances: number): this
Scale a service to N instances (`--scale service=N`); repeatable.
services(...names: string[]): this
Restrict to specific services (positional); optional.
override protected composeArgs(): string[]
Assemble the `compose up` argv.
class DockerComposeVersionSettings extends DockerComposeSettings
Settings for `compose version`.
format(value: string): this
Format the output (`--format`), `pretty` or `json`.
json(): this
Emit JSON (`--format json`).
short(): this
Print only the version number (`--short`).
override protected composeArgs(): string[]
Assemble the `compose version` argv.
class DockerComposeVolumesSettings extends DockerComposeListingSettings
Settings for `compose volumes`.
services(...names: string[]): this
Restrict the listing to the volumes these services use.
override protected composeArgs(): string[]
Assemble the `compose volumes` argv.
class DockerComposeWaitSettings extends DockerComposeSettings
Settings for `compose wait`.
The command blocks until the named services' containers stop, then exits
with the first container's own exit status. That makes its exit code a
result rather than a failure — see {@link DockerComposeTasks.waitExitCode},
which hands the code back instead of failing the target.
services(...names: string[]): this
The services to wait on (required).
downProject(): this
Tear the project down once the first container stops (`--down-project`),
so a test run cleans up after itself without a second command.
override protected composeArgs(): string[]
Assemble the `compose wait` argv.
class ReplicaIndex
The `--index` flag that picks one replica of a scaled service.
`cp`, `export`, `commit` and `port` all take it with the same meaning and
the same rendering, so they hold one of these rather than four copies of
the field and the `argv.push` that goes with it. Each still exposes its own
setter, because the public surface is per-command.
set(value: number): void
Record the replica to act on.
render(): string[]
The flag, if one was set.
class ServiceList
The trailing service-name operands most Compose subcommands accept.
Same reasoning as {@link ReplicaIndex}: the list and the way it is appended
are identical wherever it appears, so it lives here once. Each settings
class still exposes its own `services()` setter, because which subcommands
take the operand — and what it means for each — is part of the public
surface.
add(names: readonly string[]): void
Add service names to the list.
get isEmpty(): boolean
Whether any service was named.
render(): string[]
The names, in the order they were added.
interface DockerComposeTasksApi
The shape of {@link DockerComposeTasks}.
up(configure?: Configure<DockerComposeUpSettings>): Promise<CommandOutput>
Create and start services: `compose up`.
down(configure?: Configure<DockerComposeDownSettings>): Promise<CommandOutput>
Stop and remove services: `compose down`.
build(configure?: Configure<DockerComposeBuildSettings>): Promise<CommandOutput>
Build service images: `compose build`.
pull(configure?: Configure<DockerComposePullSettings>): Promise<CommandOutput>
Pull service images: `compose pull`.
push(configure?: Configure<DockerComposePushSettings>): Promise<CommandOutput>
Push service images: `compose push`.
run(configure?: Configure<DockerComposeRunSettings>): Promise<CommandOutput>
Run a one-off command: `compose run`.
exec(configure?: Configure<DockerComposeExecSettings>): Promise<CommandOutput>
Exec into a running service: `compose exec`.
logs(configure?: Configure<DockerComposeLogsSettings>): Promise<CommandOutput>
View service logs: `compose logs`.
ps(configure?: Configure<DockerComposePsSettings>): Promise<CommandOutput>
List containers: `compose ps`.
config(configure?: Configure<DockerComposeConfigSettings>): Promise<CommandOutput>
Render the resolved configuration: `compose config`.
start(configure?: Configure<DockerComposeStartSettings>): Promise<CommandOutput>
Start existing services: `compose start`.
stop(configure?: Configure<DockerComposeStopSettings>): Promise<CommandOutput>
Stop running services: `compose stop`.
restart(configure?: Configure<DockerComposeRestartSettings>): Promise<CommandOutput>
Restart services: `compose restart`.
rm(configure?: Configure<DockerComposeRmSettings>): Promise<CommandOutput>
Remove stopped service containers: `compose rm`.
create(configure?: Configure<DockerComposeCreateSettings>): Promise<CommandOutput>
Create containers without starting them: `compose create`.
kill(configure?: Configure<DockerComposeKillSettings>): Promise<CommandOutput>
Force-stop service containers: `compose kill`.
pause(configure?: Configure<DockerComposePauseSettings>): Promise<CommandOutput>
Pause services: `compose pause`.
unpause(configure?: Configure<DockerComposeUnpauseSettings>): Promise<CommandOutput>
Resume paused services: `compose unpause`.
scale(configure?: Configure<DockerComposeScaleSettings>): Promise<CommandOutput>
Set service replica counts: `compose scale`.
wait(configure?: Configure<DockerComposeWaitSettings>): Promise<CommandOutput>
Block until services stop: `compose wait`.
Keeps the ordinary contract — a non-zero container status fails the
target. Use {@link DockerComposeTasksApi.waitExitCode} when the status is
the answer rather than a failure.
cp(configure?: Configure<DockerComposeCpSettings>): Promise<CommandOutput>
Copy between a service container and the local filesystem: `compose cp`.
top(configure?: Configure<DockerComposeTopSettings>): Promise<CommandOutput>
Show running processes: `compose top`.
export(configure?: Configure<DockerComposeExportSettings>): Promise<CommandOutput>
Export a container filesystem as a tar archive: `compose export`.
commit(configure?: Configure<DockerComposeCommitSettings>): Promise<CommandOutput>
Create an image from a container: `compose commit`.
images(configure?: Configure<DockerComposeImagesSettings>): Promise<CommandOutput>
List the images the containers use: `compose images`.
volumes(configure?: Configure<DockerComposeVolumesSettings>): Promise<CommandOutput>
List the project's volumes: `compose volumes`.
ls(configure?: Configure<DockerComposeLsSettings>): Promise<CommandOutput>
List Compose projects: `compose ls`.
version(configure?: Configure<DockerComposeVersionSettings>): Promise<CommandOutput>
Report the Compose version: `compose version`.
port(configure?: Configure<DockerComposePortSettings>): Promise<CommandOutput>
Print a published port binding: `compose port`.
events(configure?: Configure<DockerComposeEventsSettings>): Promise<CommandOutput>
Stream container events: `compose events`.
waitExitCode(configure?: Configure<DockerComposeWaitSettings>): Promise<number>
The exit status the waited-on container stopped with.
`compose wait` exits with the container's own status, so every code is a
legitimate answer and none is left to mean "compose broke". This hands the
code back rather than failing the target, and still fails when compose
never reached a container at all.
servicePort(configure?: Configure<DockerComposePortSettings>): Promise<number>
The host port a service's container port was published on.
The point of letting Compose pick an ephemeral port is asking which one it
picked, which is what this returns.
composeVersion(configure?: Configure<DockerComposeVersionSettings>): Promise<DockerComposeVersion>
The installed Compose version, parsed from `compose version --format json`.
interface DockerComposeVersion
The version report `compose version --format json` emits.
version: string
The Compose version string, e.g. `v5.1.1`.
type ComposeProbe = (argv: readonly string[]) => Promise<boolean>
Probes whether a candidate Compose invocation is runnable on this host.
Receives the binary-and-prefix argv (`["docker", "compose"]` or
`["docker-compose"]`) and resolves to `true` when it works. Injectable so
detection can be unit-tested without a real Docker install.
type DockerComposePullPolicy = "always" | "missing" | "never"
When `compose up` fetches images before starting: `always` on every start,
`missing` only when the image is absent locally, `never` at all.
========================================================================
# @zuke/kubectl
========================================================================
`@zuke/kubectl` — typed `kubectl` CLI task wrappers for Zuke builds, for
deploying to and managing Kubernetes from a pipeline.
```ts
import { KubectlTasks } from "@zuke/kubectl";
await KubectlTasks.apply((s) => s.file("k8s/").namespace("prod"));
await KubectlTasks.setImage((s) =>
s.resource("deployment/api").image("api", "api:1.4").namespace("prod")
);
await KubectlTasks.rollout((s) =>
s.status().resource("deployment/api").namespace("prod").timeout("120s")
);
```
@module
function parseEvents(json: string): KubernetesEvent[]
Parse the JSON text of `kubectl events -o json` into
{@link KubernetesEvent} records. Items carrying neither a reason nor a
message are skipped; empty input yields `[]`. Throws if the text is
non-empty and not valid JSON.
function parseNamespaces(json: string): KubernetesNamespace[]
Parse the JSON text of `kubectl get namespaces -o json` — a `List`, or a
single namespace object — into {@link KubernetesNamespace} records. Items
without a `metadata.name` are skipped; empty input yields `[]`. Throws if the
text is non-empty and not valid JSON.
function parseResources(json: string): KubernetesResource[]
Parse the JSON text of any `kubectl get … -o json` — a `List`, or a single
object — into {@link KubernetesResource} records. Items without a
`metadata.name` are skipped; empty input yields `[]`. Throws if the text is
non-empty and not valid JSON.
function parseVersion(json: string): KubernetesVersion
Parse the JSON text of `kubectl version -o json` into the two version
strings. A payload that is not an object, or carries neither version,
yields an empty record rather than throwing — the versions are advisory.
const KubectlTasks: KubectlTasksApi
Typed task functions for the `kubectl` CLI.
class KubectlAnnotateSettings extends KubectlSettings
Settings for `kubectl annotate`.
resource(...tokens: string[]): this
Resource tokens, e.g. `("deploy", "api")` or `("pods", "-l", "app=web")`; repeatable.
annotation(key: string, value: string): this
Set an annotation as a `key=value` token; repeatable.
remove(key: string): this
Remove an annotation, rendered as kubectl's `key-` syntax; repeatable.
overwrite(): this
Overwrite existing annotations (`--overwrite`).
all(): this
Apply to all resources of the given type (`--all`).
selector(query: string): this
Restrict to resources matching a label selector (`-l`).
override protected buildArgs(): string[]
Assemble the `kubectl annotate` argv.
class KubectlApiResourcesSettings extends KubectlSettings
Settings for `kubectl api-resources`.
apiGroup(name: string): this
Only resources in this API group (`--api-group`).
namespaced(value: boolean): this
Whether to list namespaced resources (`--namespaced`); kubectl's default
is `true`, so pass `false` for the cluster-scoped ones.
verbs(...names: string[]): this
Only resources supporting these verbs (`--verbs`).
categories(...names: string[]): this
Only resources in these categories (`--categories`).
sortBy(field: "name" | "kind"): this
Sort by `name` or `kind` (`--sort-by`).
output(format: string): this
The output format (`-o`), e.g. `name` or `wide`.
noHeaders(): this
Leave the header row out (`--no-headers`).
cached(): this
Use the discovery cache rather than asking the server (`--cached`).
override protected buildArgs(): string[]
Assemble the `kubectl api-resources` argv.
class KubectlApiVersionsSettings extends KubectlSettings
Settings for `kubectl api-versions`.
override protected buildArgs(): string[]
Assemble the `kubectl api-versions` argv.
class KubectlApplySettings extends KubectlSettings
Settings for `kubectl apply`.
file(path: PathLike): this
Apply a manifest file, directory, or URL (`-f`); repeatable.
kustomize(dir: PathLike): this
Apply a kustomization directory (`-k`).
recursive(): this
Recurse into directories given to `-f` (`-R`).
prune(): this
Prune resources not present in the applied set (`--prune`).
serverSide(): this
Apply server-side (`--server-side`).
dryRun(mode: DryRunMode): this
Preview without persisting (`--dry-run=`; defaults to `client`).
selector(query: string): this
Restrict to resources matching a label selector (`-l`).
force(): this
Force apply by delete-and-recreate when needed (`--force`).
override protected buildArgs(): string[]
Assemble the `kubectl apply` argv.
class KubectlAuthCanISettings extends KubectlSettings
Settings for `kubectl auth can-i`.
The command answers through its exit status — 0 when the action is
allowed and non-zero when it is not — so
{@link "./kubectl.ts".KubectlTasksApi.canI} reads the code into a boolean
rather than failing the build on a routine "no".
verb(name: string): this
The API verb to check, e.g. `create` (required unless {@link list}).
resource(name: string): this
The resource, e.g. `deployments` or `deployments/api`.
subresource(name: string): this
A subresource, e.g. `log` or `scale` (`--subresource`).
allNamespaces(): this
Check across every namespace (`--all-namespaces`).
list(): this
Print every allowed action instead of checking one (`--list`).
quietAnswer(): this
Print nothing and answer only through the exit code (kubectl's
`--quiet`). Named apart from the inherited `.quiet()`, which suppresses
Zuke's own echo of the command rather than kubectl's output.
override protected buildArgs(): string[]
Assemble the `kubectl auth can-i` argv.
class KubectlClusterInfoSettings extends KubectlSettings
Settings for `kubectl cluster-info`.
override protected buildArgs(): string[]
Assemble the `kubectl cluster-info` argv.
class KubectlConfigCurrentContextSettings extends KubectlSettings
Settings for `kubectl config current-context`.
override protected buildArgs(): string[]
Assemble the `kubectl config current-context` argv.
class KubectlConfigGetContextsSettings extends KubectlSettings
Settings for `kubectl config get-contexts`.
namesOnly(): this
Print only the names (`-o name`), the one output format gh accepts here.
noHeaders(): this
Leave the header row out (`--no-headers`).
override protected buildArgs(): string[]
Assemble the `kubectl config get-contexts` argv.
class KubectlConfigSetContextSettings extends KubectlSettings
Settings for `kubectl config set-context`.
Note that `set-context` has its own `--namespace`, which sets the namespace
recorded in the context entry rather than scoping one command. The
inherited `.namespace(...)` renders that same flag, which is what a caller
of this command wants.
contextName(name: string): this
The context to write (required unless {@link current} is set).
current(): this
Modify the current context rather than a named one (`--current`).
cluster(name: string): this
The cluster the context points at (`--cluster`).
user(name: string): this
The user the context authenticates as (`--user`).
override protected buildArgs(): string[]
Assemble the `kubectl config set-context` argv.
class KubectlConfigUseContextSettings extends KubectlSettings
Settings for `kubectl config use-context`.
contextName(name: string): this
The context to switch to (required).
override protected buildArgs(): string[]
Assemble the `kubectl config use-context` argv.
class KubectlConfigViewSettings extends KubectlSettings
Settings for `kubectl config view`.
minify(): this
Keep only what the current context uses (`--minify`).
flatten(): this
Inline the referenced files, for a portable kubeconfig (`--flatten`).
raw(): this
Print the credentials in the clear (`--raw`). kubectl redacts them by
default; anything this prints belongs in a `parameter().secret()`, not in
a build's log.
output(format: string): this
The output format (`-o`), e.g. `json`; kubectl's default is `yaml`.
override protected buildArgs(): string[]
Assemble the `kubectl config view` argv.
class KubectlCordonSettings extends KubectlSettings
Settings for `kubectl cordon` and `kubectl uncordon` — marking a node
unschedulable, and letting it take pods again.
node(name: string): this
The node to act on; required unless a {@link selector} picks them.
uncordon(): this
Make the node schedulable again instead — `kubectl uncordon`.
selector(query: string): this
Act on every node matching a label selector (`-l`).
dryRun(mode: DryRunMode): this
Preview without persisting (`--dry-run=`; defaults to `client`).
override protected buildArgs(): string[]
Assemble the `kubectl cordon`/`uncordon` argv.
class KubectlCpSettings extends KubectlSettings
Settings for `kubectl cp` — copying files into and out of a container.
This is how a build gets a report out of a pod that produced it. Each side
is either a local path or a `[namespace/]pod:path` spec, and kubectl takes
exactly one of each.
from(spec: string): this
Where to copy from: a local path, or `pod:path` / `namespace/pod:path`.
to(spec: string): this
Where to copy to, in the same two forms.
container(name: string): this
Which container of the pod (`-c`).
noPreserve(): this
Do not carry ownership and permissions across (`--no-preserve`).
retries(count: number): this
Retry a copy out of a container this many times (`--retries`).
override protected buildArgs(): string[]
Assemble the `kubectl cp` argv.
class KubectlCreateSettings extends KubectlSettings
Settings for `kubectl create`.
file(path: PathLike): this
Create from a manifest file, directory, or URL (`-f`); repeatable. For
resource-form creation (`create secret …`), use the base `.args(...)`.
recursive(): this
Recurse into directories given to `-f` (`-R`).
dryRun(mode: DryRunMode): this
Preview without persisting (`--dry-run=`; defaults to `client`).
output(format: string): this
Output format, e.g. `yaml` or `json` (`-o`).
saveConfig(): this
Record the current resource in its annotation (`--save-config`).
override protected buildArgs(): string[]
Assemble the `kubectl create` argv.
class KubectlDeleteSettings extends KubectlSettings
Settings for `kubectl delete`.
file(path: PathLike): this
Delete from a manifest file or directory (`-f`); repeatable.
resource(...tokens: string[]): this
Resource tokens, e.g. `("pod", "web")` or `("deployment/api")`; repeatable.
selector(query: string): this
Restrict to resources matching a label selector (`-l`).
all(): this
Delete all resources of the given type (`--all`).
ignoreNotFound(): this
Treat "not found" as a success (`--ignore-not-found`).
force(): this
Force immediate deletion (`--force`).
gracePeriod(seconds: number): this
Seconds to wait before forceful termination (`--grace-period`).
recursive(): this
Recurse into directories given to `-f` (`-R`).
override protected buildArgs(): string[]
Assemble the `kubectl delete` argv.
class KubectlDescribeSettings extends KubectlSettings
Settings for `kubectl describe`.
resource(...tokens: string[]): this
Resource tokens, e.g. `("pod", "web")` or `("deployment/api")`; repeatable.
selector(query: string): this
Restrict to resources matching a label selector (`-l`).
override protected buildArgs(): string[]
Assemble the `kubectl describe` argv.
class KubectlDiffSettings extends KubectlSettings
Settings for `kubectl diff` — what an apply would change, without changing
it.
`diff` reports its answer through the exit status: 0 when there is no
difference and 1 when there is, with anything above 1 meaning kubectl or
the differ failed. {@link "./kubectl.ts".KubectlTasksApi.diff} keeps the
ordinary contract, so a build that wants the printed diff and a failed
target on drift gets both;
{@link "./kubectl.ts".KubectlTasksApi.diffHasChanges} is the reader that
turns the code into a boolean.
file(path: PathLike): this
Diff a manifest file, directory, or URL (`-f`); repeatable.
kustomize(dir: PathLike): this
Diff a kustomization directory (`-k`).
recursive(): this
Recurse into directories given to `-f` (`-R`).
serverSide(): this
Diff the server-side apply (`--server-side`).
forceConflicts(): this
Take ownership of conflicting fields (`--force-conflicts`).
prune(): this
Include what a prune would delete (`--prune`).
selector(query: string): this
Restrict to resources matching a label selector (`-l`).
showManagedFields(): this
Include the managed fields, which are otherwise hidden (`--show-managed-fields`).
concurrency(count: number): this
How many objects to diff in parallel (`--concurrency`).
override protected buildArgs(): string[]
Assemble the `kubectl diff` argv.
class KubectlDrainSettings extends KubectlSettings
Settings for `kubectl drain`.
kubectl refuses to drain a node whose pods it cannot safely move, and the
two flags that override that refusal are exactly the ones worth being
deliberate about: `--ignore-daemonsets` and `--delete-emptydir-data`, the
second of which destroys local data. Neither is defaulted here.
node(name: string): this
The node to drain; required unless a {@link selector} picks them.
force(): this
Evict pods no controller manages, which nothing will recreate (`--force`).
ignoreDaemonSets(): this
Proceed past DaemonSet-managed pods, which drain never deletes (`--ignore-daemonsets`).
deleteEmptyDirData(): this
Proceed past pods using emptyDir, destroying that data (`--delete-emptydir-data`).
disableEviction(): this
Delete rather than evict (`--disable-eviction`), which bypasses every
PodDisruptionBudget — the guardrail an operator wrote down on purpose.
gracePeriod(seconds: number): this
Seconds each pod gets to terminate (`--grace-period`).
timeout(duration: string): this
How long to wait for the drain overall, e.g. `5m` (`--timeout`).
podSelector(query: string): this
Only drain pods matching this label selector (`--pod-selector`).
selector(query: string): this
Drain every node matching this label selector (`-l`).
skipWaitForDeleteTimeout(seconds: number): this
Stop waiting on pods already deleting this long (`--skip-wait-for-delete-timeout`).
dryRun(mode: DryRunMode): this
Preview without persisting (`--dry-run=`; defaults to `client`).
override protected buildArgs(): string[]
Assemble the `kubectl drain` argv.
class KubectlEventsSettings extends KubectlSettings
Settings for `kubectl events` — the first thing to read when a rollout
stalls and `rollout status` will not say why.
forResource(reference: string): this
Only events about this resource, as `TYPE/NAME` (`--for`).
types(...names: string[]): this
Only events of these types, e.g. `Warning` (`--types`).
allNamespaces(): this
Across every namespace (`-A`).
watch(): this
Keep watching after the listing (`--watch`). A target that watches blocks
until something stops it, so pair it with `.killAfter(...)` unless the
wait is the point.
noHeaders(): this
Leave the header row out (`--no-headers`).
output(format: string): this
The output format (`-o`), e.g. `json`.
override protected buildArgs(): string[]
Assemble the `kubectl events` argv.
class KubectlExecSettings extends KubectlSettings
Settings for `kubectl exec`.
resource(name: string): this
The pod (or `type/name`) to exec into (required).
container(name: string): this
Target a specific container (`-c`).
stdin(): this
Keep STDIN open (`-i`).
tty(): this
Allocate a TTY (`-t`).
command(...args: Array<string | number>): this
The command and arguments to run in the container (required).
override protected buildArgs(): string[]
Assemble the `kubectl exec` argv.
class KubectlExplainSettings extends KubectlSettings
Settings for `kubectl explain` — the schema of a resource type.
type(name: string): this
The type to explain, e.g. `pods` or `deployments.spec.replicas`.
recursive(): this
Print nested fields too (`-R`).
maxDepth(depth: number): this
Cap how deep {@link recursive} goes (`--max-depth`).
apiVersion(value: string): this
Explain a particular API group/version (`--api-version`).
output(format: string): this
How to render the schema (`-o`): `plaintext` or `plaintext-openapiv2`.
override protected buildArgs(): string[]
Assemble the `kubectl explain` argv.
class KubectlExposeSettings extends KubectlSettings
Settings for `kubectl expose` — a service in front of an existing workload.
resource(reference: string): this
The workload to expose, e.g. `deployment/api`.
file(path: PathLike): this
Expose the workload a manifest identifies instead (`-f`); repeatable.
port(value: string | number): this
The port the service serves on (`--port`).
targetPort(value: string | number): this
The container port traffic goes to (`--target-port`).
type(value: string): this
The service type (`--type`), e.g. `LoadBalancer`.
name(value: string): this
The new service's name (`--name`).
protocol(value: string): this
The protocol (`--protocol`), e.g. `TCP`.
selector(query: string): this
The selector the service routes by (`--selector`). kubectl infers it from
the exposed resource when it is omitted, and only equality-based
requirements are supported here.
labels(value: string): this
Labels for the created service (`--labels`), comma-separated.
sessionAffinity(value: "None" | "ClientIP"): this
Session affinity (`--session-affinity`): `None` or `ClientIP`.
dryRun(mode: DryRunMode): this
Preview without persisting (`--dry-run=`; defaults to `client`).
override protected buildArgs(): string[]
Assemble the `kubectl expose` argv.
class KubectlGetSettings extends KubectlSettings
Settings for `kubectl get`.
resource(...tokens: string[]): this
Resource tokens, e.g. `("pods")` or `("pod", "web")`; repeatable.
output(format: string): this
Output format, e.g. `wide`, `yaml`, `json`, `jsonpath=…` (`-o`).
selector(query: string): this
Restrict to resources matching a label selector (`-l`).
fieldSelector(query: string): this
Restrict by field selector (`--field-selector`).
allNamespaces(): this
List across all namespaces (`-A`).
watch(on: boolean): this
Watch for changes instead of returning once (`-w`); pass `false` to disable.
showLabels(): this
Include resource labels as columns (`--show-labels`).
override protected buildArgs(): string[]
Assemble the `kubectl get` argv.
class KubectlKustomizeSettings extends KubectlSettings
Settings for `kubectl kustomize` — rendering a kustomization to stdout.
dir(path: PathLike): this
The kustomization directory or repository URL; kubectl assumes `.`.
output(path: PathLike): this
Write the rendered output to a file instead of stdout (`-o`).
enableHelm(): this
Allow the Helm chart inflator generator (`--enable-helm`).
loadRestrictor(value: string): this
Relax where a kustomization may load files from (`--load-restrictor`).
override protected buildArgs(): string[]
Assemble the `kubectl kustomize` argv.
class KubectlLabelSettings extends KubectlSettings
Settings for `kubectl label`.
resource(...tokens: string[]): this
Resource tokens, e.g. `("deploy", "api")` or `("pods", "-l", "app=web")`; repeatable.
label(key: string, value: string): this
Set a label as a `key=value` token; repeatable.
remove(key: string): this
Remove a label, rendered as kubectl's `key-` syntax; repeatable.
overwrite(): this
Overwrite existing labels (`--overwrite`).
all(): this
Apply to all resources of the given type (`--all`).
selector(query: string): this
Restrict to resources matching a label selector (`-l`).
override protected buildArgs(): string[]
Assemble the `kubectl label` argv.
class KubectlLogsSettings extends KubectlSettings
Settings for `kubectl logs`.
resource(name: string): this
The pod (or `type/name`) to read logs from.
container(name: string): this
Read from a specific container (`-c`).
selector(query: string): this
Select pods by label instead of naming one (`-l`).
follow(): this
Stream new log output (`-f`).
previous(): this
Read the previous container instance's logs (`--previous`).
tail(lines: number): this
Show only the last N lines (`--tail`).
since(duration: string): this
Only logs newer than a duration, e.g. `5m` (`--since`).
allContainers(): this
Include all containers in the pod (`--all-containers`).
timestamps(): this
Prefix each line with a timestamp (`--timestamps`).
override protected buildArgs(): string[]
Assemble the `kubectl logs` argv.
class KubectlPatchSettings extends KubectlSettings
Settings for `kubectl patch`.
resource(name: string): this
The resource to patch, e.g. `deployment/api` (required).
patch(content: string): this
The patch document (`-p`, required).
type(strategy: PatchType): this
The patch strategy (`--type`).
override protected buildArgs(): string[]
Assemble the `kubectl patch` argv.
class KubectlPortForwardSettings extends KubectlSettings
Settings for `kubectl port-forward`.
resource(name: string): this
The pod or service, e.g. `svc/api` (required).
port(mapping: string): this
A port mapping, e.g. `8080:80` or `8080`; repeatable, at least one.
address(value: string): this
The local address(es) to bind (`--address`).
override protected buildArgs(): string[]
Assemble the `kubectl port-forward` argv.
class KubectlReplaceSettings extends KubectlSettings
Settings for `kubectl replace`.
file(path: PathLike): this
Replace from a manifest file, directory, or URL (`-f`); repeatable.
kustomize(dir: PathLike): this
Replace from a kustomization directory (`-k`).
recursive(): this
Recurse into directories given to `-f` (`-R`).
force(): this
Delete and recreate rather than update (`--force`). This is not a retry
knob: the resource genuinely goes away first, so anything depending on it
sees it missing.
gracePeriod(seconds: number): this
Seconds each object gets to terminate (`--grace-period`).
timeout(duration: string): this
How long to wait on the delete half, e.g. `60s` (`--timeout`).
cascade(strategy: "background" | "orphan" | "foreground"): this
The cascading strategy for dependents (`--cascade`).
wait(): this
Wait for the resources to be gone before returning (`--wait`).
dryRun(mode: DryRunMode): this
Preview without persisting (`--dry-run=`; defaults to `client`).
override protected buildArgs(): string[]
Assemble the `kubectl replace` argv.
class KubectlRolloutSettings extends KubectlSettings
Settings for `kubectl rollout`.
status(): this
Show rollout status (`rollout status`).
restart(): this
Restart a rollout (`rollout restart`).
undo(): this
Roll back to the previous revision (`rollout undo`).
history(): this
Show rollout history (`rollout history`).
pause(): this
Stop the rollout where it is (`rollout pause`) — half of a canary. A
paused workload takes no further updates until {@link resume}.
resume(): this
Let a paused rollout continue (`rollout resume`).
resource(name: string): this
The resource, e.g. `deployment/api` (required).
toRevision(revision: number): this
With `undo`, the revision to roll back to (`--to-revision`).
timeout(duration: string): this
With `status`, how long to wait, e.g. `60s` (`--timeout`).
override protected buildArgs(): string[]
Assemble the `kubectl rollout <action>` argv.
class KubectlRunSettings extends KubectlSettings
Settings for `kubectl run` — one pod, imperatively.
This is for a one-off: a migration job, a debug shell. A workload a build
owns belongs in a manifest and goes through
{@link "./manifests.ts".KubectlApplySettings}, which is declarative and can
be diffed.
name(value: string): this
The pod's name (required).
image(reference: string): this
The image to run (`--image`, required).
restart(policy: "Always" | "OnFailure" | "Never"): this
The restart policy (`--restart`).
envVar(key: string, value: string): this
An environment variable for the container (`--env KEY=VALUE`);
repeatable. Named apart from the inherited `.env(...)`, which sets the
environment `kubectl` itself runs in.
labels(value: string): this
Labels for the pod (`--labels`), comma-separated.
port(value: string | number): this
The port the container exposes (`--port`).
overrides(json: string): this
An inline JSON override for the generated pod (`--overrides`).
expose(): this
Also create a ClusterIP service (`--expose`), which needs {@link port}.
command(first: string, ...rest: string[]): this
The command and arguments to run, after kubectl's `--` separator. Passing
any also sets `--command`, so they replace the image's entrypoint rather
than being appended to it.
dryRun(mode: DryRunMode): this
Preview without persisting (`--dry-run=`; defaults to `client`).
override protected buildArgs(): string[]
Assemble the `kubectl run` argv.
class KubectlScaleSettings extends KubectlSettings
Settings for `kubectl scale`.
replicas(count: number): this
Desired replica count (`--replicas`, required).
resource(name: string): this
The resource to scale, e.g. `deployment/api`.
file(path: PathLike): this
Scale a resource defined in a file (`-f`).
currentReplicas(count: number): this
Only scale if the current replica count matches (`--current-replicas`).
selector(query: string): this
Restrict to resources matching a label selector (`-l`).
all(): this
Scale all resources of the given type (`--all`).
override protected buildArgs(): string[]
Assemble the `kubectl scale` argv.
class KubectlSetEnvSettings extends KubectlSetSettings
Settings for `kubectl set env`.
override protected readonly taskName: string
The task this settings class backs.
set(key: string, value: string): this
Set a variable (`-e KEY=VALUE`); repeatable.
remove(key: string): this
Remove a variable, which kubectl spells `KEY-` (`-e KEY-`); repeatable.
from(reference: string): this
Inject every key of a ConfigMap or Secret (`--from`), e.g. `secret/db`.
keys(...names: string[]): this
Only these keys of the {@link from} resource (`--keys`).
prefix(value: string): this
Prefix the injected variable names (`--prefix`).
list(): this
Print the environment instead of changing it (`--list`).
resolve(): this
Show what the references resolve to when listing (`--resolve`).
overwrite(value: boolean): this
Whether an existing variable may be replaced (`--overwrite`).
override protected setSubcommand(): string
The `set` subcommand: `env`.
override protected setFlags(): string[]
Assemble the `kubectl set env` flags.
class KubectlSetImageSettings extends KubectlSettings
Settings for `kubectl set image`.
resource(name: string): this
The resource to update, e.g. `deployment/api` (required).
image(container: string, reference: string): this
Set a container's image (`container=image`); repeatable, at least one.
selector(query: string): this
Restrict to resources matching a label selector (`-l`).
all(): this
Apply to all resources of the given type (`--all`).
override protected buildArgs(): string[]
Assemble the `kubectl set image` argv.
class KubectlSetResourcesSettings extends KubectlSetSettings
Settings for `kubectl set resources`.
override protected readonly taskName: string
The task this settings class backs.
limit(resource: string, quantity: string): this
A resource limit, e.g. `.limit("cpu", "500m")`; repeatable.
request(resource: string, quantity: string): this
A resource request, e.g. `.request("memory", "256Mi")`; repeatable.
override protected setSubcommand(): string
The `set` subcommand: `resources`.
override protected setFlags(): string[]
Assemble the `kubectl set resources` flags.
abstract class KubectlSetSettings extends KubectlSettings
Base for the `kubectl set` subcommands that change a pod template in place:
they share the target (a resource, a manifest, or everything in the
namespace) and the container selection.
abstract protected readonly taskName: string
The task name a refusal names, e.g. `setEnv`.
resource(...names: string[]): this
The resource to change, e.g. `deployment/api`; repeatable.
file(path: PathLike): this
Change the resource identified by a manifest instead (`-f`); repeatable.
all(): this
Change every resource of the named types in the namespace (`--all`).
containers(pattern: string): this
Which containers to change (`-c`); kubectl's default is every one.
selector(query: string): this
Restrict to resources matching a label selector (`-l`).
local(): this
Rewrite the local manifest without contacting the server (`--local`).
dryRun(mode: DryRunMode): this
Preview without persisting (`--dry-run=`; defaults to `client`).
abstract protected setSubcommand(): string
The `set` subcommand's own name, e.g. `env`.
abstract protected setFlags(): string[]
The subcommand's own flags, rendered after the target.
protected targetArgs(task: string): string[]
The target flags, after refusing a target kubectl cannot resolve.
override protected buildArgs(): string[]
Assemble the `kubectl set <subcommand>` argv.
abstract class KubectlSettings extends ToolSettings
Base for all `kubectl` subcommand settings: the binary is `kubectl`, and the
cluster-targeting flags (`--namespace`, `--context`, `--kubeconfig`) are
shared by every subcommand.
override protected defaultTool(): string
The tool binary invoked by every subcommand: `kubectl`.
namespace(name: string): this
Target a namespace (`--namespace`).
context(name: string): this
Use a named kubeconfig context (`--context`).
kubeconfig(path: PathLike): this
Use an explicit kubeconfig file (`--kubeconfig`).
protected globalArgs(): string[]
The cluster-targeting flags shared by every subcommand.
class KubectlTaintSettings extends KubectlSettings
Settings for `kubectl taint`.
node(...names: string[]): this
A node to taint; repeatable.
taint(key: string, value: string, effect: TaintEffect): this
Add a taint, as `key=value:effect`; repeatable.
removeTaint(key: string, effect?: TaintEffect): this
Remove a taint, which kubectl spells with a trailing `-`; repeatable.
all(): this
Taint every node in the cluster (`--all`).
overwrite(): this
Replace a taint of the same key rather than failing (`--overwrite`).
selector(query: string): this
Taint every node matching a label selector (`-l`).
dryRun(mode: DryRunMode): this
Preview without persisting (`--dry-run=`; defaults to `client`).
override protected buildArgs(): string[]
Assemble the `kubectl taint` argv.
class KubectlTopSettings extends KubectlSettings
Settings for `kubectl top`.
pods(): this
Report pod usage (`top pods`).
nodes(): this
Report node usage (`top nodes`).
name(value: string): this
Limit to a single named pod or node.
selector(query: string): this
Restrict to resources matching a label selector (`-l`).
containers(): this
Break pod usage down by container (`--containers`).
allNamespaces(): this
Report across all namespaces (`-A`).
override protected buildArgs(): string[]
Assemble the `kubectl top <pods|nodes>` argv.
class KubectlVersionSettings extends KubectlSettings
Settings for `kubectl version`.
clientOnly(): this
Report the client's version without reaching a cluster (`--client`).
output(format: "json" | "yaml"): this
The output format (`-o`): `json` or `yaml`.
override protected buildArgs(): string[]
Assemble the `kubectl version` argv.
class KubectlWaitSettings extends KubectlSettings
Settings for `kubectl wait`.
file(path: PathLike): this
Wait on resources defined in a file (`-f`); repeatable.
resource(...tokens: string[]): this
Resource tokens, e.g. `("pod/web")` or `("pods")`; repeatable.
forCondition(condition: string): this
The condition to wait for, e.g. `condition=Available` or `delete`.
timeout(duration: string): this
How long to wait, e.g. `60s` (`--timeout`).
selector(query: string): this
Restrict to resources matching a label selector (`-l`).
all(): this
Wait on all resources of the given type (`--all`).
override protected buildArgs(): string[]
Assemble the `kubectl wait` argv.
interface KubectlTasksApi
The shape of {@link KubectlTasks}.
apply(configure?: Configure<KubectlApplySettings>): Promise<CommandOutput>
Apply manifests: `kubectl apply`.
create(configure?: Configure<KubectlCreateSettings>): Promise<CommandOutput>
Create resources: `kubectl create`.
delete(configure?: Configure<KubectlDeleteSettings>): Promise<CommandOutput>
Delete resources: `kubectl delete`.
get(configure?: Configure<KubectlGetSettings>): Promise<CommandOutput>
List resources: `kubectl get`.
getNamespaces(configure?: Configure<KubectlGetSettings>): Promise<KubernetesNamespace[]>
List namespaces as typed {@link KubernetesNamespace} records: runs
`kubectl get namespaces -o json` (forcing JSON output, quietly) and parses
the result. Use the lambda for cluster flags or a label `.selector(...)`.
describe(configure?: Configure<KubectlDescribeSettings>): Promise<CommandOutput>
Describe resources: `kubectl describe`.
logs(configure?: Configure<KubectlLogsSettings>): Promise<CommandOutput>
Read logs: `kubectl logs`.
exec(configure?: Configure<KubectlExecSettings>): Promise<CommandOutput>
Exec into a container: `kubectl exec`.
rollout(configure?: Configure<KubectlRolloutSettings>): Promise<CommandOutput>
Manage rollouts: `kubectl rollout`.
scale(configure?: Configure<KubectlScaleSettings>): Promise<CommandOutput>
Scale a workload: `kubectl scale`.
setImage(configure?: Configure<KubectlSetImageSettings>): Promise<CommandOutput>
Update a container image: `kubectl set image`.
annotate(configure?: Configure<KubectlAnnotateSettings>): Promise<CommandOutput>
Annotate resources: `kubectl annotate`.
label(configure?: Configure<KubectlLabelSettings>): Promise<CommandOutput>
Label resources: `kubectl label`.
patch(configure?: Configure<KubectlPatchSettings>): Promise<CommandOutput>
Patch a resource: `kubectl patch`.
portForward(configure?: Configure<KubectlPortForwardSettings>): Promise<CommandOutput>
Forward local ports: `kubectl port-forward`.
wait(configure?: Configure<KubectlWaitSettings>): Promise<CommandOutput>
Wait for a condition: `kubectl wait`.
top(configure?: Configure<KubectlTopSettings>): Promise<CommandOutput>
Show resource usage: `kubectl top`.
diff(configure?: Configure<KubectlDiffSettings>): Promise<CommandOutput>
Show what an apply would change: `kubectl diff`. The command exits 1 when
it finds differences, so this task fails the target on drift — which is
what a gate wants. Use {@link KubectlTasksApi.diffHasChanges} to read the
answer as a value instead.
diffHasChanges(configure?: Configure<KubectlDiffSettings>): Promise<boolean>
Whether an apply would change anything: `true` when `kubectl diff` reports
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
No one has posted yet. Be the first.

