| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
projected state from stack and supa -> supabase renaming | 7 个月前 | |
feat(cli): trace CLI runs end-to-end with opt-in OTLP export (#6935) ## Summary **TL;DR:** Every CLI run can now produce one OpenTelemetry trace that shows where time goes, down to individual migrations, containers, health waits, API operations, and SQL statements. Export is opt-in per run (`SUPABASE_TRACE_FILE` for a local OTLP/JSON file, or `SUPABASE_OTLP_ENDPOINT` for Tempo/Jaeger/Grafana), costs nothing when unset, and never exports free-form text or secrets. Two new scripts turn traces into answers: `trace-report` (critical path, self time, repeated spans) and `bench-cli` (fair A/B timing between two binaries, optionally per span). ### Before ```mermaid flowchart LR A["CLI run"] --> B["ad-hoc spans<br/>(uneven coverage)"] B --> C{"telemetry consent?"} C -->|yes| D["~/.supabase/traces/*.ndjson<br/>no parent ids, never read"] C -->|no| E["dropped"] B -->|--debug| F["one stderr line per span"] ``` ### After ```mermaid flowchart LR A["CLI run"] --> R{"SUPABASE_TRACE_FILE<br/>or SUPABASE_OTLP_ENDPOINT?"} R -->|neither| N["tracer disabled<br/>no-op spans, no I/O"] R -->|set| T["cli.run span tree<br/>commands, phases, I/O, processes"] T --> S["sanitizer<br/>no free text, no secrets"] S --> F["OTLP/JSON file"] --> TR["trace-report / bench-cli"] S --> O["OTLP collector<br/>Tempo, Jaeger, Grafana"] ``` ### Why Benchmarking or debugging the CLI meant adding temporary instrumentation for each investigation. The existing tracer wrote consent-gated NDJSON without parent span ids, so no span tree could be rebuilt, and nothing read those files. Agents and maintainers need one consistent trace per run that standard tools can open. ### What changed - **Export pipeline** (`apps/cli/src/shared/telemetry/`): one sink per run (file xor OTLP/HTTP, both is a config error); CLI-namespaced variables so a global `OTEL_*` setup never receives CLI internals; `TRACEPARENT` is adopted as the parent and passed to child processes; the trace scope flushes within a bounded window before `processControl.exit`. - **Tracing off by default**: without a sink the root runs with `TracerEnabled = false` and the exporter module is never loaded. Startup and command wall time match `develop` within noise. - **Sanitizer**: exported batches keep only structured data — exception events keep `exception.type` (plus a structured SQLSTATE), status and log messages are dropped, SQL text becomes `db.operation.name` and a per-run salted hash, URLs lose query strings and storage object keys, headers are allowlisted, resource attributes are allowlisted, trace files are `0600`. - **Instrumentation sweep** across commands, `command-internal`, `shared`, `packages/stack`, and `packages/api`, following the new conventions ADR: named spans at I/O and service boundaries, handler phase spans, outcome annotations (counts, cache hit/miss, attempts, exit codes), process spans with the docker verb, API spans named by operation id, no spans per poll attempt or loop item. - **`SUPABASE_DEBUG=1` console** keeps its behavior (spans up to depth 2 plus failures on stderr) with sanitized, length-capped attributes; `--debug` alone still prints no spans. - **Scripts**: `apps/cli/scripts/trace-report.ts` and `apps/cli/scripts/bench-cli.ts` (alternating launches, isolated environment, warm-up, untimed traced/profiled launches, `--cpu-prof`). - **Removed** the NDJSON sink and the dead `command-instrumentation.ts` copy of the command span. - **Docs**: [ADR 0027](docs/adr/0027-cli-tracing-conventions.md) for conventions and [tracing-monitoring.md](apps/cli/docs/tracing-monitoring.md) for usage; telemetry docs updated for the removed NDJSON files (old `~/.supabase/traces` files are no longer written and are safe to delete). ### Terminal captures Recorded from a fresh corpus project against a local `grafana/otel-lgtm` and with `vhs`; both builds are local binaries of `develop` and this branch. **Tempo: CLI runs, `supabase start` waterfall, cold `db diff` breakdown, a sanitized process span, two commands grouped by `TRACEPARENT`**      **Terminal: debug console, traced command, trace-report, A/B bench, per-span bench**      ## Linked issue None. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 12 天前 | |
chore(repo): move oxlint and oxfmt to native root configs (#6311) ## What Replaces the per-package nx-inferred lint/format targets with tool-native configuration at the repo root, so oxlint and oxfmt work standalone (`pnpm exec oxlint`, `pnpm exec oxfmt`) and the oxc VSCode extension sees the exact rules CI enforces. Three commits, meant to be reviewed separately: 1. **`chore: fix type-aware oxlint findings across the monorepo`** — the ~30 findings that block enabling type-aware linting repo-wide (`no-floating-promises`, `no-base-to-string`, redundant union constituents, `= undefined` optional params, …), plus two TS7 modernizations tsgolint forced in `tools/nx-plugins` (drop `node10` module resolution; replace the `Function('return import(...)')` transpile workaround with a plain `import()`, obsolete under Node 24 type stripping). 2. **`chore: format the full repo surface with oxfmt`** — mechanical one-time pass over surface the per-package targets never covered: docs markdown, workflow YAML, root/wrapper manifests, `tools/`, `apps/docs`. Generated docs outputs are excluded so they stay byte-identical with their generator. 3. **`chore: move oxlint and oxfmt to native root configs`** — the actual restructuring (details below). ## Why - Tool configuration lived in nx plugin code (`--deny-warnings`, `--type-aware` via a package.json side-channel) where only nx could see it; the oxlint VSCode extension had no config to discover at all. - Type-aware linting was scoped to `packages/api` only. Repo-wide it costs ~3.5s and immediately caught real issues (e.g. floating promises) — it is now on everywhere via `options.typeAware`/`options.denyWarnings` in `.oxlintrc.json`. - Spawning 7 per-package processes through nx for millisecond-fast Rust tools cost more than one whole-repo pass (~1s fmt, ~3.5s type-aware lint). ## Key changes - Root `.oxlintrc.json`: type-aware + deny-warnings, ignore list (`.repos`, `apps/cli-go`, fixtures, generated output), Effect-idiom rule exclusions (`no-misused-spread`, `unbound-method` — both false-positive on `Data.Class` copy-with-update and `.pipe(sem.withPermit)` patterns), and a legacy-scoped `no-base-to-string` override. - Root `.oxfmtrc.json`: ignore list only (style defaults unchanged); replaces `apps/cli-e2e/.prettierignore`, which only worked when oxfmt ran from that cwd. - `lint:*`/`fmt:*` are now plain command targets on `@supabase/root` (declared in the root package.json `nx` field); `oxlint.plugin.ts` and `oxfmt.plugin.ts` are deleted. Root `pnpm check:all`/`fix:all` and CI entrypoints are unchanged; per-package `check:all`/`fix:all` add `@supabase/root` to their `--projects` list. - The oxc devDependencies, knip ignores for them, and the `oxlint.typeAware` package.json channel are removed from every package — the tools are root devDependencies only. - Lint/format now also cover previously unchecked surface: `tools/`, `apps/docs`, root scripts, docs markdown, and workflow YAML. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Julien Goux <hi@jgoux.dev> | 1 个月前 | |
chore(repo): move oxlint and oxfmt to native root configs (#6311) ## What Replaces the per-package nx-inferred lint/format targets with tool-native configuration at the repo root, so oxlint and oxfmt work standalone (`pnpm exec oxlint`, `pnpm exec oxfmt`) and the oxc VSCode extension sees the exact rules CI enforces. Three commits, meant to be reviewed separately: 1. **`chore: fix type-aware oxlint findings across the monorepo`** — the ~30 findings that block enabling type-aware linting repo-wide (`no-floating-promises`, `no-base-to-string`, redundant union constituents, `= undefined` optional params, …), plus two TS7 modernizations tsgolint forced in `tools/nx-plugins` (drop `node10` module resolution; replace the `Function('return import(...)')` transpile workaround with a plain `import()`, obsolete under Node 24 type stripping). 2. **`chore: format the full repo surface with oxfmt`** — mechanical one-time pass over surface the per-package targets never covered: docs markdown, workflow YAML, root/wrapper manifests, `tools/`, `apps/docs`. Generated docs outputs are excluded so they stay byte-identical with their generator. 3. **`chore: move oxlint and oxfmt to native root configs`** — the actual restructuring (details below). ## Why - Tool configuration lived in nx plugin code (`--deny-warnings`, `--type-aware` via a package.json side-channel) where only nx could see it; the oxlint VSCode extension had no config to discover at all. - Type-aware linting was scoped to `packages/api` only. Repo-wide it costs ~3.5s and immediately caught real issues (e.g. floating promises) — it is now on everywhere via `options.typeAware`/`options.denyWarnings` in `.oxlintrc.json`. - Spawning 7 per-package processes through nx for millisecond-fast Rust tools cost more than one whole-repo pass (~1s fmt, ~3.5s type-aware lint). ## Key changes - Root `.oxlintrc.json`: type-aware + deny-warnings, ignore list (`.repos`, `apps/cli-go`, fixtures, generated output), Effect-idiom rule exclusions (`no-misused-spread`, `unbound-method` — both false-positive on `Data.Class` copy-with-update and `.pipe(sem.withPermit)` patterns), and a legacy-scoped `no-base-to-string` override. - Root `.oxfmtrc.json`: ignore list only (style defaults unchanged); replaces `apps/cli-e2e/.prettierignore`, which only worked when oxfmt ran from that cwd. - `lint:*`/`fmt:*` are now plain command targets on `@supabase/root` (declared in the root package.json `nx` field); `oxlint.plugin.ts` and `oxfmt.plugin.ts` are deleted. Root `pnpm check:all`/`fix:all` and CI entrypoints are unchanged; per-package `check:all`/`fix:all` add `@supabase/root` to their `--projects` list. - The oxc devDependencies, knip ignores for them, and the `oxlint.typeAware` package.json channel are removed from every package — the tools are root devDependencies only. - Lint/format now also cover previously unchecked surface: `tools/`, `apps/docs`, root scripts, docs markdown, and workflow YAML. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Julien Goux <hi@jgoux.dev> | 1 个月前 | |
chore(repo): move oxlint and oxfmt to native root configs (#6311) ## What Replaces the per-package nx-inferred lint/format targets with tool-native configuration at the repo root, so oxlint and oxfmt work standalone (`pnpm exec oxlint`, `pnpm exec oxfmt`) and the oxc VSCode extension sees the exact rules CI enforces. Three commits, meant to be reviewed separately: 1. **`chore: fix type-aware oxlint findings across the monorepo`** — the ~30 findings that block enabling type-aware linting repo-wide (`no-floating-promises`, `no-base-to-string`, redundant union constituents, `= undefined` optional params, …), plus two TS7 modernizations tsgolint forced in `tools/nx-plugins` (drop `node10` module resolution; replace the `Function('return import(...)')` transpile workaround with a plain `import()`, obsolete under Node 24 type stripping). 2. **`chore: format the full repo surface with oxfmt`** — mechanical one-time pass over surface the per-package targets never covered: docs markdown, workflow YAML, root/wrapper manifests, `tools/`, `apps/docs`. Generated docs outputs are excluded so they stay byte-identical with their generator. 3. **`chore: move oxlint and oxfmt to native root configs`** — the actual restructuring (details below). ## Why - Tool configuration lived in nx plugin code (`--deny-warnings`, `--type-aware` via a package.json side-channel) where only nx could see it; the oxlint VSCode extension had no config to discover at all. - Type-aware linting was scoped to `packages/api` only. Repo-wide it costs ~3.5s and immediately caught real issues (e.g. floating promises) — it is now on everywhere via `options.typeAware`/`options.denyWarnings` in `.oxlintrc.json`. - Spawning 7 per-package processes through nx for millisecond-fast Rust tools cost more than one whole-repo pass (~1s fmt, ~3.5s type-aware lint). ## Key changes - Root `.oxlintrc.json`: type-aware + deny-warnings, ignore list (`.repos`, `apps/cli-go`, fixtures, generated output), Effect-idiom rule exclusions (`no-misused-spread`, `unbound-method` — both false-positive on `Data.Class` copy-with-update and `.pipe(sem.withPermit)` patterns), and a legacy-scoped `no-base-to-string` override. - Root `.oxfmtrc.json`: ignore list only (style defaults unchanged); replaces `apps/cli-e2e/.prettierignore`, which only worked when oxfmt ran from that cwd. - `lint:*`/`fmt:*` are now plain command targets on `@supabase/root` (declared in the root package.json `nx` field); `oxlint.plugin.ts` and `oxfmt.plugin.ts` are deleted. Root `pnpm check:all`/`fix:all` and CI entrypoints are unchanged; per-package `check:all`/`fix:all` add `@supabase/root` to their `--projects` list. - The oxc devDependencies, knip ignores for them, and the `oxlint.typeAware` package.json channel are removed from every package — the tools are root devDependencies only. - Lint/format now also cover previously unchecked surface: `tools/`, `apps/docs`, root scripts, docs markdown, and workflow YAML. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Julien Goux <hi@jgoux.dev> | 1 个月前 | |
chore(repo): move oxlint and oxfmt to native root configs (#6311) ## What Replaces the per-package nx-inferred lint/format targets with tool-native configuration at the repo root, so oxlint and oxfmt work standalone (`pnpm exec oxlint`, `pnpm exec oxfmt`) and the oxc VSCode extension sees the exact rules CI enforces. Three commits, meant to be reviewed separately: 1. **`chore: fix type-aware oxlint findings across the monorepo`** — the ~30 findings that block enabling type-aware linting repo-wide (`no-floating-promises`, `no-base-to-string`, redundant union constituents, `= undefined` optional params, …), plus two TS7 modernizations tsgolint forced in `tools/nx-plugins` (drop `node10` module resolution; replace the `Function('return import(...)')` transpile workaround with a plain `import()`, obsolete under Node 24 type stripping). 2. **`chore: format the full repo surface with oxfmt`** — mechanical one-time pass over surface the per-package targets never covered: docs markdown, workflow YAML, root/wrapper manifests, `tools/`, `apps/docs`. Generated docs outputs are excluded so they stay byte-identical with their generator. 3. **`chore: move oxlint and oxfmt to native root configs`** — the actual restructuring (details below). ## Why - Tool configuration lived in nx plugin code (`--deny-warnings`, `--type-aware` via a package.json side-channel) where only nx could see it; the oxlint VSCode extension had no config to discover at all. - Type-aware linting was scoped to `packages/api` only. Repo-wide it costs ~3.5s and immediately caught real issues (e.g. floating promises) — it is now on everywhere via `options.typeAware`/`options.denyWarnings` in `.oxlintrc.json`. - Spawning 7 per-package processes through nx for millisecond-fast Rust tools cost more than one whole-repo pass (~1s fmt, ~3.5s type-aware lint). ## Key changes - Root `.oxlintrc.json`: type-aware + deny-warnings, ignore list (`.repos`, `apps/cli-go`, fixtures, generated output), Effect-idiom rule exclusions (`no-misused-spread`, `unbound-method` — both false-positive on `Data.Class` copy-with-update and `.pipe(sem.withPermit)` patterns), and a legacy-scoped `no-base-to-string` override. - Root `.oxfmtrc.json`: ignore list only (style defaults unchanged); replaces `apps/cli-e2e/.prettierignore`, which only worked when oxfmt ran from that cwd. - `lint:*`/`fmt:*` are now plain command targets on `@supabase/root` (declared in the root package.json `nx` field); `oxlint.plugin.ts` and `oxfmt.plugin.ts` are deleted. Root `pnpm check:all`/`fix:all` and CI entrypoints are unchanged; per-package `check:all`/`fix:all` add `@supabase/root` to their `--projects` list. - The oxc devDependencies, knip ignores for them, and the `oxlint.typeAware` package.json channel are removed from every package — the tools are root devDependencies only. - Lint/format now also cover previously unchecked surface: `tools/`, `apps/docs`, root scripts, docs markdown, and workflow YAML. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Julien Goux <hi@jgoux.dev> | 1 个月前 | |
chore(repo): move oxlint and oxfmt to native root configs (#6311) ## What Replaces the per-package nx-inferred lint/format targets with tool-native configuration at the repo root, so oxlint and oxfmt work standalone (`pnpm exec oxlint`, `pnpm exec oxfmt`) and the oxc VSCode extension sees the exact rules CI enforces. Three commits, meant to be reviewed separately: 1. **`chore: fix type-aware oxlint findings across the monorepo`** — the ~30 findings that block enabling type-aware linting repo-wide (`no-floating-promises`, `no-base-to-string`, redundant union constituents, `= undefined` optional params, …), plus two TS7 modernizations tsgolint forced in `tools/nx-plugins` (drop `node10` module resolution; replace the `Function('return import(...)')` transpile workaround with a plain `import()`, obsolete under Node 24 type stripping). 2. **`chore: format the full repo surface with oxfmt`** — mechanical one-time pass over surface the per-package targets never covered: docs markdown, workflow YAML, root/wrapper manifests, `tools/`, `apps/docs`. Generated docs outputs are excluded so they stay byte-identical with their generator. 3. **`chore: move oxlint and oxfmt to native root configs`** — the actual restructuring (details below). ## Why - Tool configuration lived in nx plugin code (`--deny-warnings`, `--type-aware` via a package.json side-channel) where only nx could see it; the oxlint VSCode extension had no config to discover at all. - Type-aware linting was scoped to `packages/api` only. Repo-wide it costs ~3.5s and immediately caught real issues (e.g. floating promises) — it is now on everywhere via `options.typeAware`/`options.denyWarnings` in `.oxlintrc.json`. - Spawning 7 per-package processes through nx for millisecond-fast Rust tools cost more than one whole-repo pass (~1s fmt, ~3.5s type-aware lint). ## Key changes - Root `.oxlintrc.json`: type-aware + deny-warnings, ignore list (`.repos`, `apps/cli-go`, fixtures, generated output), Effect-idiom rule exclusions (`no-misused-spread`, `unbound-method` — both false-positive on `Data.Class` copy-with-update and `.pipe(sem.withPermit)` patterns), and a legacy-scoped `no-base-to-string` override. - Root `.oxfmtrc.json`: ignore list only (style defaults unchanged); replaces `apps/cli-e2e/.prettierignore`, which only worked when oxfmt ran from that cwd. - `lint:*`/`fmt:*` are now plain command targets on `@supabase/root` (declared in the root package.json `nx` field); `oxlint.plugin.ts` and `oxfmt.plugin.ts` are deleted. Root `pnpm check:all`/`fix:all` and CI entrypoints are unchanged; per-package `check:all`/`fix:all` add `@supabase/root` to their `--projects` list. - The oxc devDependencies, knip ignores for them, and the `oxlint.typeAware` package.json channel are removed from every package — the tools are root devDependencies only. - Lint/format now also cover previously unchecked surface: `tools/`, `apps/docs`, root scripts, docs markdown, and workflow YAML. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Julien Goux <hi@jgoux.dev> | 1 个月前 | |
chore(repo): move oxlint and oxfmt to native root configs (#6311) ## What Replaces the per-package nx-inferred lint/format targets with tool-native configuration at the repo root, so oxlint and oxfmt work standalone (`pnpm exec oxlint`, `pnpm exec oxfmt`) and the oxc VSCode extension sees the exact rules CI enforces. Three commits, meant to be reviewed separately: 1. **`chore: fix type-aware oxlint findings across the monorepo`** — the ~30 findings that block enabling type-aware linting repo-wide (`no-floating-promises`, `no-base-to-string`, redundant union constituents, `= undefined` optional params, …), plus two TS7 modernizations tsgolint forced in `tools/nx-plugins` (drop `node10` module resolution; replace the `Function('return import(...)')` transpile workaround with a plain `import()`, obsolete under Node 24 type stripping). 2. **`chore: format the full repo surface with oxfmt`** — mechanical one-time pass over surface the per-package targets never covered: docs markdown, workflow YAML, root/wrapper manifests, `tools/`, `apps/docs`. Generated docs outputs are excluded so they stay byte-identical with their generator. 3. **`chore: move oxlint and oxfmt to native root configs`** — the actual restructuring (details below). ## Why - Tool configuration lived in nx plugin code (`--deny-warnings`, `--type-aware` via a package.json side-channel) where only nx could see it; the oxlint VSCode extension had no config to discover at all. - Type-aware linting was scoped to `packages/api` only. Repo-wide it costs ~3.5s and immediately caught real issues (e.g. floating promises) — it is now on everywhere via `options.typeAware`/`options.denyWarnings` in `.oxlintrc.json`. - Spawning 7 per-package processes through nx for millisecond-fast Rust tools cost more than one whole-repo pass (~1s fmt, ~3.5s type-aware lint). ## Key changes - Root `.oxlintrc.json`: type-aware + deny-warnings, ignore list (`.repos`, `apps/cli-go`, fixtures, generated output), Effect-idiom rule exclusions (`no-misused-spread`, `unbound-method` — both false-positive on `Data.Class` copy-with-update and `.pipe(sem.withPermit)` patterns), and a legacy-scoped `no-base-to-string` override. - Root `.oxfmtrc.json`: ignore list only (style defaults unchanged); replaces `apps/cli-e2e/.prettierignore`, which only worked when oxfmt ran from that cwd. - `lint:*`/`fmt:*` are now plain command targets on `@supabase/root` (declared in the root package.json `nx` field); `oxlint.plugin.ts` and `oxfmt.plugin.ts` are deleted. Root `pnpm check:all`/`fix:all` and CI entrypoints are unchanged; per-package `check:all`/`fix:all` add `@supabase/root` to their `--projects` list. - The oxc devDependencies, knip ignores for them, and the `oxlint.typeAware` package.json channel are removed from every package — the tools are root devDependencies only. - Lint/format now also cover previously unchecked surface: `tools/`, `apps/docs`, root scripts, docs markdown, and workflow YAML. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Julien Goux <hi@jgoux.dev> | 1 个月前 | |
chore(repo): move oxlint and oxfmt to native root configs (#6311) ## What Replaces the per-package nx-inferred lint/format targets with tool-native configuration at the repo root, so oxlint and oxfmt work standalone (`pnpm exec oxlint`, `pnpm exec oxfmt`) and the oxc VSCode extension sees the exact rules CI enforces. Three commits, meant to be reviewed separately: 1. **`chore: fix type-aware oxlint findings across the monorepo`** — the ~30 findings that block enabling type-aware linting repo-wide (`no-floating-promises`, `no-base-to-string`, redundant union constituents, `= undefined` optional params, …), plus two TS7 modernizations tsgolint forced in `tools/nx-plugins` (drop `node10` module resolution; replace the `Function('return import(...)')` transpile workaround with a plain `import()`, obsolete under Node 24 type stripping). 2. **`chore: format the full repo surface with oxfmt`** — mechanical one-time pass over surface the per-package targets never covered: docs markdown, workflow YAML, root/wrapper manifests, `tools/`, `apps/docs`. Generated docs outputs are excluded so they stay byte-identical with their generator. 3. **`chore: move oxlint and oxfmt to native root configs`** — the actual restructuring (details below). ## Why - Tool configuration lived in nx plugin code (`--deny-warnings`, `--type-aware` via a package.json side-channel) where only nx could see it; the oxlint VSCode extension had no config to discover at all. - Type-aware linting was scoped to `packages/api` only. Repo-wide it costs ~3.5s and immediately caught real issues (e.g. floating promises) — it is now on everywhere via `options.typeAware`/`options.denyWarnings` in `.oxlintrc.json`. - Spawning 7 per-package processes through nx for millisecond-fast Rust tools cost more than one whole-repo pass (~1s fmt, ~3.5s type-aware lint). ## Key changes - Root `.oxlintrc.json`: type-aware + deny-warnings, ignore list (`.repos`, `apps/cli-go`, fixtures, generated output), Effect-idiom rule exclusions (`no-misused-spread`, `unbound-method` — both false-positive on `Data.Class` copy-with-update and `.pipe(sem.withPermit)` patterns), and a legacy-scoped `no-base-to-string` override. - Root `.oxfmtrc.json`: ignore list only (style defaults unchanged); replaces `apps/cli-e2e/.prettierignore`, which only worked when oxfmt ran from that cwd. - `lint:*`/`fmt:*` are now plain command targets on `@supabase/root` (declared in the root package.json `nx` field); `oxlint.plugin.ts` and `oxfmt.plugin.ts` are deleted. Root `pnpm check:all`/`fix:all` and CI entrypoints are unchanged; per-package `check:all`/`fix:all` add `@supabase/root` to their `--projects` list. - The oxc devDependencies, knip ignores for them, and the `oxlint.typeAware` package.json channel are removed from every package — the tools are root devDependencies only. - Lint/format now also cover previously unchecked surface: `tools/`, `apps/docs`, root scripts, docs markdown, and workflow YAML. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Julien Goux <hi@jgoux.dev> | 1 个月前 | |
refactor(config): rename config document type ProjectConfig to CliConfig (CLI-2235) (#6328) ## Summary Repo-wide vocabulary rename settling `@supabase/config`'s core naming before first npm publish ([CLI-2235](https://linear.app/supabase/issue/CLI-2235/rename-config-document-type-projectconfig-cliconfig-free-projectconfig)). One prefix rule across the package: **`Cli*` = the local checkout side; bare `Project*` = the hosted Supabase project.** After this PR no exported name uses a bare "project" to mean the local checkout, and `ProjectConfig` is freed for the hosted-project subset type the API-mapping work (CLI-2230 / CLI-2156) will introduce. Renames are only free while the package is `private: true`, which is why this blocks CLI-2169 (first publish). No functional behavior changes: the config-file surface (`project_id`, section names), the generated `schema.json`, and the schema URL value are unchanged. **Four renamed error tags are user-visible and change deliberately with this release**: the stderr and `--output-format json` error code changes (`ProjectConfigParseError`→`CliConfigParseError`, `ProjectEnvParseError`→`CliProjectEnvParseError`, `MissingProjectConfigValueError`→`MissingCliConfigValueError` on malformed-config/env failures, and `ProjectHomeNotDirectoryError`→`CliProjectHomeNotDirectoryError` when `.supabase/` is blocked by a file or symlink loop), and the telemetry `error_fingerprint` series fork accordingly (`tag:<old>` goes flat, `tag:<new>` starts) — annotate dashboards/alerts keyed on the old values at release. A `normalize-error` unit test now pins the user-facing codes so the next such rename is a visible review event. Service tag strings were renamed with their services (app-internal identity keys) and moved out of the package's namespace: `supabase/config/*` → `supabase/cli/*` for all apps/cli-owned services. ADRs 0009/0018/0019 keep their pre-rename symbol names as historical records — the tables below are the mapping. ### Step 0 — free the `CliConfig` name (apps/cli) | Old | New | | --- | --- | | `CliConfig` (service, tag `supabase/config/CliConfig`) | `CliSettings` (tag `supabase/cli/CliSettings`) | | `LegacyCliConfig` (tag `supabase/legacy/CliConfig`) | `LegacyCliSettings` (tag `supabase/legacy/CliSettings`) | | `cliConfigLayer`, `mockCliConfig`, `cliConfig` vars, … | `cliSettingsLayer`, `mockCliSettings`, `cliSettings`, … | | `cli-config.service.ts` / `cli-config.layer.ts` / `legacy-cli-config.*` | `cli-settings.*` / `legacy-cli-settings.*` | ### Config-document family (`ProjectConfig*` → `CliConfig*`) | Old | New | | --- | --- | | `ProjectConfig`, `ProjectConfigSchema`, `ProjectConfigJson`, `toProjectConfigJsonSchema` | `CliConfig`, `CliConfigSchema`, `CliConfigJson`, `toCliConfigJsonSchema` | | `loadProjectConfig`, `loadProjectConfigFile`, `saveProjectConfig`, `LoadedProjectConfig`, `Load/SaveProjectConfigOptions` | `loadCliConfig`, `loadCliConfigFile`, `saveCliConfig`, `LoadedCliConfig`, `Load/SaveCliConfigOptions` | | `encodeProjectConfigToJson/Toml`, `projectConfigValueSourceAt`, `ProjectConfigValueOrigin/Source` | `encodeCliConfigToJson/Toml`, `cliConfigValueSourceAt`, `CliConfigValueOrigin/Source` | | `ProjectConfigParseError`, `MissingProjectConfigValueError` | `CliConfigParseError`, `MissingCliConfigValueError` | | `ProjectConfigStore`, `projectConfigStoreLayer` (+ file renames) | `CliConfigStore`, `cliConfigStoreLayer` (`cli-config.service.ts` / `cli-config.layer.ts`) | | `PROJECT_CONFIG_SCHEMA_URL` (value unchanged) | `CLI_CONFIG_SCHEMA_URL` | | `getDefaultProjectConfig`, `subtractProjectConfig` | `getDefaultCliConfig`, `subtractCliConfig` | | compounds re-grepped post-CLI-2231: `writeProjectConfig`, `decodeProjectConfig`, `ProjectConfigIo`, `loadFunctionsProjectConfig`, `renderProjectConfigTemplate`, `BaseProjectConfig`, `SparseProjectConfig`, … | same names with `CliConfig` substituted | ### Local-checkout family (`Project*` → `CliProject*`) | Old | New | | --- | --- | | `ProjectEnvironment`, `loadProjectEnvironment(For)`, `LoadProjectEnvironmentOptions`, `LiveProjectEnvironment` | `CliProjectEnvironment`, `loadCliProjectEnvironment(For)`, … | | `ProjectPaths`, `findProjectPaths(For)`, `FindProjectPathsOptions`, `findProjectRoot(For/FromRepoState)` | `CliProjectPaths`, `findCliProjectPaths(For)`, …, `findCliProjectRoot(…)` | | `ProjectEnvParseError` | `CliProjectEnvParseError` | | `resolveProjectValue(AtPath)`, `resolveProjectSubtree`, `ResolvedProjectValue`, `ResolveProjectOptions` — config family: they resolve `env()` refs in config values | `resolveCliConfigValue(AtPath)`, `resolveCliConfigSubtree`, `ResolvedCliConfigValue`, `ResolveCliConfigOptions` | | `ProjectContext` (apps/cli, tag `supabase/config/ProjectContext`) + `projectContextLayer(For)`, `mockProjectContext`, … + file renames | `CliProjectContext` (tag `supabase/cli/CliProjectContext`) + `cliProjectContextLayer(For)`, `mockCliProjectContext`, … (`cli-project-context.*`) | | `ProjectHome` (apps/cli) + `projectHomeLayer(For)`, `ProjectHomeNotDirectoryError`, … + file renames | `CliProjectHome` (tag `supabase/cli/CliProjectHome`) + `cliProjectHomeLayer(For)`, … (`cli-project-home.*`) | | `ProjectLocalServiceVersions` (apps/cli) + layer/mocks + file renames | `CliProjectLocalServiceVersions` (tag `supabase/cli/CliProjectLocalServiceVersions`) (`cli-project-local-service-versions.*`) | | `projectCommandBaseLayer`, `provideProjectCommandRuntime` (apps/cli) | `cliProjectCommandBaseLayer`, `provideCliProjectCommandRuntime` | | `LoadCliConfigOptions.projectEnv` (published options field) + the `resolveCliConfig*` resolvers' `projectEnv` param | `cliProjectEnv` (apps/cli-internal `CliProjectContext.projectEnv` deliberately keeps its name — filed on CLI-2234) | ### Deliberately unchanged - `DuplicateRemoteProjectIdError` / `InvalidRemoteProjectIdError` — "project id" there genuinely means the hosted project ref (the *new* meaning of "project"). (`remoteProjectConfig`, initially kept under the same carve-out, was renamed to `remoteCliConfigBlock` per review — it binds the reserved `ProjectConfig` spelling for what is a `CliConfig` sub-document.) - `packages/api` generated names (`v2GetProjectConfig`, …) — hosted-project endpoint operations. - Legacy-internal `Legacy*` / `legacy*ProjectEnv*` / `LegacyLocalProjectContext` symbols — already namespaced by their `Legacy` prefix and pending deletion with the legacy shell. - Go-source provenance comments (`getProjectRoot`, `internal/utils/misc.go`) and `apps/cli-go/**`. ### Docs `packages/config/docs/project-config-loading.md` — the doc that defined the old vocabulary — is rewritten as `cli-config-loading.md` around the final naming, with stale behavioral claims corrected against current source along the way: - missing `env(NAME)` resolution does not fail with `MissingProjectEnvVarError` (that class doesn't exist); the resolvers leave unresolved literals as plain strings - `loadCliConfig` substitutes most `env(NAME)` refs eagerly at load; only `functions.<name>.env.*` stays literal for on-demand resolution - `supabase init` writes a commented `config.toml` template, not a `$schema`-only `config.json` - managed-stack state lives under global `SUPABASE_HOME`, not repo-local `.supabase/` ## Review round 1 (2026-08-25) Addressed from the multi-pass review: - **Truthful user-visible statement** (above) + `normalize-error` unit test pinning the error codes. - **Finished the vocabulary in `apps/cli/src/next/config/`**: `CliProjectHome`, `CliProjectLocalServiceVersions`, `cliProjectCommandBaseLayer`/`provideCliProjectCommandRuntime`. Remaining bare `Project*` there (`ProjectLinkRemote`, `ProjectLinkState`, `ProjectRefResolver`) describe the hosted project or the link to it — now stated in the vocabulary doc. - **Retagged all apps/cli services out of the package's namespace**: `supabase/config/*` → `supabase/cli/*` (each tag literal verified single-occurrence). - **`remoteProjectConfig` → `remoteCliConfigBlock`**; `LoadCliConfigOptions.projectEnv` → `cliProjectEnv` (+ resolver params). - **ADR 0009/0018**: dated rename addenda (old→new tables + pointer to the vocabulary doc); ADR bodies untouched. - **Docs**: README vocabulary blockquote; vocabulary doc gains the `/io` facade section, `LegacyCliSettings` bullet, `project_id` dual-meaning note, corrected `SUPABASE_HOME` keying + `identity.json` inventory; CLI-2110 plan doc symbols updated; `packages/stack/README.md` `cliSettings.supabaseHome`; stale comment fixes (`paths.ts`, `project.unit.test.ts`, `push.handler.ts`, `run.ts` defect strings). Filed as explicit pre-publish decisions rather than rushed here: `/io` facade naming convention → [CLI-2234](https://linear.app/supabase/issue/CLI-2234); `BaseCliConfig` as the hosted-diff operand type → [CLI-2230](https://linear.app/supabase/issue/CLI-2230). ## Linked issue [CLI-2235](https://linear.app/supabase/issue/CLI-2235/rename-config-document-type-projectconfig-cliconfig-free-projectconfig) - [x] I'm a Supabase maintainer working from a Linear ticket. - [x] The PR title follows [Conventional Commits](https://www.conventionalcommits.org/). | 1 个月前 | |
| 7 天前 | ||
fix(cli): reconcile hybrid stitch+stamp identity with shared LegacyIdentityStitch service (#5607) ## Problem The #5366 gate stopped the ephemeral-env `$identify` spike, but at the cost of attribution: in CI, Docker, and `npx supabase`, `cli_*` events stay orphaned on throwaway device IDs and never link to the authenticated user. GROWTH-891 (#5559) fixes that with a hybrid stitch+stamp model. While #5559 was in review, #5579 (db lint/advisors port) landed on develop and independently extracted the legacy identity stitch into a shared `LegacyIdentityStitch` service — one per-command `stitchAttempted` guard so the advisor transports alias at most once. That's the architecture we want, but it's a port of the pre-891 behavior: it only stamps when it aliases (persistent, first login), so it doesn't restore CI/Docker/npx attribution; it sets `stitchAttempted` after the file-read yield; and it reads the `runtime.distinctId` field that 891 replaced with a mutable identity slot. Merging #5559 on top as-is would silently drop the attribution feature and reintroduce the race. This PR reconciles the two: keep #5579's shared-service architecture, fold the hybrid stitch+stamp behavior into it. Supersedes #5559. ## Changes - **The shared `LegacyIdentityStitch` now stamps everywhere.** On the first authenticated response the user UUID is stamped into `runtime.identity` in every runtime, so captures in CI/Docker/npx carry the real user. The `$create_alias` (pre-login history merge) and the `telemetry.json` write still only happen on a persistent machine. - **Hardening preserved:** `stitchAttempted` is set before the first yield (no double-stitch race); when an identity already exists we stamp without aliasing (never merge two person graphs); alias fires at most once across all transports sharing the service. - **`stitchedDistinctId()` returns `runtime.identity.current()`** so the post-run `cli_command_executed` is attributed to the real user in every runtime, including steady state. - `legacy-analytics.layer.ts` resolves `distinctId` from the identity slot while keeping develop's already-keyed `groups` map. - Stitch behavior tests live in `legacy-identity-stitch.integration.test.ts` (CI-stamp-no-alias, stale-identity-stamp-no-alias, concurrent-alias-once); the platform-api layer test keeps develop's service-mocked wiring. A few command test runtimes still using the removed `distinctId` field were updated to `makeTelemetryIdentity`. - Brings the Go + next-TS 891 changes (logout identity reset + device-id rotation, the redundant `$identify` removal, ADR 0013) along through the merge. ## Testing Typecheck clean, full unit suite (1318) green, and the affected integration suites (identity-stitch, platform-api, login, logout, advisors, lint, services, gen/types, issue, linked-project-cache) pass under bun. Also ran an independent Codex review focused on the spike-regression risk — it confirmed no alias in ephemeral runtimes, alias-at-most-once across transports, the pre-yield race guard, and the no-cross-graph-merge invariant, with no findings. GROWTH-891 --------- Co-authored-by: Julien Goux <hi@jgoux.dev> | 3 个月前 | |
test(cli): collocate live e2e coverage (#6294) ## Summary Collocate remote-platform golden-path tests beside the commands they cover in apps/cli, while keeping Docker-stack-only scenarios as ordinary e2e tests and leaving the apps/cli-e2e replay and recording suite unchanged. Use one serial live Vitest project with a single extended test fixture. Global setup requires a Management API URL and access token, provisions one disposable project through the typed Effect API client, waits with bounded retry semantics, derives the platform tenant host from project metadata, and shares cli plus project fixtures with every test. Explicit live runs fail fast when configuration is missing; there are no capability gates or runtime skips. Keep live coverage intentionally narrow: one representative golden path per covered command. Setup and teardown may invoke other commands, but assertions stay focused on the command under test. Exact owned resources are cleaned even after ambiguous command results, and target plus cleanup failures are preserved. Move the eight local Docker-stack suites to e2e naming, including functions dev synchronization on observable reload completion. The standalone live workflow retains Docker preflight, serial execution, one attempt, a 20-minute limit, and scoped project sweeping. | 1 个月前 | |
chore(repo): move oxlint and oxfmt to native root configs (#6311) ## What Replaces the per-package nx-inferred lint/format targets with tool-native configuration at the repo root, so oxlint and oxfmt work standalone (`pnpm exec oxlint`, `pnpm exec oxfmt`) and the oxc VSCode extension sees the exact rules CI enforces. Three commits, meant to be reviewed separately: 1. **`chore: fix type-aware oxlint findings across the monorepo`** — the ~30 findings that block enabling type-aware linting repo-wide (`no-floating-promises`, `no-base-to-string`, redundant union constituents, `= undefined` optional params, …), plus two TS7 modernizations tsgolint forced in `tools/nx-plugins` (drop `node10` module resolution; replace the `Function('return import(...)')` transpile workaround with a plain `import()`, obsolete under Node 24 type stripping). 2. **`chore: format the full repo surface with oxfmt`** — mechanical one-time pass over surface the per-package targets never covered: docs markdown, workflow YAML, root/wrapper manifests, `tools/`, `apps/docs`. Generated docs outputs are excluded so they stay byte-identical with their generator. 3. **`chore: move oxlint and oxfmt to native root configs`** — the actual restructuring (details below). ## Why - Tool configuration lived in nx plugin code (`--deny-warnings`, `--type-aware` via a package.json side-channel) where only nx could see it; the oxlint VSCode extension had no config to discover at all. - Type-aware linting was scoped to `packages/api` only. Repo-wide it costs ~3.5s and immediately caught real issues (e.g. floating promises) — it is now on everywhere via `options.typeAware`/`options.denyWarnings` in `.oxlintrc.json`. - Spawning 7 per-package processes through nx for millisecond-fast Rust tools cost more than one whole-repo pass (~1s fmt, ~3.5s type-aware lint). ## Key changes - Root `.oxlintrc.json`: type-aware + deny-warnings, ignore list (`.repos`, `apps/cli-go`, fixtures, generated output), Effect-idiom rule exclusions (`no-misused-spread`, `unbound-method` — both false-positive on `Data.Class` copy-with-update and `.pipe(sem.withPermit)` patterns), and a legacy-scoped `no-base-to-string` override. - Root `.oxfmtrc.json`: ignore list only (style defaults unchanged); replaces `apps/cli-e2e/.prettierignore`, which only worked when oxfmt ran from that cwd. - `lint:*`/`fmt:*` are now plain command targets on `@supabase/root` (declared in the root package.json `nx` field); `oxlint.plugin.ts` and `oxfmt.plugin.ts` are deleted. Root `pnpm check:all`/`fix:all` and CI entrypoints are unchanged; per-package `check:all`/`fix:all` add `@supabase/root` to their `--projects` list. - The oxc devDependencies, knip ignores for them, and the `oxlint.typeAware` package.json channel are removed from every package — the tools are root devDependencies only. - Lint/format now also cover previously unchecked surface: `tools/`, `apps/docs`, root scripts, docs markdown, and workflow YAML. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Julien Goux <hi@jgoux.dev> | 1 个月前 | |
refactor(stack): simplify managed stack architecture (#6218) ## Summary Replace the managed local-stack coordination design with a smaller architecture centered on one atomic JSON document per stack, deterministic loopback ownership, and one detached supervisor. - derive stack identity from the workspace, checkout, development context, canonical local-project key, and stack name, so multiple Supabase projects inside one monorepo remain isolated - keep automatic ports sticky per stack while respecting configured exact ports and deterministic control endpoints - support detached start, later-process reattachment, stop, deletion, and recovery after interrupted lifecycle operations - keep Docker and native execution behind the same Effect-native `@supabase/stack` package - report running configuration drift without rebinding live services ## Why The previous implementation spread these guarantees across SQLite, repository adapters, claims and publication protocols, transition state machines, operation polling, and a large contract-fixture corpus. Those layers duplicated ownership and recovery responsibilities and made ordinary user journeys difficult to follow. The simplified model gives each invariant one owner: - the stack document owns durable identity, sticky ports, lifecycle, launch metadata, and runtime metadata - the deterministic loopback endpoint owns process exclusivity and reattachment - the manager owns identity resolution, port planning, persistence, and repair - the supervisor owns runtime resources from reservation through shutdown ## Identity and monorepos A stack key combines workspace ID, checkout ID, development-context ID, canonical local-project key, and stack name. The local-project key is the canonical project path relative to its Git worktree, or `.` for an ordinary non-Git folder. It is local identity only: it is not a remote project reference or a config `project_id`. This keeps sibling Supabase projects in one repository independent, keeps the same relative project isolated across worktrees, and preserves identity when a whole worktree moves. Renaming a project directory deliberately creates a new local identity. ## Lifecycle and ports Automatic allocations avoid the deterministic control namespace and remain sticky. Explicit exact ports remain usable, including inside that namespace, unless they collide with a known stack control endpoint or an actual listener. A future identity with no document can theoretically derive an endpoint already used as an exact service port; that low-probability collision fails at control acquisition rather than requiring a global registry or forbidding 22,768 legitimate configured ports. Exact durable reservations and automatic runtime reservations are combined behind one scoped lease while each child lease receives only the fields it owns. This keeps placeholders held until the corresponding service binds without leaking ownership assumptions across allocation strategies. Startup, attachment, early stop, stop-during-start, start-during-stop, owner death, concurrent shutdown, deletion, and stale lifecycle recovery share one bounded ownership protocol. Node and Bun route stop through the same daemon shutdown transaction. Store listing degrades corrupt or unreadable entries independently, and read-only project discovery treats unsupported Git metadata as no managed stacks while mutating operations still fail loudly. Moved checkouts can be repaired through the package API while preserving identity and ports; duplicate checkout adoption remains intentionally unsupported. ## Deliberate boundaries - the managed document format is unreleased, so this change does not add migrations or compatibility shims - loopback control-token authentication is deferred because the current localhost risk is low - a CLI repair command is deferred; the package API remains the source of truth - supervisor test runtimes live only in test helpers and are not accepted by production IPC - ordinary unit and integration suites use isolated temp state and may run concurrently; the one full-stack Functions hot-reload smoke stays in the opt-in live suite - coverage focuses on high-value manager, control, supervisor, storage, and real Git journeys ## Follow-ups - [CLI-2198](https://linear.app/supabase/issue/CLI-2198) — remaining real-Git and project-root contract coverage - [CLI-2199](https://linear.app/supabase/issue/CLI-2199) — control diagnostics and retry-policy cleanup - [CLI-2200](https://linear.app/supabase/issue/CLI-2200) — managed list UX and nested-project deletion coverage - [CLI-2201](https://linear.app/supabase/issue/CLI-2201) — later managed workspace repair workflow - [CLI-2202](https://linear.app/supabase/issue/CLI-2202) — remaining runtime failure-path qualification - [CLI-2203](https://linear.app/supabase/issue/CLI-2203) — copied ordinary-folder identity detection Supersedes #6202, #6214, and #6216 | 1 个月前 | |
chore(repo): move oxlint and oxfmt to native root configs (#6311) ## What Replaces the per-package nx-inferred lint/format targets with tool-native configuration at the repo root, so oxlint and oxfmt work standalone (`pnpm exec oxlint`, `pnpm exec oxfmt`) and the oxc VSCode extension sees the exact rules CI enforces. Three commits, meant to be reviewed separately: 1. **`chore: fix type-aware oxlint findings across the monorepo`** — the ~30 findings that block enabling type-aware linting repo-wide (`no-floating-promises`, `no-base-to-string`, redundant union constituents, `= undefined` optional params, …), plus two TS7 modernizations tsgolint forced in `tools/nx-plugins` (drop `node10` module resolution; replace the `Function('return import(...)')` transpile workaround with a plain `import()`, obsolete under Node 24 type stripping). 2. **`chore: format the full repo surface with oxfmt`** — mechanical one-time pass over surface the per-package targets never covered: docs markdown, workflow YAML, root/wrapper manifests, `tools/`, `apps/docs`. Generated docs outputs are excluded so they stay byte-identical with their generator. 3. **`chore: move oxlint and oxfmt to native root configs`** — the actual restructuring (details below). ## Why - Tool configuration lived in nx plugin code (`--deny-warnings`, `--type-aware` via a package.json side-channel) where only nx could see it; the oxlint VSCode extension had no config to discover at all. - Type-aware linting was scoped to `packages/api` only. Repo-wide it costs ~3.5s and immediately caught real issues (e.g. floating promises) — it is now on everywhere via `options.typeAware`/`options.denyWarnings` in `.oxlintrc.json`. - Spawning 7 per-package processes through nx for millisecond-fast Rust tools cost more than one whole-repo pass (~1s fmt, ~3.5s type-aware lint). ## Key changes - Root `.oxlintrc.json`: type-aware + deny-warnings, ignore list (`.repos`, `apps/cli-go`, fixtures, generated output), Effect-idiom rule exclusions (`no-misused-spread`, `unbound-method` — both false-positive on `Data.Class` copy-with-update and `.pipe(sem.withPermit)` patterns), and a legacy-scoped `no-base-to-string` override. - Root `.oxfmtrc.json`: ignore list only (style defaults unchanged); replaces `apps/cli-e2e/.prettierignore`, which only worked when oxfmt ran from that cwd. - `lint:*`/`fmt:*` are now plain command targets on `@supabase/root` (declared in the root package.json `nx` field); `oxlint.plugin.ts` and `oxfmt.plugin.ts` are deleted. Root `pnpm check:all`/`fix:all` and CI entrypoints are unchanged; per-package `check:all`/`fix:all` add `@supabase/root` to their `--projects` list. - The oxc devDependencies, knip ignores for them, and the `oxlint.typeAware` package.json channel are removed from every package — the tools are root devDependencies only. - Lint/format now also cover previously unchecked surface: `tools/`, `apps/docs`, root scripts, docs markdown, and workflow YAML. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Julien Goux <hi@jgoux.dev> | 1 个月前 | |
feat(stack): re-plan changed endpoint ports when a stopped stack starts (#6950) With `[experimental] stack` enabled, changing a port in `config.toml` (including removing the fixed ports a legacy `supabase init` writes) used to make the next `supabase start` fail with "The saved stack cannot adopt these changes". The only way out was to destroy the stack and lose its data. Now, when a stopped stack starts, it applies endpoint port changes and prints each one, for example `api: 54321 → 26199`. JSON and stream-json output add an `endpoint_changes` array to the success payload; each entry names the endpoint, the `endpoints` keys it moved (`keys`), and its `from`/`to` port. - `config.toml` is the source of truth. The re-plan runs inside the stack owner's startup, while the owner holds the stack lease and before it binds any endpoints. It saves the requested endpoint intents, releases the changed endpoints' rows in the port registry (for example an old sticky automatic port), and claims the new ports through the owner's normal port acquisition, with the usual conflict and occupancy checks. Concurrent starts attach to the winning owner instead of racing it. - Nothing is rolled back. A requested fixed port that is already taken fails the start with the usual port conflict, naming the config key and noting that the new port is saved and the previous automatic port released, so starting again succeeds once the port is free. - Changing which services share the API port is handled too: when a different sharing service requests a fixed API port, the shared listener's old automatic claim is released. - A start request is the complete composition, so excluded or absent services no longer pin the shared API port, and removing `[api] port` moves the stack to an automatic port instead of being silently ignored. A stopped service excluded during a re-plan has its endpoints rewritten when it is requested again, so the start reuses it and its data instead of creating a second instance. - Endpoints whose configuration didn't change keep their saved ports. A running stack still has to be stopped first, and so does an idle owner kept alive by `stack prepare`: its error now suggests `stack stop` then `stack start` instead of `stack destroy`. Artifact-version and PostgreSQL major-version changes are still rejected. - The stack definition sent on first registration and the requested configuration for a re-plan reach the spawned owner through a private (`0600`), short-lived file the owner deletes after reading, instead of its command line, so secrets don't appear in process listings. ADR 0017 is amended: saved ports stay stable unless the configuration deliberately changes them on a stopped stack. | 2 天前 | |
feat(config): add toProjectConfig and the ProjectConfig hosted subset (CLI-2230) (#6339) ## What changed Implements [CLI-2230](https://linear.app/supabase/issue/CLI-2230/create-a-toprojectconfig-function-that-is-exported-from-the-config): the hosted-project subset type `ProjectConfig` and its normalizers, exported from `@supabase/config`'s pure (browser-safe) entrypoint so the CLI and Studio share one mapper. - **`fromConfigDocument(config)`** — projection of a `CliConfig` document (or any `EffectiveConfig`) down to the hosted sections (`api`, `auth`, `db`, `realtime`, `storage`, `workers`, `experimental`). - **`fromApiProjectConfig(input)`** — translation of a Management API v2 project-config response (full envelope, `data` object, or bare `data.attributes`). Registry-driven: 233 mapping rows mined from the legacy `config push` sync mappers (`config-sync/*.sync.ts`), covering renames (`rate_limit_otp` → `sign_in_sign_ups`), boolean inversions (`disable_signup`, `mailer_autoconfirm`), unit conversions (seconds/hours → Go duration strings, int64 bytes → BytesSize), the GoTrue key table (19 OAuth providers, 6 hooks, 5 SMS providers), and `x-secret` omission (the API only reports HMAC digests). Decode is lenient per ADR 0019 — unknown/API-ahead keys never fail; the raw attributes ride along as a **non-enumerable `_apiResponse`** (invisible to encodes, spreads, and structural walks; never persisted), with a registry-derived `unmappedApiFields()` reader. - **`toProjectConfig(source)`** — thin dispatcher over both (`{ cliConfig }` / `{ apiResponse }`). ### Operand widening (ruling on CLI-2230) `ProjectConfig` is deliberately **sparse** — an API response never mentions sections it doesn't manage, and flooding in schema defaults would fabricate drift. To make it a first-class operand of the comparison core, `BaseCliConfig` is replaced by the family-neutral `EffectiveConfig = DeepPartial<Omit<CliConfig, "remotes">>` on `subtractCliConfig`/`omitDefaultValues`. No runtime change — the subtraction walk already had the right absence semantics. Recorded in ADR 0018's 2026-08-26 addendum; the naming-rule generalization lands separately with CLI-2238 (#6335). ### apps/cli - A type-drift guard (`project-config-api-drift.unit.test.ts`) pins the generated `V2GetProjectConfigOutput` attributes against the package's lenient input schema: assignability (catches type widening) plus per-section key-set assertions (catch added/removed/renamed fields) — so OpenAPI drift fails compile before it can silently break the mapping. No runtime dependency on `packages/api` was added. - `ProjectConfigParseError` registered in the error-actionability table as `apiStatus` (a malformed platform response, not a user config mistake). ### Reviewer notes - Deliberate divergences from the legacy apply semantics are documented inline where they occur: API `null` → omit (sparse output has no local document to fall back to), `uri_allow_list` trimming, `smtp_host: ""` treated as disabled, `sms_autoconfirm` **not** inverted (matches `auth.sync.ts:1677/:2485` — only the mailer counterpart inverts). - An explicit `db_schema: ""` maps to `{ api: { enabled: false } }` only, mirroring `applyRemoteApiConfig`'s early return; an *absent* `db_schema` doesn't gate the sibling fields. - Follow-up candidate deliberately not in this PR: a parity test pinning the replicated legacy helpers (duration/BytesSize/parseUint16/envToMap/password charsets) against their `apps/cli` originals. (ADR 0019's attach helper *did* land in the second round below, as `attachApiResponse`.) ### Second review round (commit 93b4679b7) — adjudication record A two-set review (architect/engineer/security/DX + adversarial execution) ran against bdd607f46. The registry mapping itself survived three independent verification passes with zero defects; every accepted finding about the surrounding surface is fixed in 93b4679b7: - **Secrets**: `fromConfigDocument` now deep-copies and omits every `x-secret` leaf (schema-derived, `lib/secret-paths.ts`) — decoded documents hold plaintext credentials, and the subtract composition rendered them as drift. - **Spelling convergence**: new registry `normalizeDocument` column canonicalizes document-side duration and byte-size spellings so both normalizers emit one form for one logical value. - **Leniency**: the mirror schema types every never-mapped field `Schema.Unknown`, so a platform type change on a field nothing reads can no longer fail every decode. - **Errors**: `ProjectConfigParseError` gained `message`/`detail`/`suggestion` (upgrade-then-report), schema-issue paths lift into `apiPath`, telemetry adds `fingerprint_suffix: "api_response"` + `has_suggestion`. - **Type-mismatch unification**: string rows, `smtp_host`/`smtp_port`, and CIDR entries now throw typed errors instead of fabricating values (`enabled: false`), vanishing silently, or partially filtering a security allowlist; `expectNumber` rejects non-finite. - **`_apiResponse`**: cloned + deep-frozen at attach (no caller aliasing); `attachApiResponse` export restores it across spread/clone round-trips; invisibility claim narrowed (serializers and walks — debug inspectors like Bun's `console.log` still print it; never log an API-sourced config). - **`unmappedApiFields`**: recursion depth cap; `unmappedSecretApiPaths` deny list (`external_figma_secret` — the one genuine orphan digest; the review's other two candidates don't exist in the legacy source). - **New guards/exports**: `registry-integrity.unit.test.ts` (all 233 rows' paths resolve against the schema ASTs, 470 generated cases); drift-guard key-set levels completed; `comparableProjectConfigPaths`/`isComparableProjectConfigPath` so diff consumers never hand-maintain field lists; `inverse` implementations dropped until the push mapper derives them. **Explicit rejections** (reviewed, not silently deferred): 1. *`alsoConsumes` static consumed-set*: "consumed" means known-to-this-registry-version, not mapped-on-this-run — an `alsoConsumes` sibling whose anchor didn't run stays suppressed by design and remains in `_apiResponse`. Documented in `walkUnmapped`. 2. *WeakMap sidecar for `_apiResponse`*: deferred — clone+freeze resolves the aliasing hazard and the narrowed docstring is honest; revisit if CLI-2156 consumption shows inspectors bite (ADR 0019 records the alternative). 3. *`fromCliConfig`/`fromApiResponse` renaming*: the shipped names are already recorded in ADR 0020 (#6335), Linear, and coordinated docs — symmetry isn't worth re-coordinating three artifacts. 4. *Branding `EffectiveConfig`*: took the ADR 0018 consequence note instead (widening removes the static every-section guarantee; callers own operand completeness). 5. *`ProjectConfigApiAttributes` placement* and *`"sideEffects": false`*: deferred to their owning issues with notes filed — CLI-2234 (export-surface audit) and CLI-2232 (verify the bundler claim against a built artifact). ### Verification round (commits 5f389c943 + 0424ac7ed) An executed verification pass over 93b4679b7 re-ran all prior attacks (10/10 now pass) and surfaced residuals, fixed in 0424ac7ed: - **Orphan digests were 3, not 1** — the generated `V1GetAuthServiceConfigOutput` contract (not the legacy interface) is the authority; `external_slack_secret`, `hook_after_user_created_secrets`, and `nimbus_oauth_client_secret` join the deny list, and a new apps/cli **contract-guard test** cross-checks every auth apiPath + secret-suffixed contract key against the registry (also closing the open-Record vacuity in the integrity test's auth checks). Registry rows are now exported from the package root to serve it. - **Clone/freeze regression fixed**: deep/cyclic/non-cloneable payloads now throw `ProjectConfigParseError` instead of raw `RangeError`/`DOMException` (depth-capped pre-walk, wrapped clone, cycle-guarded `deepFreeze`). - **README example rewritten and executed** (the previous snippet self-subtracted and filtered section names — always empty); secret-stripped projections prune emptied containers; a real phantom-drift pin replaces an agreeing-case test; `comparableProjectConfigPaths`' docstring narrowed to the section-level claim it delivers; ADR 0019 gained a dated addendum (attach helper, structural "verbatim", debug-inspector caveat). - **Documented-not-changed**: byte-size canonicalization quantizes at 4 significant digits symmetrically on both arms — sub-0.1% differences comparing equal is a deliberate property (user-authored spellings are exact). The merge commit resolves the pre-agreed README conflict with #6335 (their `## Naming` section wins, its "in flight" sentence flipped) and de-stales ADR 0020's phrasing, per the coordinated rebase checklist. Known external: root `check:all` fails on `@supabase/cli-go#lint:check` (5 gosec findings in Go files byte-identical to develop — develop-side/toolchain, not this PR). ### Drift-audit round (commit 492ee25c0) A 2026-08-27 audit compared the post-codex state against CLI-2230's design intent. All eight structural commitments verified intact (pure entrypoint, no `packages/api` dependency, sparse output + `EffectiveConfig`, ADR 0019 guardrails, registry-driven with `inverse` unimplemented, purity graph, naming, secret stripping). Three executed repros surfaced semantic drift introduced by the codex rounds; fixed here: - **Leniency**: `JSON.parse('{"x":1e400}')` yields `Infinity`, so the round-12 non-finite pre-decode rejection hard-failed a real JSON payload on an *unknown* field (mis-bucketed as `caller_misuse`). The walk now rejects only bigint/`undefined`/`NaN` (values `JSON.parse` cannot produce); non-finite values decode and surface as `null` from `unmappedApiFields` (scalars and inside array leaves, identity preserved for all-finite arrays). ADR 0019 gains a dated leniency-boundary addendum. - **SMTP absence semantics**: an absent `smtp_host` previously counted as the disabled sentinel, silently dropping `smtp_user`/`smtp_admin_email`/`rate_limit.email_sent` from both the output and `unmappedApiFields` — contradicting the SMS absent-vs-sentinel rule beside it. Absence now says nothing; only the explicit `""`/`null` legacy sentinel disables (three-state, extended to the `email_sent` cross-section rule). - **Convergence-predictor ruling (ADR 0021)**: the codex rounds had incrementally turned both normalizers into *post-push convergence predictors* (SMS provider precedence flips extra enabled providers, disabled-sentinel pruning, `null`-gated booleans, CSV/uint/duration canonicalization) — defensible (it kills phantom drift for CLI-2156) but never adjudicated or documented. Ruling: **accepted**, now recorded in ADR 0021, the `ProjectConfig`/`fromConfigDocument`/`fromApiProjectConfig` docstrings, and the README ("not a verbatim representation" caveat). - **Guards**: the three hand-written sentinel/precedence tables (`DISABLED_SENTINEL_PRUNES`, `DISABLED_SENTINEL_ENTRY_SWEEPS`, `SMS_PROVIDER_PUSH_PRECEDENCE`) now resolve every path/key against the schema ASTs in `registry-integrity` — previously a schema rename silently no-opped them — and the SMS precedence order is pinned against the legacy push switch (`auth.sync.ts:2498-2539`). - Residual cleanups: the stale `Math.trunc` comment above the fraction arithmetic (round-11/14 artifact the round-20 revert missed), `ReadonlyJsonValue` exported (it appears in public types), `smtpExplicitlyDisabledInAttributes` simplified to mirror `smsProviderExplicitlyUnset`, ADR index gains 0020/0021. An engineer-review pass over the fix diff verified every change; its findings (the `email_sent` asymmetry, stale ADR citations, formatter gate, doc phrasing) are folded in. 984/984 package tests, 461/461 apps/cli guard tests, typechecks clean both packages. Follow-ups filed so they survive CLI-2230 closing: [CLI-2266](https://linear.app/supabase/issue/CLI-2266/derive-the-config-push-mapper-from-the-projectconfig-mapping-registry) (registry-derived push mapper — carries the three requirements previously parked as CLI-2230 comments) and [CLI-2267](https://linear.app/supabase/issue/CLI-2267/pin-supabaseconfigs-replicated-legacy-parsers-with-parity-fixtures-in) (parity fixtures pinning the replicated legacy parsers against `config-sync/*`). Fixes CLI-2230 🤖 Generated with [Claude Code](https://claude.com/claude-code) | 1 个月前 | |
feat(config): add toProjectConfig and the ProjectConfig hosted subset (CLI-2230) (#6339) ## What changed Implements [CLI-2230](https://linear.app/supabase/issue/CLI-2230/create-a-toprojectconfig-function-that-is-exported-from-the-config): the hosted-project subset type `ProjectConfig` and its normalizers, exported from `@supabase/config`'s pure (browser-safe) entrypoint so the CLI and Studio share one mapper. - **`fromConfigDocument(config)`** — projection of a `CliConfig` document (or any `EffectiveConfig`) down to the hosted sections (`api`, `auth`, `db`, `realtime`, `storage`, `workers`, `experimental`). - **`fromApiProjectConfig(input)`** — translation of a Management API v2 project-config response (full envelope, `data` object, or bare `data.attributes`). Registry-driven: 233 mapping rows mined from the legacy `config push` sync mappers (`config-sync/*.sync.ts`), covering renames (`rate_limit_otp` → `sign_in_sign_ups`), boolean inversions (`disable_signup`, `mailer_autoconfirm`), unit conversions (seconds/hours → Go duration strings, int64 bytes → BytesSize), the GoTrue key table (19 OAuth providers, 6 hooks, 5 SMS providers), and `x-secret` omission (the API only reports HMAC digests). Decode is lenient per ADR 0019 — unknown/API-ahead keys never fail; the raw attributes ride along as a **non-enumerable `_apiResponse`** (invisible to encodes, spreads, and structural walks; never persisted), with a registry-derived `unmappedApiFields()` reader. - **`toProjectConfig(source)`** — thin dispatcher over both (`{ cliConfig }` / `{ apiResponse }`). ### Operand widening (ruling on CLI-2230) `ProjectConfig` is deliberately **sparse** — an API response never mentions sections it doesn't manage, and flooding in schema defaults would fabricate drift. To make it a first-class operand of the comparison core, `BaseCliConfig` is replaced by the family-neutral `EffectiveConfig = DeepPartial<Omit<CliConfig, "remotes">>` on `subtractCliConfig`/`omitDefaultValues`. No runtime change — the subtraction walk already had the right absence semantics. Recorded in ADR 0018's 2026-08-26 addendum; the naming-rule generalization lands separately with CLI-2238 (#6335). ### apps/cli - A type-drift guard (`project-config-api-drift.unit.test.ts`) pins the generated `V2GetProjectConfigOutput` attributes against the package's lenient input schema: assignability (catches type widening) plus per-section key-set assertions (catch added/removed/renamed fields) — so OpenAPI drift fails compile before it can silently break the mapping. No runtime dependency on `packages/api` was added. - `ProjectConfigParseError` registered in the error-actionability table as `apiStatus` (a malformed platform response, not a user config mistake). ### Reviewer notes - Deliberate divergences from the legacy apply semantics are documented inline where they occur: API `null` → omit (sparse output has no local document to fall back to), `uri_allow_list` trimming, `smtp_host: ""` treated as disabled, `sms_autoconfirm` **not** inverted (matches `auth.sync.ts:1677/:2485` — only the mailer counterpart inverts). - An explicit `db_schema: ""` maps to `{ api: { enabled: false } }` only, mirroring `applyRemoteApiConfig`'s early return; an *absent* `db_schema` doesn't gate the sibling fields. - Follow-up candidate deliberately not in this PR: a parity test pinning the replicated legacy helpers (duration/BytesSize/parseUint16/envToMap/password charsets) against their `apps/cli` originals. (ADR 0019's attach helper *did* land in the second round below, as `attachApiResponse`.) ### Second review round (commit 93b4679b7) — adjudication record A two-set review (architect/engineer/security/DX + adversarial execution) ran against bdd607f46. The registry mapping itself survived three independent verification passes with zero defects; every accepted finding about the surrounding surface is fixed in 93b4679b7: - **Secrets**: `fromConfigDocument` now deep-copies and omits every `x-secret` leaf (schema-derived, `lib/secret-paths.ts`) — decoded documents hold plaintext credentials, and the subtract composition rendered them as drift. - **Spelling convergence**: new registry `normalizeDocument` column canonicalizes document-side duration and byte-size spellings so both normalizers emit one form for one logical value. - **Leniency**: the mirror schema types every never-mapped field `Schema.Unknown`, so a platform type change on a field nothing reads can no longer fail every decode. - **Errors**: `ProjectConfigParseError` gained `message`/`detail`/`suggestion` (upgrade-then-report), schema-issue paths lift into `apiPath`, telemetry adds `fingerprint_suffix: "api_response"` + `has_suggestion`. - **Type-mismatch unification**: string rows, `smtp_host`/`smtp_port`, and CIDR entries now throw typed errors instead of fabricating values (`enabled: false`), vanishing silently, or partially filtering a security allowlist; `expectNumber` rejects non-finite. - **`_apiResponse`**: cloned + deep-frozen at attach (no caller aliasing); `attachApiResponse` export restores it across spread/clone round-trips; invisibility claim narrowed (serializers and walks — debug inspectors like Bun's `console.log` still print it; never log an API-sourced config). - **`unmappedApiFields`**: recursion depth cap; `unmappedSecretApiPaths` deny list (`external_figma_secret` — the one genuine orphan digest; the review's other two candidates don't exist in the legacy source). - **New guards/exports**: `registry-integrity.unit.test.ts` (all 233 rows' paths resolve against the schema ASTs, 470 generated cases); drift-guard key-set levels completed; `comparableProjectConfigPaths`/`isComparableProjectConfigPath` so diff consumers never hand-maintain field lists; `inverse` implementations dropped until the push mapper derives them. **Explicit rejections** (reviewed, not silently deferred): 1. *`alsoConsumes` static consumed-set*: "consumed" means known-to-this-registry-version, not mapped-on-this-run — an `alsoConsumes` sibling whose anchor didn't run stays suppressed by design and remains in `_apiResponse`. Documented in `walkUnmapped`. 2. *WeakMap sidecar for `_apiResponse`*: deferred — clone+freeze resolves the aliasing hazard and the narrowed docstring is honest; revisit if CLI-2156 consumption shows inspectors bite (ADR 0019 records the alternative). 3. *`fromCliConfig`/`fromApiResponse` renaming*: the shipped names are already recorded in ADR 0020 (#6335), Linear, and coordinated docs — symmetry isn't worth re-coordinating three artifacts. 4. *Branding `EffectiveConfig`*: took the ADR 0018 consequence note instead (widening removes the static every-section guarantee; callers own operand completeness). 5. *`ProjectConfigApiAttributes` placement* and *`"sideEffects": false`*: deferred to their owning issues with notes filed — CLI-2234 (export-surface audit) and CLI-2232 (verify the bundler claim against a built artifact). ### Verification round (commits 5f389c943 + 0424ac7ed) An executed verification pass over 93b4679b7 re-ran all prior attacks (10/10 now pass) and surfaced residuals, fixed in 0424ac7ed: - **Orphan digests were 3, not 1** — the generated `V1GetAuthServiceConfigOutput` contract (not the legacy interface) is the authority; `external_slack_secret`, `hook_after_user_created_secrets`, and `nimbus_oauth_client_secret` join the deny list, and a new apps/cli **contract-guard test** cross-checks every auth apiPath + secret-suffixed contract key against the registry (also closing the open-Record vacuity in the integrity test's auth checks). Registry rows are now exported from the package root to serve it. - **Clone/freeze regression fixed**: deep/cyclic/non-cloneable payloads now throw `ProjectConfigParseError` instead of raw `RangeError`/`DOMException` (depth-capped pre-walk, wrapped clone, cycle-guarded `deepFreeze`). - **README example rewritten and executed** (the previous snippet self-subtracted and filtered section names — always empty); secret-stripped projections prune emptied containers; a real phantom-drift pin replaces an agreeing-case test; `comparableProjectConfigPaths`' docstring narrowed to the section-level claim it delivers; ADR 0019 gained a dated addendum (attach helper, structural "verbatim", debug-inspector caveat). - **Documented-not-changed**: byte-size canonicalization quantizes at 4 significant digits symmetrically on both arms — sub-0.1% differences comparing equal is a deliberate property (user-authored spellings are exact). The merge commit resolves the pre-agreed README conflict with #6335 (their `## Naming` section wins, its "in flight" sentence flipped) and de-stales ADR 0020's phrasing, per the coordinated rebase checklist. Known external: root `check:all` fails on `@supabase/cli-go#lint:check` (5 gosec findings in Go files byte-identical to develop — develop-side/toolchain, not this PR). ### Drift-audit round (commit 492ee25c0) A 2026-08-27 audit compared the post-codex state against CLI-2230's design intent. All eight structural commitments verified intact (pure entrypoint, no `packages/api` dependency, sparse output + `EffectiveConfig`, ADR 0019 guardrails, registry-driven with `inverse` unimplemented, purity graph, naming, secret stripping). Three executed repros surfaced semantic drift introduced by the codex rounds; fixed here: - **Leniency**: `JSON.parse('{"x":1e400}')` yields `Infinity`, so the round-12 non-finite pre-decode rejection hard-failed a real JSON payload on an *unknown* field (mis-bucketed as `caller_misuse`). The walk now rejects only bigint/`undefined`/`NaN` (values `JSON.parse` cannot produce); non-finite values decode and surface as `null` from `unmappedApiFields` (scalars and inside array leaves, identity preserved for all-finite arrays). ADR 0019 gains a dated leniency-boundary addendum. - **SMTP absence semantics**: an absent `smtp_host` previously counted as the disabled sentinel, silently dropping `smtp_user`/`smtp_admin_email`/`rate_limit.email_sent` from both the output and `unmappedApiFields` — contradicting the SMS absent-vs-sentinel rule beside it. Absence now says nothing; only the explicit `""`/`null` legacy sentinel disables (three-state, extended to the `email_sent` cross-section rule). - **Convergence-predictor ruling (ADR 0021)**: the codex rounds had incrementally turned both normalizers into *post-push convergence predictors* (SMS provider precedence flips extra enabled providers, disabled-sentinel pruning, `null`-gated booleans, CSV/uint/duration canonicalization) — defensible (it kills phantom drift for CLI-2156) but never adjudicated or documented. Ruling: **accepted**, now recorded in ADR 0021, the `ProjectConfig`/`fromConfigDocument`/`fromApiProjectConfig` docstrings, and the README ("not a verbatim representation" caveat). - **Guards**: the three hand-written sentinel/precedence tables (`DISABLED_SENTINEL_PRUNES`, `DISABLED_SENTINEL_ENTRY_SWEEPS`, `SMS_PROVIDER_PUSH_PRECEDENCE`) now resolve every path/key against the schema ASTs in `registry-integrity` — previously a schema rename silently no-opped them — and the SMS precedence order is pinned against the legacy push switch (`auth.sync.ts:2498-2539`). - Residual cleanups: the stale `Math.trunc` comment above the fraction arithmetic (round-11/14 artifact the round-20 revert missed), `ReadonlyJsonValue` exported (it appears in public types), `smtpExplicitlyDisabledInAttributes` simplified to mirror `smsProviderExplicitlyUnset`, ADR index gains 0020/0021. An engineer-review pass over the fix diff verified every change; its findings (the `email_sent` asymmetry, stale ADR citations, formatter gate, doc phrasing) are folded in. 984/984 package tests, 461/461 apps/cli guard tests, typechecks clean both packages. Follow-ups filed so they survive CLI-2230 closing: [CLI-2266](https://linear.app/supabase/issue/CLI-2266/derive-the-config-push-mapper-from-the-projectconfig-mapping-registry) (registry-derived push mapper — carries the three requirements previously parked as CLI-2230 comments) and [CLI-2267](https://linear.app/supabase/issue/CLI-2267/pin-supabaseconfigs-replicated-legacy-parsers-with-parity-fixtures-in) (parity fixtures pinning the replicated legacy parsers against `config-sync/*`). Fixes CLI-2230 🤖 Generated with [Claude Code](https://claude.com/claude-code) | 1 个月前 | |
feat(cli): replace experimental workers with gated compute commands (#6554) Replace `supabase experimental workers` with `supabase compute`, registered only when `experimental.compute` or `SUPABASE_EXPERIMENTAL_COMPUTE=1` enables it. Without opt-in, Compute is absent from help, completion, and direct invocation. Rename the local implementation, symbols, configuration, templates, storage paths, and error identities to Compute, including `[compute.<name>]` and `supabase/compute/<name>/`. Workers shipped experimentally in v2.117.0. This transition intentionally makes a clean break: no aliases, legacy config passthrough, or migrations are required for the retired local names. Compute error tags intentionally start new telemetry fingerprints. Remove the `experimental` command namespace. Keep the Compute and Stack implementations under `src/commands/experimental/` for source organization; the root command owns their public paths and feature-flag registration. Keep server-owned Workers endpoints, generated API contracts, invocation URLs, and log identifiers unchanged until the backend adopts Compute naming. Enable Effect linting across the Compute commands, shared runtime, and test fixture helper. Use Effect services for scaffold loading, filesystem paths, clocks, and JSON decoding, with typed archive errors and scoped test fixtures. Embedded application templates are excluded from CLI runtime linting; the formatter test retains a narrow native-Date exception as an independent local-time oracle. Reject invalid Compute environment opt-ins on applicable command paths. Refuse scaffolding when JSON is the authoritative configuration, and require explicit exposure when deploying a source directory without a Compute config entry, so ignored or missing settings cannot silently select public access. | 1 个月前 | |
fix(config): stop treating disabled data api as enabled (CLI-2591) (#6949) ## TL;DR fixes disabled data api detection in the shared mapping used by `config diff`, `config push` and `config pull` ## what's hurting the users? the v2 project config returns `pg_pgrst_no_exposed_schemas` when the data api is disabled, but the mapping treated any nonempty value as enabled `config push` started hitting this in stable v2.117.0 when it switched from the v1 `/postgrest` read to the shared v2 mapping ## fixed now by: `remoteDataApiDisabled` recognizes both `""` and the exact marker. `api.enabled` now uses that same check as `schemas`, `extra_search_path` and `max_rows` <details> <summary>before/after:</summary> with local `api.enabled = false`, the remote marker, and no unrelated drift: | invocation | before | now | | --- | --- | --- | | `config diff --exit-code` | reports false api drift; exits 2 | no differences; exits 0 | | `config push --yes` | sends `{"db_schema":""}` again | api config is up to date | | `config pull --yes` | proposes enabling the api and importing the marker as a schema | proposes no api changes | </details> explicit `api.enabled = true` now re-enables the api with default schemas custom schemas still work. schema-only changes with enablement undeclared require an explicit enable request... <details> <summary>rep:</summary> ```mermaid flowchart TD off["data api disabled"] off --> v1["v1: empty db_schema"] off --> v2["v2: platform marker"] v1 --> legacy["v2.116.0 push"] legacy --> disabled["reads disabled"] v2 --> shared["push from stable v2.117.0"] shared --> before["before: nonempty means enabled"] before --> wrong["reads enabled"] shared --> after["now: empty or exact marker means disabled"] after --> disabled ``` </details> ## ref: - closes #6932 - regression introduced in #6454 | 9 天前 | |
fix(config): stop treating disabled data api as enabled (CLI-2591) (#6949) ## TL;DR fixes disabled data api detection in the shared mapping used by `config diff`, `config push` and `config pull` ## what's hurting the users? the v2 project config returns `pg_pgrst_no_exposed_schemas` when the data api is disabled, but the mapping treated any nonempty value as enabled `config push` started hitting this in stable v2.117.0 when it switched from the v1 `/postgrest` read to the shared v2 mapping ## fixed now by: `remoteDataApiDisabled` recognizes both `""` and the exact marker. `api.enabled` now uses that same check as `schemas`, `extra_search_path` and `max_rows` <details> <summary>before/after:</summary> with local `api.enabled = false`, the remote marker, and no unrelated drift: | invocation | before | now | | --- | --- | --- | | `config diff --exit-code` | reports false api drift; exits 2 | no differences; exits 0 | | `config push --yes` | sends `{"db_schema":""}` again | api config is up to date | | `config pull --yes` | proposes enabling the api and importing the marker as a schema | proposes no api changes | </details> explicit `api.enabled = true` now re-enables the api with default schemas custom schemas still work. schema-only changes with enablement undeclared require an explicit enable request... <details> <summary>rep:</summary> ```mermaid flowchart TD off["data api disabled"] off --> v1["v1: empty db_schema"] off --> v2["v2: platform marker"] v1 --> legacy["v2.116.0 push"] legacy --> disabled["reads disabled"] v2 --> shared["push from stable v2.117.0"] shared --> before["before: nonempty means enabled"] before --> wrong["reads enabled"] shared --> after["now: empty or exact marker means disabled"] after --> disabled ``` </details> ## ref: - closes #6932 - regression introduced in #6454 | 9 天前 | |
fix(config): retire push-capability pruning from ProjectConfig (CLI-2314) (#6469) ## Summary `fromConfigDocument` baked the legacy `config push` pipeline's write-capability envelope into the shared `ProjectConfig` representation (ADR 0021's original "push convergence" ruling). That was right when the CLI's own push command was the only reader; it's wrong now that Studio reads the same package for drift detection via the Management API, an actor with none of push's write-path limitations. Concretely, this hid real hosted customization from every consumer — not just push — behind three families of unconditional/blanket omissions. Builds on #6454 (CLI-2313, merged into `develop`) — this branch has already been rebased onto `develop` post-merge. Nothing here depends on the v2 PATCH endpoint (BRA-245/CLI-2317); everything works over the v1 write endpoints #6454 already wired up. Linear: [CLI-2314](https://linear.app/supabase/issue/CLI-2314). ## What changed - **`applyPushUnmanagedOmissions` deleted** — this unconditionally dropped the whole `auth.oauth_server` subtree and dropped `storage.analytics`/`storage.vector` entirely whenever `enabled: false`, because legacy push never sent those shapes. That pipeline no longer exists (CLI-2313 deleted `config-sync/*.sync.ts`); push now consumes `ProjectConfig` directly. - **`DISABLED_SENTINEL_PRUNES` re-derived from the platform's actual data model**, not from what legacy push happened to send. Every surviving entry's docstring now states a real, verified reason a sibling field goes inert server-side when its container is off (e.g. `api.enabled` is the same wire fact as `db_schema.length > 0`, not an independent field the API arm doesn't already gate on its own). A new cross-arm symmetry test makes this machine-checked: a disabled document projection and the equivalent disabled API-response projection must be byte-identical. - **Deleted the `auth.enabled`/`storage.enabled` blanket section-prunes.** These had nothing to do with the platform — `auth.enabled` is literally "Enable the local GoTrue service" with no hosted/registry equivalent, yet disabling it wiped the *entire* rest of the `auth` section (SMTP, external providers, captcha, everything) from the shared representation for anyone reading it. Fixed a real bug this exposed: `push.plan.ts`'s `legacyPushResourceEnabled` was gating the *entire* `auth`/`storage` resource dispatch on that same local toggle, so a declared change could be silently dropped. `auth`/`storage` now follow the same no-gate pattern `api`/`db.settings` already used. No new "default comparison" machinery was needed — `diffProjectConfig`'s existing `declared` flag (computed generically from raw TOML presence for every comparable path) already keeps an undeclared default value from ever being pushed over a real hosted customization. - **`auth.oauth_server.*` wired up as genuinely pushable.** It's writable via `UpdateAuthConfigBody` (confirmed against the OpenAPI spec) — push's own `SIDE_EFFECTS.md` had been carrying it in the unsupported-prefix list as an explicitly-labeled placeholder for this exact issue. - **Named `ConfigAbsencePolicy`** (`"absent-is-default"` / `"absent-is-hands-off"`) for the two operand policies `fromConfigDocument` always implicitly implemented depending on which overload you called. Documents the one genuinely hazardous cell (an absent field masquerading as a real declaration when diffing in dense mode) and the `declaredRoot = {}` cliff a caller hits by omitting `document` — exactly the calling shape Studio would use. - **Unmanaged caveat reworded** to actor-neutral language ("not managed while its section is disabled" instead of "cannot be pushed"). - **ADR 0021 addendum** reframing `ProjectConfig` as a shared multi-actor representation, plus matching touch-ups to ADR 0022/0023 and cleanup of stale references to the deleted function. ## Verification (post-rebase onto `develop`) - `packages/config`: `bun --bun vitest run --project unit` — 1264 passed. - `apps/cli`: `pnpm types:check` clean; `pnpm test:unit` — 5984 passed, 1 skipped; `pnpm test:integration` — 3438 passed. ## Follow-ups (out of scope here) - Consider a `local.auth.enabled`/`local.storage.enabled` namespace so `supabase start`'s "run this Docker service" concept is structurally separate from hosted management, instead of overloading `auth.enabled`/`storage.enabled`. - Audit `LEGACY_PUSH_UNSUPPORTED_PREFIXES`'s remaining `db.pooler` entry against the OpenAPI spec — `pool_mode`/`default_pool_size` are writable via `UpdateSupavisorConfigBody`, only `max_client_conn` genuinely isn't. | 1 个月前 | |
feat(cli): add `supabase pull` orchestrator command (#6526) ## Summary Adds a new top-level `supabase pull` command that orchestrates the CLI's existing pull-style subcommands — `config pull`, an optional `migration fetch`, `db pull`, and `functions download` — behind one target resolution, one confirmation, and one aggregated result. It exists for two cases: bootstrapping a local checkout from an existing remote project, and catching local files up after out-of-band dashboard/teammate changes. - Resolves the target project/branch once (`legacyResolveConfigTarget`) and threads the resolved ref through each sub-step's existing `--project-ref`-shaped input — no re-resolution, no duplicate network calls. - Runs sequentially (config → migration_history → db → functions), refining ADR 0004's "runs in parallel" aspiration: `db pull` reads `db.major_version` that `config pull` may have just written, and the migration-history step's files are what `db pull` reconciles against. - One confirmation covering all four steps — a real diff for config, a qualitative description for the other three (none have preview machinery of their own) — respecting `--dry-run`, `--yes`, `--force`, and a new `--remote-label` (parity with `config pull`'s own escape hatch). - Migration history is fetched automatically on a fresh checkout (empty `supabase/migrations`), even without `--with-migration-history`, since `db pull` otherwise hard-fails on that exact bootstrap case. - Partial-failure isolation: one step failing doesn't stop the others from running and being reported. A failed step's suggestion now includes a "retry just this step" hint naming the exact standalone command with the resolved ref, so a failure doesn't require re-running all four steps. - Extends the git-dirty guard to `supabase/migrations` and `supabase/functions`, not just `config.toml` — `--force` now bypasses all three. - `--output-format json`/`stream-json` emit a single structured result keyed by asset type (`steps: {config, migration_history, db, functions}`), and exactly one `cli_command_executed` telemetry event fires regardless of how many sub-steps ran. - Storage bucket definitions are out of scope for v1 — the Management API's bucket-list endpoint doesn't return the fields `storage.buckets` config needs (BRA-268, unstarted upstream). See `docs/adr/0024-top-level-pull-orchestration.md` for the full design rationale and `apps/cli/src/commands/pull/SIDE_EFFECTS.md` for the complete side-effect inventory — notably, the `db` step writes to the *remote* migration history table (not just local files) and requires Docker, both called out explicitly in the confirmation prompt. ## Reviewer-relevant context This went through a 4-agent review pass (architecture/engineering/security/DX) before this PR was opened, which found and led to fixing several real issues in the implementation history (the `--dry-run` preview being silently dropped, an absolute-path leak in the JSON payload, a git-dirty-guard regression, an undisclosed migration-overwrite path, and a `Layer.mergeAll` composition whose documented rationale turned out to be empirically false — fixed by eliminating the duplicate service binding rather than reordering). Lower-severity findings from that review were triaged directly with the ticket owner; some were fixed in follow-up commits on this branch (the dirty-guard scope extension and the retry-hint feature), others were judged not worth carrying forward and closed without action. Fixes CLI-1272 | 1 个月前 | |
refactor(stack): harden the stack runtime lifecycle, ports and engines (#7036) ## Why `@supabase/stack` runs a local Supabase anywhere: in containers (Docker or Podman) or as native processes on macOS and Linux, with several stacks running side by side. This PR is an architecture and hardening pass over that runtime and the experimental `supabase stack` commands (still behind `SUPABASE_EXPERIMENTAL_STACK=1`). The aim is a design that is robust because it is simple: - one owner per fact; - derive state instead of recording it; - prevent conflicts by construction; - rely on guarantees the OS and the engine already give: kernel locks, atomic rename, pipe EOF and container labels. Several mechanisms were deleted rather than patched. The legacy `supabase start` path is untouched. ## What users stop hitting | Situation | Before | After | |---|---|---| | `supabase stack stop` | Listeners closed first, so Vector couldn't flush logs through the proxy and was force-killed after its grace period. Every stop paid that delay, plus an analytics drain and a 10 s hold for idle pooled connections. | Services stop in reverse dependency order with their listeners still open. Each listener closes when its own service has stopped. | | A service crashes | The proxy could keep routing to the dead process while cleanup ran, and a retried cleanup could lose the crash outcome. | The crash is reported first, so routing stops at once. The service ends `Failed` even across retried cleanup. It wakes again on demand, behind a crash-loop breaker. | | Restarting a stack | Public ports could change between `stop` and `start`, breaking URLs embedded in apps. | Automatic ports are reserved per OS user and kept across stop and restart and across state roots until destroy. Configured ports are held only while the stack runs, so a stopped project never blocks another. A conflict names the port, the holding project and the setting to change. | | Two native stacks on macOS | Wildcard binds let another stack's same-port loopback bind shadow a service, so requests hit the wrong stack ("Invalid JWT"). | Native services bind loopback only, and backend ports come from a dedicated range below the OS ephemeral range. | | Proxy retry after a backend restart | The retry reused the address captured before the first attempt and could reach a foreign process that took the port. | The address lives in the lifecycle's `Running` state and is re-resolved on retry. A dead workload has no routable address. | | Deleting a stack's state directory | The owner kept running and its containers leaked. | The owner notices that it lost ownership, stops only its own services without touching data or ports, and exits. Leftover labelled containers are swept by the next container stack under that state root, and `destroy --stack-id` reaches them on demand. | | `destroy` | Helper containers and files under the data root could survive, and open leases made destroy fail. | Everything labelled with the stack is removed, owned data is removed by location, and destroy succeeds despite open leases. | | Engine not running | Startup and destroy failed in opaque ways. | Startup and `destroy` fail with `runtime-unavailable`. `destroy` leaves data and ports untouched and prints the exact command to retry once the engine is back. | | Podman | Not supported. | Podman is a first-class engine. The CLI picks Docker, then Podman, then native. Rootless launches keep file ownership with `--userns=keep-id`. | | Changing stack credentials | A stopped consumer kept stale keys baked into its saved config. | Credential changes refresh the stopped consumers in the same locked save, keeping values set explicitly. | | Concurrent native preparers | Two processes preparing the same binaries could lose files. macOS temp-dir aging could empty a cached generation. | Artifacts are immutable, content-addressed generations published by rename into the user cache directory. | | Config change on a saved stack | Rejected with an opaque error. | The error names the changed settings and the exact recreate command. | ## How it works now ### One lifecycle owner per service Start, stop, sleep, restart, crash handling and destroy used to have separate writers. A single pure reducer now decides every transition under one gate. The runtime only executes the step it is given, and late outcomes are fenced by generation. ```mermaid stateDiagram-v2 [*] --> Stopped Stopped --> Starting: demand or explicit start Starting --> Running: session available Starting --> Failed: launch failed, cleanup confirmed Running --> Stopping: stop, restart, idle sleep or crash Stopping --> Stopped: stop / restart / sleep, cleanup confirmed Stopping --> Failed: crash, cleanup confirmed Stopping --> Stopping: cleanup failed, retried on request Failed --> Starting: demand, behind the breaker Failed --> Stopped: explicit stop ``` - **Routing.** An address exists only in `Running`, so traffic can't reach a stopping or crashed workload. - **Breaker.** It opens for 30 s after 3 failures, each within 5 minutes of the previous one. - **Wake waiters.** Requests that wake a sleeping service wait up to 120 s. - **Dependencies.** Prerequisites stay awake while a dependent runs. ### Ordered stop instead of a listener drain ```mermaid sequenceDiagram participant P as Stack proxy participant F as Functions participant A as Auth participant V as Vector participant DB as Postgres Note over P: Draining (one-way): wake-up traffic refused,<br/>traffic to running services still admitted P->>F: stop (dependents first) F-->>P: stopped, listener closes P->>A: stop A-->>P: stopped, listener closes V->>P: final log flush still reaches analytics P->>V: stop V-->>P: stopped, listener closes P->>DB: stop (prerequisites last) DB-->>P: stopped, listener closes ``` Draining is one-way. If stop or destroy fails, the stack stays registered and the next command reclaims it under the lease. There are no resume or recovery paths. ### Who owns what ```mermaid flowchart TB subgraph user["Per OS user"] ports[("~/.supabase/ports.sqlite<br/>public port reservations")] cache["~/.cache artifact generations<br/>immutable, content-addressed"] end subgraph root["State root (listing is per root)"] lock["<id>/owner.lock<br/>SQLite lease = stack is live"] state["<id>/state.json<br/>registration and definitions"] data["<id>/data/<instance><br/>owned by location"] end engine["Engine containers<br/>com.supabase.stack + com.supabase.stack-root labels"] owner(("Stack owner process")) owner -- holds --> lock owner -- polls --> state owner -- sweeps by label --> engine owner -- reserves --> ports owner -- removes on destroy --> data ``` - **Liveness.** A stack is live exactly while its lease is held, and the OS releases the lease when the process exits for any reason. - **Containers.** They are tracked only by two labels set atomically at creation. The claims journal is gone. - **Data.** Ownership is by location: everything under an instance or data root is owned. Borrowed caller paths may not resolve inside it. Destroy checks containment before removing. - **Ports.** Reservations live only in the per-user registry; `state.json` stores none. Automatic ports stay reserved until destroy; configured ports are held only while their listener is open, and a crashed holder's reservation is taken over. - **Engines.** Each container stack pins one engine target (Docker context or host, Podman connection or URL), and every engine call uses it. ### Removed or intentionally unsupported | Item | What changed | |---|---| | Resource-claims journal, listener-drain deadline, abandon mode and its retry loop, in-process artifact lock registry | Replaced by labels, ordered stop, an owner that exits when it loses ownership, and SQLite file locks. | | Stacks saved by earlier experimental releases | No migration. A saved stack that can't be read fails with an error naming its directory to remove. | | Filesystems without hard links | Unsupported; the non-atomic fallback was removed. | | Native mode on Windows | Out of scope; Windows runs the stack in containers. | | Container sweeps in native mode | Native owners skip them; native mode has no container concerns. | | Port reservations across OS users | Not provided; kernel binds still keep different users' running stacks apart. | | Proxy completion window during stop | None; in-flight requests survive as far as each service's own graceful stop carries them. | ## Size 240 files changed: +23,442 / −11,645 (**+11,797 net**). About two thirds of the growth is tests. | Area | Kind | Net lines | |---|---|---:| | `packages/stack` | Production code | +3,277 | | | Integration tests | +3,960 | | | Unit tests | +1,605 | | | Test helpers | +1,288 | | | E2E tests | +897 | | | Docs | +64 | | `apps/cli` | Production code | −90 | | | Tests | +123 | | | Docs | +44 | | Repo root | Namespace import-boundary lint rules | +380 | | | CI (Podman on rootless Linux, stress workflow) | +188 | | | ADRs | +58 | | **Total** | Production code | **+3,187** | | | Tests | **+7,873** | The production growth is in the stack runtime only. The CLI's production code is slightly smaller. | 7 天前 | |
feat(stack): run OrioleDB through db.orioledb_version (#6934) ## Summary **TL;DR:** The experimental local stack can now run OrioleDB through `db.orioledb_version`, on both the Docker and native runtimes. It uses the new slim-services OrioleDB release line (`<upstream>-orioledb`). A version without a published artifact fails closed, listing the supported ones. ### Before ```mermaid flowchart LR A["db.orioledb_version set"] --> B{"experimental stack?"} B -- yes --> C["error: unsupported by the experimental stack"] B -- no --> D["Compose: docker.io supabase/postgres:<v>-orioledb"] ``` ### After ```mermaid flowchart LR A["db.orioledb_version set"] --> B{"OrioleDB pin in catalog?"} B -- no --> C["fail closed: lists published versions"] B -- yes --> D["stack: slim <v>-orioledb-rN<br/>docker or native"] D --> E["db diff / declarative shadow<br/>use the same pin"] D --> F["data reuse only within<br/>the OrioleDB line"] G["slim-services publishes<br/><v>-orioledb-r0"] --> H["catalog sync opens<br/>add postgres PR"] H --> B ``` ### Why OrioleDB is leaving experimental. The new stack rejected `db.orioledb_version` with `db.orioledb_version is unsupported by the experimental stack`. supabase/slim-services#315 now builds and publishes OrioleDB as its own Postgres release line, next to stock 15 and 17, so the stack can pin it like any other artifact. ### What changed - **Catalog sync.** `-orioledb` releases form their own catalog line (`17-orioledb`). - The first publish opens an `add` PR. - Later releases upgrade or hotfix that line on their own. - An OrioleDB release never touches the stock 17 pin, and the reverse. - Legacy tags without `-rN` are ignored. - **One version resolver.** `db.orioledb_version` (and `SUPABASE_DB_ORIOLEDB_VERSION`) resolves to `<v>-orioledb` through one shared resolver. `stack start`, the `db diff` shadow and its cache key, and the versions output all use it. - The setting requires `db.major_version = 17`. - An unpinned version fails with the list of published versions. - `postgresVersion("17")` still resolves to stock. - **Data safety.** The ready-marker, `PG_VERSION` and snapshot checks compare the engine line, not only the major. Stock and OrioleDB data, snapshots and shadow caches never mix. - Unmarked data, including data left behind by an interrupted first start, is refused for an OrioleDB request. - The error suggests `supabase stack destroy`. - Switching `orioledb_version` on a saved stack requires `stack destroy`, because the stack keeps its database version. - **`test db`.** Maps OrioleDB to its major for pg_prove. - **Declarative shadow.** No CLI-side handling: pg-delta's Supabase profile treats `orioledb` as a platform extension ([supabase/pg-toolbelt#513](https://github.com/supabase/pg-toolbelt/pull/513)), so declarative export never emits it and the diff never creates or drops it. Shipped in pg-delta `1.0.0-alpha.58`, which this PR pins. - **Compose.** With `SUPABASE_USE_SLIM_IMAGES`, Compose maps `supabase/postgres:<v>-orioledb` to the pinned slim OrioleDB image. Unpinned versions stay on docker.io. - **Docs.** `stack-commands.md`, `SIDE_EFFECTS.md`, `packages/stack/ARCHITECTURE.md` and ADR 0026. It also pins the first published OrioleDB release, `17.11.0.002-orioledb-r0`, added with the catalog sync's `--release` mode. Later OrioleDB releases arrive through the existing `slim-release-published` sync. ## Linked issue Part of CLI-2387. Depends on supabase/slim-services#315. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 5 天前 | |
feat(cli): trace CLI runs end-to-end with opt-in OTLP export (#6935) ## Summary **TL;DR:** Every CLI run can now produce one OpenTelemetry trace that shows where time goes, down to individual migrations, containers, health waits, API operations, and SQL statements. Export is opt-in per run (`SUPABASE_TRACE_FILE` for a local OTLP/JSON file, or `SUPABASE_OTLP_ENDPOINT` for Tempo/Jaeger/Grafana), costs nothing when unset, and never exports free-form text or secrets. Two new scripts turn traces into answers: `trace-report` (critical path, self time, repeated spans) and `bench-cli` (fair A/B timing between two binaries, optionally per span). ### Before ```mermaid flowchart LR A["CLI run"] --> B["ad-hoc spans<br/>(uneven coverage)"] B --> C{"telemetry consent?"} C -->|yes| D["~/.supabase/traces/*.ndjson<br/>no parent ids, never read"] C -->|no| E["dropped"] B -->|--debug| F["one stderr line per span"] ``` ### After ```mermaid flowchart LR A["CLI run"] --> R{"SUPABASE_TRACE_FILE<br/>or SUPABASE_OTLP_ENDPOINT?"} R -->|neither| N["tracer disabled<br/>no-op spans, no I/O"] R -->|set| T["cli.run span tree<br/>commands, phases, I/O, processes"] T --> S["sanitizer<br/>no free text, no secrets"] S --> F["OTLP/JSON file"] --> TR["trace-report / bench-cli"] S --> O["OTLP collector<br/>Tempo, Jaeger, Grafana"] ``` ### Why Benchmarking or debugging the CLI meant adding temporary instrumentation for each investigation. The existing tracer wrote consent-gated NDJSON without parent span ids, so no span tree could be rebuilt, and nothing read those files. Agents and maintainers need one consistent trace per run that standard tools can open. ### What changed - **Export pipeline** (`apps/cli/src/shared/telemetry/`): one sink per run (file xor OTLP/HTTP, both is a config error); CLI-namespaced variables so a global `OTEL_*` setup never receives CLI internals; `TRACEPARENT` is adopted as the parent and passed to child processes; the trace scope flushes within a bounded window before `processControl.exit`. - **Tracing off by default**: without a sink the root runs with `TracerEnabled = false` and the exporter module is never loaded. Startup and command wall time match `develop` within noise. - **Sanitizer**: exported batches keep only structured data — exception events keep `exception.type` (plus a structured SQLSTATE), status and log messages are dropped, SQL text becomes `db.operation.name` and a per-run salted hash, URLs lose query strings and storage object keys, headers are allowlisted, resource attributes are allowlisted, trace files are `0600`. - **Instrumentation sweep** across commands, `command-internal`, `shared`, `packages/stack`, and `packages/api`, following the new conventions ADR: named spans at I/O and service boundaries, handler phase spans, outcome annotations (counts, cache hit/miss, attempts, exit codes), process spans with the docker verb, API spans named by operation id, no spans per poll attempt or loop item. - **`SUPABASE_DEBUG=1` console** keeps its behavior (spans up to depth 2 plus failures on stderr) with sanitized, length-capped attributes; `--debug` alone still prints no spans. - **Scripts**: `apps/cli/scripts/trace-report.ts` and `apps/cli/scripts/bench-cli.ts` (alternating launches, isolated environment, warm-up, untimed traced/profiled launches, `--cpu-prof`). - **Removed** the NDJSON sink and the dead `command-instrumentation.ts` copy of the command span. - **Docs**: [ADR 0027](docs/adr/0027-cli-tracing-conventions.md) for conventions and [tracing-monitoring.md](apps/cli/docs/tracing-monitoring.md) for usage; telemetry docs updated for the removed NDJSON files (old `~/.supabase/traces` files are no longer written and are safe to delete). ### Terminal captures Recorded from a fresh corpus project against a local `grafana/otel-lgtm` and with `vhs`; both builds are local binaries of `develop` and this branch. **Tempo: CLI runs, `supabase start` waterfall, cold `db diff` breakdown, a sanitized process span, two commands grouped by `TRACEPARENT`**      **Terminal: debug console, traced command, trace-report, A/B bench, per-span bench**      ## Linked issue None. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 12 天前 | |
| 7 天前 | ||
refactor(stack): harden the stack runtime lifecycle, ports and engines (#7036) ## Why `@supabase/stack` runs a local Supabase anywhere: in containers (Docker or Podman) or as native processes on macOS and Linux, with several stacks running side by side. This PR is an architecture and hardening pass over that runtime and the experimental `supabase stack` commands (still behind `SUPABASE_EXPERIMENTAL_STACK=1`). The aim is a design that is robust because it is simple: - one owner per fact; - derive state instead of recording it; - prevent conflicts by construction; - rely on guarantees the OS and the engine already give: kernel locks, atomic rename, pipe EOF and container labels. Several mechanisms were deleted rather than patched. The legacy `supabase start` path is untouched. ## What users stop hitting | Situation | Before | After | |---|---|---| | `supabase stack stop` | Listeners closed first, so Vector couldn't flush logs through the proxy and was force-killed after its grace period. Every stop paid that delay, plus an analytics drain and a 10 s hold for idle pooled connections. | Services stop in reverse dependency order with their listeners still open. Each listener closes when its own service has stopped. | | A service crashes | The proxy could keep routing to the dead process while cleanup ran, and a retried cleanup could lose the crash outcome. | The crash is reported first, so routing stops at once. The service ends `Failed` even across retried cleanup. It wakes again on demand, behind a crash-loop breaker. | | Restarting a stack | Public ports could change between `stop` and `start`, breaking URLs embedded in apps. | Automatic ports are reserved per OS user and kept across stop and restart and across state roots until destroy. Configured ports are held only while the stack runs, so a stopped project never blocks another. A conflict names the port, the holding project and the setting to change. | | Two native stacks on macOS | Wildcard binds let another stack's same-port loopback bind shadow a service, so requests hit the wrong stack ("Invalid JWT"). | Native services bind loopback only, and backend ports come from a dedicated range below the OS ephemeral range. | | Proxy retry after a backend restart | The retry reused the address captured before the first attempt and could reach a foreign process that took the port. | The address lives in the lifecycle's `Running` state and is re-resolved on retry. A dead workload has no routable address. | | Deleting a stack's state directory | The owner kept running and its containers leaked. | The owner notices that it lost ownership, stops only its own services without touching data or ports, and exits. Leftover labelled containers are swept by the next container stack under that state root, and `destroy --stack-id` reaches them on demand. | | `destroy` | Helper containers and files under the data root could survive, and open leases made destroy fail. | Everything labelled with the stack is removed, owned data is removed by location, and destroy succeeds despite open leases. | | Engine not running | Startup and destroy failed in opaque ways. | Startup and `destroy` fail with `runtime-unavailable`. `destroy` leaves data and ports untouched and prints the exact command to retry once the engine is back. | | Podman | Not supported. | Podman is a first-class engine. The CLI picks Docker, then Podman, then native. Rootless launches keep file ownership with `--userns=keep-id`. | | Changing stack credentials | A stopped consumer kept stale keys baked into its saved config. | Credential changes refresh the stopped consumers in the same locked save, keeping values set explicitly. | | Concurrent native preparers | Two processes preparing the same binaries could lose files. macOS temp-dir aging could empty a cached generation. | Artifacts are immutable, content-addressed generations published by rename into the user cache directory. | | Config change on a saved stack | Rejected with an opaque error. | The error names the changed settings and the exact recreate command. | ## How it works now ### One lifecycle owner per service Start, stop, sleep, restart, crash handling and destroy used to have separate writers. A single pure reducer now decides every transition under one gate. The runtime only executes the step it is given, and late outcomes are fenced by generation. ```mermaid stateDiagram-v2 [*] --> Stopped Stopped --> Starting: demand or explicit start Starting --> Running: session available Starting --> Failed: launch failed, cleanup confirmed Running --> Stopping: stop, restart, idle sleep or crash Stopping --> Stopped: stop / restart / sleep, cleanup confirmed Stopping --> Failed: crash, cleanup confirmed Stopping --> Stopping: cleanup failed, retried on request Failed --> Starting: demand, behind the breaker Failed --> Stopped: explicit stop ``` - **Routing.** An address exists only in `Running`, so traffic can't reach a stopping or crashed workload. - **Breaker.** It opens for 30 s after 3 failures, each within 5 minutes of the previous one. - **Wake waiters.** Requests that wake a sleeping service wait up to 120 s. - **Dependencies.** Prerequisites stay awake while a dependent runs. ### Ordered stop instead of a listener drain ```mermaid sequenceDiagram participant P as Stack proxy participant F as Functions participant A as Auth participant V as Vector participant DB as Postgres Note over P: Draining (one-way): wake-up traffic refused,<br/>traffic to running services still admitted P->>F: stop (dependents first) F-->>P: stopped, listener closes P->>A: stop A-->>P: stopped, listener closes V->>P: final log flush still reaches analytics P->>V: stop V-->>P: stopped, listener closes P->>DB: stop (prerequisites last) DB-->>P: stopped, listener closes ``` Draining is one-way. If stop or destroy fails, the stack stays registered and the next command reclaims it under the lease. There are no resume or recovery paths. ### Who owns what ```mermaid flowchart TB subgraph user["Per OS user"] ports[("~/.supabase/ports.sqlite<br/>public port reservations")] cache["~/.cache artifact generations<br/>immutable, content-addressed"] end subgraph root["State root (listing is per root)"] lock["<id>/owner.lock<br/>SQLite lease = stack is live"] state["<id>/state.json<br/>registration and definitions"] data["<id>/data/<instance><br/>owned by location"] end engine["Engine containers<br/>com.supabase.stack + com.supabase.stack-root labels"] owner(("Stack owner process")) owner -- holds --> lock owner -- polls --> state owner -- sweeps by label --> engine owner -- reserves --> ports owner -- removes on destroy --> data ``` - **Liveness.** A stack is live exactly while its lease is held, and the OS releases the lease when the process exits for any reason. - **Containers.** They are tracked only by two labels set atomically at creation. The claims journal is gone. - **Data.** Ownership is by location: everything under an instance or data root is owned. Borrowed caller paths may not resolve inside it. Destroy checks containment before removing. - **Ports.** Reservations live only in the per-user registry; `state.json` stores none. Automatic ports stay reserved until destroy; configured ports are held only while their listener is open, and a crashed holder's reservation is taken over. - **Engines.** Each container stack pins one engine target (Docker context or host, Podman connection or URL), and every engine call uses it. ### Removed or intentionally unsupported | Item | What changed | |---|---| | Resource-claims journal, listener-drain deadline, abandon mode and its retry loop, in-process artifact lock registry | Replaced by labels, ordered stop, an owner that exits when it loses ownership, and SQLite file locks. | | Stacks saved by earlier experimental releases | No migration. A saved stack that can't be read fails with an error naming its directory to remove. | | Filesystems without hard links | Unsupported; the non-atomic fallback was removed. | | Native mode on Windows | Out of scope; Windows runs the stack in containers. | | Container sweeps in native mode | Native owners skip them; native mode has no container concerns. | | Port reservations across OS users | Not provided; kernel binds still keep different users' running stacks apart. | | Proxy completion window during stop | None; in-flight requests survive as far as each service's own graceful stop carries them. | ## Size 240 files changed: +23,442 / −11,645 (**+11,797 net**). About two thirds of the growth is tests. | Area | Kind | Net lines | |---|---|---:| | `packages/stack` | Production code | +3,277 | | | Integration tests | +3,960 | | | Unit tests | +1,605 | | | Test helpers | +1,288 | | | E2E tests | +897 | | | Docs | +64 | | `apps/cli` | Production code | −90 | | | Tests | +123 | | | Docs | +44 | | Repo root | Namespace import-boundary lint rules | +380 | | | CI (Podman on rootless Linux, stress workflow) | +188 | | | ADRs | +58 | | **Total** | Production code | **+3,187** | | | Tests | **+7,873** | The production growth is in the stack runtime only. The CLI's production code is slightly smaller. | 7 天前 | |
docs(cli): record the hotfix flow and the forward-only release policy (#7052) Linear: [CLI-2442](https://linear.app/supabase/issue/CLI-2442) Third of three PRs for the hotfix flow on top of #7038. Documentation only. - **Runbook, hotfix section** (`apps/cli/docs/release-process.md`): when a hotfix is warranted, the patch-type title rule and why not to edit the squash subject, the checks that run automatically including the dry-run release rehearsal, the sync back into `develop` and what to do when it conflicts, the interaction with an open deploy PR, and how a hotfix reaches `@supabase/config` consumers. The manual dry-run dispatch step is gone. - **Runbook, rollback section**: replaced by the forward-only policy. A `revert:` hotfix is the response to a bad release. - **ADR 0030**: hotfix criteria, forward-only, the required `Check deploy`, the automatic rehearsal, and the notifications, referencing ADR 0028 for the sync and fast-forward mechanics. - **Maintainers guide and root instructions**: a short hotfix paragraph and one sentence respectively, pointing at the runbook. Stacked on #7050, so the workflows this runbook describes are in the checkout: review this diff against that branch. It merges after #7050 and #7051 (Slack and the fast-forward comment); once #7050 lands, GitHub retargets this PR to `develop` and I bring `develop` in before it goes through the queue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com> | 6 天前 | |
docs(cli): record the hotfix flow and the forward-only release policy (#7052) Linear: [CLI-2442](https://linear.app/supabase/issue/CLI-2442) Third of three PRs for the hotfix flow on top of #7038. Documentation only. - **Runbook, hotfix section** (`apps/cli/docs/release-process.md`): when a hotfix is warranted, the patch-type title rule and why not to edit the squash subject, the checks that run automatically including the dry-run release rehearsal, the sync back into `develop` and what to do when it conflicts, the interaction with an open deploy PR, and how a hotfix reaches `@supabase/config` consumers. The manual dry-run dispatch step is gone. - **Runbook, rollback section**: replaced by the forward-only policy. A `revert:` hotfix is the response to a bad release. - **ADR 0030**: hotfix criteria, forward-only, the required `Check deploy`, the automatic rehearsal, and the notifications, referencing ADR 0028 for the sync and fast-forward mechanics. - **Maintainers guide and root instructions**: a short hotfix paragraph and one sentence respectively, pointing at the runbook. Stacked on #7050, so the workflows this runbook describes are in the checkout: review this diff against that branch. It merges after #7050 and #7051 (Slack and the fast-forward comment); once #7050 lands, GitHub retargets this PR to `develop` and I bring `develop` in before it goes through the queue. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com> | 6 天前 |
Architecture Decision Records (ADRs)
We record architecture decisions for the supabase CLI using the MADR (Markdown Any Decision Records) format.
What is an ADR?
An Architecture Decision Record (ADR) captures important architectural decisions along with their context and consequences.
Each ADR describes:
- What decision was made — the technical choice
- Why it was made — the problem and rationale
- What trade-offs were accepted — the consequences
- What alternatives were considered — why others didn't work
ADRs are concise (1-2 pages), version-controlled in Git, and used for onboarding and decision continuity.
Why we use ADRs
For a CLI serving as the entry point to Supabase, architectural decisions affect developer experience, performance, testing strategy, observability, and error handling — for both human and LLM consumers. Recording these decisions prevents repeated debates and makes trade-offs explicit.
File naming and status
Naming convention: NNNN-short-title.md
Status lifecycle:
proposed → accepted → deprecated / superseded by [NNNN]
How to create a new ADR
- Assign the next number (e.g.,
0002-*) - Use the MADR template below
- Set status to
proposed - Open a PR for team review
- Update status to
acceptedonce consensus is reached
When an ADR becomes outdated, mark it as deprecated or reference the superseding ADR.
ADR index
Template
# NNNN. Title
**Status**: proposed
**Date**: YYYY-MM-DD
## Problem Statement
What problem are we trying to solve? Provide context about why this decision matters.
## Decision
What is the decision? State it clearly and concisely.
## Rationale
Why did we choose this approach?
## Consequences
### Positive
- Benefit 1
### Negative
- Trade-off 1
## Alternatives Considered
1. **Alternative A**: Why we didn't choose this
## Related Decisions
- ADR NNNN: Related decision
## See Also
- External link or reference
Note: Additional sections (Implementation Notes, Open Questions, Verification Checklist) may be added as needed.