feat(seed): synthetic fixture covering every pipeline stage + db:reset

Splits seed bootstrap (ports/roles/profile) into a shared module so
two seed entry points can share it:
- pnpm db:seed             realistic NocoDB-shaped fixture (existing)
- pnpm db:seed:synthetic   12 clients, one per pipeline stage + archive
                           variants (rich metadata for restore wizard)

scripts/db-reset.ts truncates all data tables (preserves migrations);
guarded by --confirm and a localhost host check. Companion npm scripts:
- pnpm db:reset
- pnpm db:reseed:realistic
- pnpm db:reseed:synthetic

scripts/dev-open-browser.ts launches a headed Chromium with no viewport
override (uses the host monitor's natural size), pre-fills the login
form for the requested role.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-06 20:19:50 +02:00
parent 758d8628cf
commit 4592789712
8 changed files with 1656 additions and 644 deletions

View File

@@ -0,0 +1,80 @@
/**
* Launch a headed Chromium with NO viewport override so it adopts the
* host monitor's natural size — useful when you want to drive the CRM
* manually and have full-screen real estate.
*
* Pre-fills the login form for the synthetic admin (admin@portnimara.test
* / SuperAdmin12345!) but does not submit; press Enter when ready.
*
* The script keeps running until the browser window is closed by the
* user or until you Ctrl-C.
*
* pnpm tsx scripts/dev-open-browser.ts # super_admin
* pnpm tsx scripts/dev-open-browser.ts sales_agent
* pnpm tsx scripts/dev-open-browser.ts viewer
* pnpm tsx scripts/dev-open-browser.ts --no-prefill
*/
import 'dotenv/config';
import { chromium } from 'playwright';
const USERS: Record<string, { email: string; password: string }> = {
super_admin: { email: 'admin@portnimara.test', password: 'SuperAdmin12345!' },
sales_agent: { email: 'agent@portnimara.test', password: 'SalesAgent12345!' },
viewer: { email: 'viewer@portnimara.test', password: 'ViewerUser12345!' },
};
const BASE_URL = process.env.DEV_BASE_URL ?? 'http://localhost:3000';
async function main() {
const args = process.argv.slice(2);
const noPrefill = args.includes('--no-prefill');
const role =
args.find((a) => !a.startsWith('--')) && USERS[args.find((a) => !a.startsWith('--'))!]
? args.find((a) => !a.startsWith('--'))!
: 'super_admin';
const user = USERS[role]!;
console.log(`Launching headed Chromium → ${BASE_URL}`);
console.log(` role: ${role} (${user.email})`);
const browser = await chromium.launch({
headless: false,
args: ['--start-maximized'],
});
// viewport: null lets the page fill the OS window. Combined with
// --start-maximized this matches the host monitor's natural size.
const context = await browser.newContext({ viewport: null });
const page = await context.newPage();
await page.goto(`${BASE_URL}/login`);
if (!noPrefill) {
try {
await page.waitForSelector('#email', { timeout: 5000 });
await page.fill('#email', user.email);
await page.fill('#password', user.password);
console.log(' Login form pre-filled — press Enter in the browser to submit.');
} catch {
console.log(' Could not find login form (page may have redirected).');
}
}
console.log('');
console.log("Browser is open. Close it when you're done; the script will exit.");
console.log('Or Ctrl-C here to force-quit.');
// Keep the process alive until the browser window is closed.
await new Promise<void>((resolve) => {
browser.on('disconnected', () => resolve());
});
await browser.close().catch(() => undefined);
process.exit(0);
}
main().catch((err) => {
console.error('Open-browser failed:', err);
process.exit(1);
});