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

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 BiomeReport with mode, exitCode, stdout, stderr, success, and durationMs so downstream callers can render summaries without re-parsing.
  • "check" mode throws LinterError(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-fatal bunx biome --version ping returning boolean. Used by the orchestrator to gate the biome lane.
  • Zod-validated options — invalid mode values raise LinterError(LINTER.INVALID_OPTIONS).
  • Injectable ClockLike for 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 to Date.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 (not npx) so the catalogued @biomejs/biome version is the one that runs. Bunx resolves the binary from the current workspace install, matching what bun run lint would invoke in every other package.
  • The default runner buffers stdout / stderr in memory and resolves on close, NOT exit — close fires after both streams are drained, so the report includes trailing output that exit would miss.
  • RunBiomeOptions is validated via Zod at the boundary so consumers passing hand-typed strings get an INVALID_OPTIONS error rather than a runtime crash deep inside the runner.
  • recordBiomeRun(mode, outcome) and recordRunDuration("biome", ms) fire on every successful runner completion (success or failure); the only path that skips them is a runner rejection, which is the RUNNER_FAILED branch.
  • probeBiomeAvailable deliberately swallows rejections — a missing binary should not bubble an unhandled rejection through the orchestrator. Callers who want strict behaviour can raise BIOME_UNAVAILABLE themselves after a false probe.