Files
moreminimore-vibe/src/ipc/handlers/import_handlers.ts
Will Chen 6e08bc5c62 Import app (#189)
Fixes #163
2025-05-19 14:03:10 -07:00

148 lines
3.9 KiB
TypeScript

import { dialog } from "electron";
import fs from "fs/promises";
import path from "path";
import { createLoggedHandler } from "./safe_handle";
import log from "electron-log";
import { getDyadAppPath } from "../../paths/paths";
import { apps } from "@/db/schema";
import { db } from "@/db";
import { chats } from "@/db/schema";
import { eq } from "drizzle-orm";
import git from "isomorphic-git";
import { getGitAuthor } from "../utils/git_author";
import { ImportAppParams, ImportAppResult } from "../ipc_types";
const logger = log.scope("import-handlers");
const handle = createLoggedHandler(logger);
export function registerImportHandlers() {
// Handler for selecting an app folder
handle("select-app-folder", async () => {
const result = await dialog.showOpenDialog({
properties: ["openDirectory"],
title: "Select App Folder to Import",
});
if (result.canceled) {
return { path: null, name: null };
}
const selectedPath = result.filePaths[0];
const folderName = path.basename(selectedPath);
return { path: selectedPath, name: folderName };
});
// Handler for checking if AI_RULES.md exists
handle("check-ai-rules", async (_, { path: appPath }: { path: string }) => {
try {
await fs.access(path.join(appPath, "AI_RULES.md"));
return { exists: true };
} catch {
return { exists: false };
}
});
// Handler for checking if an app name is already taken
handle("check-app-name", async (_, { appName }: { appName: string }) => {
// Check filesystem
const appPath = getDyadAppPath(appName);
try {
await fs.access(appPath);
return { exists: true };
} catch {
// Path doesn't exist, continue checking database
}
// Check database
const existingApp = await db.query.apps.findFirst({
where: eq(apps.name, appName),
});
return { exists: !!existingApp };
});
// Handler for importing an app
handle(
"import-app",
async (
_,
{ path: sourcePath, appName }: ImportAppParams,
): Promise<ImportAppResult> => {
// Validate the source path exists
try {
await fs.access(sourcePath);
} catch {
throw new Error("Source folder does not exist");
}
const destPath = getDyadAppPath(appName);
// Check if the app already exists
const errorMessage = "An app with this name already exists";
try {
await fs.access(destPath);
throw new Error(errorMessage);
} catch (error: any) {
if (error.message === errorMessage) {
throw error;
}
}
// Copy the app folder to the Dyad apps directory, excluding node_modules
await fs.cp(sourcePath, destPath, {
recursive: true,
filter: (source) => !source.includes("node_modules"),
});
const isGitRepo = await fs
.access(path.join(destPath, ".git"))
.then(() => true)
.catch(() => false);
if (!isGitRepo) {
// Initialize git repo and create first commit
await git.init({
fs: fs,
dir: destPath,
defaultBranch: "main",
});
// Stage all files
await git.add({
fs: fs,
dir: destPath,
filepath: ".",
});
// Create initial commit
await git.commit({
fs: fs,
dir: destPath,
message: "Init Dyad app",
author: await getGitAuthor(),
});
}
// Create a new app
const [app] = await db
.insert(apps)
.values({
name: appName,
// Use the name as the path for now
path: appName,
})
.returning();
// Create an initial chat for this app
const [chat] = await db
.insert(chats)
.values({
appId: app.id,
})
.returning();
return { appId: app.id, chatId: chat.id };
},
);
logger.debug("Registered import IPC handlers");
}