fix(dev): reset legacy dev.db and apply migrations
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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");
|
||||||
Reference in New Issue
Block a user