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. -->
105 lines
3.5 KiB
TypeScript
105 lines
3.5 KiB
TypeScript
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");
|
|
|
|
export function registerNodeHandlers() {
|
|
ipcMain.handle("nodejs-status", async (): Promise<NodeSystemInfo> => {
|
|
logger.log(
|
|
"handling ipc: nodejs-status for platform:",
|
|
platform(),
|
|
"and arch:",
|
|
arch(),
|
|
);
|
|
// Run checks in parallel
|
|
const [nodeVersion, pnpmVersion] = await Promise.all([
|
|
runShellCommand("node --version"),
|
|
// First, check if pnpm is installed.
|
|
// If not, try to install it using corepack.
|
|
// If both fail, then pnpm is not available.
|
|
runShellCommand(
|
|
"pnpm --version || (corepack enable pnpm && pnpm --version) || (npm install -g pnpm@latest-10 && pnpm --version)",
|
|
),
|
|
]);
|
|
// Default to mac download url.
|
|
let nodeDownloadUrl = "https://nodejs.org/dist/v22.14.0/node-v22.14.0.pkg";
|
|
if (platform() == "win32") {
|
|
if (arch() === "arm64" || arch() === "arm") {
|
|
nodeDownloadUrl =
|
|
"https://nodejs.org/dist/v22.14.0/node-v22.14.0-arm64.msi";
|
|
} else {
|
|
// x64 is the most common architecture for Windows so it's the
|
|
// default download url.
|
|
nodeDownloadUrl =
|
|
"https://nodejs.org/dist/v22.14.0/node-v22.14.0-x64.msi";
|
|
}
|
|
}
|
|
return { nodeVersion, pnpmVersion, nodeDownloadUrl };
|
|
});
|
|
|
|
ipcMain.handle("reload-env-path", async (): Promise<void> => {
|
|
logger.debug("Reloading env path, previously:", process.env.PATH);
|
|
if (platform() === "win32") {
|
|
const newPath = execSync("cmd /c echo %PATH%", {
|
|
encoding: "utf8",
|
|
}).trim();
|
|
process.env.PATH = newPath;
|
|
} 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 };
|
|
});
|
|
}
|