Files
platform/scripts/gen-plugin-schemas.ts
pastilhasandClaude Opus 5 23bfacf412 a missing plugin can no longer stop the platform booting
A server died on `Cannot find module '../../../../plugins/music/db/schema'`
before a line of its own code ran. The pull that broke it was the one untracking
`plugins/` — git deleted those directories from the working tree, and the
gitignored `plugin-schemas.gen.ts` still named them.

The barrel being wrong is NORMAL. It is written by `db:push` and by installing a
plugin, and nothing rewrites it when a directory goes away by other means: a
pull, `git clean -xfd`, `rm`. It describes a machine, not the project, which is
why it is gitignored in the first place.

The defect was not the stale file. It was that a file allowed to be wrong sat in
the import graph of the process that must always start — via `db.ts` →
`schema.ts` → the barrel. An import graph is resolved before `main`, so this
was not catchable, loggable or survivable. Everywhere else the plugin system
already holds the line: `refreshPluginMounts` isolates a bad plugin,
`discoverPlugins` reports a broken manifest instead of throwing. This was the one
remaining path that could take the whole server down, and it took it down.

So the two consumers get their own entry points. `schema.ts` keeps the core
tables and is what the runtime imports. `drizzle-schema.ts` re-exports it, adds
the generated barrel, and is read only by drizzle-kit — which regenerates the
barrel before every run and can afford to fail loudly on a genuinely broken
plugin. `drizzle.config.ts` points at the new one.

Verified by reproducing it: with the barrel naming a plugin directory that does
not exist, importing the server's db module now succeeds while importing
`drizzle-schema.ts` still fails with that exact error — so the runtime is immune
and drizzle-kit's strictness is intact. First attempt at that test was worthless,
since I "poisoned" the barrel with a path that existed here; redone with an
absent one. `bun db:gen` then loaded the new entry and emitted both core and
plugin tables (music_*, offscale_servers), which is what proves `db:push` still
works.

Pulling this is enough to boot a server in that state — the stale barrel stops
being loaded rather than needing to be repaired.

tsgo clean, frontend builds, 865 pass / 7 fail unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:57:48 +00:00

44 lines
2.4 KiB
TypeScript

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/drizzle-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`.
//
// (It was `schema.ts` until 2026-08-16, which put the barrel in the SERVER's import graph too. A missing
// plugin directory then stopped the platform booting, so the two entry points were split.)
//
// 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',
);