Parent
Follow-up from #1157 / #1218, under spec #1125. Lands on the prisma-8 integration branch, not main.
Problem
generateCommand() in packages/cli/src/commands/generate.ts reports every failure by calling process.exit(1) — around 13 call sites. That is fine for opensaas generate, which is the whole process, but it is now wrong for opensaas dev, which calls generateCommand() as one step of a longer-lived loop that owns resources.
process.exit skips finally blocks and never drains pending async work, so a try/finally in the dev loop cannot run its async stop() on this path no matter where the try starts. When generation fails under opensaas dev, the in-process PGlite is never closed cleanly.
#1218 mitigated what is reachable from a synchronous 'exit' listener — it kills the app child and drops the run's state file, which closes the worst hole (an orphaned next dev left running against a dead database). The clean async shutdown still needs the underlying contract to change.
What to build
Make generateCommand() signal failure by throwing rather than by exiting, so callers decide what to do. The opensaas generate command keeps its current observable behaviour — same message, same exit code — by catching at the CLI entry point. opensaas dev then gets its finally back and can await a clean stop().
Expect a contract change for every caller; check packages/cli/src/index.ts and the dev loop, and any test that asserts on the exit.
Acceptance criteria
Notes
Raised by the implementer of #1218 while addressing review, and deliberately left out of that PR: refactoring 13 exit sites and changing a contract for every caller does not belong in a PR already under review.
Parent
Follow-up from #1157 / #1218, under spec #1125. Lands on the
prisma-8integration branch, notmain.Problem
generateCommand()inpackages/cli/src/commands/generate.tsreports every failure by callingprocess.exit(1)— around 13 call sites. That is fine foropensaas generate, which is the whole process, but it is now wrong foropensaas dev, which callsgenerateCommand()as one step of a longer-lived loop that owns resources.process.exitskipsfinallyblocks and never drains pending async work, so atry/finallyin the dev loop cannot run its asyncstop()on this path no matter where thetrystarts. When generation fails underopensaas dev, the in-process PGlite is never closed cleanly.#1218 mitigated what is reachable from a synchronous
'exit'listener — it kills the app child and drops the run's state file, which closes the worst hole (an orphanednext devleft running against a dead database). The clean async shutdown still needs the underlying contract to change.What to build
Make
generateCommand()signal failure by throwing rather than by exiting, so callers decide what to do. Theopensaas generatecommand keeps its current observable behaviour — same message, same exit code — by catching at the CLI entry point.opensaas devthen gets itsfinallyback and canawaita cleanstop().Expect a contract change for every caller; check
packages/cli/src/index.tsand the dev loop, and any test that asserts on the exit.Acceptance criteria
generateCommand()throws on failure; noprocess.exitinside itopensaas generateon a failing config still prints the same error and exits 1opensaas dev, a generation failure runs the loop's asyncstop()— the Dev database is closed and its state file removed, verified by a test rather than by inspection'exit'safety net added in opensaas dev brings up the Dev database, generates, reconciles and spawns the app #1218 is reconciled with the new path: kept as a backstop or removed as redundant, deliberately either wayNotes
Raised by the implementer of #1218 while addressing review, and deliberately left out of that PR: refactoring 13 exit sites and changing a contract for every caller does not belong in a PR already under review.