/** * Writes the backend's seed fixture from the frontend's seed module. * * node scripts/seed-fixture.mjs # check: exits non-zero if stale * node scripts/seed-fixture.mjs --write # regenerate * * `src/api/seed.js` is the authored source: it has the helpers, the reasoning in * comments, and the values a person edits. `seed/fixtures/seed.json` is what the * Go seeder loads. They used to be maintained in parallel with nothing checking * them, which is a duplication that only fails quietly — the demo and the API * would answer the same question differently, and the first symptom would be a * number on a page disagreeing with a number from an agent. * * `ShiftRecord` is deliberately not carried across. Its dates are anchored to * the day the seeder runs rather than to this file's fixed calendar, so the Go * side generates it (internal/seeder/shifts.go, a port of attendanceSeed.js). * A frozen snapshot of it would read as permanently empty a fortnight later. */ import { createServer } from 'vite'; import { readFileSync, writeFileSync, existsSync } from 'node:fs'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; export const FIXTURE_PATH = join(process.cwd(), '..', 'krow-backend', 'seed', 'fixtures', 'seed.json'); /** The fixture the frontend seed implies. Pure — takes the loaded module. */ export const GENERATED_NOTE = 'Generated from krow-demo/src/api/seed.js — do not edit by hand. ' + 'Regenerate with: npm run seed:fixture'; export function fixtureFrom(seedModule) { const { ShiftRecord, ...entities } = seedModule.seedData; /* First key, so it is the first thing anyone opening the file reads. The Go loader unmarshals into a struct of demoUser + entities and ignores the rest, so this costs nothing on the reading side. */ return { _generated: GENERATED_NOTE, demoUser: seedModule.DEMO_USER, entities }; } /** Serialised exactly as the committed file is: 2-space indent, no trailing newline. */ export const serialise = (fixture) => JSON.stringify(fixture, null, 2); export async function buildFixture(server) { return serialise(fixtureFrom(await server.ssrLoadModule('/src/api/seed.js'))); } /* ── CLI ──────────────────────────────────────────────────────────────────── */ /* pathToFileURL, not string concatenation: this repo lives under a path with a space in it, and import.meta.url percent-encodes it while `file://${path}` does not — the guard silently never fires and the script does nothing. */ if (import.meta.url === pathToFileURL(process.argv[1]).href) { const server = await createServer({ root: process.cwd(), server: { middlewareMode: true }, appType: 'custom', logLevel: 'error', }); const built = await buildFixture(server); await server.close(); if (process.argv.includes('--write')) { writeFileSync(FIXTURE_PATH, built); const { entities } = JSON.parse(built); const n = Object.values(entities).reduce((s, v) => s + v.length, 0); console.log(`seed.json written — ${Object.keys(entities).length} collections, ${n} records.`); } else if (!existsSync(FIXTURE_PATH)) { console.error('seed.json is missing. Run: node scripts/seed-fixture.mjs --write'); process.exit(1); } else if (readFileSync(FIXTURE_PATH, 'utf8') !== built) { console.error('seed.json is stale — it no longer matches src/api/seed.js.'); console.error('Run: node scripts/seed-fixture.mjs --write'); process.exit(1); } else { console.log('seed.json is in step with src/api/seed.js.'); } }