3.7 KiB
3.7 KiB
cli
(no submodules)
Purpose
@suaveplan/linter/cli is the pure-function CLI entry point. It parses an argv array,
runs the unified orchestrator, and returns a resolved exit code. Unlike the upstream
package, it never calls process.exit — the shipped binary (suaveplan-lint) is a
one-line wrapper that calls runCli(process.argv.slice(2)) and then exits with the
returned code. Keeping the CLI pure means tests can assert on the resolved code without
the test process terminating mid-suite, and other tools (pre-commit hooks, IDE
integrations) can embed the CLI logic directly.
Features
- Hand-rolled argv parser — no
commanderdependency for a binary with five flags. - Recognised flags:
--help/-h,--fix/--write,--unified,--markdown-only,--cwd <path>. Anything else starting with-raisesLinterError(LINTER.UNKNOWN_FLAG)(rather than silently treating it as a positional pattern, which is how the upstream package quietly swallowed typos). - Positional arguments accumulate into
patternsso multiple globs are supported. - Injectable
out: (line: string) => voidsink for tests — defaults toconsole.log. - Translates
runUnifiedLint'sLintReport.successinto exit code 0 / 1, prints a concise summary line, and catchesLinterErrorto surface the code and message in the summary.
Usage: Basic
import { runCli } from "@suaveplan/linter/cli";
const { code } = await runCli(process.argv.slice(2));
process.exit(code);
Usage: Advanced
Embed the CLI inside another tool with full adapter control:
import { runCli } from "@suaveplan/linter/cli";
import type { CommandRunner } from "@suaveplan/linter";
const lines: string[] = [];
const out = (line: string) => lines.push(line);
const runner: CommandRunner = {
async run() {
return { exitCode: 0, stdout: "", stderr: "" };
},
};
const { code } = await runCli(["--fix", "docs/*.md"], {
out,
runner,
cwd: "/tmp/repo",
});
// code === 0, lines contains the summary
API Reference
runCli(argv, options?): Promise<{ code: number }>
argv: ReadonlyArray<string>— required. The argv slice (typicallyprocess.argv.slice(2)).options.out?: (line: string) => void— sink for help / summary / error lines.options.runner?: CommandRunner— forwarded to the orchestrator.options.fs?: FsAdapter,options.glob?: GlobAdapter,options.clock?: ClockLike— forwarded to the orchestrator.
Returns { code: 0 } on success or { code: 1 } on failure. Throws
LinterError(LINTER.UNKNOWN_FLAG) for an unrecognised flag.
Implementation Notes
- The parser uses a single forward sweep; flags are recognised by exact match and
positional args are anything that doesn't start with
-(or--cwd <path>where the next token is consumed as the cwd value). This is deliberately simple — the scope is five flags, not a general argv parser. --unifiedis accepted but no-op; the orchestrator runs both lanes by default and the flag exists for symmetry with the upstream CLI's documented surface.- The success summary names the count of markdown files touched and the count that were fixed. The failure summary names the biome exit code so consumers can grep CI output for the underlying cause.
LinterErroris caught here (not at the orchestrator) because the CLI is the layer that maps domain errors to exit codes. Letting the error propagate out ofrunCliwould force every consumer to write the same translation.- The CLI does NOT print the markdown reports inline. Consumers that want per-file
output can call
runUnifiedLintdirectly; the CLI summary is the "CI-friendly" shape.