Skip to content

Commit ae9c5b7

Browse files
committed
fix(lint): resolve tsgolint on Windows with bun package manager (#1269)
Bun stores packages in .bun/ cache dirs with symlinks, so the hardcoded node_modules/.bin paths don't contain tsgolint.exe. Add a fallback that resolves the real path of vite-plus and looks for tsgolint in the sibling .bin directory.
1 parent 3c08bc5 commit ae9c5b7

File tree

2 files changed

+44
-3
lines changed

2 files changed

+44
-3
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { existsSync } from 'node:fs';
2+
3+
import { describe, expect, it } from '@voidzero-dev/vite-plus-test';
4+
5+
import { lint } from '../resolve-lint.js';
6+
7+
describe('resolve-lint', () => {
8+
it('should resolve binPath and OXLINT_TSGOLINT_PATH to existing files', async () => {
9+
const result = await lint();
10+
11+
expect(result.binPath).toBeTruthy();
12+
expect(
13+
existsSync(result.binPath),
14+
`oxlint binPath should point to an existing file, got: ${result.binPath}`,
15+
).toBe(true);
16+
17+
const tsgolintPath = result.envs.OXLINT_TSGOLINT_PATH;
18+
expect(tsgolintPath).toBeTruthy();
19+
expect(
20+
existsSync(tsgolintPath),
21+
`OXLINT_TSGOLINT_PATH should point to an existing file, got: ${tsgolintPath}`,
22+
).toBe(true);
23+
});
24+
});

packages/cli/src/resolve-lint.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* provides ESLint-compatible linting with significantly better performance.
1212
*/
1313

14-
import { existsSync } from 'node:fs';
14+
import { existsSync, realpathSync } from 'node:fs';
1515
import { dirname, join } from 'node:path';
1616
import { relative } from 'node:path/win32';
1717
import { fileURLToPath } from 'node:url';
@@ -43,15 +43,32 @@ export async function lint(): Promise<{
4343
let oxlintTsgolintPath = resolve('oxlint-tsgolint/bin/tsgolint');
4444
if (process.platform === 'win32') {
4545
// On Windows, try .exe first (bun creates .exe), then .cmd (npm/pnpm/yarn create .cmd)
46-
const localBinDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'node_modules', '.bin');
46+
const scriptDir = dirname(fileURLToPath(import.meta.url));
47+
const localBinDir = join(scriptDir, '..', 'node_modules', '.bin');
4748
const cwdBinDir = join(process.cwd(), 'node_modules', '.bin');
4849
oxlintTsgolintPath =
4950
[
5051
join(localBinDir, 'tsgolint.exe'),
5152
join(localBinDir, 'tsgolint.cmd'),
5253
join(cwdBinDir, 'tsgolint.exe'),
5354
join(cwdBinDir, 'tsgolint.cmd'),
54-
].find((p) => existsSync(p)) ?? join(cwdBinDir, 'tsgolint.cmd');
55+
].find((p) => existsSync(p)) ?? '';
56+
// Bun stores packages in .bun/ cache dirs where the symlinked paths above won't match.
57+
if (!oxlintTsgolintPath) {
58+
try {
59+
const realPkgDir = realpathSync(join(scriptDir, '..'));
60+
const realBinDir = join(dirname(realPkgDir), '.bin');
61+
oxlintTsgolintPath =
62+
[join(realBinDir, 'tsgolint.exe'), join(realBinDir, 'tsgolint.cmd')].find((p) =>
63+
existsSync(p),
64+
) ?? '';
65+
} catch {
66+
// realpath failed, fall through to default
67+
}
68+
}
69+
if (!oxlintTsgolintPath) {
70+
oxlintTsgolintPath = join(cwdBinDir, 'tsgolint.cmd');
71+
}
5572
const relativePath = relative(process.cwd(), oxlintTsgolintPath);
5673
// Only prepend .\ if it's actually a relative path (not an absolute path returned by relative())
5774
oxlintTsgolintPath = /^[a-zA-Z]:/.test(relativePath) ? relativePath : `.\\${relativePath}`;

0 commit comments

Comments
 (0)