From ee3d2e7f4e14f2e64f7d18e90ea6e4ca7f885b66 Mon Sep 17 00:00:00 2001 From: Will Chen Date: Mon, 21 Apr 2025 16:46:10 -0700 Subject: [PATCH] Provide a rebuild option (restart w/ re-install node modules) --- src/components/chat/ChatInput.tsx | 13 --- src/components/preview_panel/PreviewPanel.tsx | 57 +++++++++++-- src/hooks/useRunApp.ts | 81 +++++++++++-------- src/ipc/handlers/app_handlers.ts | 23 +++++- src/ipc/ipc_client.ts | 8 +- 5 files changed, 125 insertions(+), 57 deletions(-) diff --git a/src/components/chat/ChatInput.tsx b/src/components/chat/ChatInput.tsx index 02339a5..c167267 100644 --- a/src/components/chat/ChatInput.tsx +++ b/src/components/chat/ChatInput.tsx @@ -283,20 +283,7 @@ export function ChatInput({ chatId }: { chatId?: number }) { } function mapActionToButton(action: SuggestedAction) { - const { restartApp } = useRunApp(); switch (action.id) { - case "restart-app": - return ( - - ); default: console.error(`Unsupported action: ${action.id}`); return ( diff --git a/src/components/preview_panel/PreviewPanel.tsx b/src/components/preview_panel/PreviewPanel.tsx index 598d53f..d037564 100644 --- a/src/components/preview_panel/PreviewPanel.tsx +++ b/src/components/preview_panel/PreviewPanel.tsx @@ -15,12 +15,23 @@ import { ChevronUp, Logs, RefreshCw, + MoreVertical, + Trash2, + Cog, + CirclePower, + Power, } from "lucide-react"; import { motion } from "framer-motion"; import { useEffect, useRef, useState, useCallback } from "react"; import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels"; import { Console } from "./Console"; import { useRunApp } from "@/hooks/useRunApp"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; type PreviewMode = "preview" | "code"; @@ -28,6 +39,7 @@ interface PreviewHeaderProps { previewMode: PreviewMode; setPreviewMode: (mode: PreviewMode) => void; onRestart: () => void; + onCleanRestart: () => void; } interface ConsoleHeaderProps { @@ -41,6 +53,7 @@ const PreviewHeader = ({ previewMode, setPreviewMode, onRestart, + onCleanRestart, }: PreviewHeaderProps) => (
@@ -73,14 +86,37 @@ const PreviewHeader = ({ Code
- +
+ + + + + + + + +
+ Rebuild + + Re-installs node_modules and restarts + +
+
+
+
+
); @@ -126,6 +162,10 @@ export function PreviewPanel() { restartApp(); }, [restartApp]); + const handleCleanRestart = useCallback(() => { + restartApp({ removeNodeModules: true }); + }, [restartApp]); + useEffect(() => { const previousAppId = runningAppIdRef.current; @@ -176,6 +216,7 @@ export function PreviewPanel() { previewMode={previewMode} setPreviewMode={setPreviewMode} onRestart={handleRestart} + onCleanRestart={handleCleanRestart} />
diff --git a/src/hooks/useRunApp.ts b/src/hooks/useRunApp.ts index a653eed..f06043f 100644 --- a/src/hooks/useRunApp.ts +++ b/src/hooks/useRunApp.ts @@ -70,41 +70,56 @@ export function useRunApp() { } }, []); - const restartApp = useCallback(async () => { - if (appId === null) { - return; - } - setLoading(true); - try { - const ipcClient = IpcClient.getInstance(); - console.debug("Restarting app", appId); + const restartApp = useCallback( + async ({ + removeNodeModules = false, + }: { removeNodeModules?: boolean } = {}) => { + if (appId === null) { + return; + } + setLoading(true); + try { + const ipcClient = IpcClient.getInstance(); + console.debug( + "Restarting app", + appId, + removeNodeModules ? "with node_modules cleanup" : "" + ); - // Clear the URL and add restart message - setAppUrlObj({ appUrl: null, appId: null }); - setAppOutput((prev) => [ - ...prev, - { message: "Restarting app...", type: "stdout", appId }, - ]); + // Clear the URL and add restart message + setAppUrlObj({ appUrl: null, appId: null }); + setAppOutput((prev) => [ + ...prev, + { message: "Restarting app...", type: "stdout", appId }, + ]); - const app = await ipcClient.getApp(appId); - setApp(app); - await ipcClient.restartApp(appId, (output) => { - setAppOutput((prev) => [...prev, output]); - // Check if the output contains a localhost URL - const urlMatch = output.message.match(/(https?:\/\/localhost:\d+\/?)/); - if (urlMatch) { - setAppUrlObj({ appUrl: urlMatch[1], appId }); - } - }); - setError(null); - } catch (error) { - console.error(`Error restarting app ${appId}:`, error); - setError(error instanceof Error ? error : new Error(String(error))); - } finally { - setPreviewPanelKey((prevKey) => prevKey + 1); - setLoading(false); - } - }, []); + const app = await ipcClient.getApp(appId); + setApp(app); + await ipcClient.restartApp( + appId, + (output) => { + setAppOutput((prev) => [...prev, output]); + // Check if the output contains a localhost URL + const urlMatch = output.message.match( + /(https?:\/\/localhost:\d+\/?)/ + ); + if (urlMatch) { + setAppUrlObj({ appUrl: urlMatch[1], appId }); + } + }, + removeNodeModules + ); + setError(null); + } catch (error) { + console.error(`Error restarting app ${appId}:`, error); + setError(error instanceof Error ? error : new Error(String(error))); + } finally { + setPreviewPanelKey((prevKey) => prevKey + 1); + setLoading(false); + } + }, + [appId, setApp, setAppOutput, setAppUrlObj, setError, setPreviewPanelKey] + ); const refreshAppIframe = useCallback(async () => { setPreviewPanelKey((prevKey) => prevKey + 1); diff --git a/src/ipc/handlers/app_handlers.ts b/src/ipc/handlers/app_handlers.ts index 1723668..3cbf73a 100644 --- a/src/ipc/handlers/app_handlers.ts +++ b/src/ipc/handlers/app_handlers.ts @@ -378,7 +378,10 @@ export function registerAppHandlers() { "restart-app", async ( event: Electron.IpcMainInvokeEvent, - { appId }: { appId: number } + { + appId, + removeNodeModules, + }: { appId: number; removeNodeModules?: boolean } ) => { logger.log(`Restarting app ${appId}`); return withLock(appId, async () => { @@ -410,6 +413,24 @@ export function registerAppHandlers() { } const appPath = getDyadAppPath(app.path); + + // Remove node_modules if requested + if (removeNodeModules) { + const nodeModulesPath = path.join(appPath, "node_modules"); + logger.log( + `Removing node_modules for app ${appId} at ${nodeModulesPath}` + ); + if (fs.existsSync(nodeModulesPath)) { + await fsPromises.rm(nodeModulesPath, { + recursive: true, + force: true, + }); + logger.log(`Successfully removed node_modules for app ${appId}`); + } else { + logger.log(`No node_modules directory found for app ${appId}`); + } + } + logger.debug( `Executing app ${appId} in path ${app.path} after restart request` ); // Adjusted log diff --git a/src/ipc/ipc_client.ts b/src/ipc/ipc_client.ts index d010c4b..5e74a53 100644 --- a/src/ipc/ipc_client.ts +++ b/src/ipc/ipc_client.ts @@ -317,10 +317,14 @@ export class IpcClient { // Restart a running app public async restartApp( appId: number, - onOutput: (output: AppOutput) => void + onOutput: (output: AppOutput) => void, + removeNodeModules?: boolean ): Promise<{ success: boolean }> { try { - const result = await this.ipcRenderer.invoke("restart-app", { appId }); + const result = await this.ipcRenderer.invoke("restart-app", { + appId, + removeNodeModules, + }); this.appStreams.set(appId, { onOutput }); return result; } catch (error) {