refactor(init): use runner detection in format.ts#124
Conversation
|
Stack: init-skills Part of a stacked PR chain. Do not merge manually. |
2ada925 to
66eee40
Compare
47dc327 to
21072f9
Compare
66eee40 to
c5bc9ef
Compare
21072f9 to
d52abd4
Compare
4edf28e to
2e3b67f
Compare
133bd4a to
ecae3c7
Compare
006f9cc to
515fd36
Compare
3f687c8 to
207adbd
Compare
62e112a to
e56a2e3
Compare
207adbd to
dcb4afa
Compare
e56a2e3 to
2e4e3a8
Compare
dcb4afa to
d700db1
Compare
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughrunFormatters signature changed to accept a ProjectContext ( Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
523c58a to
3a3375e
Compare
Replaces hardcoded `["npx", "prettier", ...]` arrays in init/format.ts with the runner detection from lib/runners.ts. A bun project with no `npx` installed (e.g. Bun via Homebrew but no Node) now falls back to bunx instead of silently failing to format. Also wraps the spawn call in try/catch, formatting is best-effort and shouldn't break init even if the runner subprocess crashes. Signature change: runFormatters now takes the full ProjectContext instead of just cwd, so it can read packageManager. Updates the lone caller in init/index.ts.
3a3375e to
f7ab840
Compare
| } catch { | ||
| // Bun.spawn may not be writable on some runtimes | ||
| } | ||
| } | ||
| function restoreSpawn() { | ||
| try { | ||
| (Bun as unknown as { spawn: typeof Bun.spawn }).spawn = origSpawn; | ||
| } catch { | ||
| // ignore | ||
| } | ||
| } | ||
|
|
||
| function mockWhich(present: ReadonlySet<string>) { |
There was a problem hiding this comment.
Silent mock failures make tests untrustworthy
The try/catch blocks silently swallow mock-assignment failures. If Bun makes these properties non-writable in a future version, every test runs against real globals with confusing results rather than failing fast.
Same issue in mockWhich (line 42) and mockSpawnSync (line 55) and all three restore* functions.
Suggestion — drop all try/catch blocks, verify the assignment took effect:
function setSpawn(impl: SpawnImpl) {
(Bun as unknown as { spawn: SpawnImpl }).spawn = impl;
if (Bun.spawn !== (impl as unknown)) {
throw new Error("Failed to mock Bun.spawn — property may be non-writable");
}
}
function restoreSpawn() {
(Bun as unknown as { spawn: typeof Bun.spawn }).spawn = origSpawn;
}Bonus — extract the cast once to reduce boilerplate (repeated 6× as-is):
const bunOverrides = Bun as unknown as {
spawn: SpawnImpl;
which: (bin: string) => string | null;
spawnSync: (cmd: string[]) => { exitCode: number };
};
function setSpawn(impl: SpawnImpl) {
bunOverrides.spawn = impl;
}
function restoreSpawn() {
bunOverrides.spawn = origSpawn as unknown as SpawnImpl;
}
// ... etc| // Pulls in the same runner detection skills.ts uses, so a bun project with | ||
| // no `npx` on PATH (entirely possible if the user installed Bun via Homebrew | ||
| // but never installed Node) will fall back to bunx instead of silently failing. | ||
| import { detectAvailableRunners, preferredRunner, runnerCommand } from "../../lib/runners.js"; |
There was a problem hiding this comment.
This reads like commit-message rationale rather than a code comment. The function-level docstring already covers the best-effort / silent-failure contract, and the import names (detectAvailableRunners, preferredRunner) are self-documenting.
Suggestion: delete this comment block entirely — the PR description is the right place for the "why we're switching" context.
| if (files.length === 0) return; | ||
|
|
||
| const deps = await readDeps(cwd); | ||
| const deps = ctx.deps && Object.keys(ctx.deps).length > 0 ? ctx.deps : await readDeps(ctx.cwd); |
There was a problem hiding this comment.
Two things on this line:
1. ctx.deps && is dead code — deps is typed Record<string, string> (not optional), and gatherContext always sets it to deps ?? {}. An empty object is truthy, so the && never short-circuits. Simplify to:
const deps = Object.keys(ctx.deps).length > 0 ? ctx.deps : await readDeps(ctx.cwd);2. The disk fallback itself is redundant in practice — gatherContext() already calls readDeps(cwd) and stores the result. If it returned null, ctx.deps becomes {}. Re-calling readDeps here will return the same null. The only scenario this fallback helps is when someone constructs a ProjectContext manually with deps: {} while a real package.json exists — which only happens in the test for this very code path.
Consider simplifying to just trust ctx.deps:
export async function runFormatters(ctx: ProjectContext, files: string[]): Promise<void> {
if (files.length === 0) return;
if (Object.keys(ctx.deps).length === 0) return;
const matchingFormatters = FORMATTERS.filter((f) => f.pkg in ctx.deps);
...Then update the "reads deps from disk when ctx.deps is empty" test to pass deps: { prettier: "3.0.0" } directly, and drop the "no-op when ctx.deps is empty and package.json is missing" test (already covered by "no-op when no supported formatter is in deps").
| await runFormatters(ctx, ["x.ts"]); | ||
| expect(seenCwd).toBe(tempDir); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Missing test: non-zero formatter exit code
Tests cover spawn throwing (swallowed correctly) and spawn succeeding (exit 0), but there's no test for a formatter exiting non-zero. The current code silently ignores exit codes (await proc.exited with no check), which is the right best-effort behavior — worth locking in with a test:
test("ignores non-zero exit code from formatter (best-effort)", async () => {
setSpawn((cmd) => {
spawnCalls.push(cmd);
return { exited: Promise.resolve(1) };
});
const ctx = makeCtx({
cwd: tempDir,
packageManager: "bun",
deps: { prettier: "3.0.0", "@biomejs/biome": "1.9.0" },
});
await runFormatters(ctx, ["x.ts"]);
// Both formatters attempted despite prettier exiting non-zero
expect(spawnCalls).toHaveLength(2);
});- Drop commit-message-style comment above runners import; the docstring and import names cover the contract. - Trust ctx.deps in runFormatters; gatherContext already reads deps from disk, so the fallback readDeps call was dead code in practice. - Drop try/catch blocks around Bun.spawn/which/spawnSync mock setters; verify the assignment took effect instead, so a future non-writable property fails fast rather than silently running against real globals. - Extract the Bun override cast once to reduce boilerplate. - Add test locking in best-effort behavior when a formatter exits non-zero.
- Drop commit-message-style comment above runners import; the docstring and import names cover the contract. - Trust ctx.deps in runFormatters; gatherContext already reads deps from disk, so the fallback readDeps call was dead code in practice. - Drop try/catch blocks around Bun.spawn/which/spawnSync mock setters; verify the assignment took effect instead, so a future non-writable property fails fast rather than silently running against real globals. - Extract the Bun override cast once to reduce boilerplate. - Add test locking in best-effort behavior when a formatter exits non-zero.
Summary
["npx", "prettier", ...]arrays ininit/format.tswith the runner detection fromlib/runners.ts. A bun project with nonpxinstalled (e.g. Bun via Homebrew but no Node) now falls back tobunxinstead of silently failing to format.Bun.spawncall in try/catch, formatting is best-effort and shouldn't break init even if the runner subprocess crashes.runFormattersnow takes the fullProjectContextinstead of justcwd, so it can readpackageManager. Updates the lone caller ininit/index.ts.Stacked on #118.
Test plan
bun run testpasses (59 passed)npx, runclerk initin a Prettier-using project and confirm formatting still runs (viabunx)