af1e67d850
yargs wraps the \`[string] [default: "..."]\` clause based on the pre-normalized default value's character length, so a different random tmpdir width produces a different leading-whitespace count on the wrapped continuation line. After normalizing the path to \`<HOME>\` we were left with a one-space drift between runs. Collapse the wrap-dependent whitespace immediately before the clause so the snapshot is byte-stable regardless of home path length. Verified by deleting the .snap and regenerating across 3 runs.
135 lines
5.0 KiB
TypeScript
135 lines
5.0 KiB
TypeScript
// Help-text snapshots for every CLI command + key subcommand. Catches
|
|
// accidental flag removals, renames, and reordering in a single sweep —
|
|
// any change to the user-visible CLI surface shows up here as a diff.
|
|
//
|
|
// This is the broad coverage layer that makes the future Effect CLI
|
|
// migration (yargs → effect-smol/cli) safe to attempt: if a refactor
|
|
// preserves the surface, the snapshots stay green; if it doesn't, the
|
|
// diff tells you exactly which command(s) changed.
|
|
//
|
|
// Snapshots are taken at COLUMNS=120 so wrapping is stable across
|
|
// terminal sizes. The default opencode tui command is excluded —
|
|
// `opencode --help` includes an ASCII banner that pulls in the install
|
|
// version (changes per release), so we'd snapshot a moving target.
|
|
import { describe, expect } from "bun:test"
|
|
import { Effect } from "effect"
|
|
import fs from "node:fs"
|
|
import os from "node:os"
|
|
import { cliIt } from "../../lib/cli-process"
|
|
|
|
// Strips dynamic content that varies per run so snapshots are stable.
|
|
// Currently only the tmpdir prefix bleeds in (via `--cwd` defaults that
|
|
// resolve to `process.cwd()`). Add new patterns here as they surface.
|
|
//
|
|
// On macOS `os.tmpdir()` returns `/var/folders/...` but `process.cwd()`
|
|
// inside the child returns the realpath `/private/var/folders/...` — so
|
|
// we strip both forms.
|
|
const TMP = os.tmpdir()
|
|
const REAL_TMP = fs.realpathSync(TMP)
|
|
function normalize(text: string): string {
|
|
return (
|
|
text
|
|
.replaceAll(REAL_TMP, "<TMPDIR>")
|
|
.replaceAll(TMP, "<TMPDIR>")
|
|
.replace(/<TMPDIR>\/oc-cli-[a-z0-9]+/g, "<HOME>")
|
|
// yargs wraps the `[string] [default: "..."]` clause based on the
|
|
// pre-normalized default's character length, so different random home
|
|
// path widths produce different leading-whitespace counts on the
|
|
// wrapped continuation. Collapse the wrap-dependent whitespace.
|
|
.replace(/\s+\[string\] \[default: "<HOME>"\]/g, ' [string] [default: "<HOME>"]')
|
|
)
|
|
}
|
|
|
|
// Top-level commands. Order matches what `opencode --help` prints today;
|
|
// keep it in that order so the snapshot file reads as a table of contents.
|
|
// `completion` is intentionally excluded — it's a yargs built-in that emits
|
|
// top-level help on `--help` and exits 1; not a real opencode command.
|
|
const TOP_LEVEL = [
|
|
"acp",
|
|
"mcp",
|
|
"attach",
|
|
"run",
|
|
"debug",
|
|
"providers", // aliased to `auth`
|
|
"agent",
|
|
"upgrade",
|
|
"uninstall",
|
|
"serve",
|
|
"web",
|
|
"models",
|
|
"stats",
|
|
"export",
|
|
"import",
|
|
"github",
|
|
"pr",
|
|
"session",
|
|
"plugin",
|
|
"db",
|
|
] as const
|
|
|
|
// Subcommands worth pinning. Not exhaustive — the goal is one snapshot per
|
|
// distinct argv shape, not every leaf. Add new entries when a subcommand
|
|
// gains user-visible flags that we want to lock in.
|
|
const SUBCOMMANDS = [
|
|
["mcp", "list"],
|
|
["mcp", "add"],
|
|
["mcp", "auth"],
|
|
["mcp", "logout"],
|
|
["providers", "list"],
|
|
["providers", "login"],
|
|
["providers", "logout"],
|
|
["agent", "create"],
|
|
["agent", "list"],
|
|
["session", "list"],
|
|
["session", "delete"],
|
|
["github", "install"],
|
|
["github", "run"],
|
|
["db", "path"],
|
|
] as const
|
|
|
|
// Fixed wrap width so a developer's terminal doesn't affect snapshots.
|
|
// yargs honors COLUMNS; CI runners typically default to 80 which produces
|
|
// different wraps from a 200-col local terminal.
|
|
const SNAPSHOT_ENV = { COLUMNS: "120" }
|
|
|
|
describe("opencode CLI help-text snapshots", () => {
|
|
// Single test, parallel spawns. Each command's help fires under
|
|
// `concurrency: 8` — wall-clock stays under ~10s even for ~35 commands,
|
|
// versus ~1 minute if we serialized.
|
|
cliIt.live(
|
|
"every documented command emits stable help text",
|
|
({ opencode }) =>
|
|
Effect.gen(function* () {
|
|
const argvs: Array<readonly string[]> = [...TOP_LEVEL.map((c) => [c] as const), ...SUBCOMMANDS]
|
|
|
|
// Spawn in parallel, then assert in argv order so snapshot output is
|
|
// deterministic and per-command failures don't abort the rest of
|
|
// the sweep. `Effect.partition` is the canonical "run all, separate
|
|
// failures from successes" primitive — no mutable accumulator needed.
|
|
const [failures, results] = yield* Effect.partition(
|
|
argvs,
|
|
(argv) =>
|
|
Effect.gen(function* () {
|
|
const result = yield* opencode.spawn([...argv, "--help"], { env: SNAPSHOT_ENV })
|
|
if (result.exitCode !== 0) {
|
|
return yield* Effect.fail(`opencode ${argv.join(" ")}: exit ${result.exitCode}`)
|
|
}
|
|
return { argv, result }
|
|
}),
|
|
{ concurrency: 8 },
|
|
)
|
|
|
|
for (const { argv, result } of results) {
|
|
// yargs writes --help to stderr, not stdout. Snapshotting stderr
|
|
// means our test catches the help body; stdout for these commands
|
|
// is expected to be empty.
|
|
expect(normalize(result.stderr)).toMatchSnapshot(`opencode ${argv.join(" ")} --help`)
|
|
}
|
|
if (failures.length > 0) {
|
|
throw new Error(`Help text failed for:\n ${failures.join("\n ")}`)
|
|
}
|
|
}),
|
|
180_000,
|
|
)
|
|
})
|