fix(deploy): move inline node -e scripts into separate files to fix shell quoting errors
Deploy to route.crispygoat.com / deploy (push) Successful in 4m16s
Deploy to route.crispygoat.com / deploy (push) Successful in 4m16s
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 <noreply@anthropic.com>
This commit is contained in:
@@ -25,74 +25,9 @@ jobs:
|
|||||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||||
run: |
|
run: |
|
||||||
set -e
|
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 scripts/preflight-check.js
|
||||||
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 */ });
|
|
||||||
'
|
|
||||||
npm run migrate:one
|
npm run migrate:one
|
||||||
echo "=== Post-migration verification: critical table admin_users must exist ==="
|
node scripts/postflight-check.js
|
||||||
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);
|
|
||||||
});
|
|
||||||
'
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -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();
|
||||||
Reference in New Issue
Block a user