2026-08-09 22:47:43 +02:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
/**
|
2026-08-10 01:31:55 +02:00
|
|
|
* Cross-platform goose migrate (+ optional sqlc generate).
|
2026-08-09 22:47:43 +02:00
|
|
|
* Loads monorepo-root `.env` when DATABASE_URL is unset.
|
|
|
|
|
*
|
2026-08-10 01:31:55 +02:00
|
|
|
* 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).
|
2026-08-09 22:47:43 +02:00
|
|
|
*/
|
|
|
|
|
import { spawnSync } from "node:child_process";
|
|
|
|
|
import path from "node:path";
|
2026-08-10 02:04:45 +02:00
|
|
|
import {
|
|
|
|
|
detectHostProfile,
|
|
|
|
|
formatHostNote,
|
|
|
|
|
withGoLowMem,
|
|
|
|
|
} from "./lowmem-env.mjs";
|
2026-08-09 22:47:43 +02:00
|
|
|
import { loadRootEnv, repoRoot } from "./root-env.mjs";
|
|
|
|
|
|
2026-08-10 01:31:55 +02:00
|
|
|
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);
|
|
|
|
|
|
2026-08-10 02:04:45 +02:00
|
|
|
const profile = detectHostProfile();
|
|
|
|
|
const env = withGoLowMem(loadRootEnv(process.env), profile);
|
2026-08-09 22:47:43 +02:00
|
|
|
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");
|
|
|
|
|
|
2026-08-10 01:31:55 +02:00
|
|
|
function run(command, cmdArgs, opts = {}) {
|
|
|
|
|
const r = spawnSync(command, cmdArgs, {
|
2026-08-09 22:47:43 +02:00
|
|
|
cwd: apiDir,
|
|
|
|
|
env,
|
|
|
|
|
stdio: "inherit",
|
2026-08-10 02:04:45 +02:00
|
|
|
// Keep go argv intact on Windows (ldflags / module paths).
|
|
|
|
|
shell: process.platform === "win32" && command !== "go",
|
2026-08-09 22:47:43 +02:00
|
|
|
...opts,
|
|
|
|
|
});
|
|
|
|
|
if (r.error) {
|
|
|
|
|
console.error(r.error.message);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
if (r.status !== 0) {
|
|
|
|
|
process.exit(r.status ?? 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 01:31:55 +02:00
|
|
|
function totalRamMiB() {
|
2026-08-10 02:04:45 +02:00
|
|
|
return profile.totalMiB;
|
2026-08-10 01:31:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function hasSqlcBinary() {
|
|
|
|
|
const r = spawnSync("sqlc", ["version"], {
|
|
|
|
|
encoding: "utf8",
|
|
|
|
|
shell: process.platform === "win32",
|
|
|
|
|
});
|
|
|
|
|
return !(r.error || r.status !== 0);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-09 22:47:43 +02:00
|
|
|
console.log("==> goose up (apps/api/sql/schema)");
|
2026-08-10 02:04:45 +02:00
|
|
|
console.log(`NOTE ${formatHostNote(profile)}`);
|
|
|
|
|
console.log(
|
|
|
|
|
`NOTE GOMAXPROCS=${env.GOMAXPROCS}` +
|
|
|
|
|
(env.GOMEMLIMIT ? ` GOMEMLIMIT=${env.GOMEMLIMIT}` : "") +
|
|
|
|
|
(env.GOGC ? ` GOGC=${env.GOGC}` : "") +
|
|
|
|
|
" (sqlc skipped by default; needs ≥6GiB)",
|
|
|
|
|
);
|
2026-08-09 22:47:43 +02:00
|
|
|
run("go", [
|
|
|
|
|
"run",
|
|
|
|
|
"github.com/pressly/goose/v3/cmd/goose@v3.24.3",
|
|
|
|
|
"-dir",
|
|
|
|
|
"sql/schema",
|
|
|
|
|
"postgres",
|
|
|
|
|
env.DATABASE_URL,
|
|
|
|
|
"up",
|
|
|
|
|
]);
|
|
|
|
|
|
2026-08-10 01:31:55 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-09 22:47:43 +02:00
|
|
|
console.log("==> sqlc generate");
|
|
|
|
|
const sqlc = spawnSync("sqlc", ["generate"], {
|
|
|
|
|
cwd: apiDir,
|
|
|
|
|
env,
|
|
|
|
|
stdio: "inherit",
|
|
|
|
|
shell: process.platform === "win32",
|
|
|
|
|
});
|
2026-08-10 01:31:55 +02:00
|
|
|
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);
|
2026-08-09 22:47:43 +02:00
|
|
|
}
|
|
|
|
|
|
2026-08-10 01:31:55 +02:00
|
|
|
run("go", [
|
|
|
|
|
"run",
|
|
|
|
|
"github.com/sqlc-dev/sqlc/cmd/sqlc@v1.28.0",
|
|
|
|
|
"generate",
|
|
|
|
|
]);
|
|
|
|
|
|
2026-08-09 22:47:43 +02:00
|
|
|
console.log("==> done");
|