Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

migrate.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

migrate.tsBlame94 lines · 1 contributor
3ef4c9dClaude1/**
2 * Migration runner — executes every `drizzle/*.sql` file in order.
3 * Tracks applied migrations in a dedicated table so repeat runs are idempotent.
4 */
5
6import { readdir, readFile } from "fs/promises";
7import { join } from "path";
fc1817aClaude8import { neon } from "@neondatabase/serverless";
9import { config } from "../lib/config";
10
11async function runMigrations() {
3ef4c9dClaude12 if (!config.databaseUrl) {
13 throw new Error("DATABASE_URL is not set");
14 }
fc1817aClaude15 const sql = neon(config.databaseUrl);
3ef4c9dClaude16
17 // Ensure tracking table exists
18 await sql`
19 CREATE TABLE IF NOT EXISTS "_migrations" (
20 "name" text PRIMARY KEY,
21 "applied_at" timestamp DEFAULT now() NOT NULL
22 )
23 `;
24
25 const applied = await sql`SELECT name FROM _migrations`;
26 const appliedSet = new Set(
27 (applied as Array<{ name: string }>).map((r) => r.name)
28 );
29
30 const migrationsDir = join(process.cwd(), "drizzle");
31 const files = (await readdir(migrationsDir))
32 .filter((f) => f.endsWith(".sql"))
33 .sort();
34
35 if (files.length === 0) {
36 console.log("No migration files found.");
37 return;
38 }
39
40 for (const file of files) {
41 if (appliedSet.has(file)) {
42 console.log(`[migrate] ${file} — already applied, skipping`);
43 continue;
44 }
45
46 console.log(`[migrate] applying ${file}...`);
47 const content = await readFile(join(migrationsDir, file), "utf8");
48
49 // Split on the drizzle breakpoint marker. Each chunk is one statement.
50 const statements = content
51 .split(/-->\s*statement-breakpoint/)
52 .map((s) => s.trim())
53 .filter((s) => s.length > 0);
54
55 for (const raw of statements) {
56 const stripped = raw
57 .split("\n")
58 .filter((line) => !line.trim().startsWith("--"))
59 .join("\n")
60 .trim();
61 if (!stripped) continue;
62
63 try {
64 await sql(stripped);
65 } catch (err) {
66 const msg = err instanceof Error ? err.message : String(err);
67 if (
68 msg.includes("already exists") ||
69 msg.includes("duplicate_column") ||
70 msg.includes("duplicate_object")
71 ) {
72 console.warn(
73 `[migrate] ${file}: ${msg.slice(0, 120)} (treated as applied)`
74 );
75 continue;
76 }
77 console.error(
78 `[migrate] ${file} failed on statement:\n${stripped.slice(0, 500)}\n\n${msg}`
79 );
80 throw err;
81 }
82 }
83
84 await sql`INSERT INTO _migrations (name) VALUES (${file})`;
85 console.log(`[migrate] ${file} applied`);
86 }
87
88 console.log("[migrate] all migrations complete");
fc1817aClaude89}
90
91runMigrations().catch((err) => {
92 console.error("Migration failed:", err);
93 process.exit(1);
94});