4.4 KiB
biome-runner
(no submodules)
Purpose
@suaveplan/linter/biome-runner is the seam between the linter and the Biome CLI. Rather
than calling execSync from library code (which makes tests impossible and blocks the
event loop), the runner shells out through an injectable CommandRunner whose default
implementation wraps node:child_process.spawn. Tests pass real fake runners that
return canned CommandResult objects; production callers get the real subprocess. Every
run is wrapped in duration measurement so the OTel histogram captures the cost of every
biome invocation across the project.
Features
- Three modes —
"check"(read-only),"check-write"(lint+fix,--write --unsafe),"format-write"(formatter only with--write). - Returns a
BiomeReportwithmode,exitCode,stdout,stderr,success, anddurationMsso downstream callers can render summaries without re-parsing. "check"mode throwsLinterError(LINTER.BIOME_FAILED)on a non-zero exit (the expected contract for CI gates);"check-write"and"format-write"return the report unconditionally so write-mode runs can report success even when biome surfaced diagnostics that it auto-fixed.probeBiomeAvailable({ runner })— non-fatalbunx biome --versionping returningboolean. Used by the orchestrator to gate the biome lane.- Zod-validated options — invalid
modevalues raiseLinterError(LINTER.INVALID_OPTIONS). - Injectable
ClockLikefor deterministic duration measurement in tests.
Usage: Basic
import {
nodeCommandRunner,
runBiome,
} from "@suaveplan/linter/biome-runner";
const report = await runBiome({
cwd: ".",
mode: "check",
runner: nodeCommandRunner,
});
console.log(report.success, report.durationMs);
Usage: Advanced
Drive the runner from a test with a recording fake runner:
import { runBiome } from "@suaveplan/linter/biome-runner";
import type { CommandInput, CommandRunner } from "@suaveplan/linter";
const calls: CommandInput[] = [];
const runner: CommandRunner = {
async run(input) {
calls.push(input);
return { exitCode: 0, stdout: "ok", stderr: "" };
},
};
const clock = { now: () => 1000 };
const report = await runBiome({
cwd: ".",
mode: "check-write",
runner,
clock,
});
// calls[0].args === ['biome', 'check', '--write', '--unsafe', '--max-diagnostics', '500', '.']
// report.success === true
// report.durationMs === 0
API Reference
runBiome(options): Promise<BiomeReport>
options.cwd: string— required, forwarded to the runner.options.mode: "check" | "check-write" | "format-write"— required.options.runner: CommandRunner— required.options.clock?: ClockLike— defaults toDate.now.
Throws LinterError with code INVALID_OPTIONS, RUNNER_FAILED, or BIOME_FAILED.
probeBiomeAvailable(options): Promise<boolean>
options.runner: CommandRunner— required.options.cwd?: string— defaults to".".
Resolves to true only on exitCode === 0; runner rejections resolve to false.
nodeCommandRunner: CommandRunner
Default runner wrapping child_process.spawn. Inherits the current process.env and
overlays any env field provided in the CommandInput.
Implementation Notes
- The runner shells through
bunx biome(notnpx) so the catalogued@biomejs/biomeversion is the one that runs. Bunx resolves the binary from the current workspace install, matching whatbun run lintwould invoke in every other package. - The default runner buffers stdout / stderr in memory and resolves on
close, NOTexit—closefires after both streams are drained, so the report includes trailing output thatexitwould miss. RunBiomeOptionsis validated via Zod at the boundary so consumers passing hand-typed strings get anINVALID_OPTIONSerror rather than a runtime crash deep inside the runner.recordBiomeRun(mode, outcome)andrecordRunDuration("biome", ms)fire on every successful runner completion (success or failure); the only path that skips them is a runner rejection, which is theRUNNER_FAILEDbranch.probeBiomeAvailabledeliberately swallows rejections — a missing binary should not bubble an unhandled rejection through the orchestrator. Callers who want strict behaviour can raiseBIOME_UNAVAILABLEthemselves after afalseprobe.