diff --git a/packages/lsp/src/server/dart.ts b/packages/lsp/src/server/dart.ts index b942f8d..051aea1 100644 --- a/packages/lsp/src/server/dart.ts +++ b/packages/lsp/src/server/dart.ts @@ -111,13 +111,47 @@ async function setupDartDependencies(platformId: PlatformId): Promise { + // Bun.which defaults to the PATH captured at startup; follow the current environment + const systemDart = Bun.which('dart', { PATH: process.env.PATH ?? '' }) + if (!systemDart) { + return undefined + } + + try { + // Run in the project root: version managers resolve the SDK per directory + const proc = Bun.spawn([systemDart, '--version'], { + cwd: root, + stdout: 'ignore', + stderr: 'ignore', + timeout: DART_PROBE_TIMEOUT_MS, + }) + const exitCode = await proc.exited + if (exitCode !== 0) { + log.warn({ systemDart, exitCode, signal: proc.signalCode }, 'System Dart failed --version, trying auto-download') + return undefined + } + } + catch (err) { + log.warn({ systemDart, err }, 'Failed to spawn system Dart for version check, trying auto-download') + return undefined + } + + return systemDart +} + export const DartServer: LSPServerInfo = { id: 'dart', extensions: ['.dart'], root: nearestRoot(['pubspec.yaml', 'pubspec.lock']), async spawn(root) { - // Try system dart first - const systemDart = Bun.which('dart') + const systemDart = await findSystemDart(root) if (systemDart) { try { const proc = spawn(systemDart, ['language-server', '--client-id', 'dora.dart', '--client-version', '1.0'], { diff --git a/packages/lsp/test/unit/server.test.ts b/packages/lsp/test/unit/server.test.ts index 1870213..1749aaa 100644 --- a/packages/lsp/test/unit/server.test.ts +++ b/packages/lsp/test/unit/server.test.ts @@ -18,6 +18,7 @@ import { TypescriptServer, VueServer, } from '../../src/server' +import { findSystemDart } from '../../src/server/dart' describe('LSP_SERVERS', () => { test('contains expected servers', () => { @@ -398,6 +399,48 @@ describe('DartServer', () => { await fs.rm(tempDir, { recursive: true, force: true }) } }) + + describe.skipIf(process.platform === 'win32')('findSystemDart', () => { + async function findSystemDartWith(dartScript: string, rootFiles: string[] = []): Promise<{ result: string | undefined, fakeDart: string }> { + const binDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dart-bin-')) + const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dart-root-')) + const savedPath = process.env.PATH + try { + const fakeDart = path.join(binDir, 'dart') + await fs.writeFile(fakeDart, dartScript, { mode: 0o755 }) + for (const file of rootFiles) { + await fs.writeFile(path.join(rootDir, file), '') + } + process.env.PATH = binDir + return { result: await findSystemDart(rootDir), fakeDart } + } + finally { + process.env.PATH = savedPath + await fs.rm(binDir, { recursive: true, force: true }) + await fs.rm(rootDir, { recursive: true, force: true }) + } + } + + test('ignores a dart on PATH that fails --version', async () => { + const { result } = await findSystemDartWith('#!/bin/sh\necho "No version is set for shim: dart" >&2\nexit 1\n') + expect(result).toBeUndefined() + }) + + test('ignores a dart on PATH that cannot be spawned', async () => { + const { result } = await findSystemDartWith('\x7FELF not a real binary') + expect(result).toBeUndefined() + }) + + test('returns a dart on PATH that runs --version', async () => { + const { result, fakeDart } = await findSystemDartWith('#!/bin/sh\nexit 0\n') + expect(result).toBe(fakeDart) + }) + + test('probes --version in the project root', async () => { + const { result, fakeDart } = await findSystemDartWith('#!/bin/sh\n[ -f .tool-versions ] || exit 1\n', ['.tool-versions']) + expect(result).toBe(fakeDart) + }) + }) }) describe('VueServer', () => {