Skip to content

Commit 39b12dd

Browse files
committed
chore(web/scripts): migration runner using @neondatabase/serverless
Single-file Node script that loads NEON_DATABASE_URL via `node --env-file=.env.local`, splits a drizzle migration on the `--> statement-breakpoint` marker, and runs each statement over the existing HTTP driver. Useful because: - psql isn't always installed on dev machines (this came up when shipping migration 0022). - .claude/rules/db-migrations.md mandates manual application against prod — this script is the manual path without requiring brew/libpq. - The same driver the runtime uses, so behavior is consistent with what production would see. Usage: node --env-file=.env.local scripts/run-migration.mjs \\ src/db/migrations/0022_ai_tagging.sql Reports per-statement OK/FAIL and bails on first error. Does not write to a migrations journal — drizzle-kit owns _journal.json, this is just for the apply step.
1 parent 3a591bf commit 39b12dd

1 file changed

Lines changed: 50 additions & 0 deletions

File tree

web/scripts/run-migration.mjs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// One-shot migration runner via @neondatabase/serverless HTTP driver.
2+
// Usage: node --env-file=.env.local scripts/run-migration.mjs <path>
3+
//
4+
// Splits on drizzle's `--> statement-breakpoint` marker and runs
5+
// each statement separately. Reports which already-applied
6+
// statements no-op'd vs which actually ran.
7+
8+
import { neon } from "@neondatabase/serverless";
9+
import { readFileSync } from "node:fs";
10+
11+
const url = process.env.NEON_DATABASE_URL;
12+
if (!url) {
13+
console.error("NEON_DATABASE_URL missing");
14+
process.exit(1);
15+
}
16+
17+
const path = process.argv[2];
18+
if (!path) {
19+
console.error("usage: run-migration.mjs <path-to-sql>");
20+
process.exit(1);
21+
}
22+
23+
const sql = neon(url);
24+
25+
// Identify which DB we're hitting before changing anything.
26+
const [{ host_db }] = await sql`SELECT current_database() AS host_db`;
27+
console.log(`Target DB: ${host_db}`);
28+
29+
const file = readFileSync(path, "utf8");
30+
const statements = file
31+
.split("--> statement-breakpoint")
32+
.map((s) => s.replace(/^\s*--.*$/gm, "").trim())
33+
.filter((s) => s.length > 0);
34+
35+
console.log(`Running ${statements.length} statements from ${path}...`);
36+
37+
for (let i = 0; i < statements.length; i++) {
38+
const stmt = statements[i];
39+
const head = stmt.split("\n")[0].slice(0, 80);
40+
try {
41+
await sql.query(stmt);
42+
console.log(` [${i + 1}/${statements.length}] OK ${head}`);
43+
} catch (err) {
44+
console.error(` [${i + 1}/${statements.length}] FAIL ${head}`);
45+
console.error(` ${err.message}`);
46+
process.exit(1);
47+
}
48+
}
49+
50+
console.log("Migration applied.");

0 commit comments

Comments
 (0)