From b2eb05a1bc72d6333a1a6246a6e6a8696a8bbeec Mon Sep 17 00:00:00 2001 From: Will Chen Date: Thu, 8 May 2025 23:47:05 -0700 Subject: [PATCH] Simplify error handling for IPC handlers (#119) --- src/ipc/handlers/safe_handle.ts | 24 +++ src/ipc/handlers/version_handlers.ts | 249 ++++++++++++--------------- 2 files changed, 138 insertions(+), 135 deletions(-) create mode 100644 src/ipc/handlers/safe_handle.ts diff --git a/src/ipc/handlers/safe_handle.ts b/src/ipc/handlers/safe_handle.ts new file mode 100644 index 0000000..9e43410 --- /dev/null +++ b/src/ipc/handlers/safe_handle.ts @@ -0,0 +1,24 @@ +import { ipcMain, IpcMainInvokeEvent } from "electron"; +import log from "electron-log"; + +export function createSafeHandler(logger: log.LogFunctions) { + return ( + channel: string, + fn: (event: IpcMainInvokeEvent, ...args: any[]) => Promise, + ) => { + ipcMain.handle( + channel, + async (event: IpcMainInvokeEvent, ...args: any[]) => { + try { + return await fn(event, ...args); + } catch (error) { + logger.error( + `Error in ${fn.name}: args: ${JSON.stringify(args)}`, + error, + ); + throw new Error(`[${channel}] ${error}`); + } + }, + ); + }; +} diff --git a/src/ipc/handlers/version_handlers.ts b/src/ipc/handlers/version_handlers.ts index 8e5cd96..b78c336 100644 --- a/src/ipc/handlers/version_handlers.ts +++ b/src/ipc/handlers/version_handlers.ts @@ -1,4 +1,3 @@ -import { ipcMain } from "electron"; import { db } from "../../db"; import { apps, messages } from "../../db/schema"; import { desc, eq, and, gt } from "drizzle-orm"; @@ -8,15 +7,17 @@ import path from "node:path"; import { getDyadAppPath } from "../../paths/paths"; import git from "isomorphic-git"; import { promises as fsPromises } from "node:fs"; -// Import our utility modules import { withLock } from "../utils/lock_utils"; import { getGitAuthor } from "../utils/git_author"; import log from "electron-log"; +import { createSafeHandler } from "./safe_handle"; const logger = log.scope("version_handlers"); +const handle = createSafeHandler(logger); + export function registerVersionHandlers() { - ipcMain.handle("list-versions", async (_, { appId }: { appId: number }) => { + handle("list-versions", async (_, { appId }: { appId: number }) => { const app = await db.query.apps.findFirst({ where: eq(apps.id, appId), }); @@ -32,26 +33,21 @@ export function registerVersionHandlers() { return []; } - try { - const commits = await git.log({ - fs, - dir: appPath, - // KEEP UP TO DATE WITH ChatHeader.tsx - depth: 10_000, // Limit to last 10_000 commits for performance - }); + const commits = await git.log({ + fs, + dir: appPath, + // KEEP UP TO DATE WITH ChatHeader.tsx + depth: 10_000, // Limit to last 10_000 commits for performance + }); - return commits.map((commit) => ({ - oid: commit.oid, - message: commit.commit.message, - timestamp: commit.commit.author.timestamp, - })) satisfies Version[]; - } catch (error: any) { - logger.error(`Error listing versions for app ${appId}:`, error); - throw new Error(`Failed to list versions: ${error.message}`); - } + return commits.map((commit) => ({ + oid: commit.oid, + message: commit.commit.message, + timestamp: commit.commit.author.timestamp, + })) satisfies Version[]; }); - ipcMain.handle( + handle( "get-current-branch", async (_, { appId }: { appId: number }): Promise => { const app = await db.query.apps.findFirst({ @@ -86,7 +82,7 @@ export function registerVersionHandlers() { }, ); - ipcMain.handle( + handle( "revert-version", async ( _, @@ -106,117 +102,109 @@ export function registerVersionHandlers() { const appPath = getDyadAppPath(app.path); - try { - await git.checkout({ - fs, - dir: appPath, - ref: "main", - force: true, - }); - // Get status matrix comparing the target commit (previousVersionId as HEAD) with current working directory - const matrix = await git.statusMatrix({ - fs, - dir: appPath, - ref: previousVersionId, - }); + await git.checkout({ + fs, + dir: appPath, + ref: "main", + force: true, + }); + // Get status matrix comparing the target commit (previousVersionId as HEAD) with current working directory + const matrix = await git.statusMatrix({ + fs, + dir: appPath, + ref: previousVersionId, + }); - // Process each file to revert to the state in previousVersionId - for (const [filepath, headStatus, workdirStatus] of matrix) { - const fullPath = path.join(appPath, filepath); + // Process each file to revert to the state in previousVersionId + for (const [filepath, headStatus, workdirStatus] of matrix) { + const fullPath = path.join(appPath, filepath); - // If file exists in HEAD (previous version) - if (headStatus === 1) { - // If file doesn't exist or has changed in working directory, restore it from the target commit - if (workdirStatus !== 1) { - const { blob } = await git.readBlob({ - fs, - dir: appPath, - oid: previousVersionId, - filepath, - }); - await fsPromises.mkdir(path.dirname(fullPath), { - recursive: true, - }); - await fsPromises.writeFile(fullPath, Buffer.from(blob)); - } - } - // If file doesn't exist in HEAD but exists in working directory, delete it - else if (headStatus === 0 && workdirStatus !== 0) { - if (fs.existsSync(fullPath)) { - await fsPromises.unlink(fullPath); - await git.remove({ - fs, - dir: appPath, - filepath: filepath, - }); - } + // If file exists in HEAD (previous version) + if (headStatus === 1) { + // If file doesn't exist or has changed in working directory, restore it from the target commit + if (workdirStatus !== 1) { + const { blob } = await git.readBlob({ + fs, + dir: appPath, + oid: previousVersionId, + filepath, + }); + await fsPromises.mkdir(path.dirname(fullPath), { + recursive: true, + }); + await fsPromises.writeFile(fullPath, Buffer.from(blob)); } } - - // Stage all changes - await git.add({ - fs, - dir: appPath, - filepath: ".", - }); - - // Create a revert commit - await git.commit({ - fs, - dir: appPath, - message: `Reverted all changes back to version ${previousVersionId}`, - author: await getGitAuthor(), - }); - - // Find the chat and message associated with the commit hash - const messageWithCommit = await db.query.messages.findFirst({ - where: eq(messages.commitHash, previousVersionId), - with: { - chat: true, - }, - }); - - // If we found a message with this commit hash, delete all subsequent messages (but keep this message) - if (messageWithCommit) { - const chatId = messageWithCommit.chatId; - - // Find all messages in this chat with IDs > the one with our commit hash - const messagesToDelete = await db.query.messages.findMany({ - where: and( - eq(messages.chatId, chatId), - gt(messages.id, messageWithCommit.id), - ), - orderBy: desc(messages.id), - }); - - logger.log( - `Deleting ${messagesToDelete.length} messages after commit ${previousVersionId} from chat ${chatId}`, - ); - - // Delete the messages - if (messagesToDelete.length > 0) { - await db - .delete(messages) - .where( - and( - eq(messages.chatId, chatId), - gt(messages.id, messageWithCommit.id), - ), - ); + // If file doesn't exist in HEAD but exists in working directory, delete it + else if (headStatus === 0 && workdirStatus !== 0) { + if (fs.existsSync(fullPath)) { + await fsPromises.unlink(fullPath); + await git.remove({ + fs, + dir: appPath, + filepath: filepath, + }); } } - } catch (error: any) { - logger.error( - `Error reverting to version ${previousVersionId} for app ${appId}:`, - error, + } + + // Stage all changes + await git.add({ + fs, + dir: appPath, + filepath: ".", + }); + + // Create a revert commit + await git.commit({ + fs, + dir: appPath, + message: `Reverted all changes back to version ${previousVersionId}`, + author: await getGitAuthor(), + }); + + // Find the chat and message associated with the commit hash + const messageWithCommit = await db.query.messages.findFirst({ + where: eq(messages.commitHash, previousVersionId), + with: { + chat: true, + }, + }); + + // If we found a message with this commit hash, delete all subsequent messages (but keep this message) + if (messageWithCommit) { + const chatId = messageWithCommit.chatId; + + // Find all messages in this chat with IDs > the one with our commit hash + const messagesToDelete = await db.query.messages.findMany({ + where: and( + eq(messages.chatId, chatId), + gt(messages.id, messageWithCommit.id), + ), + orderBy: desc(messages.id), + }); + + logger.log( + `Deleting ${messagesToDelete.length} messages after commit ${previousVersionId} from chat ${chatId}`, ); - throw new Error(`Failed to revert version: ${error.message}`); + + // Delete the messages + if (messagesToDelete.length > 0) { + await db + .delete(messages) + .where( + and( + eq(messages.chatId, chatId), + gt(messages.id, messageWithCommit.id), + ), + ); + } } }); }, ); - ipcMain.handle( + handle( "checkout-version", async ( _, @@ -233,21 +221,12 @@ export function registerVersionHandlers() { const appPath = getDyadAppPath(app.path); - try { - // Checkout the target commit - await git.checkout({ - fs, - dir: appPath, - ref: versionId, - force: true, - }); - } catch (error: any) { - logger.error( - `Error checking out version ${versionId} for app ${appId}:`, - error, - ); - throw new Error(`Failed to checkout version: ${error.message}`); - } + await git.checkout({ + fs, + dir: appPath, + ref: versionId, + force: true, + }); }); }, );