authorgravatar for mwolson@member.fsf.orgMike Olson <mwolson@member.fsf.org> 2026-07-31 05:45:00-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-31 11:45:00+02:00
log894d6d68f555b97a070791590ed99695c09e4233
treec1324dd92504fcd078dc8da572b172d430fd2bcf
parentfccec9f097ab6b89714161ccab2efc7e19d59c00
signature Signed by PGP key B5690EEEBB952194

fix(oxlint-plugin): Resolve the oxlint bin without assuming a pnpm layout (#5066)


1 files changed, 18 insertions(+), 9 deletions(-)

oxlint-plugin-t3code/test/utils.ts+18-9
...@@ -9,6 +9,17 @@ import * as Predicate from "effect/Predicate";...@@ -9,6 +9,17 @@ import * as Predicate from "effect/Predicate";
9import * as Schema from "effect/Schema";9import * as Schema from "effect/Schema";
10import * as Stream from "effect/Stream";10import * as Stream from "effect/Stream";
11import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";11import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
12import * as NodeModule from "node:module";
13
14// oxlint is only a transitive dependency (via vite-plus), so its bin placement
15// varies by package manager: pnpm hoists it into the virtual store, while
16// other layouts expose vite-plus's LSP-only wrapper instead. Resolve the real
17// package through vite-plus rather than hardcoding either layout, and do it at
18// module scope so a broken install fails once with a resolution error instead
19// of as an opaque defect in every test.
20const oxlintPackageJsonPath = NodeModule.createRequire(
21 NodeModule.createRequire(import.meta.url).resolve("vite-plus/package.json"),
22).resolve("oxlint/package.json");
1223
13class OxlintFixtureFailure extends Data.TaggedError("OxlintFixtureFailure")<{24class OxlintFixtureFailure extends Data.TaggedError("OxlintFixtureFailure")<{
14 readonly exitCode: number;25 readonly exitCode: number;
...@@ -93,14 +104,7 @@ export const createOxlintRuleHarness = (...@@ -93,14 +104,7 @@ export const createOxlintRuleHarness = (
93 const configPath = path.join(fixtureDir, ".oxlintrc.json");104 const configPath = path.join(fixtureDir, ".oxlintrc.json");
94 const sourcePath = path.join(fixtureDir, options.filename ?? "fixture.ts");105 const sourcePath = path.join(fixtureDir, options.filename ?? "fixture.ts");
95 const repoRoot = path.join(import.meta.dirname, "..", "..");106 const repoRoot = path.join(import.meta.dirname, "..", "..");
96 const oxlintBin = path.join(107 const oxlintBin = path.join(path.dirname(oxlintPackageJsonPath), "bin", "oxlint");
97 repoRoot,
98 "node_modules",
99 ".pnpm",
100 "node_modules",
101 ".bin",
102 "oxlint",
103 );
104 const pluginPath = path.join(repoRoot, "oxlint-plugin-t3code", "index.ts");108 const pluginPath = path.join(repoRoot, "oxlint-plugin-t3code", "index.ts");
105109
106 yield* fs.writeFileString(110 yield* fs.writeFileString(
...@@ -112,8 +116,13 @@ export const createOxlintRuleHarness = (...@@ -112,8 +116,13 @@ export const createOxlintRuleHarness = (
112 );116 );
113 yield* fs.writeFileString(sourcePath, source);117 yield* fs.writeFileString(sourcePath, source);
114118
119 // Run through the current Node binary: oxlint's bin is an extensionless
120 // shebang script, which Windows cannot spawn directly and which would
121 // otherwise pick up whatever node is first on PATH.
115 const output = yield* spawnAndCollectOutput(122 const output = yield* spawnAndCollectOutput(
116 ChildProcess.make(oxlintBin, ["--config", configPath, sourcePath], { cwd: repoRoot }),123 ChildProcess.make(process.execPath, [oxlintBin, "--config", configPath, sourcePath], {
124 cwd: repoRoot,
125 }),
117 );126 );
118127
119 if (output.exitCode !== 0) {128 if (output.exitCode !== 0) {