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) {
|
function mapActionToButton(action: SuggestedAction) {
|
||||||
const { restartApp } = useRunApp();
|
|
||||||
switch (action.id) {
|
switch (action.id) {
|
||||||
case "restart-app":
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
className="rounded-xl"
|
|
||||||
key={action.id}
|
|
||||||
onClick={restartApp}
|
|
||||||
>
|
|
||||||
Restart app
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
console.error(`Unsupported action: ${action.id}`);
|
console.error(`Unsupported action: ${action.id}`);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -15,12 +15,23 @@ import {
|
|||||||
ChevronUp,
|
ChevronUp,
|
||||||
Logs,
|
Logs,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
|
MoreVertical,
|
||||||
|
Trash2,
|
||||||
|
Cog,
|
||||||
|
CirclePower,
|
||||||
|
Power,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { useEffect, useRef, useState, useCallback } from "react";
|
import { useEffect, useRef, useState, useCallback } from "react";
|
||||||
import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels";
|
import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels";
|
||||||
import { Console } from "./Console";
|
import { Console } from "./Console";
|
||||||
import { useRunApp } from "@/hooks/useRunApp";
|
import { useRunApp } from "@/hooks/useRunApp";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
type PreviewMode = "preview" | "code";
|
type PreviewMode = "preview" | "code";
|
||||||
|
|
||||||
@@ -28,6 +39,7 @@ interface PreviewHeaderProps {
|
|||||||
previewMode: PreviewMode;
|
previewMode: PreviewMode;
|
||||||
setPreviewMode: (mode: PreviewMode) => void;
|
setPreviewMode: (mode: PreviewMode) => void;
|
||||||
onRestart: () => void;
|
onRestart: () => void;
|
||||||
|
onCleanRestart: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConsoleHeaderProps {
|
interface ConsoleHeaderProps {
|
||||||
@@ -41,6 +53,7 @@ const PreviewHeader = ({
|
|||||||
previewMode,
|
previewMode,
|
||||||
setPreviewMode,
|
setPreviewMode,
|
||||||
onRestart,
|
onRestart,
|
||||||
|
onCleanRestart,
|
||||||
}: PreviewHeaderProps) => (
|
}: PreviewHeaderProps) => (
|
||||||
<div className="flex items-center justify-between px-4 py-2 border-b border-border">
|
<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">
|
<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>
|
<span>Code</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
<button
|
<button
|
||||||
onClick={onRestart}
|
onClick={onRestart}
|
||||||
className="flex items-center space-x-1 px-3 py-1 rounded-md text-sm hover:bg-[var(--background-darkest)] transition-colors"
|
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"
|
title="Restart App"
|
||||||
>
|
>
|
||||||
<RefreshCw size={16} />
|
<Power size={16} />
|
||||||
<span>Restart</span>
|
<span>Restart</span>
|
||||||
</button>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -126,6 +162,10 @@ export function PreviewPanel() {
|
|||||||
restartApp();
|
restartApp();
|
||||||
}, [restartApp]);
|
}, [restartApp]);
|
||||||
|
|
||||||
|
const handleCleanRestart = useCallback(() => {
|
||||||
|
restartApp({ removeNodeModules: true });
|
||||||
|
}, [restartApp]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const previousAppId = runningAppIdRef.current;
|
const previousAppId = runningAppIdRef.current;
|
||||||
|
|
||||||
@@ -176,6 +216,7 @@ export function PreviewPanel() {
|
|||||||
previewMode={previewMode}
|
previewMode={previewMode}
|
||||||
setPreviewMode={setPreviewMode}
|
setPreviewMode={setPreviewMode}
|
||||||
onRestart={handleRestart}
|
onRestart={handleRestart}
|
||||||
|
onCleanRestart={handleCleanRestart}
|
||||||
/>
|
/>
|
||||||
<div className="flex-1 overflow-hidden">
|
<div className="flex-1 overflow-hidden">
|
||||||
<PanelGroup direction="vertical">
|
<PanelGroup direction="vertical">
|
||||||
|
|||||||
@@ -70,14 +70,21 @@ export function useRunApp() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const restartApp = useCallback(async () => {
|
const restartApp = useCallback(
|
||||||
|
async ({
|
||||||
|
removeNodeModules = false,
|
||||||
|
}: { removeNodeModules?: boolean } = {}) => {
|
||||||
if (appId === null) {
|
if (appId === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const ipcClient = IpcClient.getInstance();
|
const ipcClient = IpcClient.getInstance();
|
||||||
console.debug("Restarting app", appId);
|
console.debug(
|
||||||
|
"Restarting app",
|
||||||
|
appId,
|
||||||
|
removeNodeModules ? "with node_modules cleanup" : ""
|
||||||
|
);
|
||||||
|
|
||||||
// Clear the URL and add restart message
|
// Clear the URL and add restart message
|
||||||
setAppUrlObj({ appUrl: null, appId: null });
|
setAppUrlObj({ appUrl: null, appId: null });
|
||||||
@@ -88,14 +95,20 @@ export function useRunApp() {
|
|||||||
|
|
||||||
const app = await ipcClient.getApp(appId);
|
const app = await ipcClient.getApp(appId);
|
||||||
setApp(app);
|
setApp(app);
|
||||||
await ipcClient.restartApp(appId, (output) => {
|
await ipcClient.restartApp(
|
||||||
|
appId,
|
||||||
|
(output) => {
|
||||||
setAppOutput((prev) => [...prev, output]);
|
setAppOutput((prev) => [...prev, output]);
|
||||||
// Check if the output contains a localhost URL
|
// Check if the output contains a localhost URL
|
||||||
const urlMatch = output.message.match(/(https?:\/\/localhost:\d+\/?)/);
|
const urlMatch = output.message.match(
|
||||||
|
/(https?:\/\/localhost:\d+\/?)/
|
||||||
|
);
|
||||||
if (urlMatch) {
|
if (urlMatch) {
|
||||||
setAppUrlObj({ appUrl: urlMatch[1], appId });
|
setAppUrlObj({ appUrl: urlMatch[1], appId });
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
removeNodeModules
|
||||||
|
);
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error restarting app ${appId}:`, error);
|
console.error(`Error restarting app ${appId}:`, error);
|
||||||
@@ -104,7 +117,9 @@ export function useRunApp() {
|
|||||||
setPreviewPanelKey((prevKey) => prevKey + 1);
|
setPreviewPanelKey((prevKey) => prevKey + 1);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, []);
|
},
|
||||||
|
[appId, setApp, setAppOutput, setAppUrlObj, setError, setPreviewPanelKey]
|
||||||
|
);
|
||||||
|
|
||||||
const refreshAppIframe = useCallback(async () => {
|
const refreshAppIframe = useCallback(async () => {
|
||||||
setPreviewPanelKey((prevKey) => prevKey + 1);
|
setPreviewPanelKey((prevKey) => prevKey + 1);
|
||||||
|
|||||||
@@ -378,7 +378,10 @@ export function registerAppHandlers() {
|
|||||||
"restart-app",
|
"restart-app",
|
||||||
async (
|
async (
|
||||||
event: Electron.IpcMainInvokeEvent,
|
event: Electron.IpcMainInvokeEvent,
|
||||||
{ appId }: { appId: number }
|
{
|
||||||
|
appId,
|
||||||
|
removeNodeModules,
|
||||||
|
}: { appId: number; removeNodeModules?: boolean }
|
||||||
) => {
|
) => {
|
||||||
logger.log(`Restarting app ${appId}`);
|
logger.log(`Restarting app ${appId}`);
|
||||||
return withLock(appId, async () => {
|
return withLock(appId, async () => {
|
||||||
@@ -410,6 +413,24 @@ export function registerAppHandlers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const appPath = getDyadAppPath(app.path);
|
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(
|
logger.debug(
|
||||||
`Executing app ${appId} in path ${app.path} after restart request`
|
`Executing app ${appId} in path ${app.path} after restart request`
|
||||||
); // Adjusted log
|
); // Adjusted log
|
||||||
|
|||||||
@@ -317,10 +317,14 @@ export class IpcClient {
|
|||||||
// Restart a running app
|
// Restart a running app
|
||||||
public async restartApp(
|
public async restartApp(
|
||||||
appId: number,
|
appId: number,
|
||||||
onOutput: (output: AppOutput) => void
|
onOutput: (output: AppOutput) => void,
|
||||||
|
removeNodeModules?: boolean
|
||||||
): Promise<{ success: boolean }> {
|
): Promise<{ success: boolean }> {
|
||||||
try {
|
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 });
|
this.appStreams.set(appId, { onOutput });
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user