From f0259478b9c418e406a15390a081d0ef13ed03ff Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Tue, 9 Jan 2024 17:57:56 +0100 Subject: [PATCH] fix: evaluate 'which' via shell if Node.js is not in PATH (fallback) (#400) --- src/playwrightTest.ts | 4 ++-- src/reusedBrowser.ts | 2 +- src/traceViewer.ts | 2 +- src/utils.ts | 36 +++++++++++++++++++++++++++++++++--- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/playwrightTest.ts b/src/playwrightTest.ts index defef2b0e..8cca6098e 100644 --- a/src/playwrightTest.ts +++ b/src/playwrightTest.ts @@ -152,7 +152,7 @@ export class PlaywrightTest { // Playwright will restart itself as child process in the ESM mode and won't inherit the 3/4 pipes. // Always use ws transport to mitigate it. const reporterServer = new ReporterServer(this._vscode); - const node = await findNode(); + const node = await findNode(this._vscode); if (token?.isCancellationRequested) return; const configFolder = path.dirname(config.configFile); @@ -269,7 +269,7 @@ export class PlaywrightTest { } private async _runNode(args: string[], cwd: string): Promise { - return await spawnAsync(await findNode(), args, cwd, this._envProvider()); + return await spawnAsync(await findNode(this._vscode), args, cwd, this._envProvider()); } } diff --git a/src/reusedBrowser.ts b/src/reusedBrowser.ts index 391618adb..64ae3ba49 100644 --- a/src/reusedBrowser.ts +++ b/src/reusedBrowser.ts @@ -114,7 +114,7 @@ export class ReusedBrowser implements vscodeTypes.Disposable { return; } - const node = await findNode(); + const node = await findNode(this._vscode); const allArgs = [ config.cli, 'run-server', diff --git a/src/traceViewer.ts b/src/traceViewer.ts index 06da2adaf..335ea420f 100644 --- a/src/traceViewer.ts +++ b/src/traceViewer.ts @@ -62,7 +62,7 @@ export class TraceViewer implements vscodeTypes.Disposable { } private async _startIfNeeded(config: TestConfig, file: string) { - const node = await findNode(); + const node = await findNode(this._vscode); if (this._traceViewerProcess) return; const allArgs = [config.cli, 'show-trace', `--stdin`]; diff --git a/src/utils.ts b/src/utils.ts index 60f8c8a75..4554099e9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -20,6 +20,7 @@ import fs from 'fs'; import path from 'path'; import readline from 'readline'; import which from 'which'; +import * as vscodeTypes from './vscodeTypes'; export function calculateSha1(buffer: Buffer | string): string { const hash = crypto.createHash('sha1'); @@ -138,18 +139,47 @@ export async function resolveSourceMap(file: string, fileToSources: Map { +export async function findNode(vscode: vscodeTypes.VSCode): Promise { if (pathToNodeJS) return pathToNodeJS; + // Stage 1: Try to find Node.js via process.env.PATH let node = await which('node').catch(e => undefined); - // When extension host boots, it does not have the right env set, so we might need to wait. + // Stage 2: When extension host boots, it does not have the right env set, so we might need to wait. for (let i = 0; i < 5 && !node; ++i) { - await new Promise(f => setTimeout(f, 1000)); + await new Promise(f => setTimeout(f, 200)); node = await which('node').catch(e => undefined); } + // Stage 3: If we still haven't found Node.js, try to find it via a subprocess. + // This evaluates shell rc/profile files and makes nvm work. + node ??= await findNodeViaShell(vscode); if (!node) throw new Error('Unable to launch `node`, make sure it is in your PATH'); pathToNodeJS = node; return node; } + +async function findNodeViaShell(vscode: vscodeTypes.VSCode): Promise { + if (process.platform === 'win32') + return undefined; + return new Promise(resolve => { + const startToken = '___START_PW_SHELL__'; + const endToken = '___END_PW_SHELL__'; + const childProcess = spawn(`${vscode.env.shell} -i -c 'echo ${startToken} && which node && echo ${endToken}'`, { + stdio: 'pipe', + shell: true + }); + let output = ''; + childProcess.stdout.on('data', data => output += data.toString()); + childProcess.on('error', () => resolve(undefined)); + childProcess.on('exit', exitCode => { + if (exitCode !== 0) + return resolve(undefined); + const start = output.indexOf(startToken); + const end = output.indexOf(endToken); + if (start === -1 || end === -1) + return resolve(undefined); + return resolve(output.substring(start + startToken.length, end).trim()); + }); + }); +}