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

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 commander dependency for a binary with five flags.
  • Recognised flags: --help / -h, --fix / --write, --unified, --markdown-only, --cwd <path>. Anything else starting with - raises LinterError(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 patterns so multiple globs are supported.
  • Injectable out: (line: string) => void sink for tests — defaults to console.log.
  • Translates runUnifiedLint's LintReport.success into exit code 0 / 1, prints a concise summary line, and catches LinterError to 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 (typically process.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.
  • --unified is 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.
  • LinterError is caught here (not at the orchestrator) because the CLI is the layer that maps domain errors to exit codes. Letting the error propagate out of runCli would 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 runUnifiedLint directly; the CLI summary is the "CI-friendly" shape.