Provide a rebuild option (restart w/ re-install node modules)
This commit is contained in:
@@ -283,20 +283,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
|
||||
}
|
||||
|
||||
function mapActionToButton(action: SuggestedAction) {
|
||||
const { restartApp } = useRunApp();
|
||||
switch (action.id) {
|
||||
case "restart-app":
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="rounded-xl"
|
||||
key={action.id}
|
||||
onClick={restartApp}
|
||||
>
|
||||
Restart app
|
||||
</Button>
|
||||
);
|
||||
default:
|
||||
console.error(`Unsupported action: ${action.id}`);
|
||||
return (
|
||||
|
||||
@@ -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) => (
|
||||
<div className="flex items-center justify-between px-4 py-2 border-b border-border">
|
||||
<div className="relative flex space-x-2 bg-[var(--background-darkest)] rounded-md p-0.5">
|
||||
@@ -73,14 +86,37 @@ const PreviewHeader = ({
|
||||
<span>Code</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={onRestart}
|
||||
className="flex items-center space-x-1 px-3 py-1 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors"
|
||||
title="Restart App"
|
||||
>
|
||||
<RefreshCw size={16} />
|
||||
<span>Restart</span>
|
||||
</button>
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
onClick={onRestart}
|
||||
className="flex items-center space-x-1 px-3 py-1 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors"
|
||||
title="Restart App"
|
||||
>
|
||||
<Power size={16} />
|
||||
<span>Restart</span>
|
||||
</button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
className="flex items-center justify-center p-1.5 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors"
|
||||
title="More options"
|
||||
>
|
||||
<MoreVertical size={16} />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-60">
|
||||
<DropdownMenuItem onClick={onCleanRestart}>
|
||||
<Cog size={16} />
|
||||
<div className="flex flex-col">
|
||||
<span>Rebuild</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Re-installs node_modules and restarts
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<PanelGroup direction="vertical">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user