Status: proposed Date: 2026-05-13
ADR 0011 chooses Bun --compile single-file executables as the TypeScript CLI packaging format. That artifact shape matches the existing Go CLI distribution model, but compiled Bun does not behave exactly like bun <script> when a process tries to launch another TypeScript entrypoint.
In Bun JIT mode, process.execPath is the Bun runtime, so child_process.fork(entrypoint) effectively runs bun <entrypoint>. In a compiled executable, process.execPath is the CLI binary itself. Passing a script path as argv re-enters the compiled binary's baked entrypoint instead of running that script. Detached daemon startup and process supervision therefore need an explicit dispatch contract.
Compiled Bun also makes runtime source paths and dynamic native bindings different from a checkout. Source files can live in Bun's embedded filesystem, optional native packages are only included when they are statically referenced, and code that expects a stable filesystem path can fail at runtime.
This surfaced in:
- CLI-1452: the next binary could not run
supabase functions devbecause@parcel/watchernative bindings and the Edge Runtime bootstrap were not safe under the compiled binary. - CLI-1453:
supabase start --detachfailed because the daemon fork re-entered the CLI entrypoint instead of the daemon entrypoint.
Compiled-binary dispatch is explicit and owned by the layer that owns the process being launched:
- The CLI/stack layer owns Supabase-specific daemon dispatch.
forkDaemonmarks daemon children withSUPABASE_STACK_RUN_DAEMON=1; the compiled next CLI entrypoint checks that marker and runs the Bun daemon entrypoint in-process. @supabase/process-composeowns generic supervisor dispatch. Supervised services are launched throughprocess.execPath; when the library is bundled into a compiled Bun binary, a small internal environment protocol asks the same binary to run the supervisor runtime path.- The supervisor runtime and protocol stay as native
.tssource files. Bun and supported Node versions can run them directly, so process-compose does not need a JavaScript sidecar or Supabase-specific code. - Native watcher packages are referenced through static literal
require("@parcel/watcher-...")calls so Bun can include the platform optional dependencies in compiled binaries. - The Edge Runtime bootstrap source is embedded with Bun's text import attribute and written to the runtime temp directory before launch, instead of being read from a source path that may live inside Bun's embedded filesystem.
- The next CLI e2e harness runs against the compiled
supabase-nextbinary so regressions are caught against the same artifact users run.
The compiled binary is the public artifact, so child-process behavior must be designed around that artifact rather than around development-mode script launching.
Environment markers keep the contract small and side-effect free:
- They do not depend on argv shape that compiled Bun ignores.
- They let the compiled binary self-dispatch without extracting sidecar JavaScript files.
- They keep Supabase-specific daemon knowledge out of the generic process-compose package.
Using process.execPath in process-compose preserves runtime symmetry. If the library runs under Bun, it forks Bun; if it runs under Node, it forks Node. The only compiled-Bun-specific behavior is the self-dispatch marker, which is internal to the process-compose runtime contract.
Static native imports and text-embedded bootstrap source avoid relying on filesystem paths that are not stable after bun --compile.
- Detached stack commands and supervised services work from the compiled next CLI binary.
- Process-compose remains a generic Node/Bun supervision library, not a Supabase CLI adapter.
- The e2e suite now exercises the distributed binary for next CLI workflows.
- The environment variable names are internal contracts and must be changed carefully.
- Native dependencies used by compiled binaries need static literal references; dynamic native resolution is unsafe.
- Source files that must exist at runtime need explicit embedding or extraction.
- Keep relying on script-path argv: rejected because compiled Bun ignores this shape and re-enters the baked entrypoint.
- Ship sidecar JavaScript runtime files: rejected for now because it adds release artifacts and path management that
process.execPathself-dispatch avoids. - Move Supabase daemon knowledge into process-compose: rejected because process-compose is a generic supervision library.
- Use dynamic
@parcel/watcherresolution: rejected because Bun cannot reliably include platform optional native packages from dynamic require paths.