From c67b00cc027a146fb64b3f395d1d9a4d5b72eb04 Mon Sep 17 00:00:00 2001 From: "Charles (via Bookendi session)" Date: Sun, 30 Aug 2026 09:31:00 +1000 Subject: [PATCH] fix(database): push-schema.js CLI-entrypoint guard checks argv[1], not import.meta.url The direct-invocation guard (node packages/database/dist/push-schema.js) compared import.meta.url against process.argv[1] to decide whether to run main(). tsup's ESM output code-splits this file's top-level statements into a shared chunk (dist/chunk-*.js), so import.meta.url inside that code resolves to the CHUNK's own URL, not push-schema.js's -- confirmed with debug logging, which showed the mismatch on every invocation, direct or imported. The practical effect: running push-schema.js standalone (as opposed to via run-migrations.js, which imports and calls pushSchema() directly, bypassing this guard entirely) silently did nothing -- no thrown error, no output, exit code 0 -- because main() was never called. permission-smoke.integration.spec.ts's beforeAll hits exactly this path, running push-schema.js standalone a second time to prove role_permissions grants survive a re-run against already-seeded data, and got zero grants back as a result. Checking process.argv[1]'s own filename instead of import.meta.url sidesteps bundler chunking entirely -- it only asks what script node was told to run, never where the executing code happens to live. --- packages/database/src/push-schema.ts | 31 +++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/database/src/push-schema.ts b/packages/database/src/push-schema.ts index ff49134..df97f3c 100644 --- a/packages/database/src/push-schema.ts +++ b/packages/database/src/push-schema.ts @@ -2,8 +2,6 @@ * Push schema to database using drizzle-orm's migrate API. * Workaround for drizzle-kit CJS/.js extension issue. */ -import { resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; import postgres from 'postgres'; import { drizzle } from 'drizzle-orm/postgres-js'; import { sql } from 'drizzle-orm'; @@ -1498,7 +1496,34 @@ async function main() { await pushSchema(); } -if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { +// NOT an import.meta.url comparison (tried realpathSync AND plain +// path.resolve, both wrong for the same reason): tsup's ESM output code- +// splits this file's top-level statements into a SHARED CHUNK +// (dist/chunk-*.js), so import.meta.url here resolves to that chunk's own +// URL, not to push-schema.js's -- confirmed with debug logging, which +// showed import.meta.url pointing at the chunk file on BOTH the direct +// `node push-schema.js` invocation and run-migrations.js's import, with +// process.argv[1] correctly showing 'push-schema.js' each time. Any +// import.meta.url-based "am I the entry point" check is fundamentally +// unreliable once a bundler is free to move module code into a shared +// chunk. Checking argv[1]'s own filename instead sidesteps chunking +// entirely -- it only asks what script node was told to run, never where +// the executing code happens to live. +// +// WHY THIS MATTERS: run-migrations.ts imports and calls pushSchema() +// directly (migration-runner.ts), so it never depended on this guard and +// always worked. permission-smoke.integration.spec.ts's beforeAll runs +// `node packages/database/dist/push-schema.js` standalone a second time in +// the same CI job (to prove role_permissions grants survive a re-run +// against already-seeded data) -- with the guard false, main() was never +// called, so nothing ran: no thrown error, no output, exit code 0, and +// integration_service came back with zero grants. +function isDirectlyExecuted(): boolean { + const entry = process.argv[1]; + return !!entry && /(^|[\\/])push-schema\.(m?js|cjs)$/.test(entry); +} + +if (isDirectlyExecuted()) { main().catch((err) => { console.error('Push failed:', err); process.exit(1);