Bun monorepo for exploring the next generation of the Supabase CLI and local development stack.
Install workspace dependencies:
pnpm installClone the reference submodules used during development:
bun run repos:installThat pulls .repos/effect/, which is the local source of truth for Effect v4 APIs and patterns in this repo.
.
|-- apps/
| |-- cli/ # Published Supabase CLI package
| `-- docs/ # Next.js docs site generated from the CLI
|-- packages/
| |-- api/ # Typed Supabase Management API client
| |-- config/ # Supabase config schema and generated types
| |-- process-compose/ # Effect-based process orchestration library
| |-- stack/ # Programmatic local Supabase stack runtime
| `-- cli-*/ # Platform-specific CLI binary packages
|-- tools/
| `-- nx-plugins/ # Local Nx inference plugins
|-- docs/ # ADRs, design notes, and implementation docs
`-- .repos/effect/ # Effect v4 reference source
| Workspace | Purpose |
|---|---|
apps/cli |
Main supabase package. Contains command handlers, runtime services, auth, output, telemetry, and docs generation scripts. |
apps/cli-e2e |
Compatibility e2e test suite. Record-and-replay harness for parity testing between the Go CLI and the TS Legacy port. |
apps/docs |
Internal docs site built with Next.js and generated from the CLI docs sources. |
| Workspace | Purpose |
|---|---|
packages/api |
Auto-generated TypeScript client for the Supabase Management API. |
packages/cli-test-helpers |
CLI test harness library — createHarness/exec API for spawning Go, TS Legacy, and TS Next CLI subprocesses in tests. |
packages/config |
JSON Schema and generated TypeScript types for Supabase configuration. |
packages/process-compose |
TypeScript/Bun port of process-compose used for multi-service orchestration. |
packages/stack |
Programmatic local Supabase stack used by the CLI and other tooling. |
packages/cli-darwin-arm64 |
Published native CLI binary wrapper for macOS arm64. |
packages/cli-darwin-x64 |
Published native CLI binary wrapper for macOS x64. |
packages/cli-linux-arm64 |
Published native CLI binary wrapper for Linux arm64 (glibc). |
packages/cli-linux-arm64-musl |
Published native CLI binary wrapper for Linux arm64 (musl). |
packages/cli-linux-x64 |
Published native CLI binary wrapper for Linux x64 (glibc). |
packages/cli-linux-x64-musl |
Published native CLI binary wrapper for Linux x64 (musl). |
packages/cli-windows-x64 |
Published native CLI binary wrapper for Windows x64. |
Root-level scripts:
pnpm run repos:install
pnpm run repos:pull
pnpm run check:all # run all checks across every project
pnpm run fix:all # run all fixers across every projectAll standard TypeScript workspaces (apps/cli, packages/api, packages/config, packages/process-compose, packages/stack) expose the following scripts:
| Script | What it does |
|---|---|
test |
Run the full test suite (unit + integration + e2e) |
test:core |
Run unit and integration tests |
test:unit |
Run unit tests (inferred by Nx plugin) |
test:integration |
Run integration tests (inferred by Nx plugin) |
test:e2e |
Run end-to-end tests (inferred by Nx plugin) |
check:all |
Run all check targets for this project |
fix:all |
Run all fix targets for this project |
types:check |
Type-check with tsgo --noEmit (inferred by Nx plugin) |
lint:check |
Check for lint errors with oxlint (inferred by Nx plugin) |
lint:fix |
Auto-fix lint errors (inferred by Nx plugin) |
fmt:check |
Check formatting with oxfmt --check (inferred by Nx plugin) |
fmt:fix |
Auto-fix formatting (inferred by Nx plugin) |
knip:check |
Find unused exports and dependencies with knip-bun (inferred by Nx plugin) |
knip:fix |
Auto-remove unused exports and dependencies (inferred by Nx plugin) |
The inferred scripts (test:unit, test:integration, test:e2e, types:check, lint:*, fmt:*, knip:*) are not declared in package.json — they are injected by local Nx plugins in tools/nx-plugins/. They are fully cached and can be discovered via nx show project <name>.
Quality checks are run from the workspace you are changing:
# From a project directory — scoped to that project only:
pnpm run check:all
pnpm run fix:all
pnpm run test
# From the workspace root — runs across all projects:
pnpm run check:allapps/cli-e2e implements a record-and-replay test harness for verifying behavioral parity between the Go CLI and the TypeScript Legacy port.
The Go CLI is the source of truth. Fixtures are recorded by running the Go CLI against the Supabase staging API. The TypeScript Legacy CLI is then run against the same fixtures to verify that its output matches.
The harness works in three modes:
| Mode | When | What it does |
|---|---|---|
| Replay (default) | Every PR / local dev | Loads committed fixtures; serves recorded responses to the CLI subprocess. Fast and deterministic — no network access. Default target is ts-legacy. |
| Record | RECORD=true |
Proxies CLI traffic to staging and captures request/response pairs as fixture files. Only the Go harness is used for recording. |
| Parity | CLI_HARNESS_TARGET=go |
Runs the Go CLI against the committed fixtures — useful for verifying fixture correctness independently of the TS port. |
# Replay mode — fast, no credentials needed
cd apps/cli-e2e
pnpm test # TS Legacy parity check (default)
pnpm test:go # Go harness explicitly
pnpm test:legacy # TS Legacy parity check (explicit, same as above)
# Or via Nx from the repo root
nx run @supabase/cli-e2e:test:e2eRecording proxies CLI traffic to the Supabase staging API. Provide a staging access token and a project ref for commands that need one — everything else is baked into the script:
cd apps/cli-e2e
SUPABASE_ACCESS_TOKEN=<your-staging-token> SUPABASE_TEST_PROJECT_REF=<your-project-ref> SUPABASE_STAGING_URL=<stagingUrl> pnpm recordReview the generated files in apps/cli-e2e/fixtures/recorded/ before committing — verify that no real tokens, UUIDs, or project refs appear (they should be replaced with __ACCESS_TOKEN__, __UUID__, __PROJECT_REF__ placeholders).
After recording, run the TS Legacy CLI against the committed fixtures:
pnpm test:legacyFailing tests identify commands where the TS Legacy port does not yet match the Go CLI output.
apps/cli-e2e/fixtures/
├── recorded/ # Committed fixture pairs (Go CLI = source of truth)
│ └── <KEY>/ # e.g. GET_v1_projects/
│ ├── default.request.json
│ └── default.response.json
├── errors/ # Manually crafted error fixtures (401, 403, 404, …)
└── scenarios/ # Reserved for stateful workflow tests (Tier 2)
Fixture files must never contain real tokens, UUIDs, or project IDs. The recording step replaces all dynamic values with stable placeholders automatically.
Test code imports from @supabase/cli-test-helpers (packages/cli-test-helpers):
import { createHarness, exec } from "@supabase/cli-test-helpers";
const harness = createHarness("go", { apiUrl, accessToken });
const result = await exec(harness, ["projects", "list"]);Test a real end-to-end publish and install of the CLI against a local npm registry (Verdaccio), without touching npm and without modifying any git-tracked files.
- Bun — for compiling the CLI binary and running the scripts
- Go — only required for
--legacyshell (commands proxied to the Go binary) - pnpm — already required by this repo
- Node.js — required by
npx/npm install -gto test the published package
Terminal 1 — start the local registry:
pnpm local-registryThis starts Verdaccio on http://localhost:4873, creates a publish user, and redirects the global npm and pnpm registry config to localhost. Press Ctrl+C when done — the original registry settings are restored automatically.
Terminal 2 — build and publish:
# Publish the next (TypeScript-native) shell
pnpm cli-release --next
# Or publish the legacy (Go-backed) shell
pnpm cli-release --legacy
# Pin a specific version (default: 0.0.0-local.<epoch-seconds>)
pnpm cli-release --next --version 0.0.0-local.1The script builds the CLI binary for the current platform only, compiles the Node.js shim, and publishes two packages to the local registry:
@supabase/cli-<platform>@<version>— the compiled binarysupabase@<version>— the shim that resolves and execs the binary
No git-tracked files are modified. Build output goes to a system temp directory that is deleted after publish.
# Run directly with npx
npx --registry http://localhost:4873 supabase@0.0.0-local.1 --version
# Or install globally and run as `supabase`
npm install -g --registry http://localhost:4873 supabase@0.0.0-local.1
supabase --version| Problem | Fix |
|---|---|
Error: Something is already running on port 4873 |
Kill the leftover Verdaccio process (lsof -ti:4873 | xargs kill) and retry |
go not found in PATH (legacy only) |
Install Go from https://go.dev/dl/ |
Error: Go CLI source not found (legacy only) |
Run pnpm repos:install to clone apps/cli-go |
| Registry not restored after crash | Run npm config set registry https://registry.npmjs.org/ and pnpm config set registry https://registry.npmjs.org/ |
npx resolves from npm instead of local |
Pass --registry http://localhost:4873 explicitly to npx / npm install |
Nx is the task runner for this repo. It handles caching, parallelism, and cross-project orchestration. All tasks — whether declared in a project's package.json or inferred by a plugin — are invoked the same way.
Run a single target:
nx run @supabase/api:knip:check
nx run supabase:testRun a target across all projects:
nx run-many -t knip:check
nx run-many -t lint:check fmt:check types:check knip:checkRun only affected projects (compared to main):
nx affected -t test
nx affected -t lint:check fmt:check types:check knip:checkInspect a project's full task configuration (including inferred targets):
nx show project @supabase/apiThis is the best way to see what targets exist on a project, what their inputs and outputs are, and whether they are cached. Some targets are not declared in package.json but are injected by local Nx plugins — knip:check and knip:fix are examples of this.
Nx caches task results locally under .nx/cache. A target hits the cache when all its inputs are unchanged since the last successful run — inputs include source files, named input sets like sharedGlobals, and external dependency versions.
To force a re-run and bypass the cache:
nx run @supabase/api:knip:check --skip-nx-cacheTo clear all cached results:
nx resetSeveral targets in this repo are not explicitly declared in any project file. They are injected by local plugins in tools/nx-plugins/ that inspect each package's package.json and derive targets from the tooling configuration found there.
To see the full list of targets for a project, always use nx show project rather than reading the nx.targets field in package.json directly.
See docs/nx-inference-plugins.md for how the plugin system works and how to add new plugins.
docs/adr/contains architecture decision records.docs/contains design notes for CLI output, telemetry, environment management, distribution, migration, and monorepo tooling.apps/cli/docs/contains source material used to generate command documentation.
The repo keeps source checkouts in .repos/ for local inspection while developing:
.repos/effect/contains the complete Effect v4 source used as the reference implementation for types, APIs, and patterns.