| ... | @@ -9,6 +9,17 @@ import * as Predicate from "effect/Predicate"; | ... | @@ -9,6 +9,17 @@ import * as Predicate from "effect/Predicate"; |
| 9 | import * as Schema from "effect/Schema"; | 9 | import * as Schema from "effect/Schema"; |
| 10 | import * as Stream from "effect/Stream"; | 10 | import * as Stream from "effect/Stream"; |
| 11 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; | 11 | import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; |
| | 12 | import * 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. |
| | 20 | const oxlintPackageJsonPath = NodeModule.createRequire( |
| | 21 | NodeModule.createRequire(import.meta.url).resolve("vite-plus/package.json"), |
| | 22 | ).resolve("oxlint/package.json"); |
| 12 | | 23 | |
| 13 | class OxlintFixtureFailure extends Data.TaggedError("OxlintFixtureFailure")<{ | 24 | class 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"); |
| 105 | | 109 | |
| 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); |
| 114 | | 118 | |
| | 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 | ); |
| 118 | | 127 | |
| 119 | if (output.exitCode !== 0) { | 128 | if (output.exitCode !== 0) { |