Files
skills/docs/devtools/tooling/electrobun/src/cli/cli.md
2026-05-29 08:20:56 +01:00

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 / -h short-circuit and closed-set flag parsing.
  • Long-flag --key=value and --key value forms supported uniformly.
  • Unknown flags / subcommands raise ELECTROBUN.UNKNOWN_FLAG.
  • Missing required values raise ELECTROBUN.INVALID_OPTIONS.
  • timeRunner wrapper increments invocation + duration histograms per call and increments failure counters on ElectrobunError paths.

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.