diff --git a/package.json b/package.json index e2e11a57..7e7f4378 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "build:dashboard": "bun run ./scripts/build/dashboard.ts", "build:landing": "bun run ./scripts/build/landing.ts", "db:gen": "cd src/databases/officer_db && bun run generate", - "db:push": "cd src/databases/officer_db && bun run push", + "db:push": "bun run scripts/gen-plugin-schemas.ts && cd src/databases/officer_db && bun run push", "db:migrate": "cd src/databases/officer_db && bun run migrate", "dev:emailer": "cd src/workspaces/emailer && bun run dev", "format": "{ git diff --name-only HEAD -- 'src/**/*.ts' 'src/**/*.tsx'; git ls-files --others --exclude-standard -- 'src/**/*.ts' 'src/**/*.tsx'; } | xargs -r prettier --write", diff --git a/scripts/gen-plugin-schemas.ts b/scripts/gen-plugin-schemas.ts new file mode 100644 index 00000000..dc9c2eb5 --- /dev/null +++ b/scripts/gen-plugin-schemas.ts @@ -0,0 +1,40 @@ +import { discoverPlugins } from '../src/servers/plugins/discover'; +import { generatePluginSchemas } from '../src/servers/plugins/schema'; + +// Write `plugin-schemas.gen.ts` from whatever plugins are on disk. Runs as the first half of `db:push`. +// +// ── The bug this closes ── +// +// `officer_db/src/schema.ts` ends with `export * from './plugin-schemas.gen'` — UNCONDITIONAL, because +// drizzle-kit needs one file listing every table. That generated file is gitignored (it describes this +// machine, not the project), and until 2026-08-15 its only writer was `installPlugin`. +// +// So on a fresh clone the file did not exist, and `bun db:push` died with MODULE_NOT_FOUND before +// creating a single table. That is exactly what `scripts/setup/officer-setup.sh` section 8 runs, so +// setup failed at the schema step on any clean machine — and the only thing that would have written the +// file was installing a plugin, which needs a working database. A deadlock, shipped. +// +// It is wired into the `db:push` SCRIPT rather than added as a setup step on purpose. Setup is not the +// only caller: `pushSchema()` shells out to the same command, a developer types it by hand, and +// `git clean -xfd` removes the file at any time. A step someone has to remember is one they only forget +// once — the barrel now cannot be stale when push reads it, because push regenerates it. +// +// Empty is a correct answer, and the common one: a machine with no plugins gets a barrel with no +// exports, which is what `schema.ts` needs in order to import it at all. + +const { plugins, broken } = await discoverPlugins(); + +// Reported, never thrown. A plugin with an unreadable manifest must not stop the platform's own tables +// from being created — the same rule discovery follows everywhere else. +for (const { appName, error } of broken) { + console.warn(`[plugin-schemas] skipping ${appName}: ${error}`); +} + +const withSchema = plugins.filter((p) => p.schema); +generatePluginSchemas(plugins); + +console.log( + withSchema.length + ? `[plugin-schemas] ${withSchema.length} plugin schema(s): ${withSchema.map((p) => p.appName).join(', ')}` + : '[plugin-schemas] no plugin schemas on disk — wrote an empty barrel', +);