Skip to content

Automaticlly detect node version #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 67 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"properties": {
"nodejs-testing.nodejsPath": {
"type": "string",
"default": "node",
"description": "Path to the Node.js binary used to run tests"
"default": null,
"description": "Path to the Node.js binary used to run tests (default is the one on the PATH in the workspace directory)."
},
"nodejs-testing.nodejsParameters": {
"type": "array",
Expand Down Expand Up @@ -195,6 +195,7 @@
"@types/sinon": "^17.0.3",
"@types/split2": "^4.2.3",
"@types/vscode": "^1.88.0",
"@types/which": "^3.0.3",
"@types/ws": "^8.5.10",
"@vscode/test-electron": "^2.3.9",
"acorn": "^8.11.3",
Expand Down Expand Up @@ -228,6 +229,7 @@
"pretty-format": "^29.7.0",
"split2": "^4.2.0",
"stacktrace-parser": "^0.1.10",
"which": "^4.0.0",
"ws": "^8.16.0"
}
}
17 changes: 12 additions & 5 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Pretest } from "./pretest";
import { TestRunner } from "./runner";
import { SourceMapStore } from "./source-map-store";
import { Style } from "./styles";
import { findNode } from "./find-node";

export async function activate(context: vscode.ExtensionContext) {
const smStore = new SourceMapStore();
Expand All @@ -18,15 +19,15 @@ export async function activate(context: vscode.ExtensionContext) {
]);

const ctrls = new Map<vscode.WorkspaceFolder, Controller>();
const refreshFolders = () => {
const refreshFolders = async () => {
for (const ctrl of ctrls.values()) {
ctrl.dispose();
}
ctrls.clear();
syncWorkspaceFolders();
await syncWorkspaceFolders();
};

const syncWorkspaceFolders = () => {
const syncWorkspaceFolders = async () => {
if (!extensions.value?.length) {
const msg =
"nodejs-testing.extensions array is empty. Please remove the setting 'nodejs-testing.extensions' or define at least one element.";
Expand All @@ -36,10 +37,16 @@ export async function activate(context: vscode.ExtensionContext) {
const folders = vscode.workspace.workspaceFolders ?? [];
for (const folder of folders) {
if (!ctrls.has(folder)) {

const nodeJsPath = await findNode(folder.uri.fsPath).catch((e) => {
vscode.window.showErrorMessage("nodejs-testing failed to find node path: " + e.message);
return 'node';
});

const runner = new TestRunner(
smStore,
new ConfigValue("concurrency", 0, folder),
new ConfigValue("nodejsPath", "node", folder),
new ConfigValue("nodejsPath", nodeJsPath || "node", folder),
new ConfigValue("verbose", false, folder),
new ConfigValue("style", Style.Spec, folder),
context.extensionUri.fsPath,
Expand Down Expand Up @@ -110,7 +117,7 @@ export async function activate(context: vscode.ExtensionContext) {
new vscode.Disposable(() => ctrls.forEach((c) => c.dispose())),
);

syncWorkspaceFolders();
await syncWorkspaceFolders();
for (const editor of vscode.window.visibleTextEditors) {
syncTextDocument(editor.document);
}
Expand Down
54 changes: 54 additions & 0 deletions src/find-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { spawn } from "node:child_process";
import * as vscode from "vscode";
import which from "which";

const cwdToNodeJs = new Map<string, string>();

// based on https://github.com/microsoft/playwright-vscode/blob/main/src/utils.ts#L144
export async function findNode(cwd: string): Promise<string> {
if (cwdToNodeJs.has(cwd))
return cwdToNodeJs.get(cwd)!;

// Stage 1: Try to find Node.js via process.env.PATH
let node = await which("node").catch(() => undefined);
// 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, 200));
node = await which("node").catch(() => 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(cwd);
if (!node)
throw new Error(`Unable to find 'node' executable.\nMake sure to have Node.js installed and available in your PATH.\nCurrent PATH: '${process.env.PATH}'.`);

cwdToNodeJs.set(cwd, node);
return node;
}

async function findNodeViaShell(cwd: string): Promise<string | undefined> {
if (process.platform === "win32")
return undefined;

return new Promise<string | undefined>((resolve) => {
const startToken = "___START_SHELL__";
const endToken = "___END_SHELL__";
const childProcess = spawn(`${vscode.env.shell} -i -c 'echo ${startToken} && which node && echo ${endToken}'`, {
stdio: "pipe",
shell: true,
cwd,
});
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());
});
});
}