refactor app handler

This commit is contained in:
Will Chen
2025-04-11 14:31:23 -07:00
parent d3c1f8e34c
commit 37579d011a
2 changed files with 83 additions and 132 deletions

View File

@@ -26,6 +26,83 @@ import {
import { ALLOWED_ENV_VARS } from "../../constants/models"; import { ALLOWED_ENV_VARS } from "../../constants/models";
import { getEnvVar } from "../utils/read_env"; import { getEnvVar } from "../utils/read_env";
async function executeApp({
appPath,
appId,
event,
}: {
appPath: string;
appId: number;
event: Electron.IpcMainInvokeEvent;
}): Promise<{ processId: number }> {
const process = spawn("npm install && npm run dev", [], {
cwd: appPath,
shell: true,
stdio: "pipe", // Ensure stdio is piped so we can capture output/errors and detect close
detached: false, // Ensure child process is attached to the main process lifecycle unless explicitly backgrounded
});
// Check if process spawned correctly
if (!process.pid) {
// Attempt to capture any immediate errors if possible
let errorOutput = "";
process.stderr?.on("data", (data) => (errorOutput += data));
await new Promise((resolve) => process.on("error", resolve)); // Wait for error event
throw new Error(
`Failed to spawn process for app ${appId}. Error: ${
errorOutput || "Unknown spawn error"
}`
);
}
// Increment the counter and store the process reference with its ID
const currentProcessId = processCounter.increment();
runningApps.set(appId, { process, processId: currentProcessId });
// Log output
process.stdout?.on("data", (data) => {
console.log(
`App ${appId} (PID: ${process.pid}) stdout: ${data.toString().trim()}`
);
event.sender.send("app:output", {
type: "stdout",
message: data.toString().trim(),
appId: appId,
});
});
process.stderr?.on("data", (data) => {
console.error(
`App ${appId} (PID: ${process.pid}) stderr: ${data.toString().trim()}`
);
event.sender.send("app:output", {
type: "stderr",
message: data.toString().trim(),
appId: appId,
});
});
// Handle process exit/close
process.on("close", (code, signal) => {
console.log(
`App ${appId} (PID: ${process.pid}) process closed with code ${code}, signal ${signal}.`
);
removeAppIfCurrentProcess(appId, process);
});
// Handle errors during process lifecycle (e.g., command not found)
process.on("error", (err) => {
console.error(
`Error in app ${appId} (PID: ${process.pid}) process: ${err.message}`
);
removeAppIfCurrentProcess(appId, process);
// Note: We don't throw here as the error is asynchronous. The caller got a success response already.
// Consider adding ipcRenderer event emission to notify UI of the error.
});
return { processId: currentProcessId };
}
export function registerAppHandlers() { export function registerAppHandlers() {
ipcMain.handle("create-app", async (_, params: CreateAppParams) => { ipcMain.handle("create-app", async (_, params: CreateAppParams) => {
const appPath = params.name; const appPath = params.name;
@@ -197,74 +274,7 @@ export function registerAppHandlers() {
const appPath = getDyadAppPath(app.path); const appPath = getDyadAppPath(app.path);
console.log("appPath-CWD", appPath); console.log("appPath-CWD", appPath);
try { try {
const process = spawn("npm install && npm run dev", [], { const currentProcessId = await executeApp({ appPath, appId, event });
cwd: appPath,
shell: true,
stdio: "pipe", // Ensure stdio is piped so we can capture output/errors and detect close
detached: false, // Ensure child process is attached to the main process lifecycle unless explicitly backgrounded
});
// Check if process spawned correctly
if (!process.pid) {
// Attempt to capture any immediate errors if possible
let errorOutput = "";
process.stderr?.on("data", (data) => (errorOutput += data));
await new Promise((resolve) => process.on("error", resolve)); // Wait for error event
throw new Error(
`Failed to spawn process for app ${appId}. Error: ${
errorOutput || "Unknown spawn error"
}`
);
}
// Increment the counter and store the process reference with its ID
const currentProcessId = processCounter.increment();
runningApps.set(appId, { process, processId: currentProcessId });
// Log output
process.stdout?.on("data", (data) => {
console.log(
`App ${appId} (PID: ${process.pid}) stdout: ${data
.toString()
.trim()}`
);
event.sender.send("app:output", {
type: "stdout",
message: data.toString().trim(),
appId: appId,
});
});
process.stderr?.on("data", (data) => {
console.error(
`App ${appId} (PID: ${process.pid}) stderr: ${data
.toString()
.trim()}`
);
event.sender.send("app:output", {
type: "stderr",
message: data.toString().trim(),
appId: appId,
});
});
// Handle process exit/close
process.on("close", (code, signal) => {
console.log(
`App ${appId} (PID: ${process.pid}) process closed with code ${code}, signal ${signal}.`
);
removeAppIfCurrentProcess(appId, process);
});
// Handle errors during process lifecycle (e.g., command not found)
process.on("error", (err) => {
console.error(
`Error in app ${appId} (PID: ${process.pid}) process: ${err.message}`
);
removeAppIfCurrentProcess(appId, process);
// Note: We don't throw here as the error is asynchronous. The caller got a success response already.
// Consider adding ipcRenderer event emission to notify UI of the error.
});
return { success: true, processId: currentProcessId }; return { success: true, processId: currentProcessId };
} catch (error: any) { } catch (error: any) {
@@ -369,67 +379,7 @@ export function registerAppHandlers() {
const appPath = getDyadAppPath(app.path); const appPath = getDyadAppPath(app.path);
console.debug(`Starting app ${appId} in path ${app.path}`); console.debug(`Starting app ${appId} in path ${app.path}`);
const process = spawn("npm install && npm run dev", [], { const currentProcessId = await executeApp({ appPath, appId, event });
cwd: appPath,
shell: true,
stdio: "pipe",
detached: false,
});
if (!process.pid) {
let errorOutput = "";
process.stderr?.on("data", (data) => (errorOutput += data));
await new Promise((resolve) => process.on("error", resolve));
throw new Error(
`Failed to spawn process for app ${appId}. Error: ${
errorOutput || "Unknown spawn error"
}`
);
}
const currentProcessId = processCounter.increment();
runningApps.set(appId, { process, processId: currentProcessId });
// Set up output handlers
process.stdout?.on("data", (data) => {
console.log(
`App ${appId} (PID: ${process.pid}) stdout: ${data
.toString()
.trim()}`
);
event.sender.send("app:output", {
type: "stdout",
message: data.toString().trim(),
appId: appId,
});
});
process.stderr?.on("data", (data) => {
console.error(
`App ${appId} (PID: ${process.pid}) stderr: ${data
.toString()
.trim()}`
);
event.sender.send("app:output", {
type: "stderr",
message: data.toString().trim(),
appId: appId,
});
});
process.on("close", (code, signal) => {
console.log(
`App ${appId} (PID: ${process.pid}) process closed with code ${code}, signal ${signal}.`
);
removeAppIfCurrentProcess(appId, process);
});
process.on("error", (err) => {
console.error(
`Error in app ${appId} (PID: ${process.pid}) process: ${err.message}`
);
removeAppIfCurrentProcess(appId, process);
});
return { success: true, processId: currentProcessId }; return { success: true, processId: currentProcessId };
} catch (error) { } catch (error) {

View File

@@ -90,7 +90,7 @@ export class IpcClient {
console.debug("chat:response:end"); console.debug("chat:response:end");
this.chatStreams.delete(chatId); this.chatStreams.delete(chatId);
} else { } else {
showError( console.error(
new Error(`[IPC] No callbacks found for chat ${chatId} on stream end`) new Error(`[IPC] No callbacks found for chat ${chatId} on stream end`)
); );
} }
@@ -178,7 +178,8 @@ export class IpcClient {
}); });
return content as string; return content as string;
} catch (error) { } catch (error) {
showError(error); // No toast because sometimes the file will disappear.
console.error(error);
throw error; throw error;
} }
} }
@@ -237,7 +238,7 @@ export class IpcClient {
if (callbacks) { if (callbacks) {
this.chatStreams.delete(chatId); this.chatStreams.delete(chatId);
} else { } else {
showError(new Error("Tried canceling chat that doesn't exist")); console.error("Tried canceling chat that doesn't exist");
} }
} }