Files
moreminimore-vibe/src/ipc/handlers/version_handlers.ts
2025-06-16 23:36:43 -07:00

186 lines
5.0 KiB
TypeScript

import { db } from "../../db";
import { apps, messages } from "../../db/schema";
import { desc, eq, and, gt } from "drizzle-orm";
import type { Version, BranchResult } from "../ipc_types";
import fs from "node:fs";
import path from "node:path";
import { getDyadAppPath } from "../../paths/paths";
import git, { type ReadCommitResult } from "isomorphic-git";
import { withLock } from "../utils/lock_utils";
import log from "electron-log";
import { createLoggedHandler } from "./safe_handle";
import { gitCheckout, gitCommit, gitStageToRevert } from "../utils/git_utils";
const logger = log.scope("version_handlers");
const handle = createLoggedHandler(logger);
export function registerVersionHandlers() {
handle("list-versions", async (_, { appId }: { appId: number }) => {
const app = await db.query.apps.findFirst({
where: eq(apps.id, appId),
});
if (!app) {
// The app might have just been deleted, so we return an empty array.
return [];
}
const appPath = getDyadAppPath(app.path);
// Just return an empty array if the app is not a git repo.
if (!fs.existsSync(path.join(appPath, ".git"))) {
return [];
}
const commits = await git.log({
fs,
dir: appPath,
// KEEP UP TO DATE WITH ChatHeader.tsx
depth: 10_000, // Limit to last 10_000 commits for performance
});
return commits.map((commit: ReadCommitResult) => ({
oid: commit.oid,
message: commit.commit.message,
timestamp: commit.commit.author.timestamp,
})) satisfies Version[];
});
handle(
"get-current-branch",
async (_, { appId }: { appId: number }): Promise<BranchResult> => {
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);
// Return appropriate result if the app is not a git repo
if (!fs.existsSync(path.join(appPath, ".git"))) {
throw new Error("Not a git repository");
}
try {
const currentBranch = await git.currentBranch({
fs,
dir: appPath,
fullname: false,
});
return {
branch: currentBranch || "<no-branch>",
};
} catch (error: any) {
logger.error(`Error getting current branch for app ${appId}:`, error);
throw new Error(`Failed to get current branch: ${error.message}`);
}
},
);
handle(
"revert-version",
async (
_,
{
appId,
previousVersionId,
}: { appId: number; previousVersionId: string },
): Promise<void> => {
return withLock(appId, async () => {
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);
await gitCheckout({
path: appPath,
ref: "main",
});
await gitStageToRevert({
path: appPath,
targetOid: previousVersionId,
});
await gitCommit({
path: appPath,
message: `Reverted all changes back to version ${previousVersionId}`,
});
// Find the chat and message associated with the commit hash
const messageWithCommit = await db.query.messages.findFirst({
where: eq(messages.commitHash, previousVersionId),
with: {
chat: true,
},
});
// If we found a message with this commit hash, delete all subsequent messages (but keep this message)
if (messageWithCommit) {
const chatId = messageWithCommit.chatId;
// Find all messages in this chat with IDs > the one with our commit hash
const messagesToDelete = await db.query.messages.findMany({
where: and(
eq(messages.chatId, chatId),
gt(messages.id, messageWithCommit.id),
),
orderBy: desc(messages.id),
});
logger.log(
`Deleting ${messagesToDelete.length} messages after commit ${previousVersionId} from chat ${chatId}`,
);
// Delete the messages
if (messagesToDelete.length > 0) {
await db
.delete(messages)
.where(
and(
eq(messages.chatId, chatId),
gt(messages.id, messageWithCommit.id),
),
);
}
}
});
},
);
handle(
"checkout-version",
async (
_,
{ appId, versionId }: { appId: number; versionId: string },
): Promise<void> => {
return withLock(appId, async () => {
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);
await gitCheckout({
path: appPath,
ref: versionId,
});
});
},
);
}