From 4d2b4783bce332a791490df64cf2c11aed126721 Mon Sep 17 00:00:00 2001 From: Will Chen Date: Mon, 19 May 2025 17:58:49 -0700 Subject: [PATCH] Fix scaffold copy and better error handling for create app (#202) I was over-eager in https://github.com/dyad-sh/dyad/pull/200 and removed copyDirectoryRecursive which is actually needed to copy the scaffold out. --- src/ipc/handlers/app_handlers.ts | 80 +++++++++++++++----------------- src/ipc/utils/file_utils.ts | 20 ++++++++ src/pages/home.tsx | 2 + 3 files changed, 60 insertions(+), 42 deletions(-) diff --git a/src/ipc/handlers/app_handlers.ts b/src/ipc/handlers/app_handlers.ts index 44ec8fc..2c85e73 100644 --- a/src/ipc/handlers/app_handlers.ts +++ b/src/ipc/handlers/app_handlers.ts @@ -12,7 +12,10 @@ import { promises as fsPromises } from "node:fs"; // Import our utility modules import { withLock } from "../utils/lock_utils"; -import { getFilesRecursively } from "../utils/file_utils"; +import { + copyDirectoryRecursive, + getFilesRecursively, +} from "../utils/file_utils"; import { runningApps, processCounter, @@ -187,50 +190,43 @@ export function registerAppHandlers() { }) .returning(); - // Start async operations in background - try { - await fsPromises.cp( - path.join(__dirname, "..", "..", "scaffold"), - fullAppPath, - { - recursive: true, - // Scaffold should *not* have node_modules anyways, but - // just in case, we filter it out. - filter: (source) => !source.includes("node_modules"), - }, - ); - // Initialize git repo and create first commit - await git.init({ - fs: fs, - dir: fullAppPath, - defaultBranch: "main", - }); + // Why do we not use fs.cp here? + // Because scaffold is inside ASAR and it does NOT + // behave like a regular directory if you use fs.cp + // https://www.electronjs.org/docs/latest/tutorial/asar-archives#limitations-of-the-node-api + await copyDirectoryRecursive( + path.join(__dirname, "..", "..", "scaffold"), + fullAppPath, + ); + // Initialize git repo and create first commit + await git.init({ + fs: fs, + dir: fullAppPath, + defaultBranch: "main", + }); - // Stage all files - await git.add({ - fs: fs, - dir: fullAppPath, - filepath: ".", - }); + // Stage all files + await git.add({ + fs: fs, + dir: fullAppPath, + filepath: ".", + }); - // Create initial commit - const commitHash = await git.commit({ - fs: fs, - dir: fullAppPath, - message: "Init Dyad app", - author: await getGitAuthor(), - }); + // Create initial commit + const commitHash = await git.commit({ + fs: fs, + dir: fullAppPath, + message: "Init Dyad app", + author: await getGitAuthor(), + }); - // Update chat with initial commit hash - await db - .update(chats) - .set({ - initialCommitHash: commitHash, - }) - .where(eq(chats.id, chat.id)); - } catch (error) { - logger.error("Error in background app initialization:", error); - } + // Update chat with initial commit hash + await db + .update(chats) + .set({ + initialCommitHash: commitHash, + }) + .where(eq(chats.id, chat.id)); return { app, chatId: chat.id }; }, diff --git a/src/ipc/utils/file_utils.ts b/src/ipc/utils/file_utils.ts index 9feba02..72d4e8e 100644 --- a/src/ipc/utils/file_utils.ts +++ b/src/ipc/utils/file_utils.ts @@ -1,4 +1,5 @@ import fs from "node:fs"; +import { promises as fsPromises } from "node:fs"; import path from "node:path"; /** @@ -31,3 +32,22 @@ export function getFilesRecursively(dir: string, baseDir: string): string[] { return files; } + +export async function copyDirectoryRecursive( + source: string, + destination: string, +) { + await fsPromises.mkdir(destination, { recursive: true }); + const entries = await fsPromises.readdir(source, { withFileTypes: true }); + + for (const entry of entries) { + const srcPath = path.join(source, entry.name); + const destPath = path.join(destination, entry.name); + + if (entry.isDirectory()) { + await copyDirectoryRecursive(srcPath, destPath); + } else { + await fsPromises.copyFile(srcPath, destPath); + } + } +} diff --git a/src/pages/home.tsx b/src/pages/home.tsx index ccfb907..2641219 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -25,6 +25,7 @@ import { useTheme } from "@/contexts/ThemeContext"; import { Button } from "@/components/ui/button"; import { ExternalLink } from "lucide-react"; import { ImportAppButton } from "@/components/ImportAppButton"; +import { showError } from "@/lib/toast"; // Adding an export for attachments export interface HomeSubmitOptions { @@ -133,6 +134,7 @@ export default function HomePage() { navigate({ to: "/chat", search: { id: result.chatId } }); } catch (error) { console.error("Failed to create chat:", error); + showError("Failed to create app. " + (error as any).toString()); setIsLoading(false); // Ensure loading state is reset on error } // No finally block needed for setIsLoading(false) here if navigation happens on success