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.tsBlame119 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.
6f3ce18Claude4 *
5 * Dual-driver: uses Neon HTTP for *.neon.tech URLs, postgres.js TCP for
6 * everything else (localhost, RDS, Supabase, plain Postgres).
3ef4c9dClaude7 */
8
9import { readdir, readFile } from "fs/promises";
10import { join } from "path";
fc1817aClaude11import { neon } from "@neondatabase/serverless";
6f3ce18Claude12import postgres from "postgres";
fc1817aClaude13import { config } from "../lib/config";
6f3ce18Claude14import { isNeonUrl } from "./index";
15
16type Exec = (query: string, params?: unknown[]) => Promise<unknown>;
fc1817aClaude17
18async function runMigrations() {
3ef4c9dClaude19 if (!config.databaseUrl) {
20 throw new Error("DATABASE_URL is not set");
21 }
6f3ce18Claude22
23 let exec: Exec;
24 let cleanup: (() => Promise<void>) | null = null;
25
26 if (isNeonUrl(config.databaseUrl)) {
27 const neonSql = neon(config.databaseUrl);
28 exec = (q, p = []) => neonSql(q, p);
29 } else {
30 const client = postgres(config.databaseUrl, { max: 1, prepare: false });
31 exec = (q, p) =>
32 p && p.length > 0 ? client.unsafe(q, p as never[]) : client.unsafe(q);
33 cleanup = async () => {
34 await client.end({ timeout: 5 });
35 };
3ef4c9dClaude36 }
37
6f3ce18Claude38 try {
39 // Ensure tracking table exists
40 await exec(`
41 CREATE TABLE IF NOT EXISTS "_migrations" (
42 "name" text PRIMARY KEY,
43 "applied_at" timestamp DEFAULT now() NOT NULL
44 )
45 `);
46
47 const appliedRows = (await exec(`SELECT name FROM _migrations`)) as Array<{
48 name: string;
49 }>;
50 const appliedSet = new Set(appliedRows.map((r) => r.name));
51
52 const migrationsDir = join(process.cwd(), "drizzle");
53 const files = (await readdir(migrationsDir))
54 .filter((f) => f.endsWith(".sql"))
55 .sort();
56
57 if (files.length === 0) {
58 console.log("No migration files found.");
59 return;
3ef4c9dClaude60 }
61
6f3ce18Claude62 for (const file of files) {
63 if (appliedSet.has(file)) {
64 console.log(`[migrate] ${file} — already applied, skipping`);
65 continue;
66 }
67
68 console.log(`[migrate] applying ${file}...`);
69 const content = await readFile(join(migrationsDir, file), "utf8");
70
71 // Split on the drizzle breakpoint marker. Each chunk is one statement.
72 const statements = content
73 .split(/-->\s*statement-breakpoint/)
74 .map((s) => s.trim())
75 .filter((s) => s.length > 0);
76
77 for (const raw of statements) {
78 const stripped = raw
79 .split("\n")
80 .filter((line) => !line.trim().startsWith("--"))
81 .join("\n")
82 .trim();
83 if (!stripped) continue;
84
85 try {
86 await exec(stripped);
87 } catch (err) {
88 const msg = err instanceof Error ? err.message : String(err);
89 if (
90 msg.includes("already exists") ||
91 msg.includes("duplicate_column") ||
92 msg.includes("duplicate_object")
93 ) {
94 console.warn(
95 `[migrate] ${file}: ${msg.slice(0, 120)} (treated as applied)`
96 );
97 continue;
98 }
99 console.error(
100 `[migrate] ${file} failed on statement:\n${stripped.slice(0, 500)}\n\n${msg}`
3ef4c9dClaude101 );
6f3ce18Claude102 throw err;
3ef4c9dClaude103 }
104 }
6f3ce18Claude105
106 await exec(`INSERT INTO _migrations (name) VALUES ($1)`, [file]);
107 console.log(`[migrate] ${file} applied`);
3ef4c9dClaude108 }
109
6f3ce18Claude110 console.log("[migrate] all migrations complete");
111 } finally {
112 if (cleanup) await cleanup();
3ef4c9dClaude113 }
fc1817aClaude114}
115
116runMigrations().catch((err) => {
117 console.error("Migration failed:", err);
118 process.exit(1);
119});