Files
platform/scripts/gen-plugin-schemas.ts
T
pastilhas 9af52fd754 db:push regenerates the plugin schema barrel, so setup works on a clean machine
`officer_db/src/schema.ts` ends with an UNCONDITIONAL
`export * from './plugin-schemas.gen'`. That file is gitignored — it describes
this machine, not the project — and until now its only writer was
installPlugin.

So on a fresh clone the file does not exist and `bun db:push` dies with
MODULE_NOT_FOUND before creating a single table. That is exactly what
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 with
the plugin system on 2026-08-15.

Reproduced by deleting the file and running push, not inferred.

`db:push` now runs scripts/gen-plugin-schemas.ts first. Wired into the script
rather than added as a setup step because 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 forget once; push regenerating it means the barrel cannot be stale
when drizzle reads it.

Both paths verified: barrel deleted then push (regenerates, "No changes
detected"), and zero plugins on disk (writes a header-only barrel that
schema.ts still imports cleanly).

Also verified, on a scratch database, the premise the whole barrel design rests
on — that push drops what it cannot see. Full schema pushed, a row seeded into
music_favorites, then an empty barrel pushed: music_* and headscale_servers
were DROPPED along with the row. So "following the directory rather than the
install table" is load-bearing, not decorative.

Worth recording how close that came to being logged as the opposite. An earlier
run with an empty barrel reported "No changes detected", which read as evidence
that push does not drop. It was this fix working — the script had regenerated
the barrel before drizzle ran. The scratch database is what told them apart.

bunx tsgo clean. 797 tests, 787 pass, same 7 pre-existing failures. Scratch
database dropped; live plugin tables and rows verified intact.
2026-08-15 14:18:47 +00:00

41 lines
2.2 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/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',
);