Neon / portal template support (#713)
TODOs: - [x] Do restart when checkout / restore if there is a DB - [x] List all branches (branch id, name, date) - [x] Allow checking out versions with no DB - [x] safeguard to never delete main branches - [x] create app hook for neon template - [x] weird UX with connector on configure panel - [x] tiny neon logo in connector - [x] deploy to vercel - [x] build forgot password page - [x] what about email setup - [x] lots of imgix errors - [x] edit file - db snapshot - [x] DYAD_DISABLE_DB_PUSH - [ ] update portal doc - [x] switch preview branch to be read-only endpoint - [x] disable supabase sys prompt if neon is enabled - [ ] https://payloadcms.com/docs/upload/storage-adapters - [x] need to use main branch... Phase 2? - [x] generate DB migrations
This commit is contained in:
138
src/ipc/handlers/portal_handlers.ts
Normal file
138
src/ipc/handlers/portal_handlers.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { createLoggedHandler } from "./safe_handle";
|
||||
import log from "electron-log";
|
||||
import { db } from "../../db";
|
||||
import { apps } from "../../db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { getDyadAppPath } from "../../paths/paths";
|
||||
import { spawn } from "child_process";
|
||||
import fs from "node:fs";
|
||||
import git from "isomorphic-git";
|
||||
import { gitCommit } from "../utils/git_utils";
|
||||
import { storeDbTimestampAtCurrentVersion } from "../utils/neon_timestamp_utils";
|
||||
|
||||
const logger = log.scope("portal_handlers");
|
||||
const handle = createLoggedHandler(logger);
|
||||
|
||||
async function getApp(appId: number) {
|
||||
const app = await db.query.apps.findFirst({
|
||||
where: eq(apps.id, appId),
|
||||
});
|
||||
if (!app) {
|
||||
throw new Error(`App with id ${appId} not found`);
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
export function registerPortalHandlers() {
|
||||
handle(
|
||||
"portal:migrate-create",
|
||||
async (_, { appId }: { appId: number }): Promise<{ output: string }> => {
|
||||
const app = await getApp(appId);
|
||||
const appPath = getDyadAppPath(app.path);
|
||||
|
||||
// Run the migration command
|
||||
const migrationOutput = await new Promise<string>((resolve, reject) => {
|
||||
logger.info(`Running migrate:create for app ${appId} at ${appPath}`);
|
||||
|
||||
const process = spawn("npm run migrate:create -- --skip-empty", {
|
||||
cwd: appPath,
|
||||
shell: true,
|
||||
stdio: "pipe",
|
||||
});
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
process.stdout?.on("data", (data) => {
|
||||
const output = data.toString();
|
||||
stdout += output;
|
||||
logger.info(`migrate:create stdout: ${output}`);
|
||||
if (output.includes("created or renamed from another")) {
|
||||
process.stdin.write(`\r\n`);
|
||||
logger.info(
|
||||
`App ${appId} (PID: ${process.pid}) wrote enter to stdin to automatically respond to drizzle migrate input`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
process.stderr?.on("data", (data) => {
|
||||
const output = data.toString();
|
||||
stderr += output;
|
||||
logger.warn(`migrate:create stderr: ${output}`);
|
||||
});
|
||||
|
||||
process.on("close", (code) => {
|
||||
const combinedOutput =
|
||||
stdout + (stderr ? `\n\nErrors/Warnings:\n${stderr}` : "");
|
||||
|
||||
if (code === 0) {
|
||||
if (stdout.includes("Migration created at")) {
|
||||
logger.info(
|
||||
`migrate:create completed successfully for app ${appId}`,
|
||||
);
|
||||
resolve(combinedOutput);
|
||||
} else {
|
||||
logger.error(
|
||||
`migrate:create completed successfully for app ${appId} but no migration was created`,
|
||||
);
|
||||
reject(
|
||||
new Error(
|
||||
"No migration was created because no changes were found.",
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logger.error(
|
||||
`migrate:create failed for app ${appId} with exit code ${code}`,
|
||||
);
|
||||
const errorMessage = `Migration creation failed (exit code ${code})\n\n${combinedOutput}`;
|
||||
reject(new Error(errorMessage));
|
||||
}
|
||||
});
|
||||
|
||||
process.on("error", (err) => {
|
||||
logger.error(`Failed to spawn migrate:create for app ${appId}:`, err);
|
||||
const errorMessage = `Failed to run migration command: ${err.message}\n\nOutput:\n${stdout}\n\nErrors:\n${stderr}`;
|
||||
reject(new Error(errorMessage));
|
||||
});
|
||||
});
|
||||
|
||||
if (app.neonProjectId && app.neonDevelopmentBranchId) {
|
||||
try {
|
||||
await storeDbTimestampAtCurrentVersion({
|
||||
appId: app.id,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
"Error storing Neon timestamp at current version:",
|
||||
error,
|
||||
);
|
||||
throw new Error(
|
||||
"Could not store Neon timestamp at current version; database versioning functionality is not working: " +
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Stage all changes and commit
|
||||
try {
|
||||
await git.add({
|
||||
fs,
|
||||
dir: appPath,
|
||||
filepath: ".",
|
||||
});
|
||||
|
||||
const commitHash = await gitCommit({
|
||||
path: appPath,
|
||||
message: "[dyad] Generate database migration file",
|
||||
});
|
||||
|
||||
logger.info(`Successfully committed migration changes: ${commitHash}`);
|
||||
return { output: migrationOutput };
|
||||
} catch (gitError) {
|
||||
logger.error(`Migration created but failed to commit: ${gitError}`);
|
||||
throw new Error(`Migration created but failed to commit: ${gitError}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user