Console output
@zuke/console is the output engine behind every Zuke build — the
ruled banners, the colored status lines, and the aligned
Build Summary all come from it. It's also a package you can
import directly: a levelled logger with tag-based markup and a
set of layout primitives (line, rule,
box, table) that render cleanly to a terminal and
degrade gracefully in CI.
Import
The package exports a single namespaced object, ConsoleTasks.
It's conventional to alias it to Log:
import { ConsoleTasks as Log } from "jsr:@zuke/console"; Logging & levels
Seven methods form a severity ladder. trace, debug,
info, log, and success write to
stdout; warn and error write to
stderr. Every message accepts markup.
Log.trace("resolved 42 source files"); // most verbose
Log.debug("cache hit for 'check'");
Log.info("pushing [bold]core@1.2.0[/]"); // → stdout
Log.log("a plain line — no icon, no styling");// → stdout
Log.success("published [bold]4[/] packages"); // ✔ is added for you
Log.warn("coverage [yellow]94.2%[/] — below gate"); // → stderr
Log.error("type-check failed", { error: err }); // → stderr, prints the cause · resolved 42 source files › cache hit for 'check' ℹ pushing core@1.2.0 a plain line — no icon, no styling ✔ published 4 packages ⚠ coverage 94.2% — below gate ✖ type-check failed ↳ TypeError: expected string, got number
Only messages at or above the active level are
printed — set it to "debug" in CI, "info" locally,
or "silent" to mute everything. error() takes an
optional { error } so it can print the underlying cause, and
under GitHub Actions warn/error also emit
::warning:: / ::error:: annotations.
Markup
Wrap text in [style]…[/] tags. A tag carries one or more
space-separated style names, and [/] closes the most recent tag,
restoring the surrounding style — so tags nest cleanly.
Log.info("[bold]core[/] is [green]ready[/]"); // one tag each
Log.info("[red bold]2 errors[/] in [underline]mod.ts[/]"); // combine styles
Log.info("[yellow]outer [cyan]inner[/] still yellow[/]"); // [/] closes the nearest tag
Log.info("[muted]12 files[/] scanned"); // semantic theme token
// Literal brackets: double them. [[ renders as [ and ]] as ].
Log.info("array access looks like arr[[0]]");
// Escaping untrusted text so it can't smuggle in tags:
Log.info("branch: " + Log.escape(userInput)); ℹ core is ready ℹ 2 errors in mod.ts ℹ outer inner still yellow ℹ 12 files scanned ℹ array access looks like arr[0] ℹ branch: release/[beta]
The recognised style names are:
- Attributes —
bold,dim,italic,underline, andreset. - Colors —
black,red,green,yellow,blue,magenta,cyan,white, andgray. - Semantic tokens —
success,warn,error,info,debug,trace, andmuted. These resolve through the active theme, so they stay consistent (and re-theme-able) across your output.
A literal bracket is written by doubling it — [[ renders as
[ and ]] as ]. To print
caller-supplied text safely, pass it through Log.escape(text),
which doubles every bracket so nothing is mistaken for a tag.
Lines & rules
line draws a horizontal divider; rule is a
line with a centered title. Both accept the same options —
char (the glyph to repeat), width, and
style (an array of style names).
Log.line(); // a full-width ── divider
Log.line({ char: "═", width: 40 }); // custom glyph and width
Log.line({ char: "·", style: ["dim"] }); // styled divider
Log.rule("Deploy"); // a title centered in the rule
Log.rule("Tests", { style: ["cyan"] }); // rules take the same options as line()
Log.rule(); // untitled — identical to line() ────────────────────────────────────────────── ════════════════════════════════════════ ·············································· ─────────────────── Deploy ─────────────────── ─────────────────── Tests ──────────────────── ──────────────────────────────────────────────
Boxes
box frames content in a single-line border. Pass a string or an
array of lines, plus optional title, padding,
width, a border style, and a titleStyle.
// A single string, or an array of pre-split lines:
Log.box("Deployed core@1.2.0 to production.");
Log.box(
["core 1.2.0 ✔", "cli 1.2.0 ✔", "cmd 1.2.0 ✔"],
{
title: "Release", // shown in the top border
padding: 1, // blank cells inside the border
width: 40, // fixed width (defaults to fit the content)
border: ["green"], // style the border glyphs…
titleStyle: ["bold"], // …and the title
},
); ┌────────────────────────────────────┐ │ Deployed core@1.2.0 to production. │ └────────────────────────────────────┘ ┌ Release ─────────────────────────────┐ │ │ │ core 1.2.0 ✔ │ │ cli 1.2.0 ✔ │ │ cmd 1.2.0 ✔ │ │ │ └──────────────────────────────────────┘
border and titleStyle take an array of style
names (e.g. ["green"] or ["dim"]) — they color
the border glyphs and title, not the box shape, which is always drawn with
Unicode box-drawing characters.
Tables
table renders aligned columns. Columns are described by a
header and an optional align
("left" — the default — or "right"); rows are arrays
of strings. Options control the look:
Log.table(
[
{ header: "Package", align: "left" }, // align: "left" | "right"
{ header: "Version" }, // defaults to left
{ header: "Size", align: "right" },
],
[
["@zuke/core", "1.2.0", "18 kB"],
["@zuke/cli", "1.2.0", "12 kB"],
["@zuke/cmd", "1.2.0", "6 kB"],
],
{
divider: true, // rule between header and body
separator: " ", // gap between columns
headerStyle: ["bold"],
dividerStyle: ["dim"],
},
); Package Version Size ────────────────────────── @zuke/core 1.2.0 18 kB @zuke/cli 1.2.0 12 kB @zuke/cmd 1.2.0 6 kB
divider draws a rule under the header, separator
sets the gap between columns, and headerStyle /
dividerStyle style those rows. Cell text may contain markup, and
column widths are computed automatically from the content.
Headers & summary
Two helpers produce the framing Zuke prints around a run.
header(name) is the ruled banner before a target;
summary(reports, totalMs, ok) is the final status-and-timing
table. The default renderer calls both for you — you'd
only invoke them directly if you're driving the run loop yourself.
// The ruled banner Zuke prints before each target runs:
Log.header("build");
// The aligned end-of-run summary table. 'reports' is the per-target
// result set the runner collects; totalMs is wall-clock time; ok is the
// overall verdict. The default renderer calls this for you — reach for it
// directly only when you drive the run loop yourself.
Log.summary(reports, totalMs, ok); build Build Summary ──────────────────────────── Target Status Duration ──────────────────────────── install Succeeded 12.4s build Succeeded 2.2s ──────────────────────────── Total 14.6s
Groups
Wrap a burst of output in a named group. In a plain terminal the name is
printed as a header; under GitHub Actions the pair becomes a collapsible
::group:: / ::endgroup:: block.
Log.group("Install");
Log.info("added 214 packages in 12.4s");
Log.endGroup();
// Under GitHub Actions these become collapsible ::group:: / ::endgroup:: blocks. ────────────────── Install ────────────────── ℹ added 214 packages in 12.4s
Configuration
configure sets the log level, output sink, theme, color mode,
width, and GitHub-annotation mode. level() reads the active
threshold and reset() restores the defaults.
Log.configure({
level: "debug", // "trace" | "debug" | "info" | "warn" | "error" | "silent"
color: true, // force ANSI colour on/off (auto-detected from the TTY otherwise)
width: 100, // width used for rules, boxes, and wrapping
github: true, // emit ::group:: / ::warning:: / ::error:: annotations
// sink: a custom { out, err } target; theme: a custom palette
});
Log.level(); // read the active threshold
Log.reset(); // restore every option to its default configure prints nothing itself — it changes what later calls
emit. With level: "debug" the trace line above stays
hidden while debug and up now print; with
github: true, group and warn/error
switch to workflow-command syntax.
Renderer integration
@zuke/console is wired into a build through a
renderer. Pass consoleRenderer to
run() to route every banner and summary through it, or build a
custom-themed one with createConsoleRenderer(theme).
import { Build, run } from "jsr:@zuke/core";
import {
consoleRenderer,
createConsoleRenderer,
defaultTheme,
} from "jsr:@zuke/console";
// Route the whole build's banners and summary through @zuke/console:
await run(MyBuild, { renderer: consoleRenderer });
// …or ship your own palette by building a renderer from a Theme:
const renderer = createConsoleRenderer(defaultTheme);
await run(MyBuild, { renderer }); $ ./zuke build build ▸ astro build — 4 pages in 2.2s ✔ build succeeded in 2.2s Build Summary ──────────────────────────── Target Status Duration ──────────────────────────── clean Succeeded 0.1s build Succeeded 2.2s ──────────────────────────── Total 2.3s ✔ Build succeeded — 2/2 targets in 2.3s
Start from defaultTheme and override the tokens you want to
recolor — the semantic markup tokens (success,
warn, muted, …) then follow your palette everywhere
they appear, in both your own Log calls and Zuke's built-in
output.
Related
- Getting started — install Zuke and run your first target; the run log you see is
@zuke/consoleat work. - Core library — the other batteries in
@zuke/core: file tasks, globbing, HTTP, and more. - Examples — complete build files that produce this output.