fix(dev): reset legacy dev.db and apply migrations

This commit is contained in:
Zimeng Xiong
2026-02-06 09:54:13 -08:00
parent b075a0cf9e
commit 2e370f9821
2 changed files with 108 additions and 1 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"predev": "node -e \"process.env.DATABASE_URL=process.env.DATABASE_URL||'file:./prisma/dev.db'; require('child_process').execSync('npx prisma migrate deploy', { stdio: 'inherit' });\"", "predev": "node scripts/predev-migrate.cjs",
"dev": "nodemon src/index.ts", "dev": "nodemon src/index.ts",
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest", "test:watch": "vitest",
+107
View File
@@ -0,0 +1,107 @@
/* eslint-disable no-console */
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");
const backendRoot = path.resolve(__dirname, "..");
const resolveDatabaseUrl = (rawUrl) => {
const defaultDbPath = path.resolve(backendRoot, "prisma/dev.db");
if (!rawUrl || String(rawUrl).trim().length === 0) {
return `file:${defaultDbPath}`;
}
if (!String(rawUrl).startsWith("file:")) {
return String(rawUrl);
}
const filePath = String(rawUrl).replace(/^file:/, "");
const prismaDir = path.resolve(backendRoot, "prisma");
const normalizedRelative = filePath.replace(/^\.\/?/, "");
const hasLeadingPrismaDir =
normalizedRelative === "prisma" || normalizedRelative.startsWith("prisma/");
const absolutePath = path.isAbsolute(filePath)
? filePath
: path.resolve(hasLeadingPrismaDir ? backendRoot : prismaDir, normalizedRelative);
return `file:${absolutePath}`;
};
const databaseUrl = resolveDatabaseUrl(process.env.DATABASE_URL);
process.env.DATABASE_URL = databaseUrl;
const nodeEnv = process.env.NODE_ENV || "development";
const run = (cmd) => {
execSync(cmd, {
cwd: backendRoot,
stdio: "inherit",
env: { ...process.env, DATABASE_URL: databaseUrl },
});
};
const getDbFilePath = () => {
if (!databaseUrl.startsWith("file:")) return null;
return databaseUrl.replace(/^file:/, "");
};
const isNonEmptyLegacyDbWithoutMigrations = () => {
const dbPath = getDbFilePath();
if (!dbPath) return false;
if (!fs.existsSync(dbPath)) return false;
// Only attempt this heuristic for SQLite file DBs.
const Database = require("better-sqlite3");
const db = new Database(dbPath, { readonly: true });
try {
const hasMigrations =
db
.prepare(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='_prisma_migrations' LIMIT 1",
)
.get() !== undefined;
const nonEmptyRow = db
.prepare("SELECT COUNT(*) AS cnt FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
.get();
const nonEmpty = Number(nonEmptyRow?.cnt || 0) > 0;
return nonEmpty && !hasMigrations;
} finally {
db.close();
}
};
const backupDbIfPresent = () => {
const dbPath = getDbFilePath();
if (!dbPath) return null;
if (!fs.existsSync(dbPath)) return null;
const dir = path.dirname(dbPath);
const base = path.basename(dbPath, path.extname(dbPath));
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
const backupPath = path.join(dir, `${base}.${stamp}.backup`);
fs.copyFileSync(dbPath, backupPath);
return backupPath;
};
const isNonProd = nodeEnv !== "production";
const isFileDb = databaseUrl.startsWith("file:");
if (isNonProd && isFileDb && isNonEmptyLegacyDbWithoutMigrations()) {
const backupPath = backupDbIfPresent();
console.warn(
`[predev] Prisma migrations cannot be deployed because the database was created without migrations.\n` +
` DATABASE_URL=${databaseUrl}\n` +
(backupPath ? ` Backup: ${backupPath}\n` : "") +
` Resetting local SQLite database to apply migrations.`,
);
run("npx prisma migrate reset --force --skip-seed");
process.exit(0);
}
run("npx prisma migrate deploy");