Remove runtime mode selection & have unified setup flow for node.js + API access

This commit is contained in:
Will Chen
2025-04-17 16:52:20 -07:00
parent ba3c9f7a28
commit 24c1be224b
12 changed files with 377 additions and 674 deletions

View File

@@ -209,43 +209,6 @@ async function executeAppLocalNode({
});
}
function checkCommandExists(command: string): Promise<string | null> {
return new Promise((resolve) => {
let output = "";
const process = spawn(command, ["--version"], {
shell: true,
stdio: ["ignore", "pipe", "pipe"], // ignore stdin, pipe stdout/stderr
});
process.stdout?.on("data", (data) => {
output += data.toString();
});
process.stderr?.on("data", (data) => {
// Log stderr but don't treat it as a failure unless the exit code is non-zero
console.warn(
`Stderr from "${command} --version": ${data.toString().trim()}`
);
});
process.on("error", (error) => {
console.error(`Error executing command "${command}":`, error.message);
resolve(null); // Command execution failed
});
process.on("close", (code) => {
if (code === 0) {
resolve(output.trim()); // Command succeeded, return trimmed output
} else {
console.error(
`Command "${command} --version" failed with code ${code}`
);
resolve(null); // Command failed
}
});
});
}
// Helper to kill process on a specific port (cross-platform, using kill-port)
async function killProcessOnPort(port: number): Promise<void> {
try {
@@ -256,21 +219,6 @@ async function killProcessOnPort(port: number): Promise<void> {
}
export function registerAppHandlers() {
ipcMain.handle(
"nodejs-status",
async (): Promise<{
nodeVersion: string | null;
npmVersion: string | null;
}> => {
// Run checks in parallel
const [nodeVersion, npmVersion] = await Promise.all([
checkCommandExists("node"),
checkCommandExists("npm"),
]);
return { nodeVersion, npmVersion };
}
);
ipcMain.handle(
"get-app-sandbox-config",
async (_, { appId }: { appId: number }): Promise<SandboxConfig> => {

View File

@@ -0,0 +1,136 @@
import { ipcMain } from "electron";
import { spawn } from "child_process";
import { platform } from "os";
import { InstallNodeResult } from "../ipc_types";
type ShellResult =
| {
success: true;
output: string;
}
| {
success: false;
errorMessage: string;
};
function runShell(command: string): Promise<ShellResult> {
return new Promise((resolve) => {
let stdout = "";
let stderr = "";
const process = spawn(command, {
shell: true,
stdio: ["ignore", "pipe", "pipe"], // ignore stdin, pipe stdout/stderr
});
process.stdout?.on("data", (data) => {
stdout += data.toString();
});
process.stderr?.on("data", (data) => {
stderr += data.toString();
});
process.on("error", (error) => {
console.error(`Error executing command "${command}":`, error.message);
resolve({ success: false, errorMessage: error.message });
});
process.on("close", (code) => {
if (code === 0) {
resolve({ success: true, output: stdout.trim() });
} else {
const errorMessage =
stderr.trim() || `Command failed with code ${code}`;
console.error(
`Command "${command}" failed with code ${code}: ${stderr.trim()}`
);
resolve({ success: false, errorMessage });
}
});
});
}
function checkCommandExists(command: string): Promise<string | null> {
return new Promise((resolve) => {
let output = "";
const process = spawn(command, ["--version"], {
shell: true,
stdio: ["ignore", "pipe", "pipe"], // ignore stdin, pipe stdout/stderr
});
process.stdout?.on("data", (data) => {
output += data.toString();
});
process.stderr?.on("data", (data) => {
// Log stderr but don't treat it as a failure unless the exit code is non-zero
console.warn(
`Stderr from "${command} --version": ${data.toString().trim()}`
);
});
process.on("error", (error) => {
console.error(`Error executing command "${command}":`, error.message);
resolve(null); // Command execution failed
});
process.on("close", (code) => {
if (code === 0) {
resolve(output.trim()); // Command succeeded, return trimmed output
} else {
console.error(
`Command "${command} --version" failed with code ${code}`
);
resolve(null); // Command failed
}
});
});
}
export function registerNodeHandlers() {
ipcMain.handle(
"nodejs-status",
async (): Promise<{
nodeVersion: string | null;
npmVersion: string | null;
}> => {
// Run checks in parallel
const [nodeVersion, npmVersion] = await Promise.all([
checkCommandExists("node"),
checkCommandExists("npm"),
]);
return { nodeVersion, npmVersion };
}
);
ipcMain.handle("install-node", async (): Promise<InstallNodeResult> => {
console.log("Installing Node.js...");
if (platform() === "win32") {
let result = await runShell("winget install Volta.Volta");
if (!result.success) {
return { success: false, errorMessage: result.errorMessage };
}
} else {
let result = await runShell("curl https://get.volta.sh | bash");
if (!result.success) {
return { success: false, errorMessage: result.errorMessage };
}
}
console.log("Installed Volta");
process.env.PATH = ["~/.volta/bin", process.env.PATH].join(":");
console.log("Updated PATH");
let result = await runShell("volta install node");
if (!result.success) {
return { success: false, errorMessage: result.errorMessage };
}
console.log("Installed Node.js (via Volta)");
result = await runShell("node --version");
if (!result.success) {
return { success: false, errorMessage: result.errorMessage };
}
console.log("Node.js is setup with version");
return { success: true, version: result.output };
});
}

View File

@@ -13,6 +13,7 @@ import type {
ChatStreamParams,
CreateAppParams,
CreateAppResult,
InstallNodeResult,
ListAppsResponse,
SandboxConfig,
Version,
@@ -520,6 +521,17 @@ export class IpcClient {
}
}
// Install Node.js and npm
public async installNode(): Promise<InstallNodeResult> {
try {
const result = await this.ipcRenderer.invoke("install-node");
return result;
} catch (error) {
showError(error);
throw error;
}
}
// --- GitHub Device Flow ---
public startGithubDeviceFlow(appId: number | null): void {
this.ipcRenderer.invoke("github:start-flow", { appId });

View File

@@ -5,6 +5,7 @@ import { registerSettingsHandlers } from "./handlers/settings_handlers";
import { registerShellHandlers } from "./handlers/shell_handler";
import { registerDependencyHandlers } from "./handlers/dependency_handlers";
import { registerGithubHandlers } from "./handlers/github_handlers";
import { registerNodeHandlers } from "./handlers/node_handlers";
export function registerIpcHandlers() {
// Register all IPC handlers by category
@@ -15,4 +16,5 @@ export function registerIpcHandlers() {
registerShellHandlers();
registerDependencyHandlers();
registerGithubHandlers();
registerNodeHandlers();
}

View File

@@ -65,3 +65,13 @@ export interface SandboxConfig {
dependencies: Record<string, string>;
entry: string;
}
export type InstallNodeResult =
| {
success: true;
version: string;
}
| {
success: false;
errorMessage: string;
};