#!/usr/bin/env node /** * Cross-platform goose migrate (+ optional sqlc generate). * Loads monorepo-root `.env` when DATABASE_URL is unset. * * Usage: * node scripts/migrate.mjs # goose only (default) * node scripts/migrate.mjs --sqlc # also regenerate apps/api/internal/db/sqlc * * A 4GiB host is enough for goose + the app. sqlc's go-pgquery/wazero path * allocates a ~4GiB WASM heap by itself, so it OOMs on 4GiB VPS — skip by * default; only run --sqlc on a machine with ≥6GiB free (or install a * prebuilt `sqlc` binary and still prefer ≥6GiB). */ import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { loadRootEnv, repoRoot } from "./root-env.mjs"; const args = new Set(process.argv.slice(2)); const wantSqlc = args.has("--sqlc") || process.env.DESCRYBE_SQLC === "1" || /^true|yes|on$/i.test(String(process.env.DESCRYBE_SQLC || "")); /** Prefer this much free/total RAM (MiB) before attempting sqlc go-run/wazero. */ const SQLC_MIN_RAM_MIB = Number(process.env.DESCRYBE_SQLC_MIN_RAM_MIB || 6144); const env = loadRootEnv(process.env); if (!env.DATABASE_URL) { console.error( "DATABASE_URL is required (set in monorepo-root .env or the shell; see .env.example)", ); process.exit(1); } const apiDir = path.join(repoRoot, "apps", "api"); function run(command, cmdArgs, opts = {}) { const r = spawnSync(command, cmdArgs, { cwd: apiDir, env, stdio: "inherit", shell: process.platform === "win32", ...opts, }); if (r.error) { console.error(r.error.message); process.exit(1); } if (r.status !== 0) { process.exit(r.status ?? 1); } } /** Best-effort total RAM in MiB (Linux /proc, Windows wmic, or null). */ function totalRamMiB() { try { if (process.platform === "linux") { const text = fs.readFileSync("/proc/meminfo", "utf8"); const m = text.match(/^MemTotal:\s+(\d+)\s+kB/m); if (m) return Math.floor(Number(m[1]) / 1024); } if (process.platform === "win32") { const r = spawnSync( "powershell", [ "-NoProfile", "-Command", "[int]((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory/1MB)", ], { encoding: "utf8" }, ); const n = Number(String(r.stdout || "").trim()); if (Number.isFinite(n) && n > 0) return n; } } catch { /* ignore */ } return null; } function hasSqlcBinary() { const r = spawnSync("sqlc", ["version"], { encoding: "utf8", shell: process.platform === "win32", }); return !(r.error || r.status !== 0); } console.log("==> goose up (apps/api/sql/schema)"); run("go", [ "run", "github.com/pressly/goose/v3/cmd/goose@v3.24.3", "-dir", "sql/schema", "postgres", env.DATABASE_URL, "up", ]); if (!wantSqlc) { console.log( "==> sqlc skipped (default). 4GiB hosts: goose + app are enough; sqlc needs extra headroom.", ); console.log(" Regenerate on a larger machine: npm run migrate -- --sqlc"); console.log("==> done"); process.exit(0); } const ram = totalRamMiB(); if (ram != null && ram < SQLC_MIN_RAM_MIB && !hasSqlcBinary()) { console.error( `[migrate] refusing sqlc: ~${ram}MiB RAM detected (need ≥${SQLC_MIN_RAM_MIB}MiB for wazero go run).`, ); console.error( "[migrate] goose already applied. Install a prebuilt sqlc binary, or run --sqlc on a larger host.", ); console.error( "[migrate] Or set DESCRYBE_SQLC_MIN_RAM_MIB=0 to force (may OOM).", ); process.exit(1); } console.log("==> sqlc generate"); const sqlc = spawnSync("sqlc", ["generate"], { cwd: apiDir, env, stdio: "inherit", shell: process.platform === "win32", }); if (sqlc.status === 0) { console.log("==> done"); process.exit(0); } console.log("sqlc not on PATH (or failed); using go run"); if (ram != null && ram < SQLC_MIN_RAM_MIB) { console.error( `[migrate] go run sqlc will likely OOM on ~${ram}MiB (wazero ~4GiB heap).`, ); console.error( "[migrate] Install https://docs.sqlc.dev/en/latest/overview/install.html then retry, or use a larger host.", ); process.exit(1); } run("go", [ "run", "github.com/sqlc-dev/sqlc/cmd/sqlc@v1.28.0", "generate", ]); console.log("==> done");