Status: accepted Date: 2026-04-17 (proposed), 2026-04-21 (accepted)
The existing Supabase CLI ships via GoReleaser, which is Go-specific. As the CLI is ported to TypeScript (see ADR 0004), GoReleaser cannot be used. Before the Phase 0 Go-wrapper release ships on the latest dist-tag — and before any further command porting — we need a concrete, validated plan for how the TypeScript CLI reaches users across every existing distribution channel.
The CLI is currently distributed through:
- npm / npx — the primary install path
- Homebrew —
supabase/homebrew-tap - Scoop —
supabase/scoop-bucket - apt / rpm / apk — Linux package manager files hosted on GitHub Releases
- GitHub Releases — platform archives + checksums for direct download
All channels must continue working after the GoReleaser cutover with no user-visible regression. Tracked in CLI-1330.
We replace GoReleaser with a pipeline built on Bun --compile single-file executables, npm optionalDependencies platform packages, and Nx-driven GitHub Actions workflows. The artifact shape on every channel remains identical to the GoReleaser output (single supabase binary per platform, same archive names and layout), so downstream installers (tap formula, bucket manifest, deb/rpm/apk payloads) are drop-in replacements.
| Concern | Choice |
|---|---|
| Binary packaging | Bun --compile SFE per platform |
| Platform coverage | darwin-{arm64,x64}, linux-{arm64,x64}, linux-{arm64,x64}-musl, windows-{x64,arm64} — parity with the Go CLI's current GoReleaser targets |
| npm distribution | supabase umbrella + eight @supabase/cli-<platform> packages as optionalDependencies |
| Platform resolution at runtime | Node shim (apps/cli/src/shared/cli/bin.ts) execFileSyncs the right platform binary |
| Linux packages | nfpm (binary only, installed from GoReleaser's apt repo) for .deb / .rpm / .apk |
| Homebrew | Existing supabase/homebrew-tap, updated by apps/cli/scripts/update-homebrew.ts |
| Scoop | Existing supabase/scoop-bucket, updated by apps/cli/scripts/update-scoop.ts |
| apt / rpm repo hosting | None — stay GitHub-Release-downloads-only |
| Dist-tags | latest = stable legacy shell, beta = prerelease legacy shell from develop (X.Y.Z-beta.N), alpha = next shell (TS-native). No separate next or canary tags |
| CI | [release.yml](../../.github/workflows/release.yml) (pushes to develop / main plus manual dispatch) invokes [release-shared.yml](../../.github/workflows/release-shared.yml) — build → smoke-test matrix → publish → draft Release → finalize |
| Artifact signing | Superseded by ADR 0014. Originally "ship unsigned"; the contingent follow-up A trigger fired (macOS 26+ AMFI SIGKILLs the linker-signed Bun/Go binaries — CLI-1621), so macOS binaries are now ad-hoc signed in-pipeline |
| SLSA provenance | Deferred. Go CLI has none today; add it only after signing decisions settle |
Most of the above is already implemented and validated in this repo (build pipeline, npm publish, nfpm, GitHub Release workflows, Homebrew end-to-end on darwin-arm64). The gaps this ADR closes were: (1) adding windows-arm64 as a build target to match Go CLI parity — landed; (2) deciding the signing story — decided (ship unsigned, contingent follow-ups only if validation fails); (3) wiring the Homebrew/Scoop updaters into release-shared.yml — still open and now the single largest remaining pre-cutover item. See the Implementation progress section for the file-by-file landing record and apps/cli/docs/release-process.md for the operational playbook (local → PoC → production).
bun build --compile produces a single static binary per target triple (see [apps/cli/scripts/build.ts](../../apps/cli/scripts/build.ts) lines 94–103):
await $`bun build ${entrypoint} --compile --minify --target=${target.bunTarget} --outfile=${outfile}`;This matches the artifact shape of the current Go releases exactly: one supabase (or supabase.exe) per {darwin,linux,windows} × {arm64,x64} plus linux-{arm64,x64}-musl. Every downstream channel — Homebrew, Scoop, deb/rpm/apk, .tar.gz/.zip on GitHub Releases — receives the same drop-in binary the Go CLI used to produce. No installer logic changes downstream.
Bun is already the build toolchain and runtime for the TypeScript CLI (the CLI imports @effect/platform-bun, the monorepo uses bun for all build scripts per AGENTS.md). Keeping Bun through packaging avoids introducing a second runtime (Node) just for distribution.
Exactly the pattern used by esbuild, @swc/core, turbo, and rollup:
supabase (umbrella, Node shim in bin)
├─ @supabase/cli-darwin-arm64
├─ @supabase/cli-darwin-x64
├─ @supabase/cli-linux-arm64
├─ @supabase/cli-linux-arm64-musl
├─ @supabase/cli-linux-x64
├─ @supabase/cli-linux-x64-musl
├─ @supabase/cli-windows-arm64
└─ @supabase/cli-windows-x64
Each platform package declares os, cpu, and (on Linux) libc, so npm/pnpm/yarn resolve and install only the binary matching the user's machine. The umbrella package's bin is a Node shim (not a Bun SFE): it resolves the installed platform package via require.resolve('@supabase/cli-<platform>/package.json') and execFileSyncs the real binary. See [apps/cli/src/shared/cli/bin.ts](../../apps/cli/src/shared/cli/bin.ts). No postinstall step, no network download after install, no lifecycle script required.
This is already decided and implemented — the CLI uses effect/unstable/cli in both the legacy and next shells (see [apps/cli/src/legacy/cli/root.ts](../../apps/cli/src/legacy/cli/root.ts) and [apps/cli/src/next/cli/root.ts](../../apps/cli/src/next/cli/root.ts)). The CLI framework choice is orthogonal to packaging: Bun --compile does not care what the source code uses. Calling it out here because the original CLI-1330 discussion conflated "oclif as framework" with "oclif pack as packaging" — only the second is rejected (see Alternatives).
nfpm (the tool; GoReleaser's packager-only binary) is used inside [apps/cli/scripts/build.ts](../../apps/cli/scripts/build.ts) to emit .deb / .rpm / .apk from the same per-platform bin/ directories that feed the .tar.gz archives. This keeps one source of truth for "which binary ships where" across all Linux channels.
For the .apk (musl) packages, the Bun binary is the musl SFE; the Go binary (Phase 0 only) is reused from the glibc build since it is compiled with CGO_ENABLED=0 and is fully static.
Hosting a signed apt/rpm repo (via Cloudsmith, packagecloud, or a self-hosted mirror) adds per-release friction (key management, repo metadata regeneration) and ongoing cost. The existing supabase/cli install docs already direct Linux users to the .deb / .rpm downloads on the GitHub Release page — this is the same UX we maintain. Revisit once install-channel telemetry (see ADR 0002) shows non-trivial Linux package-manager usage that would benefit from apt update / dnf upgrade workflows.
latest— stable legacy shell (shell: legacy) promoted frommain. Default install for production workflows.beta— prerelease legacy shell built fromdevelop. Uses semantic-release prerelease versions (X.Y.Z-beta.N), npm dist-tagbeta, and separate Homebrew / Scoop packages (supabase-beta). Seeapps/cli/docs/release-process.md.alpha— TS-native next shell (shell: next) for opt-in testers; published on demand via manual workflow dispatch. npm-only today (Homebrew / Scoop skipped for alpha).
We intentionally avoid extra tags such as next or canary so install surfaces stay predictable: stable (latest), integration prerelease (beta), and experimental shell (alpha). When alpha stabilises, latest may flip to the next shell and the tag story can be simplified — one decision at a time.
The current Go CLI release — apps/cli-go/.goreleaser.yml and release.yml / release-beta.yml — does no signing of any kind:
- No
signs:ornotarize:block in.goreleaser.yml - No
codesign/notarytool submit/ macOS notarization in the release workflow - No
signtool sign/ Authenticode signing for the Windows.exe - No
cosign sign-blobover archives or checksums - No SLSA provenance generator
The CLI has shipped this way for years across npm, supabase/homebrew-tap, supabase/scoop-bucket, and direct GitHub Release downloads. The distribution channels themselves provide enough trust/integrity:
| Channel | Why unsigned binaries work today |
|---|---|
| Homebrew | brew install uses curl to fetch the .tar.gz + SHA256 verification from the formula. curl-downloaded files do not receive the com.apple.quarantine xattr, so Gatekeeper does not block them. |
| Scoop | Same pattern on Windows: scoop install fetches via Invoke-WebRequest (no Mark-of-the-Web applied) + SHA256 verification from the manifest. |
| apt / rpm | Package managers verify their own checksums; the .deb / .rpm files are not GPG-signed by the Go CLI today either. |
| npm | execFileSync of the bundled binary — no OS quarantine path since it runs from node_modules/ on a local filesystem. |
Direct .tar.gz / .zip download |
User would need to xattr -d com.apple.quarantine or Right-click → Unblock on Windows. Already the case today for the Go binary. |
Decision: the TypeScript CLI pipeline ships unsigned in v0, exactly mirroring the Go CLI. Any signing work beyond that is contingent on the validation phase (see Open Questions) showing Bun SFEs behave differently than the Go binary under Gatekeeper / SmartScreen. If they behave identically, no signing is added.
Why Bun SFEs could behave differently: a Bun SFE is not a plain Mach-O / PE — it is the Bun interpreter binary with the user's bundle appended as embedded data. macOS's hardened runtime and JIT policies apply only when the binary is codesigned; unsigned execution should be identical to any other unsigned Mach-O, but this needs empirical confirmation. The Bun docs § Code signing on macOS document the entitlements.plist required if we ever sign — captured here only as a forward reference.
Fallback plan if validation fails:
| Failure mode | Minimal response |
|---|---|
brew install on macOS 14+ refuses the Bun SFE |
Apple Developer ID + codesign --sign + notarytool submit + staple. Requires Developer ID cert secret in CI. |
SmartScreen repeatedly blocks .exe despite Scoop install |
EV / standard Authenticode code-signing cert + signtool sign. Requires HSM or cloud-signing service (e.g. Azure Trusted Signing). |
| Users raise integrity concerns on direct downloads | cosign sign-blob over archives + checksums.txt, publish .sig + public key on the GitHub Release. Low effort, no hardware key. |
| External SLSA / supply-chain compliance requirement lands | Adopt slsa-framework/slsa-github-generator for provenance. |
All four fallbacks remain concrete follow-ups, but they are now triggered by validation outcomes, not scheduled work.
flowchart TD
dispatch["workflow_dispatch<br/>channel alpha|beta|stable"]
pushDev["push: develop<br/>channel=beta"]
pushMain["push: main<br/>channel=stable"]
releaseYml["release.yml"]
shared["release-shared.yml"]
build["build job<br/>sync-versions then build.ts then nfpm<br/>upload artifact"]
smoke["smoke-test matrix<br/>ubuntu / macos-latest / macos-15-intel / windows-latest"]
publish["publish job<br/>bun publish × 8 platform pkgs<br/>then bun publish umbrella"]
ghRelease["draft GitHub Release<br/>tar/zip/deb/rpm/apk/checksums"]
finalize["gh release edit --draft=false"]
hbUpdate["update-homebrew.ts<br/>Formula push"]
scoopUpdate["update-scoop.ts<br/>manifest push"]
dispatch --> releaseYml
pushDev --> releaseYml
pushMain --> releaseYml
releaseYml --> shared
shared --> build --> smoke --> publish --> ghRelease --> finalize
finalize -.follow-up.-> hbUpdate
finalize -.follow-up.-> scoopUpdate
release.yml selects the channel (stable / beta / alpha); release-shared.yml performs build → smoke-test → publish → GitHub Release. Homebrew and Scoop updates follow stable and beta publishes (alpha stays npm-only). Operational detail: apps/cli/docs/release-process.md.
Canonical path. supabase umbrella package has no native code; it ships a single Node shim at dist/supabase.js that resolves and execFileSyncs the platform binary from the matching @supabase/cli-<platform> package pulled in by optionalDependencies:
// apps/cli/src/shared/cli/bin.ts (excerpt)
const platformMap = PLATFORMS[process.platform];
const candidates = platformMap[os.arch()];
for (const suffix of candidates) {
const pkgPath = path.dirname(require.resolve(`@supabase/cli-${suffix}/package.json`));
binPath = path.join(pkgPath, "bin", `supabase${ext}`);
break;
}
execFileSync(binPath, process.argv.slice(2), { stdio: "inherit" });Publishing is handled by apps/cli/scripts/publish.ts: all eight platform packages first (parallel bun publish), then the umbrella last (so optionalDependencies resolve on install).
[apps/cli/scripts/update-homebrew.ts](../../apps/cli/scripts/update-homebrew.ts) clones the tap repo to a temp dir, writes Formula/supabase.rb templated with SHA256s from dist/checksums.txt, commits, and pushes. The formula references the supabase_<version>_<os>_<arch>.tar.gz archives on the GitHub Release.
Production tap: supabase/homebrew-tap. A single supabase formula replaces the current Go-CLI formula. No cask, no bottle — the formula points at the GitHub Release .tar.gz (same UX as today), avoiding any Gatekeeper quarantine path. The formula's darwin-arm64 / darwin-x64 selection uses Homebrew's standard on_arm / on_intel blocks.
apps/cli/scripts/update-scoop.ts clones the bucket repo, writes supabase.json (with an autoupdate block keyed off GitHub Releases), commits, and pushes. The manifest ships an architecture block with both 64bit (x64) and arm64 entries — matching the Go CLI's current GoReleaser output, which already targets windows_arm64 in apps/cli-go/.goreleaser.yml. Dropping arm64 would be a user-visible regression for anyone on a Windows-on-ARM device (Surface Pro X, recent Copilot+ PCs, Windows-11-ARM VMs on Apple Silicon).
Windows-arm64 is live in the TS pipeline as of this branch — packages/cli-windows-arm64/, bun-windows-arm64 in apps/cli/scripts/build.ts, and the win32: { arm64, x64 } map in apps/cli/src/shared/cli/bin.ts all shipped together. Bun supports this target natively (Bun — Single-file executable § Supported targets), so no cross-compilation toolchain changes were required. See apps/cli/docs/release-process.md for the full channel walkthrough.
Production bucket: supabase/scoop-bucket.
nfpm runs inside [apps/cli/scripts/build.ts](../../apps/cli/scripts/build.ts) and emits six Linux packages (deb/rpm/apk × arm64/amd64). All six are uploaded to the GitHub Release as direct downloads; there is no hosted repo. Installation is dpkg -i supabase_<v>_linux_amd64.deb or equivalent.
Draft + finalize by the shared workflow:
# .github/workflows/release-shared.yml (publish job, excerpt)
- uses: softprops/action-gh-release@…
with:
tag_name: v${{ inputs.version }}
draft: true
prerelease: ${{ inputs.prerelease }}
files: |
dist/supabase_…_darwin_arm64.tar.gz
dist/supabase_…_linux_amd64.deb
…
dist/checksums.txt
- run: gh release edit v${{ inputs.version }} --draft=falseArchive layout (per-platform):
packages/cli-<platform>/bin/
├── supabase (Bun --compile SFE)
└── supabase-go (legacy shell only, Phase 0 proxy target)
See [apps/cli/docs/binary-distribution.md](../../apps/cli/docs/binary-distribution.md) for the runtime-resolution details of the two-binary legacy model.
- Single source of truth for binary shape. Every channel serves the exact same
supabaseSFE. Tap formula, bucket manifest, deb/rpm/apk, and GitHub Release archives all consume the samepackages/cli-<platform>/bin/supabaseoutput — no per-channel repackaging. - Drop-in replacement for GoReleaser. File names, archive layout, and checksum format are preserved. Existing users and docs continue to work.
- No
postinstallstep.optionalDependenciesis resolved natively by npm/pnpm/yarn. Security scanners and locked-down CI environments that disable lifecycle scripts are unaffected. - One toolchain end-to-end. Bun compiles, Bun publishes, Bun runs. No Node/Bun split for distribution.
- Local parity with CI.
bun apps/cli/scripts/build.tsandbun apps/cli/scripts/publish.ts --dry-runreproduce the CI pipeline bit-for-bit. (Further consolidation tracked in CLI-1344.) - Nx caches build artifacts. CI reuses
packages/cli-*/bin/across jobs viaactions/upload-artifact; incremental rebuilds are free on unchanged platforms.
- Binary size. Bun SFEs are ~60–90 MB per platform, significantly larger than the Go binary (~15 MB). Acceptable for npm users (JS toolchains routinely ship > 100 MB of deps) but noticeable for apt/rpm users on metered connections.
- Unsigned binaries — same as today. The Go CLI already ships unsigned and has for years; we inherit the same situation. Risk is that Bun SFEs trigger Gatekeeper / SmartScreen differently than Go binaries, which is why end-to-end validation on the three PoC repos is a gating item before cutover rather than a follow-up.
- musl targets not smoke-tested in CI. The smoke-test matrix covers
ubuntu-latest(glibc) only; Alpine / Void validation is manual. - Homebrew and Scoop updaters are not automated.
update-homebrew.tsandupdate-scoop.tsexist and work from a checkout but are not called fromrelease-shared.yml. Every release requires a manualbun apps/cli/scripts/update-{homebrew,scoop}.ts --version …until the follow-up lands. - GoReleaser apt repo dependency for nfpm. The shared workflow installs
nfpmfromhttps://repo.goreleaser.com/apt/. If that repo is ever unavailable we need a fallback (direct GitHub Release download of thenfpmbinary). archlinuxformat not yet shipped. The Go CLI's nfpm config also generatesarchlinuxpackages; ours does not. Minor Arch-user regression unless we extendapps/cli/scripts/build.ts.
- Node SEA (Single Executable Application) — proposed in the original CLI-1330 comment. Node SEA packs a Node binary + embedded bundle + assets; artifact shape would match Bun
--compile, but:- Bun SFEs are already wired into the build; switching means rewiring
build.ts. - Node SEA requires a
sea.getAsset(...)+ extract-to-tmp dance for any native modules or dynamic imports. Bun SFEs don't. - Node SEA binaries are generally larger than Bun SFEs (V8 + SEA assets vs JSC).
- The CLI depends on
@effect/platform-bun, which is Bun-specific — a Node SEA port would need@effect/platform-nodewiring.
- Bun SFEs are already wired into the build; switching means rewiring
**oclif pack** — produces a directory tree + shell shim, not a single binary. It would work for Homebrew and manual installs but does not fit Scoop's single-manifest expectation or the deb/rpm/apk "drop one binary at/usr/bin/supabase" model. Also moot: the CLI uses Effect CLI, not oclif, so oclif's packaging is doubly inapplicable.**vercel/pkg** — unmaintained since 2023, explicitly deprecated by Vercel in favour of Node SEA. Non-starter.**deno compile**— would require porting the runtime away from Bun. The CLI's platform layer (@effect/platform-bun) has no Deno equivalent today. Taking two bets at once (SEA + runtime) is the kind of risk the Linear comment already flagged.- Keep GoReleaser, call
bun buildfrom inside it — possible but inverts the ownership: a Go tool driving a TypeScript build is confusing, and GoReleaser's value (cross-compiling Go + packaging + publishing in one) is wasted when the Go part goes away. Preserves none of GoReleaser's benefits and keeps its YAML configuration as a liability. - Host an apt/rpm repo (Cloudsmith / packagecloud / self-hosted) — rejected for v0, revisit per usage signal. See Rationale.
These split into pre-cutover gates (must complete before latest flips from Go CLI to the TypeScript pipeline) and contingent follow-ups (triggered only by specific validation failures).
-
Windows arm64 build target. ✅ DONE (2026-04-21) — landed on this branch.
bun-windows-arm64inapps/cli/scripts/build.tsTARGETS+GO_TARGETS, newpackages/cli-windows-arm64/,win32: { arm64, x64 }inapps/cli/src/shared/cli/bin.tsandapps/cli/src/shared/legacy/go-proxy.layer.ts, eighth entry inapps/cli/package.jsonoptionalDependencies,windows_arm64.zipinrelease-shared.ymlrelease files,arm64sub-key inupdate-scoop.ts. Extending the CI smoke-test matrix towindows-11-armremains open (tracked as a follow-up under gate 6). -
Homebrew tap end-to-end validation. ✅
darwin-arm64validated onv0.0.1(2026-04-20) —brew tap avallete/supabase-shim-poc && brew install supabase-shim-poc && supabase-shim-poc --versionproducedsupabase v0.0.1with no Gatekeeper quarantine prompt;brew testpassed; and invoking a Go-backed subcommand (supabase-shim-poc projects list --help) correctly spawned the colocatedsupabase-gosidecar. Remaining: repeat ondarwin-x64(Intel Mac or Rosetta) andlinux-{arm64,x64}(brew on Linux) to fully close out this gate. If Gatekeeper ever does block the unsigned Bun SFE on a fresh macOS install, escalate to contingent follow-up A below. Full reproducer inapps/cli/docs/release-process.mdRing 2.Bug discovered & fixed during validation: the initial formula only ran
bin.install "supabase"and dropped thesupabase-gosidecar on the floor. Any Go-proxied subcommand then fell through the resolution chain inapps/cli/src/shared/legacy/go-proxy.layer.ts(colocated lookup misses →require.resolve("@supabase/cli-*")fails inside a Bun SFE → final fallback spawnssupabaseon PATH), yieldingNotFound: ChildProcess.spawn (supabase <subcmd>). Renaming the installed bin tosupabaseturned the fallback into a self-exec fork-bomb (infinite hang). Fix:apps/cli/scripts/update-homebrew.tsnow emitsbin.install "supabase-go" if File.exist?("supabase-go")alongside the SFE install line. TheFile.exist?guard keeps the same formula valid for the futurenextshell (SFE only, no sidecar). Scoop is unaffected because its bucket manifests extract the whole zip into~/scoop/apps/<name>/current/— thebinfield only controls PATH shimming, not extraction, sosupabase-go.exeis always colocated with the SFE on disk. -
Scoop bucket end-to-end validation. ✅ Windows x64 validated on
v0.0.1(2026-04-21) —scoop bucket add avallete-poc https://github.com/avallete/scoop-bucket && scoop install supabase-shim-poc && supabase-shim-poc --versionproducedsupabase v0.0.1with no SmartScreen block on an unsigned Bun SFE. This closes the Scoop leg against the defaultwindows_amd64.ziparchive that GoReleaser also ships. Manifest used:avallete/scoop-bucket/supabase-shim-poc.json, generated by the--namesupport onapps/cli/scripts/update-scoop.tsthat landed on this branch — see Implementation progress theme B. Remaining: repeat on a Windows-arm64 host (Surface Pro X / Copilot+ PC / Apple Silicon Windows-11-ARM VM) to exercise the newwindows_arm64.ziparchive. If SmartScreen ever blocks the unsigned.exeon a fresh Windows install, escalate to contingent follow-up B below.scoop bucket add avallete-poc https://github.com/avallete/scoop-bucket scoop install supabase-shim-poc supabase-shim-poc --version # expect: supabase v0.0.1
-
GitHub Release artifact host for PoC.
avallete/supabase-cli-release-pochosts thedist/supabase_<v>_*.{tar.gz,zip,deb,rpm,apk}+checksums.txtthe PoC formula/manifest URLs resolve against. At cutover this is replaced bysupabase/cli(the current Go CLI Release host). The repo's git tree is effectively empty; only its Releases matter. -
Wire updaters into
release-shared.yml. Remaining largest item before cutover. Todayrelease-shared.ymlfinishes atgh release edit --draft=false; no Homebrew/Scoop push happens from CI. Add apost-publishjob that runsupdate-homebrew.tsandupdate-scoop.tsbehind the!inputs.dry_rungate, only whennpm_tag == 'latest'(we do not push Homebrew/Scoop for alpha). Needs a GitHub App token (following the pattern inapps/cli-go/.github/workflows/release.yml:APP_ID+GH_APP_PRIVATE_KEYsecrets, scoped tohomebrew-tapandscoop-bucketrepos) so release tokens never hold broader access. Until this lands, the Ring 3 playbook inapps/cli/docs/release-process.mddocuments the manualbun apps/cli/scripts/update-{homebrew,scoop}.ts --version <v>post-release step. -
Bun
--compilemusl validation + Windows arm64 smoke-test.buildMuslBinariesproduces musl SFEs but the smoke-test matrix only runsubuntu-latest(glibc). Add an Alpine container job. Same matrix gap applies to Windows arm64 now that gate 1 is DONE: add awindows-11-armrunner (GitHub-hosted preview) so we catch arm64 regressions in CI rather than post-release. -
Local vs pipeline release-logic consolidation. Tracked in sub-issue CLI-1344. Intent:
bun apps/cli/scripts/{build,publish}.ts --dry-runproduces the identical output CI does;sync-versions.tsis the single versioning entry point. -
findConfigInRootENOTDIRhandling. Discovered while validating gate 2:packages/config/src/paths.ts'sfs.exists(supabase/config.json)throwsBadResource(ENOTDIR) instead of returningfalsewhencwdcontains a file namedsupabase(e.g., the extracted SFE in a scratch dir during local install tests). The walk-upward resolver should treatENOTDIRthe same asENOENT. Does not block the production brew path (/opt/homebrew/bin/supabase-shim-pochas no collidingsupabasefile in any parent), but it's a latent bug for any user whose$HOMEor a parent contains asupabasefile. Low-effort fix.
These are not scheduled work. They activate only if the matching pre-cutover gate fails. Each one mirrors a specific capability the Go CLI does not have today — so activating any of them is a net improvement over current behavior, not a prerequisite for parity.
- A. macOS notarization —
⚠️ TRIGGERED — see ADR 0014. macOS 26+ AMFI SIGKILLs the linker-signed Bun/Go binaries (CLI-1621 / #5556). Resolved in two phases: full ad-hoc signing in-pipeline now (no Apple account needed), Developer ID + notarization deferred behind credential secrets. Original note: Apple Developer ID cert in CI +codesign --sign --options runtime+notarytool submit --wait(no stapling — bare Mach-O can't be stapled). - B. Windows Authenticode signing — trigger: gate 3 shows SmartScreen reliably blocks the unsigned
.exeon fresh Windows machines beyond what the Go binary currently hits. Solution: EV or standard code-signing cert +signtool sign /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /a. For a cloud-native flow, Azure Trusted Signing or similar avoids HSM provisioning. - C. Linux artifact signing (
cosign) — trigger: external integrator / security audit requests verifiable signatures on.tar.gz/.deb/.rpm/.apk. Solution:cosign sign-blob --yes --output-signature <file>.sigin CI, publish.sig+cosign.pubon the GitHub Release. Low effort; no hardware key. - D. SLSA provenance — trigger: supply-chain compliance requirement from a downstream consumer. Solution:
slsa-framework/slsa-github-generator(level-3 attestations on npm + GitHub Release artifacts). - E.
archlinuxpackage format — trigger:aur/supabase-binmaintainers request it, or telemetry shows meaningful Arch usage. Solution: addarchlinuxto theformats:list inapps/cli/scripts/build.ts(nfpm supports it natively, same as our current apk/deb/rpm config).
The three PoC repos (avallete/homebrew-supabase-shim-poc, avallete/scoop-bucket, avallete/supabase-cli-release-poc) are explicitly not added as .repos/ submodules*, even though [.gitmodules](../../.gitmodules) already tracks eight vendored reference repos (effect, supabase-cli-go, process-compose, etc.). Existing .repos/* entries are read-only source-code references for grep / inspection; the PoC repos contain one generated artifact each (Formula/supabase.rb, supabase.json) or no interesting tree content at all. The update-*.ts scripts gh repo clone on demand into a tmpdir, so a submodule would change nothing about the release pipeline. Since these repos are PoC-phase only, vendoring now means a .gitmodules edit + git rm at cutover for zero operational benefit.
This section tracks the work that has landed against the pre-cutover gates. Detailed operational steps live in apps/cli/docs/release-process.md; the PR changelog lives on GitHub.
A. Windows arm64 parity (closes gate 1)
: bun-windows-arm64 added to apps/cli/scripts/build.ts TARGETS + GO_TARGETS; new packages/cli-windows-arm64/ workspace package; win32: { arm64, x64 } in apps/cli/src/shared/cli/bin.ts and apps/cli/src/shared/legacy/go-proxy.layer.ts; 8th entry in apps/cli/package.json optionalDependencies; platform arrays extended in apps/cli/scripts/publish.ts, sync-versions.ts, tools/release/local-release.ts, apps/cli/tests/helpers/npm-registry.ts; windows_arm64.zip uploaded by release-shared.yml.
B. --name + user-owned-repo support on updater scripts (unblocks gates 2 & 3 end-to-end validation)
: apps/cli/scripts/update-homebrew.ts and apps/cli/scripts/update-scoop.ts now accept --name <custom> plus the pre-existing --repo, --tap/--bucket, so the exact production updater code paths can be exercised against a reviewer's own homebrew-* / scoop-* repos under a non-supabase name (e.g., supabase-shim-poc). The --name flag controls the formula filename + Ruby class on Homebrew, and the manifest filename on Scoop, so supabase (stable) and supabase-beta can coexist as separate formulas / manifests in the same tap / bucket. The installed binary is always supabase (matching the Go CLI's historical behaviour — both Formula/supabase.rb and Formula/supabase-beta.rb ran bin.install "supabase"); PoC reviewers must brew uninstall supabase first if the official CLI is already installed. Side-effect bug fix: the Homebrew formula now installs the supabase-go sidecar alongside the SFE (bin.install "supabase-go" if File.exist?("supabase-go")) — see gate 2 above for root-cause analysis.
C. Real CLI version plumbing (hygiene; surfaced via gate 2 validation)
: The CLI used to hard-code "0.1.0" for both --version output and telemetry. New apps/cli/src/shared/cli/version.ts exports CLI_VERSION = process.env.SUPABASE_CLI_VERSION ?? "0.0.0-dev". apps/cli/scripts/build.ts injects the real version at compile time via bun build --define=process.env.SUPABASE_CLI_VERSION=... (both glibc and musl builds). apps/cli/src/shared/cli/run.ts feeds it into Effect CLI's Command.runWith; apps/cli/src/shared/telemetry/runtime.layer.ts uses the same constant.
D. Build correctness fix
: apps/cli/scripts/build.ts now runs go build -trimpath -ldflags="-s -w" -o ${outfile} . with .cwd(goSource) instead of passing goSource as a positional argument. Passing an absolute path caused Go to resolve the module from the invocation CWD (the repo root, which has no go.mod) and fail.
Still open: gate 2 (Homebrew on darwin-x64 + linux-{arm64,x64} — Ring 2 reproducer lives in release-process.md), gate 3 (Scoop on Windows arm64 only — x64 validated on v0.0.1), gate 5 (wire updaters into release-shared.yml), gate 6 (musl + Windows-arm64 smoke-test matrix), gate 7 (local ↔ pipeline release-logic consolidation, CLI-1344), gate 8 (findConfigInRoot ENOTDIR handling).
- ADR 0001: CLI DX Architecture Pillars — performance budgets the Bun SFE startup cost is measured against.
- ADR 0004: CLI Design Goals — legacy vs next shell split that drives the
latest/beta/alphadist-tag layout.
apps/cli/docs/release-process.md— the operational playbook: local Verdaccio → user-owned PoC repos → production pipeline.apps/cli/docs/binary-distribution.md— runtime resolution details for the two-binary legacy model (TS SFE + Go binary)..github/workflows/release-shared.yml,release.yml— the pipeline implementation (release.ymlselects stable / beta / alpha channel per trigger).apps/cli/scripts/build.ts,publish.ts,sync-versions.ts,update-homebrew.ts,update-scoop.ts— release scripts.apps/cli-go/.goreleaser.yml,release.yml,release-beta.yml— the Go CLI release config we mirror (no signing,windows_arm64target, GitHub-App-scoped publish tokens).- CLI-1330 — origin ticket.
- CLI-1344 — sub-issue: consolidate local and pipeline release logic.