feat: allow manual Node.js path configuration (#1577)

Add ability to manually configure Node.js path for users who have
Node.js
installed but not in their system PATH.

Features:
- Browse and select custom Node.js installation folder
- Visual status indicator showing Node.js version or "Not found"
- Reset to system default PATH option
- Manual configuration option in setup banner
- Real-time Node.js status checking

closes #1050

    
<!-- This is an auto-generated description by cubic. -->
---

## Summary by cubic
Adds manual Node.js path configuration so the app works even when Node
isn’t on PATH, fulfilling #1050. Users can browse to their install,
reset to default, and see real-time status in Settings and during setup.

- New Features
- Settings: NodePathSelector to browse a Node.js folder, show
version/“Not found” status, and reset to system PATH (persists
customNodePath).
- Setup banner: manual config flow with a folder picker if Node is
already installed.
- IPC: select-node-folder, set-node-path, get-node-path; reloads env and
prepends custom path to PATH.
- Real-time Node.js status check with visual indicator (CheckCircle on
valid).
  - E2E tests for browse, reset, and valid-status display.

<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Adeniji Adekunle James
2025-10-21 01:18:29 +01:00
committed by GitHub
parent cc435d1ed4
commit b1095b7951
9 changed files with 379 additions and 1 deletions

View File

@@ -1,10 +1,13 @@
import { ipcMain } from "electron";
import { ipcMain, dialog } from "electron";
import { execSync } from "child_process";
import { platform, arch } from "os";
import { NodeSystemInfo } from "../ipc_types";
import fixPath from "fix-path";
import { runShellCommand } from "../utils/runShellCommand";
import log from "electron-log";
import { existsSync } from "fs";
import { join } from "path";
import { readSettings } from "../../main/settings";
const logger = log.scope("node_handlers");
@@ -52,6 +55,50 @@ export function registerNodeHandlers() {
} else {
fixPath();
}
const settings = readSettings();
if (settings.customNodePath) {
const separator = platform() === "win32" ? ";" : ":";
process.env.PATH = `${settings.customNodePath}${separator}${process.env.PATH}`;
logger.debug(
"Added custom Node.js path to PATH:",
settings.customNodePath,
);
}
logger.debug("Reloaded env path, now:", process.env.PATH);
});
ipcMain.handle("select-node-folder", async () => {
const result = await dialog.showOpenDialog({
title: "Select Node.js Installation Folder",
properties: ["openDirectory"],
message: "Select the folder where Node.js is installed",
});
if (result.canceled) {
return { path: null, canceled: true, selectedPath: null };
}
if (!result.filePaths[0]) {
return { path: null, canceled: false, selectedPath: null };
}
const selectedPath = result.filePaths[0];
// Verify Node.js exists in selected path
const nodeBinary = platform() === "win32" ? "node.exe" : "node";
const nodePath = join(selectedPath, nodeBinary);
if (!existsSync(nodePath)) {
// Check bin subdirectory (common on Unix systems)
const binPath = join(selectedPath, "bin", nodeBinary);
if (existsSync(binPath)) {
return {
path: join(selectedPath, "bin"),
canceled: false,
selectedPath,
};
}
return { path: null, canceled: false, selectedPath };
}
return { path: selectedPath, canceled: false, selectedPath };
});
}