diff --git a/editors/vscode/client/ConfigService.ts b/editors/vscode/client/ConfigService.ts index 11a22c7908879..2f4bf416712a1 100644 --- a/editors/vscode/client/ConfigService.ts +++ b/editors/vscode/client/ConfigService.ts @@ -144,7 +144,30 @@ export class ConfigService implements IDisposable { settingsBinary = this.removeWindowsLeadingSlash(settingsBinary); } - return settingsBinary; + if (process.platform !== "win32" && settingsBinary.endsWith(".exe")) { + // on non-Windows, remove `.exe` extension if present + settingsBinary = settingsBinary.slice(0, -4); + } + + try { + await workspace.fs.stat(Uri.file(settingsBinary)); + return settingsBinary; + } catch {} + + // on Windows, also check for `.exe` extension (bun uses `.exe` for its binaries) + if (process.platform === "win32") { + if (!settingsBinary.endsWith(".exe")) { + settingsBinary += ".exe"; + } + + try { + await workspace.fs.stat(Uri.file(settingsBinary)); + return settingsBinary; + } catch {} + } + + // no valid binary found + return undefined; } /** @@ -178,15 +201,32 @@ export class ConfigService implements IDisposable { // not found, continue to glob search } + // on Windows, also check for `.exe` extension + if (process.platform === "win32") { + const binPathExe = `${binPath}.exe`; + try { + await workspace.fs.stat(Uri.file(binPathExe)); + return binPathExe; + } catch { + // not found, continue to glob search + } + } + const cts = new CancellationTokenSource(); setTimeout(() => cts.cancel(), 10000); // cancel after 10 seconds try { + // bun package manager uses `.exe` extension on Windows + // search for both with and without `.exe` extension + const extension = process.platform === "win32" ? "{,.exe}" : ""; // fallback: search with glob // maybe use `tinyglobby` later for better performance, VSCode can be slow on globbing large projects. const files = await workspace.findFiles( - // search up to 3 levels deep - new RelativePattern(workspacePath, `{*/,*/*,*/*/*}/node_modules/.bin/${binaryName}`), + // search up to 3 levels deep for the binary path + new RelativePattern( + workspacePath, + `{*/,*/*,*/*/*}/node_modules/.bin/${binaryName}${extension}`, + ), undefined, 1, cts.token, diff --git a/editors/vscode/tests/unit/ConfigService.spec.ts b/editors/vscode/tests/unit/ConfigService.spec.ts index 4a6aadce646ec..d6c824c3c878c 100644 --- a/editors/vscode/tests/unit/ConfigService.spec.ts +++ b/editors/vscode/tests/unit/ConfigService.spec.ts @@ -29,31 +29,62 @@ suite('ConfigService', () => { return workspace_path; }; + const createWorkspaceFolderFileUri = async (relativePath: string) => { + const workspace_path = getWorkspaceFolderPlatformSafe(); + const path = process.platform === 'win32' + ? `${workspace_path}\\${relativePath}` + : `${workspace_path}/${relativePath}`; + + await workspace.fs.writeFile( + WORKSPACE_FOLDER.uri.with({ path }), + new Uint8Array(), + ); + } + + const deleteWorkspaceFolderFileUri = async (relativePath: string) => { + const workspace_path = getWorkspaceFolderPlatformSafe(); + const path = process.platform === 'win32' + ? `${workspace_path}\\${relativePath}` + : `${workspace_path}/${relativePath}`; + + await workspace.fs.delete( + WORKSPACE_FOLDER.uri.with({ path }), + ); + } + suite('getOxfmtServerBinPath', () => { test('resolves relative server path with workspace folder', async () => { const service = new ConfigService(); + const workspace_path = getWorkspaceFolderPlatformSafe(); const nonDefinedServerPath = await service.getOxfmtServerBinPath(); + await createWorkspaceFolderFileUri('absolute/oxfmt'); + await createWorkspaceFolderFileUri('relative/oxfmt'); + strictEqual(nonDefinedServerPath, undefined); - await conf.update('path.oxfmt', '/absolute/oxfmt'); + await conf.update('path.oxfmt', `${workspace_path}/absolute/oxfmt`); const absoluteServerPath = await service.getOxfmtServerBinPath(); - strictEqual(absoluteServerPath, '/absolute/oxfmt'); + strictEqual(absoluteServerPath, `${workspace_path}/absolute/oxfmt`); await conf.update('path.oxfmt', './relative/oxfmt'); const relativeServerPath = await service.getOxfmtServerBinPath(); - const workspace_path = getWorkspaceFolderPlatformSafe(); strictEqual(relativeServerPath, `${workspace_path}/relative/oxfmt`); + + await deleteWorkspaceFolderFileUri('absolute/oxfmt'); + await deleteWorkspaceFolderFileUri('relative/oxfmt'); }); test('returns undefined for unsafe server path', async () => { + await createWorkspaceFolderFileUri('../unsafe/oxfmt'); const service = new ConfigService(); await conf.update('path.oxfmt', '../unsafe/oxfmt'); const unsafeServerPath = await service.getOxfmtServerBinPath(); strictEqual(unsafeServerPath, undefined); + await deleteWorkspaceFolderFileUri('../unsafe/oxfmt'); }); test('returns backslashes path on Windows', async () => { @@ -74,27 +105,36 @@ suite('ConfigService', () => { test('resolves relative server path with workspace folder', async () => { const service = new ConfigService(); const nonDefinedServerPath = await service.getOxlintServerBinPath(); + const workspace_path = getWorkspaceFolderPlatformSafe(); + + await createWorkspaceFolderFileUri('absolute/oxlint'); + await createWorkspaceFolderFileUri('relative/oxlint'); strictEqual(nonDefinedServerPath, undefined); - await conf.update('path.oxlint', '/absolute/oxlint'); + await conf.update('path.oxlint', `${workspace_path}/absolute/oxlint`); const absoluteServerPath = await service.getOxlintServerBinPath(); - strictEqual(absoluteServerPath, '/absolute/oxlint'); + strictEqual(absoluteServerPath, `${workspace_path}/absolute/oxlint`); await conf.update('path.oxlint', './relative/oxlint'); const relativeServerPath = await service.getOxlintServerBinPath(); - const workspace_path = getWorkspaceFolderPlatformSafe(); strictEqual(relativeServerPath, `${workspace_path}/relative/oxlint`); + + + await deleteWorkspaceFolderFileUri('absolute/oxlint'); + await deleteWorkspaceFolderFileUri('relative/oxlint'); }); test('returns undefined for unsafe server path', async () => { + await createWorkspaceFolderFileUri('../unsafe/oxlint'); const service = new ConfigService(); await conf.update('path.oxlint', '../unsafe/oxlint'); const unsafeServerPath = await service.getOxlintServerBinPath(); strictEqual(unsafeServerPath, undefined); + await deleteWorkspaceFolderFileUri('../unsafe/oxlint'); }); test('returns backslashes path on Windows', async () => {