deckhearth/scripts/setup-neon-db.js

109 lines
3.8 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
* First-time / re-onboarding setup for the Deck Hearth Neon database.
*
* Pipeline (post-`migration-tool` convoy, 2026-05-26):
* 1. Validate `ADMIN_INITIAL_PASSWORD` is set (fail loud BEFORE touching the DB).
* 2. Spawn `npm run migrate up` to apply every pending migration under
* `migrations/`. The initial backfill migration (1779853647564_initial-schema)
* uses `CREATE TABLE IF NOT EXISTS` and is idempotent against fresh or
* pre-existing envs.
* 3. Seed the admin user with `ON CONFLICT (email) DO NOTHING`.
*
* Make sure you have `POSTGRES_URL` set in `.env.local`. See README §
* "First-time admin setup" for the operator runbook.
*/
import dotenv from 'dotenv';
dotenv.config({ path: '.env.local' });
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
import { spawn } from 'node:child_process';
import { neon } from '@neondatabase/serverless';
import bcrypt from 'bcryptjs';
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
function runMigrations() {
return new Promise((resolve, reject) => {
console.log('✅ Running migrations (npm run migrate up)...');
const child = spawn('npm', ['run', 'migrate', '--', 'up'], {
stdio: 'inherit',
shell: false,
});
child.on('error', (err) => reject(err));
child.on('exit', (code, signal) => {
if (code === 0) {
resolve();
} else {
reject(
new Error(
`npm run migrate up exited with code=${code} signal=${signal}. ` +
'See output above for the failing migration.'
)
);
}
});
});
}
async function setupNeonDatabase() {
feat(seed): require ADMIN_INITIAL_PASSWORD env var; strip admin123 from README Closes P0 #3 from .convoys/ship-readiness.md. scripts/setup-neon-db.js: - Read ADMIN_INITIAL_PASSWORD env var at the top of setupNeonDatabase() before any DB connection. Fail loudly (process.exit(1)) with an actionable message if unset or empty. - Replace bcrypt.hash('admin123', 12) with bcrypt.hash(adminPassword, 12). - Delete the two console.log lines that echoed admin user + password to stdout (R3 - stdout leak into CI logs). - Keep ON CONFLICT (email) DO NOTHING unchanged. Re-running setup-db on an env with the admin row already present is a no-op for the password (R4 - silent rotation prevention). Rotation of existing weak-hash admin rows is out of scope (Decision A - queued for the rotate-default-admin follow-up convoy). README.md: - Add ADMIN_INITIAL_PASSWORD to the install-step env-example block with a CI-secret note (and add KV_REST_API_URL/KV_REST_API_TOKEN for completeness; they're optional for local dev). - Replace the "Default Admin Account" section with "First-time admin setup", documenting the env var, openssl rand suggestion, and the operator rotation note for envs that predate this change. - Zero occurrences of 'admin123' remain in README.md (the operator rotation note refers to "the prior weak default" instead of naming the literal string, so grep verification A2 holds). Decisions A1 (going-forward only), B (operational change allowed), C1 (no vitest coverage - manual smoke in PR description) per .convoys/drop-public-setup.md section Decisions. Smoke output: see PR description. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:57:31 -04:00
const adminPassword = process.env.ADMIN_INITIAL_PASSWORD;
if (!adminPassword || !adminPassword.trim()) {
console.error(
'❌ ADMIN_INITIAL_PASSWORD environment variable is not set.\n' +
'\n' +
' Set it in .env.local for local dev, or as a CI secret if you run setup from CI.\n' +
' Generate a strong password with: openssl rand -base64 24\n' +
' See README.md → "First-time admin setup" for the full flow.\n'
);
process.exit(1);
}
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
if (!process.env.POSTGRES_URL) {
console.error(
'❌ POSTGRES_URL environment variable is not set.\n' +
' Set it in .env.local (Neon connection string) before running setup.\n'
);
process.exit(1);
}
try {
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
await runMigrations();
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
console.log('✅ Connecting to Neon database to seed admin user...');
const sql = neon(process.env.POSTGRES_URL);
feat(seed): require ADMIN_INITIAL_PASSWORD env var; strip admin123 from README Closes P0 #3 from .convoys/ship-readiness.md. scripts/setup-neon-db.js: - Read ADMIN_INITIAL_PASSWORD env var at the top of setupNeonDatabase() before any DB connection. Fail loudly (process.exit(1)) with an actionable message if unset or empty. - Replace bcrypt.hash('admin123', 12) with bcrypt.hash(adminPassword, 12). - Delete the two console.log lines that echoed admin user + password to stdout (R3 - stdout leak into CI logs). - Keep ON CONFLICT (email) DO NOTHING unchanged. Re-running setup-db on an env with the admin row already present is a no-op for the password (R4 - silent rotation prevention). Rotation of existing weak-hash admin rows is out of scope (Decision A - queued for the rotate-default-admin follow-up convoy). README.md: - Add ADMIN_INITIAL_PASSWORD to the install-step env-example block with a CI-secret note (and add KV_REST_API_URL/KV_REST_API_TOKEN for completeness; they're optional for local dev). - Replace the "Default Admin Account" section with "First-time admin setup", documenting the env var, openssl rand suggestion, and the operator rotation note for envs that predate this change. - Zero occurrences of 'admin123' remain in README.md (the operator rotation note refers to "the prior weak default" instead of naming the literal string, so grep verification A2 holds). Decisions A1 (going-forward only), B (operational change allowed), C1 (no vitest coverage - manual smoke in PR description) per .convoys/drop-public-setup.md section Decisions. Smoke output: see PR description. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 15:57:31 -04:00
const hashedPassword = await bcrypt.hash(adminPassword, 12);
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
await sql`
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
INSERT INTO users (email, password, role)
feat(brand): unify on Deck Hearth across in-repo strings + infra (P1 brand decision) Resolves the launch-blocking 'TCG Vault vs Deck Hearth' inconsistency called out in AGENTS.md line 5 since project setup. Operator gate-0 decision: Deck Hearth wins. Two briefs applied serially. B1 (mechanical): 7-file display + comment sweep. B2 (infrastructure): Redis prefix rename in lib/rate-limit.js (5 prefixes, accept one-time counter reset), package.json + lockfile regen (STOP-on-churn confirmed only name lines changed), admin/alice/bob email rename in seed scripts + login pre-fill + NEW idempotent migration script scripts/migrations/2026-05-24-rename-admin-email.js. Risk 4 PRESERVE applied: test/lib/permission-middleware.test.js retains admin@tcgvault.com literal with 7-line architect-authored why comment (documents pre-fix-auth-bypass bug shape; preserves historical truth per project's gotcha-documentation convention). All 5 D-decisions ratified at gate-1 (Deck Hearth / deck-hearth / deckhearth / admin@deckhearth.com / full deckhearth Redis prefix). Local: lint 128 baseline (B1 + B2), vitest 21/21 (B1 + B2). CI all green: Playwright smoke 3/3 against rebranded preview in 1m4s, forbidden-cors-headers pass, forbidden-endpoints pass, Screenshot diff pass, Vercel deployment complete. Cross-validation lineage: 4th convoy where the same 3-test smoke spec defends auth surface through sweeping change (after PR #15 Layout default-user, PR #19 CORS, PR #20 rate-limit, now this PR #21 brand rename). OPERATOR POST-MERGE ACTION REQUIRED: run 'node scripts/migrations/2026-05-24-rename-admin-email.js' against prod Neon DB before next admin login (ordering: migration FIRST, then any subsequent setup-db invocation). Migration is ESM, idempotent, UNIQUE-collision-safe. PR #21 architect-commit 50ce9ab, B1 ac8c998, B2 1c18d21.
2026-05-25 03:28:29 -04:00
VALUES (${'admin@deckhearth.com'}, ${hashedPassword}, ${'admin'})
ON CONFLICT (email) DO NOTHING
`;
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
console.log('✅ Admin user ready (email: admin@deckhearth.com)');
console.log('🎉 Neon database setup completed successfully!');
console.log('');
console.log('📋 Database Details:');
console.log(' Database: Neon PostgreSQL');
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
console.log(' Schema: applied via node-pg-migrate (see migrations/)');
feat(brand): unify on Deck Hearth across in-repo strings + infra (P1 brand decision) Resolves the launch-blocking 'TCG Vault vs Deck Hearth' inconsistency called out in AGENTS.md line 5 since project setup. Operator gate-0 decision: Deck Hearth wins. Two briefs applied serially. B1 (mechanical): 7-file display + comment sweep. B2 (infrastructure): Redis prefix rename in lib/rate-limit.js (5 prefixes, accept one-time counter reset), package.json + lockfile regen (STOP-on-churn confirmed only name lines changed), admin/alice/bob email rename in seed scripts + login pre-fill + NEW idempotent migration script scripts/migrations/2026-05-24-rename-admin-email.js. Risk 4 PRESERVE applied: test/lib/permission-middleware.test.js retains admin@tcgvault.com literal with 7-line architect-authored why comment (documents pre-fix-auth-bypass bug shape; preserves historical truth per project's gotcha-documentation convention). All 5 D-decisions ratified at gate-1 (Deck Hearth / deck-hearth / deckhearth / admin@deckhearth.com / full deckhearth Redis prefix). Local: lint 128 baseline (B1 + B2), vitest 21/21 (B1 + B2). CI all green: Playwright smoke 3/3 against rebranded preview in 1m4s, forbidden-cors-headers pass, forbidden-endpoints pass, Screenshot diff pass, Vercel deployment complete. Cross-validation lineage: 4th convoy where the same 3-test smoke spec defends auth surface through sweeping change (after PR #15 Layout default-user, PR #19 CORS, PR #20 rate-limit, now this PR #21 brand rename). OPERATOR POST-MERGE ACTION REQUIRED: run 'node scripts/migrations/2026-05-24-rename-admin-email.js' against prod Neon DB before next admin login (ordering: migration FIRST, then any subsequent setup-db invocation). Migration is ESM, idempotent, UNIQUE-collision-safe. PR #21 architect-commit 50ce9ab, B1 ac8c998, B2 1c18d21.
2026-05-25 03:28:29 -04:00
console.log(' Admin user ready (email: admin@deckhearth.com)');
console.log('');
console.log('🔧 Next Steps:');
console.log(' 1. Test the API endpoints');
console.log(' 2. Start building the frontend');
} catch (error) {
console.error('❌ Database setup failed:', error.message);
console.log('');
console.log('🔧 Troubleshooting:');
console.log(' 1. Make sure POSTGRES_URL is set in .env.local');
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
console.log(' 2. Make sure ADMIN_INITIAL_PASSWORD is set in .env.local');
console.log(' 3. Check your Neon database connection');
console.log(' 4. If the migrate step failed, inspect the SQL above and');
console.log(' see migrations/ for the failing file. To re-try just the');
console.log(' migration step run: npm run migrate up');
process.exit(1);
}
}
feat(infra): adopt node-pg-migrate + backfill initial schema migration (#32) Closes P1 #11 of .convoys/ship-readiness.md (launch sequence step 7) — "No migration tool — scripts/add-*.js graveyard". Schema changes post-this-convoy ship as node-pg-migrate migrations under migrations/ at the repo root; the legacy 27 scripts/add-*.js / scripts/fix-*.js / scripts/seed-*.js jobs remain append-only history per the no-go-zones rule. Decisions (full record in .convoys/migration-tool.md § Decisions): D1 — Tool: node-pg-migrate@^8. Rejected drizzle-kit / prisma migrate / kysely because each forces broader TypeScript surface than AGENTS.md Gotcha #9 allows (TS is a devDep only). node-pg-migrate is JavaScript-native, raw-SQL-friendly via pgm.sql(), and ESM-clean for the post-bump-next-js "type": "module" repo. Brings pg@^8.21.0 as a peer dep (dev-only; never loaded in the Next.js bundle). D2 — Migrations directory: migrations/ at the repo root. Separates the tool-wrapped artifacts from the historical scripts/migrations/ placeholder folder (which housed the lone pre-tool 2026-05-24-rename-admin-email.js migration and remains preserved for the audit trail). Matches node-pg-migrate's default flag. D3 — Tracking table: default pgmigrations (no name collision with the existing 7-table bootstrap; zero CLI noise). D4 — Backfill strategy: hand-translate scripts/setup-neon-db.js's DDL into the initial migration verbatim. Each await sql`...` block becomes one pgm.sql(`...`) call. Each CREATE uses IF NOT EXISTS, so the migration is idempotent against fresh AND pre-existing envs — re-running setup-db on an env that already has the schema is a no-op DDL-wise (only records the pgmigrations row). Documented assumption: prod has drifted via the 27 historical add-*.js scripts; reconciling those into the migration history is the queued reconcile-historical-add-scripts follow-up convoy. D5 — Bootstrap reconciliation: split. setup-neon-db.js now (1) validates ADMIN_INITIAL_PASSWORD + POSTGRES_URL, (2) spawns `npm run migrate up` via child_process with stdio inherited, (3) seeds the admin row with ON CONFLICT (email) DO NOTHING. The seven DDL blocks are deleted from setup-neon-db.js; success/error message copy is updated to mention the migration step explicitly. D6 — CI integration: defer. Wiring a CI job that runs migrate up against a test DB needs either a dedicated Neon branch + secret OR a Postgres service container; both are real work. Surface as wire-migrate-into-ci follow-up. Risk acknowledged in .convoys/migration-tool.md § R3. D7 — Down-migration on the initial backfill: hard stub. Rolling back the initial schema would drop every user / card / collection / deck row in the DB. The stub throws with a long-form error pointing at the recommended alternative (branch the Neon database + forward-apply). Future migrations that touch one of the seven bootstrap tables write their own dated migration with a real down(). Verification (pre-PR): - npm run lint → 128 problems (baseline preserved, zero regression; migration file is lint-clean, no new ignore patterns) - npm run test:run → 21/21 pass - node --check on migrations/1779853647564_initial-schema.js + on scripts/setup-neon-db.js → exit 0 - Module load + down() throw verified via dynamic import - npm run migrate -- --help reaches the node-pg-migrate CLI through the wrapper Live verification against a Neon branch is deferred (no throwaway branch available); the operator's optional post-merge sequence is documented in .convoys/migration-tool.md § Operator runbook. See .convoys/migration-tool.md § Follow-ups for the queued wire-migrate-into-ci / reconcile-historical-add-scripts / retire-graveyard-scripts-after-audit / audit-node-pg-migrate-transitive-deps / add-migration-template follow-up convoys. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-27 00:01:58 -04:00
setupNeonDatabase();