Getting started

Zuke is a code-first, strongly-typed build automation system for Deno & TypeScript. Builds are plain TypeScript classes — targets are class fields, dependencies are real references, and Zuke runs everything in topological order. This guide gets you from zero to your first green build.

Install

Zuke runs on Deno and ships on JSR. Install the CLI globally, then let it scaffold a project:

# Install the CLI globally with Deno
deno install -A -g -n zuke jsr:@zuke/cli

# Scaffold zuke.ts, launchers, and config in your project
zuke setup

# Run it — the ./zuke launcher bootstraps Deno if needed
./zuke

No Deno yet? The generated ./zuke launcher will install a pinned, known-good version on first run — so contributors don't need to set anything up by hand.

Scaffold a build

zuke setup creates a zuke.ts at your project root alongside the zuke / zuke.ps1 launchers and a small zuke.json. The zuke.ts file is your build — edit it like any other TypeScript module.

Already have package.json scripts or a Makefile? zuke import reads them and generates a zuke.ts with one target() per task — a working starting point to refine into typed wrappers, rather than a blank page. It auto-detects the source (package.json first, then a Makefile); pass --from makefile to pin it. A command maps to CmdTasks.exec(...), an && chain becomes sequential steps, a script delegation or Makefile prerequisite becomes a .dependsOn(...), and anything too shell-specific to translate is preserved behind a // TODO so the file still compiles. Like zuke setup, it also writes the launchers and deno.json.

Your first target

A target is a class field built with target(). Give it a description, declare what it dependsOn using this.* references, and provide the work in executes:

import { Build, run, target } from "jsr:@zuke/core";
import { DenoTasks } from "jsr:@zuke/deno";

class MyBuild extends Build {
  clean = target()
    .description("Remove build output")
    .executes(async () => {
      await $`rm -rf dist`;
    });

  compile = target()
    .description("Type-check the project")
    .dependsOn(this.clean)
    .executes(async () => {
      await DenoTasks.check((s) => s.paths("mod.ts"));
    });
}

await run(MyBuild);

Because compile depends on this.clean, Zuke runs clean first — automatically and exactly once. Rename clean and the reference moves with it; there are no strings to keep in sync.

The closing await run(MyBuild) makes the file runnable. run() is entry-aware — it executes only when the file is run directly, and is a no-op when imported (say, from a test) — so no import.meta.main guard is needed.

Running targets

Invoke any target through the launcher:

./zuke compile         # run a target (and its dependencies)
./zuke compile --parallel  # run independent targets concurrently
./zuke compile --skip clean  # run it, but skip a named dependency (repeatable)
./zuke ci --affected=origin/main  # only targets affected by changes since a base
./zuke --list          # list every available target
./zuke --list --json   # …the build surface (targets, flags, params) as JSON
./zuke graph           # print the dependency graph
./zuke doc jsr:@zuke/deno  # prints the typed API docs for a @zuke package
./zuke generate-ci     # write declared CI/CD pipeline files
./zuke completions install zsh   # install shell tab-completion
./zuke mcp             # run an MCP server over the build for AI agents
./zuke                 # run the default target

Each target is framed with a ruled banner and closes with a pass/fail line and its duration. The run ends with an aligned Build Summary — status (Succeeded / Failed / Skipped / Cached) and timing per target, a total, and a one-line verdict with a timestamp. A target named default runs when you pass no arguments.

A few flags narrow what runs. --skip <dep> runs the target but drops a named dependency (repeatable). --affected[=<base>] keeps only the targets a git diff can reach — a changed file inside a target's .inputs(), or anything downstream of one — skipping the rest; the base defaults to HEAD, and --affected=origin/main is the usual CI form. And --list --json emits the whole build surface — targets, flags, and parameters — as JSON for tools and agents.

zuke doc <spec> prints the typed API docs for a package. The spec is passed to deno doc as-is: a jsr:/npm:/https: specifier or an absolute path passes through unchanged (say zuke doc jsr:@zuke/deno), while a relative or bare name is resolved as a file path against the working directory. It runs deno doc from an isolated directory, so a surrounding Node project's type resolution can't drown the output.

$ ./zuke build

Build Summary
────────────────────────────
Target   Status     Duration
────────────────────────────
clean    Succeeded      0.1s
install  Succeeded     12.4s
check    Succeeded      3.1s
build    Succeeded      2.2s
────────────────────────────
Total                  17.8s

✔ Build succeeded — 4/4 targets in 17.8s · 2026-06-23 07:19

Under GitHub Actions the same run renders as collapsible ::group:: blocks with ::error:: annotations and secret masking, plus a matching Markdown summary table on the job page.

Visualizing the graph

zuke graph prints the dependency graph as a terminal listing. Add --output=html to render it instead as a self-contained, interactive Cytoscape page written to <repo root>/.zuke/graph.html — which Zuke then opens in your browser:

./zuke graph                 # print the dependency graph as text
./zuke graph --output=html   # render an interactive HTML page and open it
./zuke graph --output=html --no-open   # write .zuke/graph.html without opening

Nodes are colour-coded by dependency depth (roots through leaves) on a dark canvas. Pan and zoom freely, click a target to highlight everything it connects to — its transitive dependencies and dependents — and switch the layout direction or export a PNG from the header. Targets in a group() are drawn inside a labelled box. Pass --no-open to write the file without launching a browser (handy in CI). See Zuke's own build graph for a rendered example.

Cytoscape loads from a pinned CDN, so the first view needs internet access; the page itself is otherwise self-contained.

Shell completions

Zuke ships tab-completion for bash, zsh, and fish. The install sub-command writes the script and wires it into your shell in one step; print emits it to stdout if you'd rather source it yourself:

# Install completions for your shell — writes the script and wires it in
./zuke completions install bash   # → ~/.config/zuke/completions/zuke.bash  (+ ~/.bashrc)
./zuke completions install zsh    # → ~/.config/zuke/completions/zuke.zsh   (+ ~/.zshrc)
./zuke completions install fish   # → ~/.config/fish/completions/zuke.fish  (auto-loaded)

# Or print the script to stdout and source it yourself
source <(./zuke completions print bash)
source <(./zuke completions print zsh)
./zuke completions print fish | source

Completions cover your target names, the reserved commands (graph, generate-ci, completions), the built-in flags, and any declared parameters as --flag candidates. The sub-action is required — zuke completions bash on its own prints Usage: zuke completions <install|print> <bash|zsh|fish> and exits 1.

install honours $XDG_CONFIG_HOME and is idempotent — re-running won't duplicate the line in your rc file. The script is a static snapshot of the build, so re-run it after adding or renaming targets.

Next steps

  • Core concepts — targets, dependencies, groups, caching, conditional execution, the $ shell, parameters, code-first CI/CD, and extending Zuke.
  • Service targets — declare a long-lived process (dev server, database, mock API) with service(); Zuke starts it, waits until it's ready, and tears it down.
  • Caching — the incremental build cache (.inputs()/.outputs()/.cacheKey()), the remote cache, and the AI response cache (aiCache).
  • Secrets — source a secret from a manager with .from(execSecret/fileSecret) and have Zuke redact it from every log, summary, and error.
  • Core library — the built-in helpers: file tasks, globbing, HTTP, compression, tool installs, assertions, and paths.
  • Installing tools — fetch the CLIs a build drives, pinned by SHA-256, verified, and cached (ToolTasks.install(), toolchain()).
  • Console output — rich terminal output with @zuke/console: markup, rules, boxes, tables, and a levelled logger.
  • AI code review — gate a build on a model-assessed security review with @zuke/ai.
  • Self-healing builds — let an AI fixer diagnose, suggest, and auto-fix a failed target, then re-run the real command to verify.
  • MCP server — expose the build to AI agents as typed tools over the Model Context Protocol with zuke mcp.
  • Examples — real-world build files for Node, Deno libraries, Docker, and generated CI pipelines.
  • JSR packages — the full set of typed tool wrappers.