// apps/hq/scripts/apply-name-fixes.ts — one-off (D35 data correction): apply the approved // client-name corrections from name-review.json via the audited updateClient path. // REVIEW_JSON= DATABASE_URL=postgres://… npx tsx apps/hq/scripts/apply-name-fixes.ts // SQLite is never the default: to target a local SQLite database, opt in explicitly with // HQ_DB_TARGET=sqlite (or the --sqlite flag). Without either, the script refuses to run. import { readFileSync } from 'node:fs' import { openDb, sqliteFilePath } from '../src/db' import { openPgDb, pgTargetDescription, requireDbUrlForEnv } from '../src/db-pg' import { updateClient } from '../src/repos-clients' import type { DB } from '../src/db' interface Row { id: string; current: string; suggested: string; changed: boolean } async function main() { const path = process.env['REVIEW_JSON'] if (path === undefined) throw new Error('Set REVIEW_JSON to the name-review.json path') const rows = JSON.parse(readFileSync(path, 'utf8')) as Row[] // Pre-ship audit FIX A: the SAME resolver server.ts uses (never re-derived locally) — and, // unlike the server, this one-off script hard-fails rather than silently falling back to a // throwaway SQLite file when NODE_ENV=production resolves no Postgres target. const pgUrl = requireDbUrlForEnv() if (pgUrl !== '') { console.log(`[db] engine: postgres ${pgTargetDescription(pgUrl)}`) } else { console.log(`[db] engine: sqlite at ${sqliteFilePath(process.env['HQ_DATA_DIR'])}`) } const db: DB = pgUrl !== '' ? await openPgDb(pgUrl) : openDb(process.env['HQ_DATA_DIR']) try { let applied = 0, skipped = 0 for (const r of rows) { if (!r.changed || r.suggested === r.current) continue try { // Guard: only touch a client whose stored name still matches what was reviewed. const cur = await db.get<{ name: string }>(`SELECT name FROM client WHERE id=?`, r.id) if (cur === undefined || cur.name !== r.current) { skipped++; continue } await updateClient(db, 'system', r.id, { name: r.suggested }) applied++ } catch (e) { skipped++ // eslint-disable-next-line no-console console.error(` skip ${r.current}: ${e instanceof Error ? e.message : String(e)}`) } } // eslint-disable-next-line no-console console.log(`Name corrections: ${applied} applied, ${skipped} skipped.`) } finally { await db.close() } } main().catch((e) => { console.error(e); process.exit(1) })