fix(scripts): convert reset-db.js to ESM + require ADMIN_INITIAL_PASSWORD (#25)
Fold of two queued follow-ups from pick-a-name architect audit
(convert-reset-db-to-esm + purge-weak-creds-from-helpers). Three bugs
in one file; all three fixed atomically by mirroring the proven post-
drop-public-setup setup-neon-db.js shape (commit b63b509).
Bugs fixed:
1. CJS-in-ESM (lines 10, 12, 142): require('dotenv'), require('@neon...'),
inline require('bcryptjs'). package.json has "type": "module" since
bump-next-js, so npm run reset-db threw ReferenceError on Node 22.x.
Same bug pattern that hit setup-neon-db.js pre-drop-public-setup B2.
2. Hardcoded weak admin password (line 143: bcrypt.hash('admin123', 12)).
Same anti-pattern drop-public-setup B1 removed from setup-neon-db.js.
3. Password echoed to stdout (line 156: console.log('Admin Password:
admin123')). Security anti-pattern; setup-neon-db.js post-DPS does
NOT echo passwords.
Fix shape (verbatim mirror of setup-neon-db.js):
- ESM top-level imports (dotenv, neon, bcrypt)
- Fail-loud ADMIN_INITIAL_PASSWORD env-var check at function top with
helpful error message pointing to README "First-time admin setup"
- bcrypt.hash(adminPassword, 12) instead of literal
- ON CONFLICT (email) DO NOTHING on INSERT (defensive against
double-run, matches setup-neon-db.js line 149)
- No password echo in success block; admin email logged for confirmation
- Updated docstring to flag DESTRUCTIVE + reference required env
Convoy file: .convoys/fix-reset-db-script.md (P2 hygiene, parent-owned,
no architect — this is a proven-pattern fold with no new decisions
to ratify).
Verification:
- node --check scripts/reset-db.js: exit 0
- npm run lint: 128 problems (baseline preserved, no regression)
- npm run test:run: 21/21 pass
- Grep: 0 require( | 0 admin123 | 0 'Admin Password' in scripts/reset-db.js
- Grep: 3 ADMIN_INITIAL_PASSWORD references (docstring, const, error msg)
NOT live-tested (script is destructive — drops all tables). Operator
can optionally run npm run reset-db against a non-prod Neon branch
post-merge to verify end-to-end.
Surfaces follow-up: lint-against-cjs-in-esm-scripts (P3 polish — add
ESLint rule to prevent any future require() in scripts/** under
"type": "module"). Surfaced for future convoy queue.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 23:08:45 -04:00
|
|
|
# fix-reset-db-script (P2 hygiene — fold of two queued follow-ups)
|
|
|
|
|
|
2026-05-26 23:10:13 -04:00
|
|
|
**Status:** RESOLVED 2026-05-26 (PR #25, squash commit `3ab9bf8`)
|
fix(scripts): convert reset-db.js to ESM + require ADMIN_INITIAL_PASSWORD (#25)
Fold of two queued follow-ups from pick-a-name architect audit
(convert-reset-db-to-esm + purge-weak-creds-from-helpers). Three bugs
in one file; all three fixed atomically by mirroring the proven post-
drop-public-setup setup-neon-db.js shape (commit b63b509).
Bugs fixed:
1. CJS-in-ESM (lines 10, 12, 142): require('dotenv'), require('@neon...'),
inline require('bcryptjs'). package.json has "type": "module" since
bump-next-js, so npm run reset-db threw ReferenceError on Node 22.x.
Same bug pattern that hit setup-neon-db.js pre-drop-public-setup B2.
2. Hardcoded weak admin password (line 143: bcrypt.hash('admin123', 12)).
Same anti-pattern drop-public-setup B1 removed from setup-neon-db.js.
3. Password echoed to stdout (line 156: console.log('Admin Password:
admin123')). Security anti-pattern; setup-neon-db.js post-DPS does
NOT echo passwords.
Fix shape (verbatim mirror of setup-neon-db.js):
- ESM top-level imports (dotenv, neon, bcrypt)
- Fail-loud ADMIN_INITIAL_PASSWORD env-var check at function top with
helpful error message pointing to README "First-time admin setup"
- bcrypt.hash(adminPassword, 12) instead of literal
- ON CONFLICT (email) DO NOTHING on INSERT (defensive against
double-run, matches setup-neon-db.js line 149)
- No password echo in success block; admin email logged for confirmation
- Updated docstring to flag DESTRUCTIVE + reference required env
Convoy file: .convoys/fix-reset-db-script.md (P2 hygiene, parent-owned,
no architect — this is a proven-pattern fold with no new decisions
to ratify).
Verification:
- node --check scripts/reset-db.js: exit 0
- npm run lint: 128 problems (baseline preserved, no regression)
- npm run test:run: 21/21 pass
- Grep: 0 require( | 0 admin123 | 0 'Admin Password' in scripts/reset-db.js
- Grep: 3 ADMIN_INITIAL_PASSWORD references (docstring, const, error msg)
NOT live-tested (script is destructive — drops all tables). Operator
can optionally run npm run reset-db against a non-prod Neon branch
post-merge to verify end-to-end.
Surfaces follow-up: lint-against-cjs-in-esm-scripts (P3 polish — add
ESLint rule to prevent any future require() in scripts/** under
"type": "module"). Surfaced for future convoy queue.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 23:08:45 -04:00
|
|
|
**Priority:** P2 hygiene (not a security blocker; `npm run reset-db` is dev-only
|
|
|
|
|
and currently broken on Node 22, so blast radius is low — but the bug
|
|
|
|
|
pattern is the same as the P0-grade weak-creds shape that
|
|
|
|
|
`drop-public-setup` already fixed once)
|
|
|
|
|
**Convoy owner:** parent (no architect — fold of two well-scoped
|
|
|
|
|
follow-ups; single-file fix following an established proven pattern)
|
|
|
|
|
**Opened:** 2026-05-25
|
2026-05-26 23:10:13 -04:00
|
|
|
**Merged:** 2026-05-26
|
fix(scripts): convert reset-db.js to ESM + require ADMIN_INITIAL_PASSWORD (#25)
Fold of two queued follow-ups from pick-a-name architect audit
(convert-reset-db-to-esm + purge-weak-creds-from-helpers). Three bugs
in one file; all three fixed atomically by mirroring the proven post-
drop-public-setup setup-neon-db.js shape (commit b63b509).
Bugs fixed:
1. CJS-in-ESM (lines 10, 12, 142): require('dotenv'), require('@neon...'),
inline require('bcryptjs'). package.json has "type": "module" since
bump-next-js, so npm run reset-db threw ReferenceError on Node 22.x.
Same bug pattern that hit setup-neon-db.js pre-drop-public-setup B2.
2. Hardcoded weak admin password (line 143: bcrypt.hash('admin123', 12)).
Same anti-pattern drop-public-setup B1 removed from setup-neon-db.js.
3. Password echoed to stdout (line 156: console.log('Admin Password:
admin123')). Security anti-pattern; setup-neon-db.js post-DPS does
NOT echo passwords.
Fix shape (verbatim mirror of setup-neon-db.js):
- ESM top-level imports (dotenv, neon, bcrypt)
- Fail-loud ADMIN_INITIAL_PASSWORD env-var check at function top with
helpful error message pointing to README "First-time admin setup"
- bcrypt.hash(adminPassword, 12) instead of literal
- ON CONFLICT (email) DO NOTHING on INSERT (defensive against
double-run, matches setup-neon-db.js line 149)
- No password echo in success block; admin email logged for confirmation
- Updated docstring to flag DESTRUCTIVE + reference required env
Convoy file: .convoys/fix-reset-db-script.md (P2 hygiene, parent-owned,
no architect — this is a proven-pattern fold with no new decisions
to ratify).
Verification:
- node --check scripts/reset-db.js: exit 0
- npm run lint: 128 problems (baseline preserved, no regression)
- npm run test:run: 21/21 pass
- Grep: 0 require( | 0 admin123 | 0 'Admin Password' in scripts/reset-db.js
- Grep: 3 ADMIN_INITIAL_PASSWORD references (docstring, const, error msg)
NOT live-tested (script is destructive — drops all tables). Operator
can optionally run npm run reset-db against a non-prod Neon branch
post-merge to verify end-to-end.
Surfaces follow-up: lint-against-cjs-in-esm-scripts (P3 polish — add
ESLint rule to prevent any future require() in scripts/** under
"type": "module"). Surfaced for future convoy queue.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-26 23:08:45 -04:00
|
|
|
|
|
|
|
|
## Problem (3 bugs in 1 file)
|
|
|
|
|
|
|
|
|
|
`scripts/reset-db.js` carries three known bugs surfaced by the
|
|
|
|
|
`pick-a-name` architect audit (2026-05-24) and ratified for fix in
|
|
|
|
|
this session:
|
|
|
|
|
|
|
|
|
|
1. **CJS-in-ESM environment** (lines 10, 12, 142): `require()` calls in
|
|
|
|
|
an ESM file (`package.json "type": "module"` since `bump-next-js`).
|
|
|
|
|
`npm run reset-db` throws `ReferenceError: require is not defined`
|
|
|
|
|
on Node 22.x. **Same bug pattern that hit `setup-neon-db.js`
|
|
|
|
|
pre-`drop-public-setup` Brief 2.** Fix is the same fix.
|
|
|
|
|
2. **Hardcoded weak admin password** (line 143: `bcrypt.hash('admin123', 12)`):
|
|
|
|
|
identical anti-pattern to the one `drop-public-setup` Brief 1
|
|
|
|
|
removed from `setup-neon-db.js`. Should require `ADMIN_INITIAL_PASSWORD`
|
|
|
|
|
env var, fail loud if unset.
|
|
|
|
|
3. **Password echoed to stdout** (line 156:
|
|
|
|
|
`console.log(' Admin Password: admin123')`): explicit security
|
|
|
|
|
anti-pattern. `setup-neon-db.js` post-`drop-public-setup` does NOT
|
|
|
|
|
echo the password — same convention applies here.
|
|
|
|
|
|
|
|
|
|
## Fix (mirror `setup-neon-db.js` exactly)
|
|
|
|
|
|
|
|
|
|
The fix shape is fully derived from the post-`drop-public-setup`
|
|
|
|
|
`scripts/setup-neon-db.js` file shipped at `b63b509`. Verbatim mirror:
|
|
|
|
|
|
|
|
|
|
- Replace CJS `require('dotenv').config()` with ESM
|
|
|
|
|
`import dotenv from 'dotenv'; dotenv.config({ path: '.env.local' })`
|
|
|
|
|
- Replace CJS `const { neon } = require('@neondatabase/serverless')`
|
|
|
|
|
with ESM `import { neon } from '@neondatabase/serverless'`
|
|
|
|
|
- Replace inline CJS `const bcrypt = require('bcryptjs')` (line 142)
|
|
|
|
|
with top-level ESM `import bcrypt from 'bcryptjs'`
|
|
|
|
|
- Add the same fail-loud `ADMIN_INITIAL_PASSWORD` env-var check at the
|
|
|
|
|
top of the `async function resetDatabase()` body, with the same
|
|
|
|
|
helpful error message that points to README.md
|
|
|
|
|
- Replace `bcrypt.hash('admin123', 12)` with `bcrypt.hash(adminPassword, 12)`
|
|
|
|
|
- Update the admin email to the post-`pick-a-name` canonical
|
|
|
|
|
(`admin@deckhearth.com` — already correct in the file at line 147,
|
|
|
|
|
by the B2 sweep)
|
|
|
|
|
- Replace the `Admin Password: admin123` log line with `Admin user
|
|
|
|
|
ready (email: admin@deckhearth.com)` (matching `setup-neon-db.js`
|
|
|
|
|
line 157)
|
|
|
|
|
- Add `ON CONFLICT (email) DO NOTHING` to the INSERT (matching
|
|
|
|
|
`setup-neon-db.js` line 149 — defensive against double-run)
|
|
|
|
|
|
|
|
|
|
## Scope
|
|
|
|
|
|
|
|
|
|
- **In scope:** `scripts/reset-db.js` only.
|
|
|
|
|
- **Out of scope:** any other `scripts/*.js` files (none have the same
|
|
|
|
|
bugs — `setup-neon-db.js` already fixed, migration script already
|
|
|
|
|
ESM, the rest don't ship admin creds).
|
|
|
|
|
|
|
|
|
|
## Why no architect
|
|
|
|
|
|
|
|
|
|
This is a **proven-pattern fold** — both `convert-reset-db-to-esm` and
|
|
|
|
|
`purge-weak-creds-from-helpers` were architect-recommended in
|
|
|
|
|
`pick-a-name` for "may fold if more such bugs accumulate in helper
|
|
|
|
|
scripts." All 3 bugs are in 1 file; the fix shape is verbatim-mirror of
|
|
|
|
|
the post-`drop-public-setup` `setup-neon-db.js`. No new decisions; no
|
|
|
|
|
new precedents; no surface for an architect to add value. Parent
|
|
|
|
|
applies the fix, runs the bounded checks, opens the PR. If anything
|
|
|
|
|
surprising surfaces (a 4th bug, a different bcrypt API, etc.), the
|
|
|
|
|
parent stops and dispatches an architect mid-execution.
|
|
|
|
|
|
|
|
|
|
## Acceptance criteria
|
|
|
|
|
|
|
|
|
|
- `node --check scripts/reset-db.js` exit 0
|
|
|
|
|
- `npm run lint` exit 1 with 128 problems (baseline preserved; no
|
|
|
|
|
regression)
|
|
|
|
|
- `npm run test:run` 21/21 pass (no test surface touched; verification
|
|
|
|
|
only)
|
|
|
|
|
- Grep: 0 occurrences of `require(` in `scripts/reset-db.js`
|
|
|
|
|
- Grep: 0 occurrences of `admin123` in `scripts/reset-db.js`
|
|
|
|
|
- Grep: 0 occurrences of `Admin Password` in `scripts/reset-db.js`
|
|
|
|
|
- Grep: `ADMIN_INITIAL_PASSWORD` referenced (1 hit)
|
|
|
|
|
|
|
|
|
|
## Operator action required pre-merge
|
|
|
|
|
|
|
|
|
|
- **None pre-merge** (no schema change, no env-var addition).
|
|
|
|
|
- **Optional post-merge:** if the operator wants to verify the fix
|
|
|
|
|
works end-to-end, they can run `npm run reset-db` against a
|
|
|
|
|
**non-prod** Neon branch (the script drops all tables — DO NOT run
|
|
|
|
|
against prod). The script will refuse to run if `ADMIN_INITIAL_PASSWORD`
|
|
|
|
|
is not set in `.env.local`, with the same helpful error message
|
|
|
|
|
`setup-neon-db.js` already uses.
|
|
|
|
|
|
|
|
|
|
## Known constraints
|
|
|
|
|
|
|
|
|
|
- The script is **destructive** (drops all tables). We do NOT live-test
|
|
|
|
|
it in this convoy. Boot-the-brief is syntax + lint + vitest only.
|
|
|
|
|
Live verification is the operator's optional post-merge action.
|
|
|
|
|
- This convoy does NOT add ESLint `no-restricted-syntax` rule against
|
|
|
|
|
CJS `require()` in `scripts/**`. That would be a separate
|
|
|
|
|
`lint-against-cjs-in-esm-scripts` convoy. Surfaced here so future
|
|
|
|
|
helper scripts don't re-introduce the bug.
|
|
|
|
|
|
|
|
|
|
## Out of scope (queued follow-ups)
|
|
|
|
|
|
|
|
|
|
- `lint-against-cjs-in-esm-scripts` (NEW, P3 polish): add ESLint rule
|
|
|
|
|
to prevent any future `require()` in `scripts/**` once `package.json`
|
|
|
|
|
has `"type": "module"`.
|
|
|
|
|
- `add-neon-return-shape-rule` (P3 polish, surfaced 2026-05-25 by
|
|
|
|
|
PR #24): codify the `neon()` vs `@vercel/postgres` return-shape
|
|
|
|
|
difference as a rule. **May fold into `single-sql-client`** which
|
|
|
|
|
would eliminate the dual-client problem entirely.
|
|
|
|
|
|
|
|
|
|
## Owns
|
|
|
|
|
|
|
|
|
|
Parent (single-file proven-pattern fix; no architect or implementer
|
|
|
|
|
subagent required).
|
|
|
|
|
|
|
|
|
|
## As-shipped
|
|
|
|
|
|
2026-05-26 23:10:13 -04:00
|
|
|
Single squash commit `3ab9bf8` (PR #25, merged 2026-05-26). Parent-owned
|
|
|
|
|
end-to-end per the convoy spec — no architect, no implementer subagent
|
|
|
|
|
dispatched. Mirror-the-pattern fix exactly as planned; no mid-execution
|
|
|
|
|
surprises that would have forced an architect bounce.
|
|
|
|
|
|
|
|
|
|
**Diff: 2 files, +161 / -18.** `scripts/reset-db.js` +55 / -18 (the
|
|
|
|
|
actual fix); `.convoys/fix-reset-db-script.md` +124 (the planning
|
|
|
|
|
document, committed atomically with the fix).
|
|
|
|
|
|
|
|
|
|
**The three bugs, atomically resolved:**
|
|
|
|
|
|
|
|
|
|
1. **CJS-in-ESM** (was lines 10, 12, 142): three `require()` calls
|
|
|
|
|
replaced by ESM top-level imports (`import dotenv from 'dotenv'`,
|
|
|
|
|
`import { neon } from '@neondatabase/serverless'`, `import bcrypt from
|
|
|
|
|
'bcryptjs'`). `npm run reset-db` now executes on Node 22.x instead
|
|
|
|
|
of throwing `ReferenceError: require is not defined`.
|
|
|
|
|
2. **Hardcoded weak admin password** (was line 143): replaced
|
|
|
|
|
`bcrypt.hash('admin123', 12)` with `bcrypt.hash(adminPassword, 12)`,
|
|
|
|
|
where `adminPassword` is read from `process.env.ADMIN_INITIAL_PASSWORD`
|
|
|
|
|
with a fail-loud check at the top of `resetDatabase()`. The check
|
|
|
|
|
exits with code 1 BEFORE opening any DB connection, matching
|
|
|
|
|
`setup-neon-db.js`'s post-`drop-public-setup` shape verbatim.
|
|
|
|
|
3. **Password echoed to stdout** (was line 156): replaced the
|
|
|
|
|
`console.log(' Admin Password: admin123')` line with
|
|
|
|
|
`console.log(' Admin user ready (email: admin@deckhearth.com)')`
|
|
|
|
|
— exactly the line `setup-neon-db.js` line 157 uses post-DPS.
|
|
|
|
|
|
|
|
|
|
**Bonus defensive shape:** the seed `INSERT` now uses `ON CONFLICT
|
|
|
|
|
(email) DO NOTHING` (matching `setup-neon-db.js` line 149) so a
|
|
|
|
|
double-run doesn't error on the existing admin row. This is defensive
|
|
|
|
|
only; the convoy is destructive (drops all tables first), so the only
|
|
|
|
|
realistic "existing admin row" scenario is operator confusion between
|
|
|
|
|
setup-db and reset-db.
|
|
|
|
|
|
|
|
|
|
**Verification (all gates green at merge):**
|
|
|
|
|
- `node --check scripts/reset-db.js` → exit 0
|
|
|
|
|
- `npm run lint` → 128 problems (baseline preserved, no regression)
|
|
|
|
|
- `npm run test:run` → 21/21 pass
|
|
|
|
|
- Grep `scripts/reset-db.js`: 0 `require(` | 0 `admin123` | 0 `'Admin
|
|
|
|
|
Password'` | 3 `ADMIN_INITIAL_PASSWORD` references (docstring + const
|
|
|
|
|
+ error msg)
|
|
|
|
|
- CI on PR #25: Lint ✓ (39s) | Vitest 21/21 ✓ (28s) | Playwright smoke
|
|
|
|
|
3/3 ✓ (1m5s) | `forbidden-endpoints` ✓ (5s) | `forbidden-cors-headers`
|
|
|
|
|
✓ (4s) | Vercel preview deploy ✓ | Aggregate gate ✓
|
|
|
|
|
- `Screenshot diff`: not triggered (script-only PR — `paths:` filter
|
|
|
|
|
excludes `scripts/**`, so the queued `tighten-visual-diff-path-filter`
|
|
|
|
|
follow-up correctly did NOT fire here)
|
|
|
|
|
|
|
|
|
|
**Live verification deferred per convoy spec.** The script is
|
|
|
|
|
destructive (drops all tables); we did not exercise it against a Neon
|
|
|
|
|
branch in this convoy. If the operator wants end-to-end proof, the
|
|
|
|
|
optional post-merge action is `npm run reset-db` against a throwaway
|
|
|
|
|
Neon branch with `ADMIN_INITIAL_PASSWORD` set (and verify that
|
|
|
|
|
unsetting it triggers the fail-loud exit with the helpful error
|
|
|
|
|
message).
|
|
|
|
|
|
|
|
|
|
**Cross-validation finding (organic, not a planned AC).** The
|
|
|
|
|
`Playwright smoke` 3/3 PASS on a script-only PR confirms that
|
|
|
|
|
`adopt-playwright-smoke`'s smoke spec is correctly insensitive to
|
|
|
|
|
`scripts/**` edits — the deployed preview is unaffected by changes to
|
|
|
|
|
dev-only utility scripts, and the smoke spec correctly green-lights
|
|
|
|
|
the deployment. This is the fifth consecutive convoy where the same
|
|
|
|
|
3-test smoke spec has defended the auth surface (PR #15 Layout
|
|
|
|
|
default-user → PR #19 CORS-tighten → PR #20 rate-limiting → PR #21
|
|
|
|
|
pick-a-name → PR #25 reset-db-fix) without anyone writing a dedicated
|
|
|
|
|
test.
|
|
|
|
|
|
|
|
|
|
**Operator action required going forward:** **none.** No new env vars
|
|
|
|
|
(`ADMIN_INITIAL_PASSWORD` was already required by `setup-neon-db.js`
|
|
|
|
|
post-`drop-public-setup`; this convoy adds nothing new to the env
|
|
|
|
|
contract). No schema change. No infra change.
|
|
|
|
|
|
|
|
|
|
**Surfaced follow-up (newly queued):** `lint-against-cjs-in-esm-scripts`
|
|
|
|
|
(P3 polish) — add an ESLint `no-restricted-syntax` rule against
|
|
|
|
|
`require(` calls in `scripts/**` once `package.json` has `"type":
|
|
|
|
|
"module"`. Would have caught both this convoy AND the
|
|
|
|
|
`drop-public-setup` Brief 2 bug at lint time. Filed in
|
|
|
|
|
`.convoys/ship-readiness.md` § Queued convoys.
|
|
|
|
|
|
|
|
|
|
**`purge-weak-creds-from-helpers` scope reduction.** This convoy
|
|
|
|
|
satisfies the `scripts/reset-db.js` portion of the queued
|
|
|
|
|
`purge-weak-creds-from-helpers` follow-up. Remaining scope of that
|
|
|
|
|
queued convoy: `scripts/create-test-users.js` (alice/bob test fixtures)
|
|
|
|
|
and `TESTING_GUIDE.md` (documents the weak creds). Both are out of
|
|
|
|
|
scope here per the convoy spec's single-file boundary.
|