From edf3989ef225a4650a40dffe96f5d2cb5d4b1196 Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 9 Jun 2026 18:02:08 -0600 Subject: [PATCH] fix(deploy): move inline node -e scripts into separate files to fix shell quoting errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inline node -e '...' blocks inside the YAML run: block had shell quoting conflicts (nested parens, single-quote escapes) that caused act/local tooling to fail with 'syntax error near unexpected token ('. Split into two dedicated scripts: - scripts/preflight-check.js — neon_auth schema check + 0001_init.sql tracking repair - scripts/postflight-check.js — admin_users table verification after migrations Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/deploy.yml | 69 ++--------------------------------- scripts/postflight-check.js | 33 +++++++++++++++++ scripts/preflight-check.js | 71 +++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 67 deletions(-) create mode 100644 scripts/postflight-check.js create mode 100644 scripts/preflight-check.js diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 62b0e5c..c088942 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -25,74 +25,9 @@ jobs: DATABASE_URL: ${{ secrets.DATABASE_URL }} run: | set -e - echo "=== Pre-flight: checking for neon_auth schema (required by 0001_init.sql for FKs to neon_auth.user and related RPCs) ===" - node -e ' - const { Client } = require("pg"); - const url = process.env.DATABASE_URL; - if (!url) { console.error("No DATABASE_URL"); process.exit(1); } - const c = new Client({ connectionString: url }); - c.connect() - .then(() => c.query("SELECT 1 FROM information_schema.schemata WHERE schema_name = \'neon_auth\'")) - .then(res => { - if (res.rows.length === 0) { - console.error("✗ FATAL: neon_auth schema does not exist in the target database."); - console.error("Enable Neon Auth on the Neon project/branch that this DATABASE_URL points to first."); - console.error("Run: neonctl neon-auth (or equivalent) against the correct Neon branch."); - console.error("The neon_auth schema is created by Neon Auth and is a prerequisite for 0001_init.sql."); - process.exit(1); - } - console.log("✓ neon_auth schema exists"); - return c.end(); - }) - .then(() => process.exit(0)) - .catch(e => { console.error("Pre-flight check failed:", e.message); process.exit(1); }); - ' - echo "=== Running migrations against DATABASE_URL (masked value for logs) ===" - # The migrate runner (scripts/migrate.js) now includes repair logic for - # DBs that had 0001_init.sql applied before _migrations tracking was added. - # We also run a tiny pre-repair here so the step is resilient even if an - # older copy of the runner is present in the checkout. - node -e ' - const { Client } = require("pg"); - const url = process.env.DATABASE_URL; - if (!url) { process.exit(0); } - const c = new Client({ connectionString: url }); - c.connect() - .then(() => c.query("SELECT 1 FROM information_schema.tables WHERE table_name = '\''admin_users'\'' LIMIT 1")) - .then(async (res) => { - if (res.rows.length > 0) { - await c.query(` - CREATE TABLE IF NOT EXISTS _migrations ( - filename TEXT PRIMARY KEY, - applied_at TIMESTAMPTZ NOT NULL DEFAULT now() - ); - INSERT INTO _migrations (filename) - VALUES ('\''0001_init.sql'\'') - ON CONFLICT (filename) DO NOTHING; - `); - console.log("✓ 0001_init.sql tracking repaired (admin_users already present)"); - } - return c.end(); - }) - .catch(() => { /* best effort */ }); - ' + node scripts/preflight-check.js npm run migrate:one - echo "=== Post-migration verification: critical table admin_users must exist ===" - node -e ' - const { Client } = require("pg"); - const url = process.env.DATABASE_URL; - if (!url) { console.error("No DATABASE_URL"); process.exit(1); } - const c = new Client({ connectionString: url }); - c.connect() - .then(() => c.query("SELECT 1 FROM admin_users LIMIT 1")) - .then(() => { console.log("✓ admin_users table exists and is queryable"); return c.end(); }) - .then(() => process.exit(0)) - .catch(e => { - console.error("✗ FATAL: admin_users relation missing after migrate:", e.message); - console.error("The deploy cannot continue. The secret DATABASE_URL must have had db/migrations/0001_init.sql applied successfully."); - process.exit(1); - }); - ' + node scripts/postflight-check.js - name: Build env: diff --git a/scripts/postflight-check.js b/scripts/postflight-check.js new file mode 100644 index 0000000..8804817 --- /dev/null +++ b/scripts/postflight-check.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node +/** + * Post-migration verification. + * Called by .gitea/workflows/deploy.yml after npm run migrate:one. + * Confirms critical table admin_users is queryable. + */ +const { Client } = require("pg"); + +async function main() { + const url = process.env.DATABASE_URL; + if (!url) { + console.error("No DATABASE_URL"); + process.exit(1); + } + + const c = new Client({ connectionString: url }); + await c.connect(); + + try { + console.log("=== Post-migration verification: critical table admin_users must exist ==="); + await c.query("SELECT 1 FROM admin_users LIMIT 1"); + console.log("✓ admin_users table exists and is queryable"); + process.exit(0); + } catch (e) { + console.error("✗ FATAL: admin_users relation missing after migrate:", e.message); + console.error("The deploy cannot continue. The secret DATABASE_URL must have had db/migrations/0001_init.sql applied successfully."); + process.exit(1); + } finally { + await c.end(); + } +} + +main(); \ No newline at end of file diff --git a/scripts/preflight-check.js b/scripts/preflight-check.js new file mode 100644 index 0000000..f1176ff --- /dev/null +++ b/scripts/preflight-check.js @@ -0,0 +1,71 @@ +#!/usr/bin/env node +/** + * Preflight check + migration repair runner. + * Called by .gitea/workflows/deploy.yml before running migrations. + * + * 1. Verify neon_auth schema exists (prerequisite for 0001_init.sql FKs) + * 2. Ensure 0001_init.sql is tracked in _migrations if admin_users already exists + * (repair for DBs that had 0001_init.sql applied before _migrations was added) + */ +const { Client } = require("pg"); + +async function main() { + const url = process.env.DATABASE_URL; + if (!url) { + console.error("No DATABASE_URL"); + process.exit(1); + } + + const c = new Client({ connectionString: url }); + await c.connect(); + + try { + // 1. Pre-flight: check neon_auth schema + console.log("=== Pre-flight: checking for neon_auth schema (required by 0001_init.sql for FKs to neon_auth.user and related RPCs) ==="); + const schemaRes = await c.query( + "SELECT 1 FROM information_schema.schemata WHERE schema_name = 'neon_auth'" + ); + if (schemaRes.rows.length === 0) { + console.error("✗ FATAL: neon_auth schema does not exist in the target database."); + console.error("Enable Neon Auth on the Neon project/branch that this DATABASE_URL points to first."); + console.error("Run: neonctl neon-auth (or equivalent) against the correct Neon branch."); + console.error("The neon_auth schema is created by Neon Auth and is a prerequisite for 0001_init.sql."); + process.exit(1); + } + console.log("✓ neon_auth schema exists"); + + // 2. Pre-repair: ensure 0001_init.sql is tracked if admin_users already exists + // This handles DBs where 0001_init.sql was applied before _migrations tracking existed. + console.log("=== Pre-repair: checking _migrations tracking (best-effort) ==="); + try { + const tableRes = await c.query( + "SELECT 1 FROM information_schema.tables WHERE table_name = 'admin_users' LIMIT 1" + ); + if (tableRes.rows.length > 0) { + await c.query(` + CREATE TABLE IF NOT EXISTS _migrations ( + filename TEXT PRIMARY KEY, + applied_at TIMESTAMPTZ NOT NULL DEFAULT now() + ); + INSERT INTO _migrations (filename) + VALUES ('0001_init.sql') + ON CONFLICT (filename) DO NOTHING; + `); + console.log("✓ 0001_init.sql tracking repaired (admin_users already present)"); + } + } catch (e) { + // best-effort + console.log("(pre-repair skipped: " + e.message + ")"); + } + + console.log("Preflight complete. Proceeding to migrations..."); + process.exit(0); + } catch (e) { + console.error("Preflight failed:", e.message); + process.exit(1); + } finally { + await c.end(); + } +} + +main(); \ No newline at end of file