3.9 KiB
CLI — cli.ts
(no submodules)
Purpose
Owns the five command surfaces — createCommand, devCommand,
buildCommand, packageCommand, signCommand — plus the
runCli(argv, deps?) dispatcher that the published binary delegates to.
Every command is a pure function: it validates its option struct against
the matching Zod schema, runs the orchestrated work, and returns a typed
report. runCli never calls process.exit; the only exit-call lives in
src/bin/suaveplan-electrobun.ts, so the entire CLI is unit-testable
without a child process.
createCommand is the scaffolder entry: it loads the layered config,
applies CLI overrides, optionally runs the prompt flow, then hands off
to generate. The other four commands are runner-only — they probe the
upstream electrobun binary, raise ELECTROBUN.BINARY_UNAVAILABLE when
missing, spawn the binary with the canonical argv, and return a
CommandReport carrying exit code, stdout, stderr, and duration.
Features
- Five command functions, each with Zod-validated options and structured reports.
- Pure
runCli(argv)dispatcher with--help/-hshort-circuit and closed-set flag parsing. - Long-flag
--key=valueand--key valueforms supported uniformly. - Unknown flags / subcommands raise
ELECTROBUN.UNKNOWN_FLAG. - Missing required values raise
ELECTROBUN.INVALID_OPTIONS. timeRunnerwrapper increments invocation + duration histograms per call and increments failure counters onElectrobunErrorpaths.
Usage: Basic
import { runCli } from "@suaveplan/electrobun/cli";
const { code } = await runCli(process.argv.slice(2));
process.exit(code);
Usage: Advanced
import { buildCommand } from "@suaveplan/electrobun/cli";
import type { CommandRunner } from "@suaveplan/electrobun/types";
const customRunner: CommandRunner = async (input) => {
// ship the spawn to a remote CI worker
return await postToWorker(input);
};
const report = await buildCommand(
{ cwd: "/proj", target: "linux", update: true },
{ runner: customRunner }
);
API Reference
createCommand(opts: CreateCommandOptions, deps: CommandDeps & { prompter: Prompter }): Promise<GenerateReport>.devCommand(opts: DevCommandOptions, deps?: { runner? }): Promise<CommandReport>.buildCommand(opts: BuildCommandOptions, deps?: { runner? }): Promise<CommandReport>.packageCommand(opts: PackageCommandOptions, deps?: { runner? }): Promise<CommandReport>.signCommand(opts: SignCommandOptions, deps?: { runner? }): Promise<CommandReport>.runCli(argv: readonly string[], deps?: RunCliOptions): Promise<{ code: number }>.HELP_TEXT: string— the full help banner.
Implementation Notes
The arg parser uses a closed ALL_KNOWN_FLAGS set. New flags must be
added to the set before the parser will accept them; an unknown flag
fails the run with ELECTROBUN.UNKNOWN_FLAG, which keeps subtle typos
from being silently swallowed. The --help and -h short forms are
intercepted before subcommand dispatch so they work uniformly across
every subcommand.
runCli catches every ElectrobunError and folds it into code: 1
with a one-line stderr message. Any other thrown value re-propagates,
on the principle that the runtime should not eat unexpected exceptions
— if something other than a typed error escapes the call, the bin
wrapper surfaces it with code: 2.
The timeRunner helper increments electrobun_runner_invocations_total
on entry, records the duration histogram on every exit (success or
failure), and records electrobun_runner_failures_total when the thrown
ElectrobunError carries a numeric details.exitCode. The failure
counter labels the exit code with -1 when no numeric exit-code is
available — for instance when the probe stage raises
BINARY_UNAVAILABLE — so dashboards can still attribute the failure
to the right command without inventing fake exit codes.