burgee
API reference

burgee/testing

Every export of burgee/testing, with its signature and doc comment: processRuntime, captureConsole, codeOf, fakeClock, fakeRuntime, finish and 7 more, plus 7 types.

burgee/testing — run a burgee CLI in-process, with argv, env, stdin, cwd and TTY-ness injected, and get back { code, stdout, stderr, json }. Requirement T1.

A separate entry point so it is paid for per import (K6): a user's shipped CLI imports burgee and never pulls a byte of this.

import { processRuntime, captureConsole, codeOf, … } from 'burgee/testing';

Functions

captureConsole

Route console.* into the fake runtime while a run lasts. Hosts print through console in a few paths a public seam does not cover (yargs' help printer, a handler that never met the output layer); a harness that let those reach the real terminal would be measuring the wrong thing.

function captureConsole(rt: FakeRuntime): () => void;
ParameterType
rtFakeRuntime

Returns () => void

codeOf

The E1 code an unwound error carries, or RUNTIME when it carries none.

function codeOf(e: unknown): ExitCode;
ParameterType
eunknown

Returns ExitCode

fakeClock

Deterministic time for the harness. Starts at start (0 by default) and never moves on its own.

function fakeClock(start?: number): FakeClock;
ParameterType
start (optional)number

Returns FakeClock

fakeRuntime

A Runtime whose every part is under the test's control.

function fakeRuntime(opts: RunOptions): FakeRuntime;
ParameterType
optsRunOptions

Returns FakeRuntime

finish

Assemble the result (R2). json is set only when --json was in argv and stdout parsed; a parse failure turns the run into RUNTIME with the parse error on stderr, so a broken envelope cannot pass a test by accident.

function finish(rt: FakeRuntime, code: ExitCode, startedAt: number): RunResult;
ParameterType
rtFakeRuntime
codeExitCode
startedAtnumber

Returns RunResult

isExitCode

True for the seven codes in the contract and nothing else.

function isExitCode(n: unknown): n is ExitCode;
ParameterType
nunknown

Returns n is ExitCode

runBurgee

Run a burgee program in-process — the T1 harness for burgee itself. Env is injected, never swapped: the engine reads env-bound options from the runtime it is given, so process.env is untouched by construction. Exit unwinds through RuntimeExit.

function runBurgee(program: Manifest, opts: RunOptions): Promise<RunResult>;
ParameterType
programManifest
optsRunOptions

Returns Promise<RunResult>

stripAnsi

Strip ANSI escape sequences — the decision from the intent: stdout stays raw.

linegauge's strip, not a regex of this file's own. The one this used to carry, ESC[[0-9;]*[A-Za-z], left the private modes (ESC[?25l, which every spinner writes), the colon form of an extended colour (ESC[38:2::255:0:0m, which chalk emits for truecolor) and every OSC 8 hyperlink in the text a test asserted against.

function stripAnsi(text: string): string;
ParameterType
textstring

Returns string

swapEnv

function swapEnv(env: Record<string, string> | undefined): () => void;
ParameterType
envRecord<string, string> | undefined

Returns () => void

Classes

RuntimeExit

Thrown by fakeRuntime.exit so a handler that exits unwinds to the harness.

class RuntimeExit extends Error {
    readonly code: ExitCode;
    constructor(code: ExitCode);
}

Constants

ExitCode

E1 — exit codes are a contract. No other literal may reach process.exitCode.

const ExitCode: {
    /** Command completed. */
    readonly OK: 0;
    /** The command ran and failed. Never accompanied by help text (E2). */
    readonly RUNTIME: 1;
    /** Bad arguments, unknown command, missing flag, prompt needed in a non-TTY (P2). */
    readonly USAGE: 2;
    /** Config file or environment could not be loaded or validated (V1). */
    readonly CONFIG: 3;
    /** The user or caller cancelled. */
    readonly CANCELLED: 4;
    /**
     * E6 — the far side said no: a credential is missing, expired, or refused.
     *
     * Its own code because it is the most actionable one in the survey. `RUNTIME` means *it
     * failed, read the message*; `AUTH` means *log in and run it again*, and a script or an
     * agent can branch on that without parsing prose. `USAGE` says fix the script, `CONFIG`
     * says fix the runner, and this says fix the credential — three different responses that
     * collapsed into one code before it existed.
     *
     * **5, where `gh` uses 4.** Four is `CANCELLED` here and has been since the contract was
     * written, and moving a published code to match another tool's is a breaking change for
     * every consumer that already branches on it. The survey's other citation, `aws` v2, uses
     * 252/253/254 and agrees with nobody either; what matters is that the code is stable and
     * documented, not that it matches a particular neighbour.
     */
    readonly AUTH: 5;
    /** SIGINT after the terminal was restored (E5). */
    readonly SIGINT: 130;
};

type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];

ExitCodeValue

E1 — exit codes are a contract. No other literal may reach process.exitCode.

const ExitCode: {
    /** Command completed. */
    readonly OK: 0;
    /** The command ran and failed. Never accompanied by help text (E2). */
    readonly RUNTIME: 1;
    /** Bad arguments, unknown command, missing flag, prompt needed in a non-TTY (P2). */
    readonly USAGE: 2;
    /** Config file or environment could not be loaded or validated (V1). */
    readonly CONFIG: 3;
    /** The user or caller cancelled. */
    readonly CANCELLED: 4;
    /**
     * E6 — the far side said no: a credential is missing, expired, or refused.
     *
     * Its own code because it is the most actionable one in the survey. `RUNTIME` means *it
     * failed, read the message*; `AUTH` means *log in and run it again*, and a script or an
     * agent can branch on that without parsing prose. `USAGE` says fix the script, `CONFIG`
     * says fix the runner, and this says fix the credential — three different responses that
     * collapsed into one code before it existed.
     *
     * **5, where `gh` uses 4.** Four is `CANCELLED` here and has been since the contract was
     * written, and moving a published code to match another tool's is a breaking change for
     * every consumer that already branches on it. The survey's other citation, `aws` v2, uses
     * 252/253/254 and agrees with nobody either; what matters is that the code is stable and
     * documented, not that it matches a particular neighbour.
     */
    readonly AUTH: 5;
    /** SIGINT after the terminal was restored (E5). */
    readonly SIGINT: 130;
};

type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];

processRuntime

The real Runtime, for a caller that injects none.

argv, env and cwd are getters for the reason the block above gives; isTTY is one too, because a stream's isTTY is a property of whatever stream is installed now, and the harness swaps streams. stdin/stdout/stderr likewise.

const processRuntime: Runtime;

Interfaces

Clock

Time, as the layers above the parser see it (R14 of cli-output-stack). now is monotonic milliseconds from an arbitrary origin; schedule runs fn after ms and hands back the cancel. Typed structurally so the output stack can accept a Runtime without importing one.

interface Clock {
    now(): number;
    schedule(fn: () => void, ms: number): () => void;
}

FakeClock

A Clock that moves only when the test says so (R14): tick is the only source of time.

interface FakeClock extends Clock {
    /**
     * Advance by `ms`, running every callback that falls due, earliest first, then by order
     * scheduled. A callback that schedules inside the window runs in the same tick, so one that
     * reschedules itself at `0` would never leave the loop (real Node yields between turns);
     * a tick runs at most `TICK_CAP` callbacks and throws, naming the cap, when exceeded.
     */
    tick(ms: number): void;
    /** Callbacks scheduled and neither run nor cancelled. */
    pending(): number;
}

FakeRuntime

interface FakeRuntime extends Runtime {
    out: string[];
    err: string[];
    clock: FakeClock;
}

RunOptions

interface RunOptions {
    argv: string[];
    env?: Record<string, string>;
    stdin?: string | Readable;
    cwd?: string;
    /** `true` = every stream is a TTY; an object sets each; default: none is. */
    tty?: boolean | Partial<Runtime['isTTY']>;
    /** The clock the runtime reports; a fresh `fakeClock()` at 0 when not given. */
    clock?: FakeClock;
}

RunResult

interface RunResult {
    code: ExitCode;
    stdout: string;
    stderr: string;
    /** Parsed stdout when `--json` was in argv and stdout parsed; see `finish`. */
    json?: unknown;
    durationMs: number;
}

Runtime

The world, as the layers above the parser see it (design R1 of cli-testing-harness). Nothing above the parser reads process.* directly; it reads its Runtime, so a test can substitute every part of it.

interface Runtime {
    argv: string[];
    env: Record<string, string | undefined>;
    cwd: string;
    stdin: NodeJS.ReadableStream;
    stdout: Writer;
    stderr: Writer;
    isTTY: {
        stdin: boolean;
        stdout: boolean;
        stderr: boolean;
    };
    /** Ends the run with an E1 code. In the real runtime this never returns. */
    exit(code: ExitCode): never;
    /** `performance.now` and `setTimeout` in the real runtime; a manual tick in the harness. */
    clock: Clock;
}

Writer

Anything that accepts text; process.stdout satisfies it, so does an array push.

interface Writer {
    write(chunk: string): unknown;
}

On this page