Files
shared-memory/apps/web/scripts/migrate.ts
T
shadowdaoandClaude Opus 4.7 973cc68832 fix(migrate): resolve migrations dir alongside or above the script
When esbuild bundles migrate.ts to apps/web/migrate.mjs (one directory
higher than the source location), the relative `..` path in the previous
implementation pointed at apps/, not apps/web/. Try both layouts and
allow an explicit MIGRATIONS_DIR override.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 08:06:06 -07:00

84 lines
2.6 KiB
TypeScript

/**
* Run pending SQL migrations from ./drizzle in lexical filename order.
*
* Lightweight runner — drizzle-kit's TS migrator doesn't handle the raw SQL
* features we need (pgvector, generated columns), so we manage migration
* state ourselves in `_migrations` and apply files as plain SQL.
*/
import { existsSync } from "node:fs";
import { readFile, readdir } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import postgres from "postgres";
const __dirname = dirname(fileURLToPath(import.meta.url));
// Resolve the migrations directory. Try, in order:
// 1. $MIGRATIONS_DIR env override (explicit deploy-time control)
// 2. `<script>/drizzle` — production: migrate.mjs sits alongside drizzle/
// 3. `<script>/../drizzle` — dev: scripts/migrate.ts has drizzle/ one up
function findMigrationsDir(): string {
if (process.env.MIGRATIONS_DIR) return process.env.MIGRATIONS_DIR;
const sibling = join(__dirname, "drizzle");
if (existsSync(sibling)) return sibling;
const parent = join(__dirname, "..", "drizzle");
if (existsSync(parent)) return parent;
throw new Error(
`Couldn't locate migrations directory (tried ${sibling}, ${parent}). ` +
`Set MIGRATIONS_DIR to override.`,
);
}
const MIGRATIONS_DIR = findMigrationsDir();
async function main() {
const url = process.env.DATABASE_URL;
if (!url) {
console.error("DATABASE_URL is not set");
process.exit(1);
}
const sql = postgres(url, { max: 1, prepare: false });
try {
await sql`
CREATE TABLE IF NOT EXISTS "_migrations" (
"id" serial PRIMARY KEY,
"name" text NOT NULL UNIQUE,
"applied_at" timestamptz NOT NULL DEFAULT now()
)
`;
const files = (await readdir(MIGRATIONS_DIR))
.filter((f) => f.endsWith(".sql"))
.sort();
const applied = new Set(
(await sql<{ name: string }[]>`SELECT name FROM "_migrations"`).map((r) => r.name),
);
for (const file of files) {
if (applied.has(file)) {
console.log(`✓ ${file} (already applied)`);
continue;
}
const body = await readFile(join(MIGRATIONS_DIR, file), "utf8");
console.log(`→ ${file} (applying)`);
await sql.begin(async (tx) => {
await tx.unsafe(body);
await tx`INSERT INTO "_migrations" (name) VALUES (${file})`;
});
console.log(`✓ ${file}`);
}
console.log("Migrations complete.");
} finally {
await sql.end({ timeout: 5 });
}
}
main().catch((err) => {
console.error("Migration failed:", err);
process.exit(1);
});