746 lines
22 KiB
TypeScript
746 lines
22 KiB
TypeScript
import { ipcMain } from "electron";
|
|
import { db, getDatabasePath } from "../../db";
|
|
import { apps, chats } from "../../db/schema";
|
|
import { desc, eq } from "drizzle-orm";
|
|
import type { App, CreateAppParams } from "../ipc_types";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { getDyadAppPath, getUserDataPath } from "../../paths/paths";
|
|
import { spawn } from "node:child_process";
|
|
import git from "isomorphic-git";
|
|
import { promises as fsPromises } from "node:fs";
|
|
|
|
// Import our utility modules
|
|
import { withLock } from "../utils/lock_utils";
|
|
import {
|
|
copyDirectoryRecursive,
|
|
getFilesRecursively,
|
|
} from "../utils/file_utils";
|
|
import {
|
|
runningApps,
|
|
processCounter,
|
|
killProcess,
|
|
removeAppIfCurrentProcess,
|
|
} from "../utils/process_manager";
|
|
import { ALLOWED_ENV_VARS } from "../../constants/models";
|
|
import { getEnvVar } from "../utils/read_env";
|
|
import { readSettings } from "../../main/settings";
|
|
|
|
import fixPath from "fix-path";
|
|
import { getGitAuthor } from "../utils/git_author";
|
|
import killPort from "kill-port";
|
|
import util from "util";
|
|
import log from "electron-log";
|
|
import { getSupabaseProjectName } from "../../supabase_admin/supabase_management_client";
|
|
import { createLoggedHandler } from "./safe_handle";
|
|
|
|
const logger = log.scope("app_handlers");
|
|
const handle = createLoggedHandler(logger);
|
|
|
|
// Needed, otherwise electron in MacOS/Linux will not be able
|
|
// to find node/pnpm.
|
|
fixPath();
|
|
|
|
async function executeApp({
|
|
appPath,
|
|
appId,
|
|
event, // Keep event for local-node case
|
|
}: {
|
|
appPath: string;
|
|
appId: number;
|
|
event: Electron.IpcMainInvokeEvent;
|
|
}): Promise<void> {
|
|
await executeAppLocalNode({ appPath, appId, event });
|
|
}
|
|
async function executeAppLocalNode({
|
|
appPath,
|
|
appId,
|
|
event,
|
|
}: {
|
|
appPath: string;
|
|
appId: number;
|
|
event: Electron.IpcMainInvokeEvent;
|
|
}): Promise<void> {
|
|
const process = spawn(
|
|
"(pnpm install && pnpm run dev --port 32100) || (npm install && npm run dev -- --port 32100)",
|
|
[],
|
|
{
|
|
cwd: appPath,
|
|
shell: true,
|
|
stdio: "pipe", // Ensure stdio is piped so we can capture output/errors and detect close
|
|
detached: false, // Ensure child process is attached to the main process lifecycle unless explicitly backgrounded
|
|
},
|
|
);
|
|
|
|
// Check if process spawned correctly
|
|
if (!process.pid) {
|
|
// Attempt to capture any immediate errors if possible
|
|
let errorOutput = "";
|
|
process.stderr?.on("data", (data) => (errorOutput += data));
|
|
await new Promise((resolve) => process.on("error", resolve)); // Wait for error event
|
|
throw new Error(
|
|
`Failed to spawn process for app ${appId}. Error: ${
|
|
errorOutput || "Unknown spawn error"
|
|
}`,
|
|
);
|
|
}
|
|
|
|
// Increment the counter and store the process reference with its ID
|
|
const currentProcessId = processCounter.increment();
|
|
runningApps.set(appId, { process, processId: currentProcessId });
|
|
|
|
// Log output
|
|
process.stdout?.on("data", (data) => {
|
|
const message = util.stripVTControlCharacters(data.toString());
|
|
logger.debug(`App ${appId} (PID: ${process.pid}) stdout: ${message}`);
|
|
event.sender.send("app:output", {
|
|
type: "stdout",
|
|
message,
|
|
appId,
|
|
});
|
|
});
|
|
|
|
process.stderr?.on("data", (data) => {
|
|
const message = util.stripVTControlCharacters(data.toString());
|
|
logger.error(`App ${appId} (PID: ${process.pid}) stderr: ${message}`);
|
|
event.sender.send("app:output", {
|
|
type: "stderr",
|
|
message,
|
|
appId,
|
|
});
|
|
});
|
|
|
|
// Handle process exit/close
|
|
process.on("close", (code, signal) => {
|
|
logger.log(
|
|
`App ${appId} (PID: ${process.pid}) process closed with code ${code}, signal ${signal}.`,
|
|
);
|
|
removeAppIfCurrentProcess(appId, process);
|
|
});
|
|
|
|
// Handle errors during process lifecycle (e.g., command not found)
|
|
process.on("error", (err) => {
|
|
logger.error(
|
|
`Error in app ${appId} (PID: ${process.pid}) process: ${err.message}`,
|
|
);
|
|
removeAppIfCurrentProcess(appId, process);
|
|
// Note: We don't throw here as the error is asynchronous. The caller got a success response already.
|
|
// Consider adding ipcRenderer event emission to notify UI of the error.
|
|
});
|
|
}
|
|
|
|
// Helper to kill process on a specific port (cross-platform, using kill-port)
|
|
async function killProcessOnPort(port: number): Promise<void> {
|
|
try {
|
|
await killPort(port, "tcp");
|
|
} catch {
|
|
// Ignore if nothing was running on that port
|
|
}
|
|
}
|
|
|
|
export function registerAppHandlers() {
|
|
handle(
|
|
"create-app",
|
|
async (
|
|
_,
|
|
params: CreateAppParams,
|
|
): Promise<{ app: any; chatId: number }> => {
|
|
const appPath = params.name;
|
|
const fullAppPath = getDyadAppPath(appPath);
|
|
if (fs.existsSync(fullAppPath)) {
|
|
throw new Error(`App already exists at: ${fullAppPath}`);
|
|
}
|
|
// Create a new app
|
|
const [app] = await db
|
|
.insert(apps)
|
|
.values({
|
|
name: params.name,
|
|
// Use the name as the path for now
|
|
path: appPath,
|
|
})
|
|
.returning();
|
|
|
|
// Create an initial chat for this app
|
|
const [chat] = await db
|
|
.insert(chats)
|
|
.values({
|
|
appId: app.id,
|
|
})
|
|
.returning();
|
|
|
|
// Start async operations in background
|
|
try {
|
|
// Copy scaffold asynchronously
|
|
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: ".",
|
|
});
|
|
|
|
// Create initial commit
|
|
const commitHash = await git.commit({
|
|
fs: fs,
|
|
dir: fullAppPath,
|
|
message: "Init from react vite template",
|
|
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);
|
|
}
|
|
|
|
return { app, chatId: chat.id };
|
|
},
|
|
);
|
|
|
|
handle("get-app", async (_, appId: number): Promise<App> => {
|
|
const app = await db.query.apps.findFirst({
|
|
where: eq(apps.id, appId),
|
|
});
|
|
|
|
if (!app) {
|
|
throw new Error("App not found");
|
|
}
|
|
|
|
// Get app files
|
|
const appPath = getDyadAppPath(app.path);
|
|
let files: string[] = [];
|
|
|
|
try {
|
|
files = getFilesRecursively(appPath, appPath);
|
|
} catch (error) {
|
|
logger.error(`Error reading files for app ${appId}:`, error);
|
|
// Return app even if files couldn't be read
|
|
}
|
|
|
|
let supabaseProjectName: string | null = null;
|
|
const settings = readSettings();
|
|
if (app.supabaseProjectId && settings.supabase?.accessToken?.value) {
|
|
supabaseProjectName = await getSupabaseProjectName(app.supabaseProjectId);
|
|
}
|
|
|
|
return {
|
|
...app,
|
|
files,
|
|
supabaseProjectName,
|
|
};
|
|
});
|
|
|
|
ipcMain.handle("list-apps", async () => {
|
|
const allApps = await db.query.apps.findMany({
|
|
orderBy: [desc(apps.createdAt)],
|
|
});
|
|
return {
|
|
apps: allApps,
|
|
appBasePath: getDyadAppPath("$APP_BASE_PATH"),
|
|
};
|
|
});
|
|
|
|
ipcMain.handle(
|
|
"read-app-file",
|
|
async (_, { appId, filePath }: { appId: number; filePath: string }) => {
|
|
const app = await db.query.apps.findFirst({
|
|
where: eq(apps.id, appId),
|
|
});
|
|
|
|
if (!app) {
|
|
throw new Error("App not found");
|
|
}
|
|
|
|
const appPath = getDyadAppPath(app.path);
|
|
const fullPath = path.join(appPath, filePath);
|
|
|
|
// Check if the path is within the app directory (security check)
|
|
if (!fullPath.startsWith(appPath)) {
|
|
throw new Error("Invalid file path");
|
|
}
|
|
|
|
if (!fs.existsSync(fullPath)) {
|
|
throw new Error("File not found");
|
|
}
|
|
|
|
try {
|
|
const contents = fs.readFileSync(fullPath, "utf-8");
|
|
return contents;
|
|
} catch (error) {
|
|
logger.error(`Error reading file ${filePath} for app ${appId}:`, error);
|
|
throw new Error("Failed to read file");
|
|
}
|
|
},
|
|
);
|
|
|
|
// Do NOT use handle for this, it contains sensitive information.
|
|
ipcMain.handle("get-env-vars", async () => {
|
|
const envVars: Record<string, string | undefined> = {};
|
|
for (const key of ALLOWED_ENV_VARS) {
|
|
envVars[key] = getEnvVar(key);
|
|
}
|
|
return envVars;
|
|
});
|
|
|
|
ipcMain.handle(
|
|
"run-app",
|
|
async (
|
|
event: Electron.IpcMainInvokeEvent,
|
|
{ appId }: { appId: number },
|
|
): Promise<void> => {
|
|
return withLock(appId, async () => {
|
|
// Check if app is already running
|
|
if (runningApps.has(appId)) {
|
|
logger.debug(`App ${appId} is already running.`);
|
|
return;
|
|
}
|
|
|
|
const app = await db.query.apps.findFirst({
|
|
where: eq(apps.id, appId),
|
|
});
|
|
|
|
if (!app) {
|
|
throw new Error("App not found");
|
|
}
|
|
|
|
logger.debug(`Starting app ${appId} in path ${app.path}`);
|
|
|
|
const appPath = getDyadAppPath(app.path);
|
|
try {
|
|
await executeApp({ appPath, appId, event });
|
|
|
|
return;
|
|
} catch (error: any) {
|
|
logger.error(`Error running app ${appId}:`, error);
|
|
// Ensure cleanup if error happens during setup but before process events are handled
|
|
if (
|
|
runningApps.has(appId) &&
|
|
runningApps.get(appId)?.processId === processCounter.value
|
|
) {
|
|
runningApps.delete(appId);
|
|
}
|
|
throw new Error(`Failed to run app ${appId}: ${error.message}`);
|
|
}
|
|
});
|
|
},
|
|
);
|
|
|
|
ipcMain.handle(
|
|
"stop-app",
|
|
async (_, { appId }: { appId: number }): Promise<void> => {
|
|
logger.log(
|
|
`Attempting to stop app ${appId}. Current running apps: ${runningApps.size}`,
|
|
);
|
|
return withLock(appId, async () => {
|
|
const appInfo = runningApps.get(appId);
|
|
|
|
if (!appInfo) {
|
|
logger.log(
|
|
`App ${appId} not found in running apps map. Assuming already stopped.`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
const { process, processId } = appInfo;
|
|
logger.log(
|
|
`Found running app ${appId} with processId ${processId} (PID: ${process.pid}). Attempting to stop.`,
|
|
);
|
|
|
|
// Check if the process is already exited or closed
|
|
if (process.exitCode !== null || process.signalCode !== null) {
|
|
logger.log(
|
|
`Process for app ${appId} (PID: ${process.pid}) already exited (code: ${process.exitCode}, signal: ${process.signalCode}). Cleaning up map.`,
|
|
);
|
|
runningApps.delete(appId); // Ensure cleanup if somehow missed
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Use the killProcess utility to stop the process
|
|
await killProcess(process);
|
|
|
|
// Now, safely remove the app from the map *after* confirming closure
|
|
removeAppIfCurrentProcess(appId, process);
|
|
|
|
return;
|
|
} catch (error: any) {
|
|
logger.error(
|
|
`Error stopping app ${appId} (PID: ${process.pid}, processId: ${processId}):`,
|
|
error,
|
|
);
|
|
// Attempt cleanup even if an error occurred during the stop process
|
|
removeAppIfCurrentProcess(appId, process);
|
|
throw new Error(`Failed to stop app ${appId}: ${error.message}`);
|
|
}
|
|
});
|
|
},
|
|
);
|
|
|
|
ipcMain.handle(
|
|
"restart-app",
|
|
async (
|
|
event: Electron.IpcMainInvokeEvent,
|
|
{
|
|
appId,
|
|
removeNodeModules,
|
|
}: { appId: number; removeNodeModules?: boolean },
|
|
): Promise<void> => {
|
|
logger.log(`Restarting app ${appId}`);
|
|
return withLock(appId, async () => {
|
|
try {
|
|
// First stop the app if it's running
|
|
const appInfo = runningApps.get(appId);
|
|
if (appInfo) {
|
|
const { process, processId } = appInfo;
|
|
logger.log(
|
|
`Stopping app ${appId} (processId ${processId}) before restart`,
|
|
);
|
|
|
|
await killProcess(process);
|
|
runningApps.delete(appId);
|
|
} else {
|
|
logger.log(`App ${appId} not running. Proceeding to start.`);
|
|
}
|
|
|
|
// Kill any orphaned process on port 32100 (in case previous run left it)
|
|
await killProcessOnPort(32100);
|
|
|
|
// Now start the app again
|
|
const app = await db.query.apps.findFirst({
|
|
where: eq(apps.id, appId),
|
|
});
|
|
|
|
if (!app) {
|
|
throw new Error("App not found");
|
|
}
|
|
|
|
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
|
|
|
|
await executeApp({ appPath, appId, event }); // This will handle starting either mode
|
|
|
|
return;
|
|
} catch (error) {
|
|
logger.error(`Error restarting app ${appId}:`, error);
|
|
throw error;
|
|
}
|
|
});
|
|
},
|
|
);
|
|
|
|
ipcMain.handle(
|
|
"edit-app-file",
|
|
async (
|
|
_,
|
|
{
|
|
appId,
|
|
filePath,
|
|
content,
|
|
}: { appId: number; filePath: string; content: string },
|
|
): Promise<void> => {
|
|
const app = await db.query.apps.findFirst({
|
|
where: eq(apps.id, appId),
|
|
});
|
|
|
|
if (!app) {
|
|
throw new Error("App not found");
|
|
}
|
|
|
|
const appPath = getDyadAppPath(app.path);
|
|
const fullPath = path.join(appPath, filePath);
|
|
|
|
// Check if the path is within the app directory (security check)
|
|
if (!fullPath.startsWith(appPath)) {
|
|
throw new Error("Invalid file path");
|
|
}
|
|
|
|
// Ensure directory exists
|
|
const dirPath = path.dirname(fullPath);
|
|
await fsPromises.mkdir(dirPath, { recursive: true });
|
|
|
|
try {
|
|
await fsPromises.writeFile(fullPath, content, "utf-8");
|
|
|
|
// Check if git repository exists and commit the change
|
|
if (fs.existsSync(path.join(appPath, ".git"))) {
|
|
await git.add({
|
|
fs,
|
|
dir: appPath,
|
|
filepath: filePath,
|
|
});
|
|
|
|
await git.commit({
|
|
fs,
|
|
dir: appPath,
|
|
message: `Updated ${filePath}`,
|
|
author: await getGitAuthor(),
|
|
});
|
|
}
|
|
|
|
return;
|
|
} catch (error: any) {
|
|
logger.error(`Error writing file ${filePath} for app ${appId}:`, error);
|
|
throw new Error(`Failed to write file: ${error.message}`);
|
|
}
|
|
},
|
|
);
|
|
|
|
ipcMain.handle(
|
|
"delete-app",
|
|
async (_, { appId }: { appId: number }): Promise<void> => {
|
|
// Static server worker is NOT terminated here anymore
|
|
|
|
return withLock(appId, async () => {
|
|
// Check if app exists
|
|
const app = await db.query.apps.findFirst({
|
|
where: eq(apps.id, appId),
|
|
});
|
|
|
|
if (!app) {
|
|
throw new Error("App not found");
|
|
}
|
|
|
|
// Stop the app if it's running
|
|
if (runningApps.has(appId)) {
|
|
const appInfo = runningApps.get(appId)!;
|
|
try {
|
|
logger.log(`Stopping app ${appId} before deletion.`); // Adjusted log
|
|
await killProcess(appInfo.process);
|
|
runningApps.delete(appId);
|
|
} catch (error: any) {
|
|
logger.error(`Error stopping app ${appId} before deletion:`, error); // Adjusted log
|
|
// Continue with deletion even if stopping fails
|
|
}
|
|
}
|
|
|
|
// Delete app files
|
|
const appPath = getDyadAppPath(app.path);
|
|
try {
|
|
await fsPromises.rm(appPath, { recursive: true, force: true });
|
|
} catch (error: any) {
|
|
logger.error(`Error deleting app files for app ${appId}:`, error);
|
|
throw new Error(`Failed to delete app files: ${error.message}`);
|
|
}
|
|
|
|
// Delete app from database
|
|
try {
|
|
await db.delete(apps).where(eq(apps.id, appId));
|
|
// Note: Associated chats will cascade delete if that's set up in the schema
|
|
return;
|
|
} catch (error: any) {
|
|
logger.error(`Error deleting app ${appId} from database:`, error);
|
|
throw new Error(
|
|
`Failed to delete app from database: ${error.message}`,
|
|
);
|
|
}
|
|
});
|
|
},
|
|
);
|
|
|
|
ipcMain.handle(
|
|
"rename-app",
|
|
async (
|
|
_,
|
|
{
|
|
appId,
|
|
appName,
|
|
appPath,
|
|
}: { appId: number; appName: string; appPath: string },
|
|
): Promise<void> => {
|
|
return withLock(appId, async () => {
|
|
// Check if app exists
|
|
const app = await db.query.apps.findFirst({
|
|
where: eq(apps.id, appId),
|
|
});
|
|
|
|
if (!app) {
|
|
throw new Error("App not found");
|
|
}
|
|
|
|
// Check for conflicts with existing apps
|
|
const nameConflict = await db.query.apps.findFirst({
|
|
where: eq(apps.name, appName),
|
|
});
|
|
|
|
const pathConflict = await db.query.apps.findFirst({
|
|
where: eq(apps.path, appPath),
|
|
});
|
|
|
|
if (nameConflict && nameConflict.id !== appId) {
|
|
throw new Error(`An app with the name '${appName}' already exists`);
|
|
}
|
|
|
|
if (pathConflict && pathConflict.id !== appId) {
|
|
throw new Error(`An app with the path '${appPath}' already exists`);
|
|
}
|
|
|
|
// Stop the app if it's running
|
|
if (runningApps.has(appId)) {
|
|
const appInfo = runningApps.get(appId)!;
|
|
try {
|
|
await killProcess(appInfo.process);
|
|
runningApps.delete(appId);
|
|
} catch (error: any) {
|
|
logger.error(`Error stopping app ${appId} before renaming:`, error);
|
|
throw new Error(
|
|
`Failed to stop app before renaming: ${error.message}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
const oldAppPath = getDyadAppPath(app.path);
|
|
const newAppPath = getDyadAppPath(appPath);
|
|
// Only move files if needed
|
|
if (newAppPath !== oldAppPath) {
|
|
// Move app files
|
|
try {
|
|
// Check if destination directory already exists
|
|
if (fs.existsSync(newAppPath)) {
|
|
throw new Error(
|
|
`Destination path '${newAppPath}' already exists`,
|
|
);
|
|
}
|
|
|
|
// Create parent directory if it doesn't exist
|
|
await fsPromises.mkdir(path.dirname(newAppPath), {
|
|
recursive: true,
|
|
});
|
|
|
|
// Move the files
|
|
await fsPromises.rename(oldAppPath, newAppPath);
|
|
} catch (error: any) {
|
|
logger.error(
|
|
`Error moving app files from ${oldAppPath} to ${newAppPath}:`,
|
|
error,
|
|
);
|
|
throw new Error(`Failed to move app files: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
// Update app in database
|
|
try {
|
|
await db
|
|
.update(apps)
|
|
.set({
|
|
name: appName,
|
|
path: appPath,
|
|
})
|
|
.where(eq(apps.id, appId))
|
|
.returning();
|
|
|
|
return;
|
|
} catch (error: any) {
|
|
// Attempt to rollback the file move
|
|
if (newAppPath !== oldAppPath) {
|
|
try {
|
|
await fsPromises.rename(newAppPath, oldAppPath);
|
|
} catch (rollbackError) {
|
|
logger.error(
|
|
`Failed to rollback file move during rename error:`,
|
|
rollbackError,
|
|
);
|
|
}
|
|
}
|
|
|
|
logger.error(`Error updating app ${appId} in database:`, error);
|
|
throw new Error(`Failed to update app in database: ${error.message}`);
|
|
}
|
|
});
|
|
},
|
|
);
|
|
|
|
ipcMain.handle("reset-all", async (): Promise<void> => {
|
|
logger.log("start: resetting all apps and settings.");
|
|
// Stop all running apps first
|
|
logger.log("stopping all running apps...");
|
|
const runningAppIds = Array.from(runningApps.keys());
|
|
for (const appId of runningAppIds) {
|
|
try {
|
|
const appInfo = runningApps.get(appId)!;
|
|
await killProcess(appInfo.process);
|
|
runningApps.delete(appId);
|
|
} catch (error) {
|
|
logger.error(`Error stopping app ${appId} during reset:`, error);
|
|
// Continue with reset even if stopping fails
|
|
}
|
|
}
|
|
logger.log("all running apps stopped.");
|
|
logger.log("deleting database...");
|
|
// 1. Drop the database by deleting the SQLite file
|
|
const dbPath = getDatabasePath();
|
|
if (fs.existsSync(dbPath)) {
|
|
// Close database connections first
|
|
if (db.$client) {
|
|
db.$client.close();
|
|
}
|
|
await fsPromises.unlink(dbPath);
|
|
logger.log(`Database file deleted: ${dbPath}`);
|
|
}
|
|
logger.log("database deleted.");
|
|
logger.log("deleting settings...");
|
|
// 2. Remove settings
|
|
const userDataPath = getUserDataPath();
|
|
const settingsPath = path.join(userDataPath, "user-settings.json");
|
|
|
|
if (fs.existsSync(settingsPath)) {
|
|
await fsPromises.unlink(settingsPath);
|
|
logger.log(`Settings file deleted: ${settingsPath}`);
|
|
}
|
|
logger.log("settings deleted.");
|
|
// 3. Remove all app files recursively
|
|
// Doing this last because it's the most time-consuming and the least important
|
|
// in terms of resetting the app state.
|
|
logger.log("removing all app files...");
|
|
const dyadAppPath = getDyadAppPath(".");
|
|
if (fs.existsSync(dyadAppPath)) {
|
|
await fsPromises.rm(dyadAppPath, { recursive: true, force: true });
|
|
// Recreate the base directory
|
|
await fsPromises.mkdir(dyadAppPath, { recursive: true });
|
|
}
|
|
logger.log("all app files removed.");
|
|
logger.log("reset all complete.");
|
|
});
|
|
|
|
ipcMain.handle("get-app-version", async (): Promise<{ version: string }> => {
|
|
// Read version from package.json at project root
|
|
const packageJsonPath = path.resolve(__dirname, "..", "..", "package.json");
|
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
return { version: packageJson.version };
|
|
});
|
|
}
|