Skip to content

Commit ae9ed31

Browse files
committed
fix: delay looking for node until PATH is set
1 parent cf34661 commit ae9ed31

File tree

3 files changed

+77
-7
lines changed

3 files changed

+77
-7
lines changed

src/api/child_process.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { createServer } from 'node:http'
66
import getPort from 'get-port'
77
import type WebSocket from 'ws'
88
import { WebSocketServer } from 'ws'
9-
import { formatPkg, showVitestError } from '../utils'
9+
import { findNode, formatPkg, showVitestError } from '../utils'
1010
import { createErrorLogger, log } from '../log'
1111
import { getConfig } from '../config'
1212
import { workerPath } from '../constants'
@@ -32,14 +32,15 @@ export async function createVitestProcess(pkg: VitestPackage) {
3232
]
3333
: runtimeArgs
3434
const arvString = execArgv.join(' ')
35-
const script = `node ${arvString ? `${arvString} ` : ''}${workerPath}`.trim()
35+
const executable = await findNode(pkg.cwd)
36+
const script = `${executable} ${arvString ? `${arvString} ` : ''}${workerPath}`.trim()
3637
log.info('[API]', `Running ${formatPkg(pkg)} with "${script}"`)
3738
const logLevel = getConfig(pkg.folder).logLevel
3839
const port = await getPort()
3940
const server = createServer().listen(port).unref()
4041
const wss = new WebSocketServer({ server })
4142
const wsAddress = `ws://localhost:${port}`
42-
const vitest = spawn(getConfig(pkg.folder).nodeExecutable || 'node', [...execArgv, workerPath], {
43+
const vitest = spawn(executable, [...execArgv, workerPath], {
4344
env: {
4445
...process.env,
4546
...env,

src/debug.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { workerPath } from './constants'
1414
import type { WsConnectionMetadata } from './api/ws'
1515
import { waitForWsConnection } from './api/ws'
1616
import type { ExtensionWorkerProcess } from './api/types'
17+
import { findNode } from './utils'
1718

1819
export async function debugTests(
1920
controller: vscode.TestController,
@@ -172,8 +173,9 @@ async function getRuntimeOptions(pkg: VitestPackage) {
172173
]
173174
: runtimeArgs
174175
if (config.shellType === 'child_process') {
176+
const executable = await findNode(pkg.cwd)
175177
return {
176-
runtimeExecutable: config.nodeExecutable || 'node',
178+
runtimeExecutable: executable,
177179
runtimeArgs: execArgv,
178180
}
179181
}
@@ -197,17 +199,18 @@ class ExtensionDebugProcess implements ExtensionWorkerProcess {
197199
this._stopped = new Promise((resolve) => {
198200
const { dispose } = vscode.debug.onDidTerminateDebugSession((terminatedSession) => {
199201
if (session === terminatedSession) {
200-
dispose()
201-
resolve()
202202
this._onDidExit.fire()
203203
this._onDidExit.dispose()
204204
this.closed = true
205+
resolve()
206+
dispose()
205207
}
206208
})
207209
})
208210
// if websocket connection stopped working, close the debug session
209-
// otherwise it might hand indefinitely
211+
// otherwise it might hang indefinitely
210212
ws.on('close', () => {
213+
this.closed = true
211214
this.close()
212215
})
213216
}

src/utils.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import fs from 'node:fs'
2+
import { spawn } from 'node:child_process'
23
import * as vscode from 'vscode'
34
import { dirname, relative } from 'pathe'
5+
import which from 'which'
46
import type { VitestPackage } from './api/pkg'
57
import { log } from './log'
8+
import { getConfig } from './config'
69

710
export function noop() {}
811

@@ -91,3 +94,66 @@ export function waitUntilExists(file: string, timeoutMs = 5000) {
9194
}, 50)
9295
})
9396
}
97+
98+
let pathToNodeJS: string | undefined
99+
100+
// based on https://github.com/microsoft/playwright-vscode/blob/main/src/utils.ts#L144
101+
export async function findNode(cwd: string): Promise<string> {
102+
if (getConfig().nodeExecutable)
103+
// if empty string, keep as undefined
104+
pathToNodeJS = getConfig().nodeExecutable || undefined
105+
106+
if (pathToNodeJS)
107+
return pathToNodeJS
108+
109+
// Stage 1: Try to find Node.js via process.env.PATH
110+
let node: string | null = await which('node', { nothrow: true })
111+
// Stage 2: When extension host boots, it does not have the right env set, so we might need to wait.
112+
for (let i = 0; i < 5 && !node; ++i) {
113+
await new Promise(f => setTimeout(f, 200))
114+
node = await which('node', { nothrow: true })
115+
}
116+
// Stage 3: If we still haven't found Node.js, try to find it via a subprocess.
117+
// This evaluates shell rc/profile files and makes nvm work.
118+
node ??= await findNodeViaShell(cwd)
119+
120+
if (!node) {
121+
const msg = `Unable to find 'node' executable.\nMake sure to have Node.js installed and available in your PATH.\nCurrent PATH: '${process.env.PATH}'.`
122+
log.error(msg)
123+
throw new Error(msg)
124+
}
125+
pathToNodeJS = node
126+
return node
127+
}
128+
129+
async function findNodeViaShell(cwd: string): Promise<string | null> {
130+
if (process.platform === 'win32')
131+
return null
132+
return new Promise<string | null>((resolve) => {
133+
const startToken = '___START_SHELL__'
134+
const endToken = '___END_SHELL__'
135+
try {
136+
const childProcess = spawn(`${vscode.env.shell} -i -c 'if [[ $(type node 2>/dev/null) == *function* ]]; then node --version; fi; echo ${startToken} && which node && echo ${endToken}'`, {
137+
stdio: 'pipe',
138+
shell: true,
139+
cwd,
140+
})
141+
let output = ''
142+
childProcess.stdout.on('data', data => output += data.toString())
143+
childProcess.on('error', () => resolve(null))
144+
childProcess.on('exit', (exitCode) => {
145+
if (exitCode !== 0)
146+
return resolve(null)
147+
const start = output.indexOf(startToken)
148+
const end = output.indexOf(endToken)
149+
if (start === -1 || end === -1)
150+
return resolve(null)
151+
return resolve(output.substring(start + startToken.length, end).trim())
152+
})
153+
}
154+
catch (e) {
155+
log.error('[SPAWN]', vscode.env.shell, e)
156+
resolve(null)
157+
}
158+
})
159+
}

0 commit comments

Comments
 (0)