|
1 | 1 | import fs from 'node:fs' |
| 2 | +import { spawn } from 'node:child_process' |
2 | 3 | import * as vscode from 'vscode' |
3 | 4 | import { dirname, relative } from 'pathe' |
| 5 | +import which from 'which' |
4 | 6 | import type { VitestPackage } from './api/pkg' |
5 | 7 | import { log } from './log' |
| 8 | +import { getConfig } from './config' |
6 | 9 |
|
7 | 10 | export function noop() {} |
8 | 11 |
|
@@ -91,3 +94,66 @@ export function waitUntilExists(file: string, timeoutMs = 5000) { |
91 | 94 | }, 50) |
92 | 95 | }) |
93 | 96 | } |
| 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