From 2ee7c348bafd9688cc918f9782c5af05b070c76c Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 27 Aug 2026 16:24:44 +0100 Subject: [PATCH 1/9] refactor(config): trim CLI-internal exports into ./internal and apps/cli (CLI-2234) --- .../commands/config/push/push.handler.ts | 3 +- .../commands/functions/new/new.handler.ts | 2 +- .../commands/gen/gen.signing-keys-config.ts | 3 +- .../commands/gen/types/types.handler.ts | 2 +- .../commands/secrets/set/set.handler.ts | 3 +- .../legacy/commands/start/start.handler.ts | 3 +- .../legacy/commands/storage/storage.frame.ts | 10 +- .../src/legacy/shared/kong-local-ca-cert.ts | 0 .../shared/kong-local-ca-cert.unit.test.ts | 2 +- .../shared/legacy-local-config-values.ts | 3 +- .../shared/legacy-local-project-context.ts | 2 +- .../src/legacy/shared/legacy-seed-buckets.ts | 10 +- .../shared/legacy-storage-credentials.ts | 2 +- .../project-config-api-drift.unit.test.ts | 3 +- .../project-config-auth-contract.unit.test.ts | 9 +- ...roject-config-presence-parity.unit.test.ts | 3 +- .../src/shared/functions/functions-config.ts | 3 +- apps/cli/src/shared/functions/serve.ts | 8 +- packages/config/AGENTS.md | 15 +- packages/config/docs/cli-config-loading.md | 34 ++-- packages/config/package.json | 1 + packages/config/src/bun.ts | 8 +- packages/config/src/cli-config.service.ts | 30 ++- packages/config/src/config-document.ts | 9 +- packages/config/src/effect.ts | 71 ++++++-- .../config/src/entrypoint-purity.unit.test.ts | 70 +++++-- packages/config/src/index.ts | 19 +- packages/config/src/internal.ts | 18 ++ packages/config/src/io-browser.ts | 16 +- packages/config/src/io.ts | 6 +- packages/config/src/io.unit.test.ts | 4 +- packages/config/src/lib/resolve.ts | 171 ++++++++++++++++++ .../src/monorepo-import-contract.unit.test.ts | 4 +- packages/config/src/node.ts | 8 +- packages/config/src/project.ts | 139 +++----------- packages/config/src/project.unit.test.ts | 19 +- .../src/promise-facade.stdin.unit.test.ts | 4 +- packages/config/src/promise-facade.ts | 27 ++- .../config/src/promise-facade.unit.test.ts | 44 ++--- 39 files changed, 515 insertions(+), 273 deletions(-) rename packages/config/src/tls.ts => apps/cli/src/legacy/shared/kong-local-ca-cert.ts (100%) rename packages/config/src/tls.unit.test.ts => apps/cli/src/legacy/shared/kong-local-ca-cert.unit.test.ts (81%) create mode 100644 packages/config/src/internal.ts create mode 100644 packages/config/src/lib/resolve.ts diff --git a/apps/cli/src/legacy/commands/config/push/push.handler.ts b/apps/cli/src/legacy/commands/config/push/push.handler.ts index ba0004663c..f493bddec9 100644 --- a/apps/cli/src/legacy/commands/config/push/push.handler.ts +++ b/apps/cli/src/legacy/commands/config/push/push.handler.ts @@ -1,5 +1,6 @@ import { dirname } from "node:path"; -import { findCliProjectRoot, loadCliConfig } from "@supabase/config/effect"; +import { findCliProjectRoot } from "@supabase/config/effect"; +import { loadCliConfig } from "@supabase/config/internal"; import { Effect, FileSystem, Path } from "effect"; import { LegacyPlatformApi } from "../../../auth/legacy-platform-api.service.ts"; diff --git a/apps/cli/src/legacy/commands/functions/new/new.handler.ts b/apps/cli/src/legacy/commands/functions/new/new.handler.ts index 1531f5df53..2612206fc3 100644 --- a/apps/cli/src/legacy/commands/functions/new/new.handler.ts +++ b/apps/cli/src/legacy/commands/functions/new/new.handler.ts @@ -1,4 +1,4 @@ -import { loadCliConfig } from "@supabase/config/effect"; +import { loadCliConfig } from "@supabase/config/internal"; import { defaultPublishableKey } from "@supabase/stack/effect"; import { Effect, FileSystem, Option, Path } from "effect"; diff --git a/apps/cli/src/legacy/commands/gen/gen.signing-keys-config.ts b/apps/cli/src/legacy/commands/gen/gen.signing-keys-config.ts index a94a349ded..672df4a18a 100644 --- a/apps/cli/src/legacy/commands/gen/gen.signing-keys-config.ts +++ b/apps/cli/src/legacy/commands/gen/gen.signing-keys-config.ts @@ -1,4 +1,5 @@ -import { loadCliConfig, loadCliProjectEnvironment } from "@supabase/config/effect"; +import { loadCliProjectEnvironment } from "@supabase/config/effect"; +import { loadCliConfig } from "@supabase/config/internal"; import { Effect, FileSystem, Option, Path } from "effect"; import { legacyAssertDecodableJwkAlgorithm } from "../../shared/legacy-go-jwt.ts"; import { legacyGoJsonKindName } from "../../shared/legacy-go-json.ts"; diff --git a/apps/cli/src/legacy/commands/gen/types/types.handler.ts b/apps/cli/src/legacy/commands/gen/types/types.handler.ts index 63a47aa0fd..4be9f7ad24 100644 --- a/apps/cli/src/legacy/commands/gen/types/types.handler.ts +++ b/apps/cli/src/legacy/commands/gen/types/types.handler.ts @@ -1,4 +1,4 @@ -import { loadCliConfig } from "@supabase/config/effect"; +import { loadCliConfig } from "@supabase/config/internal"; import { ChildProcessSpawner } from "effect/unstable/process"; import { Effect, FileSystem, Option, Path, Stdio, Stream } from "effect"; import { diff --git a/apps/cli/src/legacy/commands/secrets/set/set.handler.ts b/apps/cli/src/legacy/commands/secrets/set/set.handler.ts index 104406cb58..9184400567 100644 --- a/apps/cli/src/legacy/commands/secrets/set/set.handler.ts +++ b/apps/cli/src/legacy/commands/secrets/set/set.handler.ts @@ -1,11 +1,10 @@ import { - loadCliConfig, loadCliProjectEnvironment, CliConfigSchema, - resolveCliConfigSubtree, type CliConfig, type CliConfigParseError, } from "@supabase/config/effect"; +import { loadCliConfig, resolveCliConfigSubtree } from "@supabase/config/internal"; import { V1BulkCreateSecretsInput } from "@supabase/api/effect"; import { parse as parseDotenv } from "dotenv"; import { Effect, FileSystem, Option, Path, Redacted, Schema } from "effect"; diff --git a/apps/cli/src/legacy/commands/start/start.handler.ts b/apps/cli/src/legacy/commands/start/start.handler.ts index 2d6034999d..a38d50ecf1 100644 --- a/apps/cli/src/legacy/commands/start/start.handler.ts +++ b/apps/cli/src/legacy/commands/start/start.handler.ts @@ -2,7 +2,8 @@ * Native TS implementation of `start` — see `SIDE_EFFECTS.md` for the full * behavior contract. */ -import { inferFunctionsManifest, resolveCliConfigSubtree } from "@supabase/config/effect"; +import { inferFunctionsManifest } from "@supabase/config/effect"; +import { resolveCliConfigSubtree } from "@supabase/config/internal"; import { Effect, FileSystem, Option, Path, Result } from "effect"; import { FetchHttpClient } from "effect/unstable/http"; import { ChildProcessSpawner } from "effect/unstable/process"; diff --git a/apps/cli/src/legacy/commands/storage/storage.frame.ts b/apps/cli/src/legacy/commands/storage/storage.frame.ts index ce203e442f..1d187e23c3 100644 --- a/apps/cli/src/legacy/commands/storage/storage.frame.ts +++ b/apps/cli/src/legacy/commands/storage/storage.frame.ts @@ -1,9 +1,5 @@ -import { - loadCliConfig, - type LoadCliConfigOptions, - CliConfigSchema, - type CliConfig, -} from "@supabase/config/effect"; +import { CliConfigSchema, type CliConfig } from "@supabase/config/effect"; +import { loadCliConfig, type InternalLoadCliConfigOptions } from "@supabase/config/internal"; import { Effect, Schema } from "effect"; import { FetchHttpClient } from "effect/unstable/http"; @@ -49,7 +45,7 @@ export const legacyLoadStorageConfig = Effect.fnUntraced(function* ( workdir: string, projectRef: string, ) { - const loadOptions: LoadCliConfigOptions = + const loadOptions: InternalLoadCliConfigOptions = projectRef !== "" ? { projectRef, goViperCompat: true } : { goViperCompat: true }; const loaded = yield* loadCliConfig(workdir, loadOptions).pipe( Effect.catchTag( diff --git a/packages/config/src/tls.ts b/apps/cli/src/legacy/shared/kong-local-ca-cert.ts similarity index 100% rename from packages/config/src/tls.ts rename to apps/cli/src/legacy/shared/kong-local-ca-cert.ts diff --git a/packages/config/src/tls.unit.test.ts b/apps/cli/src/legacy/shared/kong-local-ca-cert.unit.test.ts similarity index 81% rename from packages/config/src/tls.unit.test.ts rename to apps/cli/src/legacy/shared/kong-local-ca-cert.unit.test.ts index 1f266f5d80..cbe030bcd5 100644 --- a/packages/config/src/tls.unit.test.ts +++ b/apps/cli/src/legacy/shared/kong-local-ca-cert.unit.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { KONG_LOCAL_CA_CERT } from "./tls.ts"; +import { KONG_LOCAL_CA_CERT } from "./kong-local-ca-cert.ts"; describe("KONG_LOCAL_CA_CERT", () => { it("is a non-empty PEM certificate", () => { diff --git a/apps/cli/src/legacy/shared/legacy-local-config-values.ts b/apps/cli/src/legacy/shared/legacy-local-config-values.ts index 4b6e4042b8..68b8985419 100644 --- a/apps/cli/src/legacy/shared/legacy-local-config-values.ts +++ b/apps/cli/src/legacy/shared/legacy-local-config-values.ts @@ -1,7 +1,8 @@ import { readFileSync } from "node:fs"; import { basename } from "node:path"; -import { ENV_CAPTURE_REGEX, type CliConfig } from "@supabase/config"; +import type { CliConfig } from "@supabase/config"; +import { ENV_CAPTURE_REGEX } from "@supabase/config/internal"; import { defaultJwtSecret, defaultPublishableKey, defaultSecretKey } from "@supabase/stack/effect"; import { Schema } from "effect"; diff --git a/apps/cli/src/legacy/shared/legacy-local-project-context.ts b/apps/cli/src/legacy/shared/legacy-local-project-context.ts index 0a9a46dc71..01cfbf6fc7 100644 --- a/apps/cli/src/legacy/shared/legacy-local-project-context.ts +++ b/apps/cli/src/legacy/shared/legacy-local-project-context.ts @@ -1,10 +1,10 @@ import { - loadCliConfig, loadCliProjectEnvironment, CliConfigSchema, type LoadedCliConfig, type CliConfig, } from "@supabase/config/effect"; +import { loadCliConfig } from "@supabase/config/internal"; import { Effect, FileSystem, Path, Schema } from "effect"; import { LEGACY_BITBUCKET_CLONE_DIR_ENV_KEY } from "./legacy-bitbucket-pipeline.ts"; diff --git a/apps/cli/src/legacy/shared/legacy-seed-buckets.ts b/apps/cli/src/legacy/shared/legacy-seed-buckets.ts index afa752df64..383703b62a 100644 --- a/apps/cli/src/legacy/shared/legacy-seed-buckets.ts +++ b/apps/cli/src/legacy/shared/legacy-seed-buckets.ts @@ -1,9 +1,5 @@ -import { - loadCliConfig, - type LoadCliConfigOptions, - type CliConfig, - CliConfigSchema, -} from "@supabase/config/effect"; +import { type CliConfig, CliConfigSchema } from "@supabase/config/effect"; +import { loadCliConfig, type InternalLoadCliConfigOptions } from "@supabase/config/internal"; import { Effect, FileSystem, Path, Schema } from "effect"; import { FetchHttpClient } from "effect/unstable/http"; import type { PlatformError } from "effect/PlatformError"; @@ -184,7 +180,7 @@ export const legacySeedBucketsRun = Effect.fnUntraced(function* (opts: { // --linked. A parse failure aborts before any network call. Skipped entirely // when the caller already supplied `resolvedConfig` — see that option's doc // comment above. - const loadOptions: LoadCliConfigOptions = + const loadOptions: InternalLoadCliConfigOptions = projectRef !== "" ? { projectRef, goViperCompat: true } : { goViperCompat: true }; const loaded = opts.resolvedConfig !== undefined diff --git a/apps/cli/src/legacy/shared/legacy-storage-credentials.ts b/apps/cli/src/legacy/shared/legacy-storage-credentials.ts index 59cf625f09..07f3ce74d9 100644 --- a/apps/cli/src/legacy/shared/legacy-storage-credentials.ts +++ b/apps/cli/src/legacy/shared/legacy-storage-credentials.ts @@ -1,4 +1,3 @@ -import { KONG_LOCAL_CA_CERT } from "@supabase/config"; import { defaultJwtSecret, generateJwt } from "@supabase/stack/effect"; import { Effect, FileSystem, Path } from "effect"; @@ -7,6 +6,7 @@ import { LegacyCliSettings } from "../config/legacy-cli-settings.service.ts"; import { legacyResolveApiExternalUrl } from "./legacy-api-url.ts"; import { legacyMapTenantApiKeysError } from "./legacy-get-tenant-api-keys.ts"; import { legacyGetHostname } from "./legacy-hostname.ts"; +import { KONG_LOCAL_CA_CERT } from "./kong-local-ca-cert.ts"; import { legacyExtractServiceKeys } from "./legacy-tenant-keys.ts"; import { LegacyStorageApiKeysNetworkError, diff --git a/apps/cli/src/shared/config/project-config-api-drift.unit.test.ts b/apps/cli/src/shared/config/project-config-api-drift.unit.test.ts index 4998105326..8c3cdc6f77 100644 --- a/apps/cli/src/shared/config/project-config-api-drift.unit.test.ts +++ b/apps/cli/src/shared/config/project-config-api-drift.unit.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { V2GetProjectConfigOutput } from "@supabase/api/effect"; -import { toProjectConfig, type ProjectConfigApiAttributes } from "@supabase/config"; +import { toProjectConfig } from "@supabase/config"; +import type { ProjectConfigApiAttributes } from "@supabase/config/internal"; /** * Compile-time drift guards (CLI-2230 design requirement): `@supabase/config` diff --git a/apps/cli/src/shared/config/project-config-auth-contract.unit.test.ts b/apps/cli/src/shared/config/project-config-auth-contract.unit.test.ts index 4e03078143..a92b1a091a 100644 --- a/apps/cli/src/shared/config/project-config-auth-contract.unit.test.ts +++ b/apps/cli/src/shared/config/project-config-auth-contract.unit.test.ts @@ -4,7 +4,7 @@ import { projectConfigMappingRows, unmappedSecretApiPaths, type ProjectConfigMappingRow, -} from "@supabase/config"; +} from "@supabase/config/internal"; /** * Contract-derived auth guard (CLI-2230's residual review): closes two gaps @@ -27,9 +27,10 @@ import { * package must stay decoupled so it can publish to npm independently), so * this guard lives in `apps/cli`, which can import both. It needs `@supabase/ * config`'s row data and orphan-secret list at runtime, which is why - * `projectConfigMappingRows`/`unmappedSecretApiPaths` are exported from the - * package root (`packages/config/src/index.ts`) — otherwise-internal registry - * data, exposed solely so this cross-package guard can walk it. + * `projectConfigMappingRows`/`unmappedSecretApiPaths` are exported from + * `@supabase/config/internal` (`packages/config/src/internal.ts`) — + * otherwise-internal registry data, exposed solely so this cross-package + * guard (and `apps/cli`'s own contract tests) can walk it. * * `V1GetAuthServiceConfigOutput` (not the v2 project-config resource) is the * authority here: it is the generated schema whose field names are the real, diff --git a/apps/cli/src/shared/config/project-config-presence-parity.unit.test.ts b/apps/cli/src/shared/config/project-config-presence-parity.unit.test.ts index 0dd15d541e..b7e936241c 100644 --- a/apps/cli/src/shared/config/project-config-presence-parity.unit.test.ts +++ b/apps/cli/src/shared/config/project-config-presence-parity.unit.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { Schema } from "effect"; -import { AUTH_HOOK_NAMES, CliConfigSchema, fromConfigDocument } from "@supabase/config"; +import { CliConfigSchema, fromConfigDocument } from "@supabase/config"; +import { AUTH_HOOK_NAMES } from "@supabase/config/internal"; import { legacyPresenceIn, type LegacyConfigPushPresence, diff --git a/apps/cli/src/shared/functions/functions-config.ts b/apps/cli/src/shared/functions/functions-config.ts index 2c5cbaf52a..3f59cd8b7f 100644 --- a/apps/cli/src/shared/functions/functions-config.ts +++ b/apps/cli/src/shared/functions/functions-config.ts @@ -1,6 +1,7 @@ import { basename } from "node:path"; import { Effect, type FileSystem, type Path } from "effect"; -import { loadCliConfig, type LoadedCliConfig } from "@supabase/config/effect"; +import type { LoadedCliConfig } from "@supabase/config/effect"; +import { loadCliConfig } from "@supabase/config/internal"; import { normalizeProjectId } from "./functions-docker.ts"; /** diff --git a/apps/cli/src/shared/functions/serve.ts b/apps/cli/src/shared/functions/serve.ts index a763e2ae78..eeccfc21a0 100644 --- a/apps/cli/src/shared/functions/serve.ts +++ b/apps/cli/src/shared/functions/serve.ts @@ -2,14 +2,16 @@ import { CliConfigSchema, findCliProjectPaths, inferFunctionsManifest, - loadCliConfig, - resolveCliConfigSubtree, - resolveCliConfigValue, type CliConfig, type CliProjectEnvironment, type ResolvedCliConfigValue, type ResolvedFunctionConfig as ManifestFunctionConfig, } from "@supabase/config/effect"; +import { + loadCliConfig, + resolveCliConfigSubtree, + resolveCliConfigValue, +} from "@supabase/config/internal"; import { defaultJwtSecret, defaultPublishableKey, diff --git a/packages/config/AGENTS.md b/packages/config/AGENTS.md index 4556305cc4..08c4fa3fe3 100644 --- a/packages/config/AGENTS.md +++ b/packages/config/AGENTS.md @@ -5,7 +5,7 @@ Supabase project configuration package built on Effect V4 Schema — owns the ca ## Entrypoints -Three entrypoints plus a generated artifact (see ADR 0009's 2026-08-24 decision for the full +Four entrypoints plus a generated artifact (see ADR 0009's 2026-08-24 decision for the full rationale): - `@supabase/config` (`.`) — pure, browser/edge-safe surface. The `CliConfigSchema` and @@ -18,6 +18,13 @@ rationale): - `@supabase/config/effect` — the Effect-native superset. Re-exports everything from `.` plus the Effect-returning config-loading/saving programs, `CliConfigStore`/`cliConfigStoreLayer`, project-environment resolution, and functions-manifest inference. +- `@supabase/config/internal` (CLI-2234) — NOT covered by semver. Exists solely for `apps/cli`'s + own Go-parity call sites and contract-guard tests: the internal-only `goViperCompat` typings + (`InternalLoadCliConfigOptions`/`InternalResolveCliConfigOptions`) and the otherwise-internal + registry data (`AUTH_HOOK_NAMES`, `unmappedSecretApiPaths`, `projectConfigMappingRows`, + `ProjectConfigMappingRow`, `ProjectConfigApiAttributes`, `ENV_CAPTURE_REGEX`). Unlike `./io`, + `apps/cli` IS an expected consumer of this subpath. Anything here can change or vanish in any + release. - `@supabase/config/schema.json` — generated JSON Schema for `CliConfig` (a `dist/` build output). @@ -31,7 +38,11 @@ rationale): from `@supabase/config`. - `@supabase/config/io` is exclusively for external consumers outside this monorepo that aren't Effect-native. Do not add an internal consumer of it. -- Never deep-import this package's internals (e.g. `@supabase/config/src/io.ts`). Only the four +- `@supabase/config/internal` is for `apps/cli`'s own Go-parity call sites and contract-guard + tests only — a symbol that needs the internal-only `goViperCompat` typings, or the internal + registry data, imports it from there; every other symbol in the same import statement stays on + its public specifier (`.`/`./effect`). +- Never deep-import this package's internals (e.g. `@supabase/config/src/io.ts`). Only the five entrypoints above are supported import paths. ## Pure-graph invariant diff --git a/packages/config/docs/cli-config-loading.md b/packages/config/docs/cli-config-loading.md index 45266b7647..7cd6ddd2a8 100644 --- a/packages/config/docs/cli-config-loading.md +++ b/packages/config/docs/cli-config-loading.md @@ -61,19 +61,17 @@ against `packages/config/src/node.ts`/`bun.ts`) are: - `loadCliConfig` - `saveCliConfig` - `loadCliConfigFile` -- `findCliProjectRootFor` -- `findCliProjectPathsFor` -- `loadCliProjectEnvironmentFor` -- `loadFunctionsManifest` - -`loadCliConfig`, `saveCliConfig`, and `loadCliConfigFile` share their name with the `./effect` -program they wrap (only the return type changes, `Effect` to `Promise`). The other four don't: -`findCliProjectRootFor`/`findCliProjectPathsFor`/`loadCliProjectEnvironmentFor` add a `For` suffix -their `./effect` counterparts (`findCliProjectRoot`, `findCliProjectPaths`, -`loadCliProjectEnvironment`) don't carry, and `loadFunctionsManifest` wraps `./effect`'s -`inferFunctionsManifest` under an unrelated verb. Settling this naming — the `For` suffix -convention, and the `loadFunctionsManifest`/`inferFunctionsManifest` divergence — is tracked in -CLI-2234. +- `findCliProjectRoot` +- `findCliProjectPaths` +- `loadCliProjectEnvironment` +- `inferFunctionsManifest` + +Every name here matches its `./effect` counterpart one-to-one (only the return type changes, +`Effect` to `Promise`) — the subpath itself (`/io` vs `/effect`) is what conveys Promise-vs-Effect. +`findCliProjectRootFor`/`findCliProjectPathsFor`/`loadCliProjectEnvironmentFor`/ +`loadFunctionsManifest` were the pre-CLI-2234 names: the first three carried a `For` suffix their +`./effect` counterparts didn't, and the fourth wrapped `./effect`'s `inferFunctionsManifest` under +an unrelated verb. CLI-2234 renamed all four to match. ## Overview @@ -209,7 +207,10 @@ literal, unresolved `env(NAME)`. ## Lazy `env(NAME)` Resolution A caller can also resolve `env(NAME)` references explicitly, after config is loaded. The package -exposes two helpers, from `@supabase/config/effect`: +exposes two helpers, under the same names from both `.` (plain, synchronous — throws instead of +failing an `Effect`) and `@supabase/config/effect` (Effect-typed; the Effect-typed variant wins +when both are in scope via `@supabase/config/effect`, since explicit named exports take precedence +over a star re-export of the same name): - `resolveCliConfigValue(value, cliProjectEnv, configPath, options?)` - `resolveCliConfigSubtree(value, cliProjectEnv, pathPrefix, options?)` @@ -237,7 +238,10 @@ resolves and redacts leaves nested inside `[remotes.*]` blocks. An optional `goViperCompat` flag switches the `env(NAME)` matcher from the default, strict `SCREAMING_SNAKE_CASE`-only pattern to Go/viper's case-agnostic `^env\((.*)\)$` form; only the -Go-parity legacy shell sets it. +Go-parity legacy shell sets it. `goViperCompat` is not part of the public `ResolveCliConfigOptions` +type on `.`/`./effect` — it is internal-only (CLI-2234), typed on `InternalResolveCliConfigOptions` +and exported from `@supabase/config/internal`, which `apps/cli`'s Go-parity call sites import from +instead. Callers such as `functions serve`/`functions dev`, `secrets set`, and `start` call these resolvers on the subtrees they actually need (e.g. `auth`, `edge_runtime`, `functions`), so dormant diff --git a/packages/config/package.json b/packages/config/package.json index 042c7974f4..0e64bfd649 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -5,6 +5,7 @@ "type": "module", "exports": { ".": "./src/index.ts", + "./internal": "./src/internal.ts", "./io": { "bun": "./src/bun.ts", "node": "./src/node.ts", diff --git a/packages/config/src/bun.ts b/packages/config/src/bun.ts index 979fef9209..dffb36ef32 100644 --- a/packages/config/src/bun.ts +++ b/packages/config/src/bun.ts @@ -7,12 +7,12 @@ const cliConfigIo: CliConfigIo = makeCliConfigIo( ); export const loadCliConfig = cliConfigIo.loadCliConfig; -export const findCliProjectRootFor = cliConfigIo.findCliProjectRootFor; -export const findCliProjectPathsFor = cliConfigIo.findCliProjectPathsFor; +export const findCliProjectRoot = cliConfigIo.findCliProjectRoot; +export const findCliProjectPaths = cliConfigIo.findCliProjectPaths; export const loadCliConfigFile = cliConfigIo.loadCliConfigFile; -export const loadCliProjectEnvironmentFor = cliConfigIo.loadCliProjectEnvironmentFor; +export const loadCliProjectEnvironment = cliConfigIo.loadCliProjectEnvironment; export const saveCliConfig = cliConfigIo.saveCliConfig; -export const loadFunctionsManifest = cliConfigIo.loadFunctionsManifest; +export const inferFunctionsManifest = cliConfigIo.inferFunctionsManifest; export type { CliConfigIo } from "./promise-facade.ts"; // Re-exports every pure symbol from `.` (types, schema, errors, etc.) so // `./io` consumers can name `LoadedCliConfig`/`CliProjectPaths`/etc. without diff --git a/packages/config/src/cli-config.service.ts b/packages/config/src/cli-config.service.ts index 9493751bdb..2820deadac 100644 --- a/packages/config/src/cli-config.service.ts +++ b/packages/config/src/cli-config.service.ts @@ -1,18 +1,42 @@ import type { Effect } from "effect"; import { Context } from "effect"; +import type { PlatformError } from "effect/PlatformError"; import type { LoadedCliConfig, LoadCliConfigOptions, SaveCliConfigOptions, } from "./config-document.ts"; +import type { + CliConfigParseError, + CliProjectEnvParseError, + DuplicateRemoteProjectIdError, + InvalidRemoteProjectIdError, +} from "./errors.ts"; + +/** + * Every error a `load`/`loadFile`/`save` rejection can carry: this package's + * own tagged failures (a malformed config document, a duplicate or + * malformed `[remotes.*]` block, a malformed `.env`/`.env.local` file) plus + * `PlatformError`, the single tagged wrapper Effect's `FileSystem` service + * uses for every host/OS failure (`effect/PlatformError`). A Promise-based + * consumer (`@supabase/config/io`) can distinguish these via `instanceof`. + */ +type CliConfigStoreError = + | CliConfigParseError + | DuplicateRemoteProjectIdError + | InvalidRemoteProjectIdError + | CliProjectEnvParseError + | PlatformError; interface CliConfigStoreShape { readonly load: ( cwd: string, options?: LoadCliConfigOptions, - ) => Effect.Effect; - readonly loadFile: (path: string) => Effect.Effect; - readonly save: (options: SaveCliConfigOptions) => Effect.Effect; + ) => Effect.Effect; + readonly loadFile: (path: string) => Effect.Effect; + readonly save: ( + options: SaveCliConfigOptions, + ) => Effect.Effect; } export class CliConfigStore extends Context.Service()( diff --git a/packages/config/src/config-document.ts b/packages/config/src/config-document.ts index 2cb90095bb..5718ae7777 100644 --- a/packages/config/src/config-document.ts +++ b/packages/config/src/config-document.ts @@ -71,7 +71,7 @@ export const cliConfigValueSourceAt = ( * duplicate-`project_id`/project-ref-format checks across every * `[remotes.*]` block (`config.go:594-602,996-1001`) run unconditionally on * every config load in Go, not only when a caller ends up selecting a - * remote — but here they only run when {@link LoadCliConfigOptions.goViperCompat} + * remote — but here they only run when {@link InternalLoadCliConfigOptions.goViperCompat} * is `true`, regardless of whether `projectRef` is set, so non-Go-parity * callers that never select a remote (and never opt into Go parity) aren't * broken by an unrelated duplicate/malformed `[remotes.*]` block. @@ -99,6 +99,13 @@ export interface LoadCliConfigOptions { * would never see. */ readonly tomlOnly?: boolean; +} + +/** + * Not covered by semver — exported from `@supabase/config/internal` only. See + * that module's header for why. + */ +export interface InternalLoadCliConfigOptions extends LoadCliConfigOptions { /** * Opt into the Go/viper-parity decode+validation semantics this loader * otherwise omits, so only the Go-parity legacy shell (and shared modules diff --git a/packages/config/src/effect.ts b/packages/config/src/effect.ts index b73e31107b..9354d2f86b 100644 --- a/packages/config/src/effect.ts +++ b/packages/config/src/effect.ts @@ -1,19 +1,64 @@ // Effect-native surface — superset of the default entrypoint. export * from "./index.ts"; -export { - configJsonPath, - configTomlPath, - loadCliConfig, - loadCliConfigFile, - saveCliConfig, -} from "./io.ts"; +import type { Effect } from "effect"; +import type { LoadCliConfigOptions } from "./config-document.ts"; +import type { ResolvedCliConfigValue, ResolveCliConfigOptions } from "./lib/resolve.ts"; +import * as io from "./io.ts"; +import type { CliProjectEnvironment } from "./project.ts"; +import * as project from "./project.ts"; + +export { configJsonPath, configTomlPath, saveCliConfig } from "./io.ts"; + +/** + * Narrowed to the public `LoadCliConfigOptions` (no `goViperCompat`). The + * underlying implementation in `./io.ts` is typed against the wider + * `InternalLoadCliConfigOptions` (a strict superset — one additional optional + * field), so assigning it here is a safe, cast-free narrowing: a function + * accepting the wider options type is assignable to a variable typed to + * accept only the narrower one. `@supabase/config/internal` re-exports this + * same runtime function typed to additionally show `goViperCompat`. + */ +export const loadCliConfig: ( + cwd: string, + options?: LoadCliConfigOptions, +) => ReturnType = io.loadCliConfig; + +/** See {@link loadCliConfig}'s doc comment for the narrowing rationale. */ +export const loadCliConfigFile: ( + filePath: string, + options?: LoadCliConfigOptions, +) => ReturnType = io.loadCliConfigFile; + export { inferFunctionsManifest } from "./functions-manifest.ts"; -export { - loadDotEnvFile, - loadCliProjectEnvironment, - resolveCliConfigSubtree, - resolveCliConfigValue, -} from "./project.ts"; +export { loadDotEnvFile, loadCliProjectEnvironment } from "./project.ts"; + +/** + * Explicit named exports take precedence over `export * from "./index.ts"` + * above for a shared name (ESM re-export resolution), so these Effect-typed + * variants deliberately shadow `./index.ts`'s plain sync + * `resolveCliConfigValue`/`resolveCliConfigSubtree` on this subpath — the + * Effect-typed variant wins on `./effect`; the sync variant lives on `.`. + * + * Narrowed to the public `ResolveCliConfigOptions` (no `goViperCompat`) for + * the same reason as {@link loadCliConfig} above; `@supabase/config/internal` + * re-exports these same runtime functions typed to additionally show + * `goViperCompat`. + */ +export const resolveCliConfigValue: ( + value: T, + cliProjectEnv: Pick, + configPath: string, + options?: ResolveCliConfigOptions, +) => Effect.Effect> = project.resolveCliConfigValue; + +/** See {@link resolveCliConfigValue}'s doc comment for the shadowing and narrowing rationale. */ +export const resolveCliConfigSubtree: ( + value: T, + cliProjectEnv: Pick, + pathPrefix: string, + options?: ResolveCliConfigOptions, +) => Effect.Effect> = project.resolveCliConfigSubtree; + export { findCliProjectPaths, findCliProjectRoot } from "./paths.ts"; export { cliConfigStoreLayer } from "./cli-config.layer.ts"; export { CliConfigStore } from "./cli-config.service.ts"; diff --git a/packages/config/src/entrypoint-purity.unit.test.ts b/packages/config/src/entrypoint-purity.unit.test.ts index 696c7d0b4e..e883720991 100644 --- a/packages/config/src/entrypoint-purity.unit.test.ts +++ b/packages/config/src/entrypoint-purity.unit.test.ts @@ -5,6 +5,7 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import * as defaultEntrypoint from "./index.ts"; import * as effectEntrypoint from "./effect.ts"; +import * as internalEntrypoint from "./internal.ts"; // `src/index.ts` is the entrypoint Studio (a browser bundle) imports // directly. It must stay bundlable with no Node/Bun runtime underneath it — @@ -21,6 +22,7 @@ const packageRoot = join(srcDir, ".."); const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")) as { readonly exports: { readonly ".": string; + readonly "./internal": string; readonly "./io": Readonly>; readonly "./effect": string; readonly "./schema.json": string; @@ -280,8 +282,8 @@ const expectedPureGraphFiles = [ "functions-manifest-model.ts", "sparse.ts", "schema-metadata.ts", - "tls.ts", "lib/env.ts", + "lib/resolve.ts", "lib/schema.ts", "lib/secret-paths.ts", "project-config/api-attributes.ts", @@ -338,15 +340,12 @@ describe("src/index.ts export surface", () => { test("pins the exact set of runtime export names", () => { expect(Object.keys(defaultEntrypoint).sort()).toMatchInlineSnapshot(` [ - "AUTH_HOOK_NAMES", "CLI_CONFIG_SCHEMA_URL", "CliConfigParseError", "CliConfigSchema", "CliProjectEnvParseError", "DuplicateRemoteProjectIdError", - "ENV_CAPTURE_REGEX", "InvalidRemoteProjectIdError", - "KONG_LOCAL_CA_CERT", "MissingCliConfigValueError", "ProjectConfigParseError", "attachApiResponse", @@ -362,12 +361,12 @@ describe("src/index.ts export surface", () => { "getDefaultCliConfig", "isComparableProjectConfigPath", "omitDefaultValues", - "projectConfigMappingRows", + "resolveCliConfigSubtree", + "resolveCliConfigValue", "subtractCliConfig", "toCliConfigJsonSchema", "toProjectConfig", "unmappedApiFields", - "unmappedSecretApiPaths", ] `); }); @@ -377,16 +376,13 @@ describe("src/effect.ts is a superset of src/index.ts", () => { test("pins the exact set of runtime export names", () => { expect(Object.keys(effectEntrypoint).sort()).toMatchInlineSnapshot(` [ - "AUTH_HOOK_NAMES", "CLI_CONFIG_SCHEMA_URL", "CliConfigParseError", "CliConfigSchema", "CliConfigStore", "CliProjectEnvParseError", "DuplicateRemoteProjectIdError", - "ENV_CAPTURE_REGEX", "InvalidRemoteProjectIdError", - "KONG_LOCAL_CA_CERT", "MissingCliConfigValueError", "ProjectConfigParseError", "attachApiResponse", @@ -412,7 +408,6 @@ describe("src/effect.ts is a superset of src/index.ts", () => { "loadCliProjectEnvironment", "loadDotEnvFile", "omitDefaultValues", - "projectConfigMappingRows", "resolveCliConfigSubtree", "resolveCliConfigValue", "saveCliConfig", @@ -420,12 +415,17 @@ describe("src/effect.ts is a superset of src/index.ts", () => { "toCliConfigJsonSchema", "toProjectConfig", "unmappedApiFields", - "unmappedSecretApiPaths", ] `); }); - test("every runtime export key of index.ts is also exported by effect.ts, with an identical (not shadowed) binding", () => { + // `resolveCliConfigValue`/`resolveCliConfigSubtree` are the one deliberate + // exception (see `effect.ts`'s doc comment): `./effect`'s Effect-typed + // variant intentionally shadows `./index.ts`'s plain sync variant, since + // explicit named exports win over a star re-export of the same name. + const deliberatelyShadowedKeys = new Set(["resolveCliConfigValue", "resolveCliConfigSubtree"]); + + test("every runtime export key of index.ts is also exported by effect.ts, identically bound except the deliberately shadowed resolve helpers", () => { const defaultKeys = Object.keys(defaultEntrypoint); // Guards against both namespace objects being empty due to a broken @@ -438,13 +438,53 @@ describe("src/effect.ts is a superset of src/index.ts", () => { } const defaultValue = (defaultEntrypoint as Record)[key]; const effectValue = (effectEntrypoint as Record)[key]; - return effectValue === defaultValue ? [] : [`mismatched (shadowed): ${key}`]; + const identical = effectValue === defaultValue; + if (deliberatelyShadowedKeys.has(key)) { + return identical + ? [`expected ${key} to be shadowed on ./effect, but it was identical`] + : []; + } + return identical ? [] : [`mismatched (shadowed): ${key}`]; }); expect(mismatches).toEqual([]); }); }); +describe("src/internal.ts export surface", () => { + test("pins the exact set of runtime export names", () => { + expect(Object.keys(internalEntrypoint).sort()).toMatchInlineSnapshot(` + [ + "AUTH_HOOK_NAMES", + "ENV_CAPTURE_REGEX", + "loadCliConfig", + "loadCliConfigFile", + "projectConfigMappingRows", + "resolveCliConfigSubtree", + "resolveCliConfigValue", + "unmappedSecretApiPaths", + ] + `); + }); + + // `./internal`'s `resolveCliConfigValue`/`resolveCliConfigSubtree`/ + // `loadCliConfig` are the SAME runtime functions `./effect` exports + // (only the accepted options TYPE differs — internal.ts's is the wider, + // `goViperCompat`-capable one), so unlike the deliberate shadowing between + // `.` and `./effect` above, there is no shadowing to assert here. + test("resolveCliConfigValue, resolveCliConfigSubtree, and loadCliConfig are identical to effect.ts's bindings", () => { + for (const key of [ + "resolveCliConfigValue", + "resolveCliConfigSubtree", + "loadCliConfig", + ] as const) { + expect((internalEntrypoint as Record)[key]).toBe( + (effectEntrypoint as Record)[key], + ); + } + }); +}); + describe("package.json exports map", () => { test("./io exposes exactly the bun/node/browser/default conditions, in that order", () => { const ioExports = packageJson.exports["./io"]; @@ -458,10 +498,10 @@ describe("package.json exports map", () => { } }); - test("the '.' and './effect' export targets exist on disk", () => { + test("the '.', './effect', and './internal' export targets exist on disk", () => { // `./schema.json` is a build output (`dist/schema.json`) and intentionally // skipped here — it only exists after running `pnpm run build`. - for (const key of [".", "./effect"] as const) { + for (const key of [".", "./effect", "./internal"] as const) { const target = packageJson.exports[key]; expect(() => readFileSync(join(packageRoot, target))).not.toThrow(); } diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index ce27b13fdb..eba155d811 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -38,12 +38,13 @@ export { type FunctionsManifest, type ResolvedFunctionConfig, } from "./functions-manifest-model.ts"; -export type { - LoadCliProjectEnvironmentOptions, - CliProjectEnvironment, - ResolvedCliConfigValue, - ResolveCliConfigOptions, -} from "./project.ts"; +export type { LoadCliProjectEnvironmentOptions, CliProjectEnvironment } from "./project.ts"; +export { + type ResolvedCliConfigValue, + type ResolveCliConfigOptions, + resolveCliConfigValue, + resolveCliConfigSubtree, +} from "./lib/resolve.ts"; export type { CliProjectPaths } from "./paths.ts"; export { CLI_CONFIG_SCHEMA_URL } from "./schema-metadata.ts"; export { @@ -53,8 +54,6 @@ export { omitDefaultValues, subtractCliConfig, } from "./sparse.ts"; -export { KONG_LOCAL_CA_CERT } from "./tls.ts"; -export { ENV_CAPTURE_REGEX } from "./lib/env.ts"; export { type CliConfigWithRawPresence, type ProjectConfig, @@ -68,7 +67,3 @@ export { toProjectConfig, unmappedApiFields, } from "./project-config/project-config.ts"; -export { type ProjectConfigApiAttributes } from "./project-config/api-attributes.ts"; -export { type ProjectConfigMappingRow } from "./project-config/registry-row.ts"; -export { projectConfigMappingRows } from "./project-config/registry.ts"; -export { AUTH_HOOK_NAMES, unmappedSecretApiPaths } from "./project-config/registry-auth.ts"; diff --git a/packages/config/src/internal.ts b/packages/config/src/internal.ts new file mode 100644 index 0000000000..5af9f06c2b --- /dev/null +++ b/packages/config/src/internal.ts @@ -0,0 +1,18 @@ +/** + * NOT covered by semver. This subpath exists solely for `apps/cli`'s own use + * and its contract-guard tests — every export here (its existence, its shape, + * its behavior) can change or vanish in any release without notice. External + * consumers must use `.`, `./effect`, or `./io` instead. + */ +export { ENV_CAPTURE_REGEX } from "./lib/env.ts"; +export { AUTH_HOOK_NAMES, unmappedSecretApiPaths } from "./project-config/registry-auth.ts"; +export { projectConfigMappingRows } from "./project-config/registry.ts"; +export { type ProjectConfigMappingRow } from "./project-config/registry-row.ts"; +export { type ProjectConfigApiAttributes } from "./project-config/api-attributes.ts"; +export { type InternalLoadCliConfigOptions } from "./config-document.ts"; +export { + type InternalResolveCliConfigOptions, + resolveCliConfigValue, + resolveCliConfigSubtree, +} from "./project.ts"; +export { loadCliConfig, loadCliConfigFile } from "./io.ts"; diff --git a/packages/config/src/io-browser.ts b/packages/config/src/io-browser.ts index 4d5918832d..66ea9bf9b0 100644 --- a/packages/config/src/io-browser.ts +++ b/packages/config/src/io-browser.ts @@ -23,21 +23,21 @@ async function unavailableInBrowser(): Promise { // the real facades' export shape. const cliConfigIo: CliConfigIo = { loadCliConfig: unavailableInBrowser, - findCliProjectRootFor: unavailableInBrowser, - findCliProjectPathsFor: unavailableInBrowser, + findCliProjectRoot: unavailableInBrowser, + findCliProjectPaths: unavailableInBrowser, loadCliConfigFile: unavailableInBrowser, - loadCliProjectEnvironmentFor: unavailableInBrowser, + loadCliProjectEnvironment: unavailableInBrowser, saveCliConfig: unavailableInBrowser, - loadFunctionsManifest: unavailableInBrowser, + inferFunctionsManifest: unavailableInBrowser, }; export const loadCliConfig = cliConfigIo.loadCliConfig; -export const findCliProjectRootFor = cliConfigIo.findCliProjectRootFor; -export const findCliProjectPathsFor = cliConfigIo.findCliProjectPathsFor; +export const findCliProjectRoot = cliConfigIo.findCliProjectRoot; +export const findCliProjectPaths = cliConfigIo.findCliProjectPaths; export const loadCliConfigFile = cliConfigIo.loadCliConfigFile; -export const loadCliProjectEnvironmentFor = cliConfigIo.loadCliProjectEnvironmentFor; +export const loadCliProjectEnvironment = cliConfigIo.loadCliProjectEnvironment; export const saveCliConfig = cliConfigIo.saveCliConfig; -export const loadFunctionsManifest = cliConfigIo.loadFunctionsManifest; +export const inferFunctionsManifest = cliConfigIo.inferFunctionsManifest; export type { CliConfigIo } from "./promise-facade.ts"; // Re-exports every pure symbol from `.` (types, schema, errors, etc.) so // `./io` consumers can name `LoadedCliConfig`/`CliProjectPaths`/etc. without diff --git a/packages/config/src/io.ts b/packages/config/src/io.ts index c8fb9a9d8d..30f2c1e6db 100644 --- a/packages/config/src/io.ts +++ b/packages/config/src/io.ts @@ -6,7 +6,7 @@ import { encodeCliConfigToTomlDocument, isObject, type LoadedCliConfig, - type LoadCliConfigOptions, + type InternalLoadCliConfigOptions, cliConfigSchemaKey, type CliConfigValueSource, type SaveCliConfigOptions, @@ -482,7 +482,7 @@ export const configTomlPath = Effect.fnUntraced(function* (cwd: string) { export const loadCliConfigFile = Effect.fnUntraced(function* ( filePath: string, - options?: LoadCliConfigOptions, + options?: InternalLoadCliConfigOptions, ) { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; @@ -640,7 +640,7 @@ export const loadCliConfigFile = Effect.fnUntraced(function* ( export const loadCliConfig = Effect.fnUntraced(function* ( cwd: string, - options?: LoadCliConfigOptions, + options?: InternalLoadCliConfigOptions, ) { const fs = yield* FileSystem.FileSystem; const project = yield* findCliProjectPaths(cwd, { search: options?.search }); diff --git a/packages/config/src/io.unit.test.ts b/packages/config/src/io.unit.test.ts index 89b20a451c..43cd8dd257 100644 --- a/packages/config/src/io.unit.test.ts +++ b/packages/config/src/io.unit.test.ts @@ -13,7 +13,7 @@ import { encodeCliConfigToToml, cliConfigValueSourceAt, type LoadedCliConfig, - type LoadCliConfigOptions, + type InternalLoadCliConfigOptions, } from "./config-document.ts"; import { configJsonPath, @@ -2173,7 +2173,7 @@ describe("config io deprecated [auth.external.{linkedin,slack}] back-compat", () errorSpy = undefined; }); - async function loadToml(contents: string, options?: LoadCliConfigOptions) { + async function loadToml(contents: string, options?: InternalLoadCliConfigOptions) { const cwd = makeTempProject(); const path = await runConfigEffect(configTomlPath(cwd)); await mkdir(join(cwd, "supabase"), { recursive: true }); diff --git a/packages/config/src/lib/resolve.ts b/packages/config/src/lib/resolve.ts new file mode 100644 index 0000000000..e7ba3eb5de --- /dev/null +++ b/packages/config/src/lib/resolve.ts @@ -0,0 +1,171 @@ +import { Redacted } from "effect"; +import { isEnvReference, ENV_CAPTURE_REGEX, ENV_CAPTURE_REGEX_STRICT } from "./env.ts"; +import { isSecretPath } from "./secret-paths.ts"; +import type { CliProjectEnvironment } from "../project.ts"; + +type ResolvedString = string | Redacted.Redacted; + +export type ResolvedCliConfigValue = T extends string + ? ResolvedString + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends Array + ? Array> + : T extends Record + ? { readonly [K in keyof T]: ResolvedCliConfigValue } & { + readonly [key: string]: ResolvedCliConfigValue; + } + : T extends object + ? { readonly [K in keyof T]: ResolvedCliConfigValue } + : T; + +/** + * Currently empty: this package's one `resolveCliConfigValue`/ + * `resolveCliConfigSubtree` option (`goViperCompat`) is internal-only — see + * {@link InternalResolveCliConfigOptions} in `../project.ts`, exported from + * `@supabase/config/internal`. Kept as a named type (rather than removed + * entirely) so the public sync resolvers below have a stable options + * parameter to extend if a public knob is ever added. + */ +export interface ResolveCliConfigOptions {} + +export function toPathSegments(path: string): ReadonlyArray { + if (path === "") { + return []; + } + + return path.split(".").filter((segment) => segment.length > 0); +} + +function interpolateLeafValue( + value: string, + env: Readonly>, + goViperCompat: boolean, +): string { + const match = (goViperCompat ? ENV_CAPTURE_REGEX : ENV_CAPTURE_REGEX_STRICT).exec(value); + const envName = match?.[1]; + + if (envName === undefined) { + return value; + } + + const resolved = env[envName]; + // Preserve the literal `env(VAR)` verbatim when VAR is unset OR present but + // empty (e.g. a dotenv `KEY=` line). Matches Go's `LoadEnvHook` + // (`apps/cli-go/pkg/config/decode_hooks.go:19-24`: `len(env) > 0`), which + // only substitutes a non-empty value — same gate as `substituteEnvLeaf` in + // `./env.ts`. Without this, a present-but-empty `env(...)` secret (e.g. + // `edge_runtime.secrets.FOO = "env(EMPTY)"`) resolves to `""` here, gets + // redacted by `redactValue` as a real value instead of skipped as an + // unresolved literal, and `secrets set` uploads a blank secret Go would + // never send. + if (resolved === undefined || resolved === "") { + return value; + } + + return resolved; +} + +function interpolateValue( + value: unknown, + env: Readonly>, + goViperCompat: boolean, +): unknown { + if (Array.isArray(value)) { + return value.map((item) => interpolateValue(item, env, goViperCompat)); + } + + if (typeof value === "object" && value !== null) { + const result: Record = {}; + + for (const [key, child] of Object.entries(value)) { + result[key] = interpolateValue(child, env, goViperCompat); + } + + return result; + } + + if (typeof value === "string") { + return interpolateLeafValue(value, env, goViperCompat); + } + + return value; +} + +function redactValue(value: unknown, path: ReadonlyArray, goViperCompat: boolean): unknown { + if (Array.isArray(value)) { + return value.map((item, index) => redactValue(item, [...path, String(index)], goViperCompat)); + } + + if (typeof value === "object" && value !== null) { + const result: Record = {}; + + for (const [key, child] of Object.entries(value)) { + result[key] = redactValue(child, [...path, key], goViperCompat); + } + + return result; + } + + if (typeof value === "string" && isSecretPath(path) && !isEnvReference(value, goViperCompat)) { + return Redacted.make(value, { label: path.join(".") }); + } + + return value; +} + +/** + * Shared by the plain sync resolvers below and `../project.ts`'s + * Effect-typed `resolveCliConfigValue`/`resolveCliConfigSubtree` (which wrap + * this in `Effect.sync` and additionally accept the internal-only + * `goViperCompat` option). + */ +export function resolveCliConfigValueAtPath( + value: unknown, + cliProjectEnv: Pick, + path: ReadonlyArray, + goViperCompat: boolean, +): unknown { + const interpolated = interpolateValue(value, cliProjectEnv.values, goViperCompat); + return redactValue(interpolated, path, goViperCompat); +} + +/** + * Plain synchronous counterpart of `../project.ts`'s Effect-typed + * `resolveCliConfigValue`, exported from `.` under the same name — `./effect` + * re-exports the Effect-typed variant explicitly, which wins over this one's + * star re-export through `./index.ts` (see `../effect.ts`'s doc comment). + * + * `cliProjectEnv` only needs `.values` (`Pick`) — + * a caller that already has a project's env values but not the full + * `CliProjectEnvironment` shape (e.g. `paths`/`loadedPaths`/`sources`) can pass + * `{ values }` directly instead of threading through the whole loaded object. + */ +export function resolveCliConfigValue( + value: T, + cliProjectEnv: Pick, + configPath: string, + _options?: ResolveCliConfigOptions, +): ResolvedCliConfigValue { + return resolveCliConfigValueAtPath( + value, + cliProjectEnv, + toPathSegments(configPath), + false, + ) as ResolvedCliConfigValue; +} + +/** See {@link resolveCliConfigValue}'s doc comment for why `cliProjectEnv` only needs `.values`. */ +export function resolveCliConfigSubtree( + value: T, + cliProjectEnv: Pick, + pathPrefix: string, + _options?: ResolveCliConfigOptions, +): ResolvedCliConfigValue { + return resolveCliConfigValueAtPath( + value, + cliProjectEnv, + toPathSegments(pathPrefix), + false, + ) as ResolvedCliConfigValue; +} diff --git a/packages/config/src/monorepo-import-contract.unit.test.ts b/packages/config/src/monorepo-import-contract.unit.test.ts index 87bbabe479..7fdb6baf43 100644 --- a/packages/config/src/monorepo-import-contract.unit.test.ts +++ b/packages/config/src/monorepo-import-contract.unit.test.ts @@ -7,7 +7,9 @@ import { fileURLToPath } from "node:url"; // ("Monorepo import rule"): `@supabase/config/io` has zero internal // consumers by design (it exists only for external, non-Effect-native // Node/Bun code), and this package's internals must never be deep-imported -// (only the `.`/`./io`/`./effect` entrypoints are supported import paths). +// (only the `.`/`./io`/`./effect`/`./internal` entrypoints are supported +// import paths — `@supabase/config/internal` is deliberately NOT checked by +// either rule below: unlike `./io`, `apps/cli` is an expected consumer). // // A plain substring scan (no parsing) is enough for this — it's fast and the // two forbidden specifiers can't appear by accident. The forbidden strings diff --git a/packages/config/src/node.ts b/packages/config/src/node.ts index 5364151d6e..c1cfe4e695 100644 --- a/packages/config/src/node.ts +++ b/packages/config/src/node.ts @@ -7,12 +7,12 @@ const cliConfigIo: CliConfigIo = makeCliConfigIo( ); export const loadCliConfig = cliConfigIo.loadCliConfig; -export const findCliProjectRootFor = cliConfigIo.findCliProjectRootFor; -export const findCliProjectPathsFor = cliConfigIo.findCliProjectPathsFor; +export const findCliProjectRoot = cliConfigIo.findCliProjectRoot; +export const findCliProjectPaths = cliConfigIo.findCliProjectPaths; export const loadCliConfigFile = cliConfigIo.loadCliConfigFile; -export const loadCliProjectEnvironmentFor = cliConfigIo.loadCliProjectEnvironmentFor; +export const loadCliProjectEnvironment = cliConfigIo.loadCliProjectEnvironment; export const saveCliConfig = cliConfigIo.saveCliConfig; -export const loadFunctionsManifest = cliConfigIo.loadFunctionsManifest; +export const inferFunctionsManifest = cliConfigIo.inferFunctionsManifest; export type { CliConfigIo } from "./promise-facade.ts"; // Re-exports every pure symbol from `.` (types, schema, errors, etc.) so // `./io` consumers can name `LoadedCliConfig`/`CliProjectPaths`/etc. without diff --git a/packages/config/src/project.ts b/packages/config/src/project.ts index 489375182a..229ebee950 100644 --- a/packages/config/src/project.ts +++ b/packages/config/src/project.ts @@ -1,7 +1,11 @@ -import { Effect, FileSystem, Redacted } from "effect"; +import { Effect, FileSystem } from "effect"; import { CliProjectEnvParseError } from "./errors.ts"; -import { ENV_CAPTURE_REGEX, ENV_CAPTURE_REGEX_STRICT, isEnvReference } from "./lib/env.ts"; -import { isSecretPath } from "./lib/secret-paths.ts"; +import { + resolveCliConfigValueAtPath, + toPathSegments, + type ResolvedCliConfigValue, + type ResolveCliConfigOptions, +} from "./lib/resolve.ts"; import { findCliProjectPaths, type CliProjectPaths } from "./paths.ts"; const dotEnvLinePattern = @@ -14,22 +18,6 @@ export interface CliProjectEnvironment { readonly sources: Readonly>; } -type ResolvedString = string | Redacted.Redacted; - -export type ResolvedCliConfigValue = T extends string - ? ResolvedString - : T extends ReadonlyArray - ? ReadonlyArray> - : T extends Array - ? Array> - : T extends Record - ? { readonly [K in keyof T]: ResolvedCliConfigValue } & { - readonly [key: string]: ResolvedCliConfigValue; - } - : T extends object - ? { readonly [K in keyof T]: ResolvedCliConfigValue } - : T; - function normalizeAmbientEnv( baseEnv: Readonly> | undefined, ): Record { @@ -205,7 +193,11 @@ export interface LoadCliProjectEnvironmentOptions { readonly skipEnvLocal?: boolean; } -export interface ResolveCliConfigOptions { +/** + * Not covered by semver — exported from `@supabase/config/internal` only. See + * that module's header for why. + */ +export interface InternalResolveCliConfigOptions extends ResolveCliConfigOptions { /** * Opt into Go/viper-parity `env()` matching (case-agnostic * `^env\((.*)\)$`). Defaults to `false`, which uses the pre-PR-#5765 strict @@ -253,102 +245,15 @@ export const loadCliProjectEnvironment = Effect.fnUntraced(function* ( } satisfies CliProjectEnvironment; }); -function interpolateLeafValue( - value: string, - env: Readonly>, - goViperCompat: boolean, -): string { - const match = (goViperCompat ? ENV_CAPTURE_REGEX : ENV_CAPTURE_REGEX_STRICT).exec(value); - const envName = match?.[1]; - - if (envName === undefined) { - return value; - } - - const resolved = env[envName]; - // Preserve the literal `env(VAR)` verbatim when VAR is unset OR present but - // empty (e.g. a dotenv `KEY=` line). Matches Go's `LoadEnvHook` - // (`apps/cli-go/pkg/config/decode_hooks.go:19-24`: `len(env) > 0`), which - // only substitutes a non-empty value — same gate as `substituteEnvLeaf` in - // `lib/env.ts`. Without this, a present-but-empty `env(...)` secret (e.g. - // `edge_runtime.secrets.FOO = "env(EMPTY)"`) resolves to `""` here, gets - // redacted by `redactValue` as a real value instead of skipped as an - // unresolved literal, and `secrets set` uploads a blank secret Go would - // never send. - if (resolved === undefined || resolved === "") { - return value; - } - - return resolved; -} - -function toPathSegments(path: string): ReadonlyArray { - if (path === "") { - return []; - } - - return path.split(".").filter((segment) => segment.length > 0); -} - -function interpolateValue( - value: unknown, - env: Readonly>, - goViperCompat: boolean, -): unknown { - if (Array.isArray(value)) { - return value.map((item) => interpolateValue(item, env, goViperCompat)); - } - - if (typeof value === "object" && value !== null) { - const result: Record = {}; - - for (const [key, child] of Object.entries(value)) { - result[key] = interpolateValue(child, env, goViperCompat); - } - - return result; - } - - if (typeof value === "string") { - return interpolateLeafValue(value, env, goViperCompat); - } - - return value; -} - -function redactValue(value: unknown, path: ReadonlyArray, goViperCompat: boolean): unknown { - if (Array.isArray(value)) { - return value.map((item, index) => redactValue(item, [...path, String(index)], goViperCompat)); - } - - if (typeof value === "object" && value !== null) { - const result: Record = {}; - - for (const [key, child] of Object.entries(value)) { - result[key] = redactValue(child, [...path, key], goViperCompat); - } - - return result; - } - - if (typeof value === "string" && isSecretPath(path) && !isEnvReference(value, goViperCompat)) { - return Redacted.make(value, { label: path.join(".") }); - } - - return value; -} - -function resolveCliConfigValueAtPath( - value: unknown, - cliProjectEnv: Pick, - path: ReadonlyArray, - goViperCompat: boolean, -): unknown { - const interpolated = interpolateValue(value, cliProjectEnv.values, goViperCompat); - return redactValue(interpolated, path, goViperCompat); -} - /** + * Effect-typed counterpart of `./lib/resolve.ts`'s plain sync + * `resolveCliConfigValue`, additionally accepting the internal-only + * `goViperCompat` option (see {@link InternalResolveCliConfigOptions}). + * `../effect.ts` re-exports this explicitly, which wins over the sync + * version's star re-export through `./index.ts` (see that module's doc + * comment on the deliberate shadowing) — `@supabase/config/internal` + * re-exports this same function typed to show `goViperCompat`. + * * `cliProjectEnv` only needs `.values` (`Pick`) — * a caller that already has a project's env values but not the full * `CliProjectEnvironment` shape (e.g. `paths`/`loadedPaths`/`sources`) can pass @@ -358,7 +263,7 @@ export function resolveCliConfigValue( value: T, cliProjectEnv: Pick, configPath: string, - options?: ResolveCliConfigOptions, + options?: InternalResolveCliConfigOptions, ): Effect.Effect> { return Effect.sync( () => @@ -376,7 +281,7 @@ export function resolveCliConfigSubtree( value: T, cliProjectEnv: Pick, pathPrefix: string, - options?: ResolveCliConfigOptions, + options?: InternalResolveCliConfigOptions, ): Effect.Effect> { return Effect.sync( () => diff --git a/packages/config/src/project.unit.test.ts b/packages/config/src/project.unit.test.ts index b42c92b25e..e8f16508b7 100644 --- a/packages/config/src/project.unit.test.ts +++ b/packages/config/src/project.unit.test.ts @@ -5,7 +5,10 @@ import { mkdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { Effect, FileSystem, Path, Redacted } from "effect"; -import { findCliProjectRootFor, loadCliProjectEnvironmentFor } from "./bun.ts"; +import { + findCliProjectRoot as findCliProjectRootFromBun, + loadCliProjectEnvironment as loadCliProjectEnvironmentFromBun, +} from "./bun.ts"; import { CliConfigParseError, CliProjectEnvParseError } from "./errors.ts"; import { findCliProjectPaths, @@ -14,6 +17,7 @@ import { resolveCliConfigSubtree, resolveCliConfigValue, } from "./effect.ts"; +import { resolveCliConfigValue as resolveCliConfigValueInternal } from "./internal.ts"; function makeTempProject(): string { return mkdtempSync(join(tmpdir(), "supabase-project-config-")); @@ -44,7 +48,7 @@ describe("project discovery and lazy env resolution", () => { expect(paths?.projectRoot).toBe(packageRoot); expect(paths?.supabaseDir).toBe(join(packageRoot, "supabase")); expect(paths?.configPath).toBe(join(packageRoot, "supabase", "config.toml")); - expect(await findCliProjectRootFor(nestedCwd)).toBe(packageRoot); + expect(await findCliProjectRootFromBun(nestedCwd)).toBe(packageRoot); } finally { await rm(cwd, { recursive: true, force: true }); } @@ -169,7 +173,7 @@ describe("project discovery and lazy env resolution", () => { join(packageRoot, "supabase", ".env.local"), ]); - const fromBun = await loadCliProjectEnvironmentFor({ + const fromBun = await loadCliProjectEnvironmentFromBun({ cwd: nestedCwd, baseEnv: { OVERRIDE_ME: "from-ambient", @@ -608,9 +612,12 @@ jwt_secret = "env(lowercase_secret)" const projectEnv = await runConfigEffect(loadCliProjectEnvironment({ cwd: projectRoot })); const resolved = await runConfigEffect( - resolveCliConfigValue(loaded!.config.auth.jwt_secret, projectEnv!, "auth.jwt_secret", { - goViperCompat: true, - }), + resolveCliConfigValueInternal( + loaded!.config.auth.jwt_secret, + projectEnv!, + "auth.jwt_secret", + { goViperCompat: true }, + ), ); expect(Redacted.isRedacted(resolved)).toBe(true); diff --git a/packages/config/src/promise-facade.stdin.unit.test.ts b/packages/config/src/promise-facade.stdin.unit.test.ts index 294083c7f9..d7ffa70ecb 100644 --- a/packages/config/src/promise-facade.stdin.unit.test.ts +++ b/packages/config/src/promise-facade.stdin.unit.test.ts @@ -5,7 +5,7 @@ import { rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { Context, Effect, Layer, Option, Terminal } from "effect"; -import { findCliProjectRootFor } from "./bun.ts"; +import { findCliProjectRoot } from "./bun.ts"; // CLI-2231 regression guard: `BunServices.layer` (the full Bun platform // services bundle) pulls in `Terminal`, which attaches a permanent @@ -32,7 +32,7 @@ describe("promise-facade stdin-leak regression (CLI-2231)", () => { const before = process.stdin.listenerCount("end"); try { - await findCliProjectRootFor(cwd); + await findCliProjectRoot(cwd); expect(process.stdin.listenerCount("end")).toBe(before); } finally { diff --git a/packages/config/src/promise-facade.ts b/packages/config/src/promise-facade.ts index b056b73458..a42a0fced9 100644 --- a/packages/config/src/promise-facade.ts +++ b/packages/config/src/promise-facade.ts @@ -14,19 +14,30 @@ import { findCliProjectPaths, findCliProjectRoot } from "./paths.ts"; import { cliConfigStoreLayer } from "./cli-config.layer.ts"; import { CliConfigStore } from "./cli-config.service.ts"; +/** + * Names deliberately mirror `@supabase/config/effect` one-to-one — the + * subpath itself (`/io` vs `/effect`) conveys Promise-vs-Effect, not the + * member names. + * + * A rejection from `loadCliConfig`, `loadCliConfigFile`, or `saveCliConfig` + * can carry any of `CliConfigStoreError`'s members (`cli-config.service.ts`): + * this package's own `CliConfigParseError` / `DuplicateRemoteProjectIdError` / + * `InvalidRemoteProjectIdError` / `CliProjectEnvParseError`, or `PlatformError` + * for a host/OS failure — distinguish via `instanceof`. + */ export interface CliConfigIo { readonly loadCliConfig: ( cwd: string, options?: LoadCliConfigOptions, ) => Promise; - readonly findCliProjectRootFor: (cwd: string) => Promise; - readonly findCliProjectPathsFor: (cwd: string) => Promise; + readonly findCliProjectRoot: (cwd: string) => Promise; + readonly findCliProjectPaths: (cwd: string) => Promise; readonly loadCliConfigFile: (path: string) => Promise; - readonly loadCliProjectEnvironmentFor: ( + readonly loadCliProjectEnvironment: ( options: LoadCliProjectEnvironmentOptions, ) => Promise; readonly saveCliConfig: (options: SaveCliConfigOptions) => Promise; - readonly loadFunctionsManifest: (cwd: string) => Promise; + readonly inferFunctionsManifest: (cwd: string) => Promise; } /** @@ -61,16 +72,16 @@ export function makeCliConfigIo( return { loadCliConfig: async (cwd, options) => getRuntime().runPromise(CliConfigStore.use((store) => store.load(cwd, options))), - findCliProjectRootFor: async (cwd) => getRuntime().runPromise(findCliProjectRoot(cwd)), - findCliProjectPathsFor: async (cwd) => getRuntime().runPromise(findCliProjectPaths(cwd)), + findCliProjectRoot: async (cwd) => getRuntime().runPromise(findCliProjectRoot(cwd)), + findCliProjectPaths: async (cwd) => getRuntime().runPromise(findCliProjectPaths(cwd)), loadCliConfigFile: async (path) => getRuntime().runPromise(CliConfigStore.use((store) => store.loadFile(path))), - loadCliProjectEnvironmentFor: async (options) => + loadCliProjectEnvironment: async (options) => getRuntime().runPromise( loadCliProjectEnvironment({ ...options, baseEnv: options.baseEnv ?? process.env }), ), saveCliConfig: async (options) => getRuntime().runPromise(CliConfigStore.use((store) => store.save(options))), - loadFunctionsManifest: async (cwd) => getRuntime().runPromise(inferFunctionsManifest({ cwd })), + inferFunctionsManifest: async (cwd) => getRuntime().runPromise(inferFunctionsManifest({ cwd })), }; } diff --git a/packages/config/src/promise-facade.unit.test.ts b/packages/config/src/promise-facade.unit.test.ts index cc42b71018..920b1c7f28 100644 --- a/packages/config/src/promise-facade.unit.test.ts +++ b/packages/config/src/promise-facade.unit.test.ts @@ -14,12 +14,12 @@ import * as nodeFacade from "./node.ts"; import { makeCliConfigIo } from "./promise-facade.ts"; const { - findCliProjectPathsFor, - findCliProjectRootFor, - loadFunctionsManifest, + findCliProjectPaths, + findCliProjectRoot, + inferFunctionsManifest, loadCliConfig, loadCliConfigFile, - loadCliProjectEnvironmentFor, + loadCliProjectEnvironment, saveCliConfig, } = bunFacade; @@ -86,7 +86,7 @@ describe("promise-facade via the Bun entrypoint", () => { } }); - test("findCliProjectRootFor and findCliProjectPathsFor resolve from a nested cwd inside a temp project", async () => { + test("findCliProjectRoot and findCliProjectPaths resolve from a nested cwd inside a temp project", async () => { const cwd = makeTempProject(); const nested = join(cwd, "apps", "web", "src", "components"); @@ -95,8 +95,8 @@ describe("promise-facade via the Bun entrypoint", () => { await mkdir(nested, { recursive: true }); await writeFile(join(cwd, "supabase", "config.toml"), 'project_id = "nested-ref"\n'); - const root = await findCliProjectRootFor(nested); - const paths = await findCliProjectPathsFor(nested); + const root = await findCliProjectRoot(nested); + const paths = await findCliProjectPaths(nested); expect(root).toBe(cwd); expect(paths).toEqual({ @@ -111,18 +111,18 @@ describe("promise-facade via the Bun entrypoint", () => { } }); - test("findCliProjectRootFor and findCliProjectPathsFor resolve to null when there is no project", async () => { + test("findCliProjectRoot and findCliProjectPaths resolve to null when there is no project", async () => { const cwd = makeTempProject(); try { - await expect(findCliProjectRootFor(cwd)).resolves.toBeNull(); - await expect(findCliProjectPathsFor(cwd)).resolves.toBeNull(); + await expect(findCliProjectRoot(cwd)).resolves.toBeNull(); + await expect(findCliProjectPaths(cwd)).resolves.toBeNull(); } finally { await rm(cwd, { recursive: true, force: true }); } }); - test("loadCliProjectEnvironmentFor reads supabase/.env layered under an explicit baseEnv", async () => { + test("loadCliProjectEnvironment reads supabase/.env layered under an explicit baseEnv", async () => { const cwd = makeTempProject(); try { @@ -133,7 +133,7 @@ describe("promise-facade via the Bun entrypoint", () => { // `baseEnv` is passed explicitly (never the default `process.env`) so // this assertion can't be satisfied by an unrelated variable leaking in // from the real process environment. - const projectEnv = await loadCliProjectEnvironmentFor({ cwd, baseEnv: {} }); + const projectEnv = await loadCliProjectEnvironment({ cwd, baseEnv: {} }); expect(projectEnv?.values.GREETING).toBe("hello-from-dotenv"); expect(projectEnv?.sources.GREETING).toBe(".env"); @@ -143,7 +143,7 @@ describe("promise-facade via the Bun entrypoint", () => { } }); - test("loadCliProjectEnvironmentFor honors an explicit baseEnv instead of silently defaulting to process.env", async () => { + test("loadCliProjectEnvironment honors an explicit baseEnv instead of silently defaulting to process.env", async () => { const cwd = makeTempProject(); try { @@ -151,7 +151,7 @@ describe("promise-facade via the Bun entrypoint", () => { await writeFile(join(cwd, "supabase", "config.toml"), 'project_id = "env-ref"\n'); await writeFile(join(cwd, "supabase", ".env"), "GREETING=from-dotenv\n"); - const projectEnv = await loadCliProjectEnvironmentFor({ + const projectEnv = await loadCliProjectEnvironment({ cwd, baseEnv: { GREETING: "from-explicit-base-env" }, }); @@ -163,14 +163,14 @@ describe("promise-facade via the Bun entrypoint", () => { } }); - test("loadFunctionsManifest resolves an empty manifest when no functions directory exists", async () => { + test("inferFunctionsManifest resolves an empty manifest when no functions directory exists", async () => { const cwd = makeTempProject(); try { await mkdir(join(cwd, "supabase"), { recursive: true }); await writeFile(join(cwd, "supabase", "config.toml"), 'project_id = "functions-ref"\n'); - await expect(loadFunctionsManifest(cwd)).resolves.toEqual({}); + await expect(inferFunctionsManifest(cwd)).resolves.toEqual({}); } finally { await rm(cwd, { recursive: true, force: true }); } @@ -203,12 +203,12 @@ describe("promise-facade via the Bun entrypoint", () => { }); const expectedFacadeFunctionNames = [ - "findCliProjectPathsFor", - "findCliProjectRootFor", - "loadFunctionsManifest", + "findCliProjectPaths", + "findCliProjectRoot", + "inferFunctionsManifest", "loadCliConfig", "loadCliConfigFile", - "loadCliProjectEnvironmentFor", + "loadCliProjectEnvironment", "saveCliConfig", ]; @@ -295,8 +295,8 @@ describe("promise-facade singleton runtime", () => { ); const io = makeCliConfigIo(countingLayer); - await io.findCliProjectRootFor(cwd); - await io.findCliProjectRootFor(cwd); + await io.findCliProjectRoot(cwd); + await io.findCliProjectRoot(cwd); expect(builds).toBe(1); } finally { From ccc7e5dd101ad920ed1253de52785da0d282d099 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 27 Aug 2026 16:57:09 +0100 Subject: [PATCH 2/9] feat(config): derive a runtime ProjectConfigSchema and project-schema.json artifact (CLI-2234, CLI-2232) --- packages/config/package.json | 3 +- packages/config/scripts/build.ts | 41 +-- .../config/src/entrypoint-purity.unit.test.ts | 12 +- packages/config/src/index.ts | 1 + .../src/project-config/hosted-sections.ts | 21 ++ .../src/project-config/project-config.ts | 14 +- .../src/project-config/project-schema.ts | 226 +++++++++++++++ .../project-schema.unit.test.ts | 260 ++++++++++++++++++ 8 files changed, 544 insertions(+), 34 deletions(-) create mode 100644 packages/config/src/project-config/hosted-sections.ts create mode 100644 packages/config/src/project-config/project-schema.ts create mode 100644 packages/config/src/project-config/project-schema.unit.test.ts diff --git a/packages/config/package.json b/packages/config/package.json index 0e64bfd649..b74f24d36a 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -13,7 +13,8 @@ "default": "./src/node.ts" }, "./effect": "./src/effect.ts", - "./schema.json": "./dist/schema.json" + "./schema.json": "./dist/schema.json", + "./project-schema.json": "./dist/project-schema.json" }, "scripts": { "build": "bun run ./scripts/build.ts", diff --git a/packages/config/scripts/build.ts b/packages/config/scripts/build.ts index 66b19cde69..a234ac550d 100644 --- a/packages/config/scripts/build.ts +++ b/packages/config/scripts/build.ts @@ -1,25 +1,30 @@ import { mkdir } from "node:fs/promises"; import { toCliConfigJsonSchema } from "../src/base.ts"; +import { toProjectConfigJsonSchema } from "../src/project-config/project-schema.ts"; -const json = toCliConfigJsonSchema(); -const schema = `${JSON.stringify(json, null, 2)}\n`; +async function renderJsonSchema(outputPath: string, json: unknown): Promise { + const schema = `${JSON.stringify(json, null, 2)}\n`; -const formatter = Bun.spawn(["bun", "x", "oxfmt", "--stdin-filepath=./dist/schema.json"], { - stdin: "pipe", - stdout: "pipe", - stderr: "pipe", -}); -await formatter.stdin.write(schema); -await formatter.stdin.end(); + const formatter = Bun.spawn(["bun", "x", "oxfmt", `--stdin-filepath=${outputPath}`], { + stdin: "pipe", + stdout: "pipe", + stderr: "pipe", + }); + await formatter.stdin.write(schema); + await formatter.stdin.end(); -const [exitCode, formatted, stderr] = await Promise.all([ - formatter.exited, - new Response(formatter.stdout).text(), - new Response(formatter.stderr).text(), -]); -if (exitCode !== 0) { - throw new Error(`oxfmt failed with exit code ${exitCode}: ${stderr.trim()}`); + const [exitCode, formatted, stderr] = await Promise.all([ + formatter.exited, + new Response(formatter.stdout).text(), + new Response(formatter.stderr).text(), + ]); + if (exitCode !== 0) { + throw new Error(`oxfmt failed with exit code ${exitCode}: ${stderr.trim()}`); + } + + await mkdir("./dist", { recursive: true }); + await Bun.write(outputPath, formatted); } -await mkdir("./dist", { recursive: true }); -await Bun.write("./dist/schema.json", formatted); +await renderJsonSchema("./dist/schema.json", toCliConfigJsonSchema()); +await renderJsonSchema("./dist/project-schema.json", toProjectConfigJsonSchema()); diff --git a/packages/config/src/entrypoint-purity.unit.test.ts b/packages/config/src/entrypoint-purity.unit.test.ts index e883720991..1588d37520 100644 --- a/packages/config/src/entrypoint-purity.unit.test.ts +++ b/packages/config/src/entrypoint-purity.unit.test.ts @@ -26,6 +26,7 @@ const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), " readonly "./io": Readonly>; readonly "./effect": string; readonly "./schema.json": string; + readonly "./project-schema.json": string; }; }; @@ -287,7 +288,9 @@ const expectedPureGraphFiles = [ "lib/schema.ts", "lib/secret-paths.ts", "project-config/api-attributes.ts", + "project-config/hosted-sections.ts", "project-config/project-config.ts", + "project-config/project-schema.ts", "project-config/registry-auth.ts", "project-config/registry-row.ts", "project-config/registry.ts", @@ -348,6 +351,7 @@ describe("src/index.ts export surface", () => { "InvalidRemoteProjectIdError", "MissingCliConfigValueError", "ProjectConfigParseError", + "ProjectConfigSchema", "attachApiResponse", "cliConfigValueSourceAt", "comparableProjectConfigPaths", @@ -366,6 +370,7 @@ describe("src/index.ts export surface", () => { "subtractCliConfig", "toCliConfigJsonSchema", "toProjectConfig", + "toProjectConfigJsonSchema", "unmappedApiFields", ] `); @@ -385,6 +390,7 @@ describe("src/effect.ts is a superset of src/index.ts", () => { "InvalidRemoteProjectIdError", "MissingCliConfigValueError", "ProjectConfigParseError", + "ProjectConfigSchema", "attachApiResponse", "cliConfigStoreLayer", "cliConfigValueSourceAt", @@ -414,6 +420,7 @@ describe("src/effect.ts is a superset of src/index.ts", () => { "subtractCliConfig", "toCliConfigJsonSchema", "toProjectConfig", + "toProjectConfigJsonSchema", "unmappedApiFields", ] `); @@ -499,8 +506,9 @@ describe("package.json exports map", () => { }); test("the '.', './effect', and './internal' export targets exist on disk", () => { - // `./schema.json` is a build output (`dist/schema.json`) and intentionally - // skipped here — it only exists after running `pnpm run build`. + // `./schema.json`/`./project-schema.json` are build outputs + // (`dist/schema.json`/`dist/project-schema.json`) and intentionally + // skipped here — they only exist after running `pnpm run build`. for (const key of [".", "./effect", "./internal"] as const) { const target = packageJson.exports[key]; expect(() => readFileSync(join(packageRoot, target))).not.toThrow(); diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index eba155d811..115fb3dde6 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -67,3 +67,4 @@ export { toProjectConfig, unmappedApiFields, } from "./project-config/project-config.ts"; +export { ProjectConfigSchema, toProjectConfigJsonSchema } from "./project-config/project-schema.ts"; diff --git a/packages/config/src/project-config/hosted-sections.ts b/packages/config/src/project-config/hosted-sections.ts new file mode 100644 index 0000000000..f83d228b2c --- /dev/null +++ b/packages/config/src/project-config/hosted-sections.ts @@ -0,0 +1,21 @@ +/** + * The seven {@link CliConfig} (`../base.ts`) section keys a hosted + * project-config API response can speak for — the vocabulary ceiling for + * {@link ProjectConfig} (`./project-config.ts`)'s compile-time type and + * {@link ProjectConfigSchema} (`./project-schema.ts`)'s runtime derivation. + * Owned here rather than duplicated in either consumer, per this repo's + * policy of moving a shared constant to its correct owner instead of + * hand-keeping two copies in sync. + */ +export const HOSTED_SECTION_KEYS = [ + "api", + "auth", + "db", + "realtime", + "storage", + "workers", + "experimental", +] as const; + +/** The seven keys {@link ProjectConfig}/{@link ProjectConfigSchema} can carry. */ +export type HostedSectionKey = (typeof HOSTED_SECTION_KEYS)[number]; diff --git a/packages/config/src/project-config/project-config.ts b/packages/config/src/project-config/project-config.ts index d03fb6d1c3..5eb0a43a53 100644 --- a/packages/config/src/project-config/project-config.ts +++ b/packages/config/src/project-config/project-config.ts @@ -12,23 +12,11 @@ import { ProjectConfigApiAttributesSchema, type ProjectConfigApiAttributes, } from "./api-attributes.ts"; +import { HOSTED_SECTION_KEYS, type HostedSectionKey } from "./hosted-sections.ts"; import { AUTH_HOOK_NAMES, unmappedSecretApiPaths } from "./registry-auth.ts"; import { expectString } from "./registry-row.ts"; import { projectConfigMappingRows } from "./registry.ts"; -const HOSTED_SECTION_KEYS = [ - "api", - "auth", - "db", - "realtime", - "storage", - "workers", - "experimental", -] as const; - -/** The seven keys {@link ProjectConfig} can carry, derived once so the type and the runtime walk below can't drift apart. */ -type HostedSectionKey = (typeof HOSTED_SECTION_KEYS)[number]; - /** * A deeply-readonly JSON value — the shape of everything under * `_apiResponse`, which holds (a clone of) a parsed Management API JSON diff --git a/packages/config/src/project-config/project-schema.ts b/packages/config/src/project-config/project-schema.ts new file mode 100644 index 0000000000..545f78e603 --- /dev/null +++ b/packages/config/src/project-config/project-schema.ts @@ -0,0 +1,226 @@ +import { Schema, SchemaAST } from "effect"; +import { CliConfigSchema } from "../base.ts"; +import { HOSTED_SECTION_KEYS } from "./hosted-sections.ts"; +import type { ProjectConfig } from "./project-config.ts"; + +/** + * Runtime companion to {@link ProjectConfig} (`./project-config.ts`) — a + * schema that VALIDATES the same sparse hosted-section overlay + * `ProjectConfig` only describes at compile time. Derived from + * {@link CliConfigSchema} (`../base.ts`), never hand-declared, so the two can + * never independently drift: every leaf type, annotation, and leaf-level + * check traces back to the exact schema `base.ts` decodes a config document + * with. + * + * Derivation, in order: + * + * 1. {@link hostedSectionsStruct} picks the seven {@link HOSTED_SECTION_KEYS} + * fields off `CliConfigSchema.fields` and rebuilds a fresh `Schema.Struct` + * from them — the same field schemas `CliConfigSchema` itself embeds, not + * copies. + * 2. `SchemaAST.toType` strips every encoding/transformation (decoding + * defaults, `env()` deferred substitution, …), leaving the DECODED shape — + * exactly what `ProjectConfig` describes; a `ProjectConfig` value is never + * re-encoded. + * 3. {@link toDeepOptionalHostedAst} then recursively rebuilds the result: + * - In every `Objects` node (struct OR record), drops any + * `PropertySignature`/`IndexSignature` whose value AST carries the + * `x-secret` annotation (ADR 0019 rule 5 — `fromConfigDocument`/ + * `fromApiProjectConfig` never populate a secret leaf either), the same + * detection `../lib/secret-paths.ts`'s own walk uses. A container whose + * value type consists ENTIRELY of secret leaves (e.g. `db.vault`, a + * `Record`) ends up an empty `Objects` node (no + * surviving properties or index signatures) — `SchemaAST`'s own + * documented behavior for that shape is "accepts any value except + * `null`/`undefined`", which is the closest a schema can get to "this + * container held nothing but secrets, so nothing concrete is left to + * validate here" without special-casing an empty-object type that JSON + * Schema has no way to express either. + * - Wraps every SURVIVING property in `optionalKey` (via + * {@link toOptionalAst}), recursing into its type — mirroring + * `DeepPartial`'s `{ readonly [K in keyof T]?: DeepPartial }` + * mapped type (`../sparse.ts`) at every object level reached, and + * recursing the same way into index-signature VALUE types (matching + * `DeepPartial`'s recursion into a `Record`'s value type — + * `Record` deep-partializes to `Record>`, not `X` verbatim). + * - Leaves an `Arrays` node completely untouched, INCLUDING its element + * types: `DeepPartial` special-cases arrays to pass `T` through + * verbatim rather than partializing element types (`../sparse.ts`), and + * no `x-secret` leaf sits inside an array anywhere in this schema + * (`../lib/secret-paths.ts`'s own docstring), so there is nothing this + * walk would otherwise need to change there anyway. + * - Strips every `checks` array attached DIRECTLY to an `Objects` node — + * the cross-field business-rule refinements this repo attaches with + * `.check()` on a whole struct (`requiredWhenEnabled` in + * `../auth/email.ts`/`../auth/providers.ts`, `validateSmsProviderSwitch` + * in `../auth/sms.ts`) encode invariants a deliberately sparse overlay + * cannot generally satisfy — e.g. `{ auth: { email: { smtp: { enabled: + * true } } } }` with no `host` yet is a legal, if incomplete, + * `ProjectConfig` fragment, but `requiredWhenEnabled("host", ...)` would + * reject it. Every LEAF-level check (`Schema.Number.check(isInt(), + * isGreaterThanOrEqualTo(0))`, `Schema.isPattern(...)`, port-range + * bounds, …) lives on a non-`Objects` node and is left untouched. + * - Recurses into `Union` members (e.g. `storage.file_size_limit`'s + * `Schema.Union([String, Number])`, and every `Schema.Literals`-backed + * enum, which V4 also compiles to a `Union`) and `Suspend` thunks, so a + * secret-bearing or object-shaped member nested inside either would + * still be reached. Every other node kind (every leaf: `String`, + * `Number`, `Boolean`, `Literal`, …) is returned unchanged — there is + * nothing further to drop or partialize on a leaf. This module's own AST + * node kinds are enumerated explicitly, via each class's PUBLIC + * constructor, rather than through a generic `.recur()`-style + * mechanism: unlike `.repos/effect`'s vendored source, the installed + * `effect` release's own `AST#recur` is `@internal` (absent from its + * published `.d.ts`), so a truly generic fallback isn't available + * through the public API surface this package is allowed to depend on. + * + * `_apiResponse` (ADR 0019) is deliberately NOT part of this schema: it's + * attached as a non-enumerable property that ordinary decode/validation can + * never see, so there is nothing here for a schema to describe. + * + * Never `additionalProperties: false` ({@link toProjectConfigJsonSchema} + * passes `{ additionalProperties: true }` to `Schema.toJsonSchemaDocument`, + * and `ProjectConfigSchema` itself is never decoded with + * `onExcessProperty: "error"`): a `ProjectConfig` value can carry extra own + * keys a given schema VERSION doesn't yet model (a registry-mapped field a + * future release adds), and JSON Schema's own default is permissive — this + * derivation matches that norm rather than rejecting anything unrecognized. + */ +function isSecretAst(ast: SchemaAST.AST): boolean { + return ast.annotations?.["x-secret"] === true; +} + +/** + * Marks `ast` optional through the PUBLIC `Schema.optionalKey` combinator + * (`Schema.optionalKey(Schema.make(ast)).ast`) rather than the internal + * `SchemaAST.optionalKey` this repo's vendored `.repos/effect` snapshot + * exposes publicly but the installed `effect` release does not — see this + * module's own doc comment. `Schema.make` performs no structural check + * against the throwaway `unknown` `Codec` parameter here; only `ast` itself + * (read straight back off the wrapped schema) is used. + */ +function toOptionalAst(ast: SchemaAST.AST): SchemaAST.AST { + return Schema.optionalKey(Schema.make>(ast)).ast; +} + +function toDeepOptionalHostedAst(ast: SchemaAST.AST): SchemaAST.AST { + if (SchemaAST.isObjects(ast)) { + const propertySignatures = ast.propertySignatures + .filter((property) => !isSecretAst(property.type)) + .map( + (property) => + new SchemaAST.PropertySignature( + property.name, + toOptionalAst(toDeepOptionalHostedAst(property.type)), + ), + ); + const indexSignatures = ast.indexSignatures + .filter((indexSignature) => !isSecretAst(indexSignature.type)) + .map( + (indexSignature) => + new SchemaAST.IndexSignature( + indexSignature.parameter, + toDeepOptionalHostedAst(indexSignature.type), + ), + ); + return new SchemaAST.Objects( + propertySignatures, + indexSignatures, + ast.annotations, + undefined, + undefined, + ast.context, + undefined, + ); + } + if (SchemaAST.isArrays(ast)) { + return ast; + } + if (SchemaAST.isUnion(ast)) { + return new SchemaAST.Union( + ast.types.map(toDeepOptionalHostedAst), + ast.mode, + ast.annotations, + ast.checks, + ast.encoding, + ast.context, + ast.encodingChecks, + ); + } + if (SchemaAST.isSuspend(ast)) { + return new SchemaAST.Suspend( + () => toDeepOptionalHostedAst(ast.thunk()), + ast.annotations, + ast.checks, + ast.encoding, + ast.context, + ); + } + return ast; +} + +// A literal field-picking object, not a `HOSTED_SECTION_KEYS.map(...)` +// reflection: `Schema.Struct`'s field type is inferred per-property from a +// literal object type, which a programmatic pick loses without an `as` cast +// (disallowed by this repo's typing policy) to restore. Each field schema +// below is still the exact one `CliConfigSchema` itself embeds (`../base.ts`), +// never a copy. +const hostedSectionsStruct = Schema.Struct({ + api: CliConfigSchema.fields.api, + auth: CliConfigSchema.fields.auth, + db: CliConfigSchema.fields.db, + realtime: CliConfigSchema.fields.realtime, + storage: CliConfigSchema.fields.storage, + workers: CliConfigSchema.fields.workers, + experimental: CliConfigSchema.fields.experimental, +}); + +// The literal pick above still names the same seven keys as +// `HOSTED_SECTION_KEYS` by hand, since a type-safe `Schema.Struct` field +// object can't be built from an array without an `as` cast — this guard +// catches the two lists drifting apart (an edit to one without the other) at +// import time instead of silently validating the wrong section set. +const pickedHostedSectionKeys = Object.keys(hostedSectionsStruct.fields).toSorted(); +const declaredHostedSectionKeys = HOSTED_SECTION_KEYS.toSorted(); +if (JSON.stringify(pickedHostedSectionKeys) !== JSON.stringify(declaredHostedSectionKeys)) { + throw new Error( + "project-schema.ts's picked hosted-section fields drifted from HOSTED_SECTION_KEYS", + ); +} + +const projectConfigAst = toDeepOptionalHostedAst(SchemaAST.toType(hostedSectionsStruct.ast)); + +/** + * The runtime shape {@link projectConfigAst} validates: {@link ProjectConfig} + * minus `_apiResponse`, which — being non-enumerable and never serialized — + * has no runtime representation for a schema to check. `Schema.make` performs + * no structural verification against this annotation (the same trust-the- + * caller contract as effect's own `Json: Codec = make(SchemaAST.Json)` + * precedent); the type-level pin in `./project-schema.unit.test.ts` cross- + * checks this exact type expression against `ProjectConfig` independently, so + * a future edit to either side that silently drifts fails to compile there. + */ +type ProjectConfigSchemaType = Omit; + +/** + * Runtime validation for {@link ProjectConfig} — both an Effect-native schema + * (decode/encode, `.ast`, …) and a spec-compliant Standard Schema + * (`~standard`), since {@link Schema.toStandardSchemaV1} augments and returns + * the SAME object rather than wrapping it in a second value. + */ +export const ProjectConfigSchema = Schema.toStandardSchemaV1( + Schema.make>(projectConfigAst), +); + +/** JSON Schema (draft 2020-12) rendering of {@link ProjectConfigSchema}, mirroring `../base.ts`'s `toCliConfigJsonSchema`. */ +export function toProjectConfigJsonSchema() { + const document = Schema.toJsonSchemaDocument(ProjectConfigSchema, { + additionalProperties: true, + }); + return { + $schema: "https://json-schema.org/draft/2020-12/schema", + ...document.schema, + ...(Object.keys(document.definitions).length > 0 ? { $defs: document.definitions } : {}), + }; +} diff --git a/packages/config/src/project-config/project-schema.unit.test.ts b/packages/config/src/project-config/project-schema.unit.test.ts new file mode 100644 index 0000000000..ee4e73829f --- /dev/null +++ b/packages/config/src/project-config/project-schema.unit.test.ts @@ -0,0 +1,260 @@ +import { describe, expect, test } from "vitest"; +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { Schema, SchemaAST } from "effect"; +import * as SmolToml from "smol-toml"; +import { CliConfigSchema } from "../base.ts"; +import { isSecretPath, secretPathPatterns } from "../lib/secret-paths.ts"; +import { getDefaultCliConfig } from "../sparse.ts"; +import { HOSTED_SECTION_KEYS } from "./hosted-sections.ts"; +import { fromApiProjectConfig, fromConfigDocument, toProjectConfig } from "./project-config.ts"; +import type { ProjectConfig } from "./project-config.ts"; +import { ProjectConfigSchema, toProjectConfigJsonSchema } from "./project-schema.ts"; + +const decodeCliConfig = Schema.decodeUnknownSync(CliConfigSchema); +const decodeProjectConfig = Schema.decodeUnknownSync(ProjectConfigSchema); + +const legacyFixturePath = join( + dirname(fileURLToPath(import.meta.url)), + "../../testdata/legacy-config.toml", +); + +function apiEnvelope(attributes: Record): unknown { + return { data: { type: "project_config", id: "abcdefghijklmnopqrst", attributes } }; +} + +describe("ProjectConfigSchema acceptance", () => { + test("an empty overlay validates", () => { + expect(decodeProjectConfig({})).toEqual({}); + }); + + test("a sparse, deeply nested overlay validates", () => { + expect(decodeProjectConfig({ auth: { site_url: "https://example.com" } })).toEqual({ + auth: { site_url: "https://example.com" }, + }); + }); + + test("a sparse overlay leaving required-looking siblings unset still validates", () => { + // `db.pooler`'s own fields (`pool_mode`, `default_pool_size`, …) are all + // present in `CliConfigSchema`, but this schema wraps every one of them + // `optionalKey` — a fragment naming only `enabled` must not fail just + // because it says nothing about the rest of the section. + expect(() => decodeProjectConfig({ db: { pooler: { enabled: true } } })).not.toThrow(); + }); + + test("fromConfigDocument's output over the default CliConfig validates", () => { + const projected = fromConfigDocument(getDefaultCliConfig()); + expect(() => decodeProjectConfig(projected)).not.toThrow(); + }); + + test("fromConfigDocument's output over the real legacy-config.toml fixture validates", () => { + const raw = SmolToml.parse(readFileSync(legacyFixturePath, "utf8")); + const config = decodeCliConfig(raw); + const projected = fromConfigDocument(config); + expect(() => decodeProjectConfig(projected)).not.toThrow(); + }); + + test("toProjectConfig's cliConfig arm validates", () => { + const projected = toProjectConfig({ cliConfig: { api: { max_rows: 100 } } }); + expect(() => decodeProjectConfig(projected)).not.toThrow(); + }); + + test("toProjectConfig's apiResponse arm validates, including the attached _apiResponse own property", () => { + const projected = toProjectConfig({ + apiResponse: apiEnvelope({ database: { major_version: 17 } }), + }); + expect(Object.getOwnPropertyNames(projected)).toContain("_apiResponse"); + expect(() => decodeProjectConfig(projected)).not.toThrow(); + }); + + test("an API-sourced value built directly through fromApiProjectConfig validates", () => { + const projected = fromApiProjectConfig(apiEnvelope({ database: { major_version: 17 } })); + expect(() => decodeProjectConfig(projected)).not.toThrow(); + }); +}); + +describe("ProjectConfigSchema rejection", () => { + test("auth.site_url as a number is rejected", () => { + expect(() => decodeProjectConfig({ auth: { site_url: 123 } })).toThrow(); + }); + + test("db.pooler.pool_mode with an unrecognized literal is rejected", () => { + expect(() => + decodeProjectConfig({ db: { pooler: { pool_mode: "not-a-real-mode" } } }), + ).toThrow(); + }); + + test("db.pooler.pool_mode with a recognized literal is accepted", () => { + expect(() => + decodeProjectConfig({ db: { pooler: { pool_mode: "transaction" } } }), + ).not.toThrow(); + }); +}); + +describe("ProjectConfigSchema secret-strip exhaustiveness", () => { + // Schema-derived, exhaustive counterpart to a hand-picked field list + // (matching `project-config.unit.test.ts`'s own exhaustive-probe + // precedent): every `x-secret` path pattern the schema declares, rooted in + // one of the seven hosted sections, must be structurally absent from + // `ProjectConfigSchema`'s own AST — not merely absent from one hand-picked + // example. + const reachablePatterns = secretPathPatterns.filter((pattern) => + HOSTED_SECTION_KEYS.some((key) => key === (pattern[0] ?? "")), + ); + + test("guards the probe against a broken import silently emptying the pattern list", () => { + expect(reachablePatterns.length).toBeGreaterThan(0); + for (const pattern of reachablePatterns) { + const concretePath = pattern.map((segment) => (segment === "*" ? "probe_key" : segment)); + expect(isSecretPath(concretePath)).toBe(true); + } + }); + + /** + * Walks {@link ProjectConfigSchema}'s own AST along `pattern`, treating a + * `"*"` segment as "descend into the node's own index signature" and every + * other segment as "descend into the property signature of that name" — + * returns `undefined` the moment the path can no longer be followed, which + * is exactly the outcome a dropped secret property/index-signature + * produces. + */ + function findAtPattern( + ast: SchemaAST.AST, + pattern: ReadonlyArray, + ): SchemaAST.AST | undefined { + let current: SchemaAST.AST | undefined = ast; + for (const segment of pattern) { + if (current === undefined || !SchemaAST.isObjects(current)) { + return undefined; + } + current = + segment === "*" + ? current.indexSignatures[0]?.type + : current.propertySignatures.find((property) => property.name === segment)?.type; + } + return current; + } + + test("no x-secret path from the schema's own pattern list survives in ProjectConfigSchema's AST", () => { + for (const pattern of reachablePatterns) { + expect(findAtPattern(ProjectConfigSchema.ast, pattern)).toBeUndefined(); + } + }); +}); + +describe("ProjectConfigSchema local-only sections", () => { + test("a full CliConfig's local-only sections are silently ignored, not validated or echoed back", () => { + const result = decodeProjectConfig(getDefaultCliConfig()); + for (const localOnlyKey of [ + "project_id", + "studio", + "edge_runtime", + "analytics", + "functions", + "local_smtp", + "remotes", + ]) { + expect(Object.hasOwn(result, localOnlyKey)).toBe(false); + } + }); +}); + +describe("ProjectConfigSchema Standard Schema interop", () => { + test("~standard reports the effect vendor", () => { + expect(ProjectConfigSchema["~standard"].vendor).toBe("effect"); + expect(ProjectConfigSchema["~standard"].version).toBe(1); + }); + + test("~standard.validate returns a value on success", async () => { + const outcome = ProjectConfigSchema["~standard"].validate({ + auth: { site_url: "https://example.com" }, + }); + const result = outcome instanceof Promise ? await outcome : outcome; + expect(result.issues).toBeUndefined(); + if (!result.issues) { + expect(result.value).toEqual({ auth: { site_url: "https://example.com" } }); + } + }); + + test("~standard.validate returns issues with paths on failure", async () => { + const outcome = ProjectConfigSchema["~standard"].validate({ auth: { site_url: 123 } }); + const result = outcome instanceof Promise ? await outcome : outcome; + expect(result.issues).toBeDefined(); + expect(result.issues?.[0]?.path).toBeDefined(); + }); +}); + +describe("toProjectConfigJsonSchema", () => { + const typedDocument = toProjectConfigJsonSchema(); + // `JsonSchema.JsonSchema` (`effect`) is an open `[x: string]: unknown` + // record with no named properties, so TypeScript can't statically type + // `typedDocument`'s nested `properties`/`required`/… fields — the same + // reason `io.unit.test.ts`'s own `toCliConfigJsonSchema` coverage asserts + // through a stringified rendering rather than typed property access. A + // JSON round trip gives every assertion below a plainly-navigable value + // without an `as` cast. + const document = JSON.parse(JSON.stringify(typedDocument)); + + test("declares the draft 2020-12 dialect", () => { + expect(typedDocument.$schema).toBe("https://json-schema.org/draft/2020-12/schema"); + }); + + test("top-level properties are exactly the seven hosted sections", () => { + expect(Object.keys(document.properties).sort()).toEqual([...HOSTED_SECTION_KEYS].toSorted()); + }); + + test("no required array forces presence anywhere spot-checked", () => { + expect(document.required).toBeUndefined(); + expect(document.properties.auth.required).toBeUndefined(); + expect(document.properties.db.properties.pooler.required).toBeUndefined(); + }); + + test("the db.vault secret record collapses to a schema with no properties left to leak", () => { + const vault = document.properties.db.properties.vault; + expect(vault.properties).toBeUndefined(); + expect(vault.patternProperties).toBeUndefined(); + }); + + test("is JSON-serializable and stable across two calls", () => { + expect(() => JSON.stringify(typedDocument)).not.toThrow(); + expect(JSON.parse(JSON.stringify(toProjectConfigJsonSchema()))).toEqual(document); + }); +}); + +describe("ProjectConfigSchema type-level pin", () => { + // Compile-time drift guard (CLI-2234 design requirement, mirroring + // `apps/cli/src/shared/config/project-config-api-drift.unit.test.ts`'s + // `_typeDriftGuard`/`AssertNever` style): `ProjectConfigSchema`'s own + // generic annotation (`project-schema.ts`) and `ProjectConfig` + // (`project-config.ts`) are independent expressions of the same shape — + // this file re-derives the expected shape from `ProjectConfig` itself + // (rather than importing `project-schema.ts`'s private type alias) so a + // future edit to either side that silently drifts fails to compile here. + // + // Both directions hold because the only structural difference between the + // two sides is optional-property PRESENCE: `ProjectConfigSchema`'s Type + // never carries an `_apiResponse` key at all (never modeled, ADR 0019), and + // `ProjectConfig` types every `x-secret` leaf as present-but-optional even + // though the runtime derivation drops those keys entirely from the schema. + // TypeScript's structural assignability does not require a source type to + // have (or lack) an optional property the target also lacks (or has), so a + // missing or extra OPTIONAL property never blocks assignability in either + // direction — verified by actually compiling both functions below, not + // merely asserted in prose. + type ExpectedProjectConfigSchemaType = Omit; + type DerivedProjectConfigSchemaType = typeof ProjectConfigSchema.Type; + + const _derivedAssignableToExpected: ( + value: DerivedProjectConfigSchemaType, + ) => ExpectedProjectConfigSchemaType = (value) => value; + + const _expectedAssignableToDerived: ( + value: ExpectedProjectConfigSchemaType, + ) => DerivedProjectConfigSchemaType = (value) => value; + + test("both assignability directions compile", () => { + expect(typeof _derivedAssignableToExpected).toBe("function"); + expect(typeof _expectedAssignableToDerived).toBe("function"); + }); +}); From 333489e9a1f76dec2cbebb74b7887e531c571901 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 27 Aug 2026 17:26:08 +0100 Subject: [PATCH 3/9] feat(config): compile dist JS and d.ts, seal the published tarball, and check in an API report (CLI-2232) --- .oxfmtrc.json | 3 +- .oxlintrc.json | 1 + knip.json | 4 + packages/config/api-report/analytics.d.ts | 10 + packages/config/api-report/api.d.ts | 15 + packages/config/api-report/auth/captcha.d.ts | 6 + packages/config/api-report/auth/email.d.ts | 28 + packages/config/api-report/auth/hooks.d.ts | 33 + packages/config/api-report/auth/index.d.ts | 361 ++ packages/config/api-report/auth/mfa.d.ts | 19 + .../config/api-report/auth/providers.d.ts | 183 + .../config/api-report/auth/rate_limit.d.ts | 10 + packages/config/api-report/auth/sessions.d.ts | 5 + packages/config/api-report/auth/sms.d.ts | 36 + .../config/api-report/auth/third_party.d.ts | 25 + packages/config/api-report/auth/web3.d.ts | 9 + packages/config/api-report/base.d.ts | 1621 ++++++++ packages/config/api-report/bun.d.ts | 9 + .../config/api-report/cli-config.layer.d.ts | 3 + .../config/api-report/cli-config.service.d.ts | 23 + .../config/api-report/config-document.d.ts | 128 + packages/config/api-report/config-format.d.ts | 11 + packages/config/api-report/db.d.ts | 56 + packages/config/api-report/edge_runtime.d.ts | 8 + packages/config/api-report/effect.d.ts | 39 + packages/config/api-report/errors.d.ts | 162 + packages/config/api-report/experimental.d.ts | 24 + .../api-report/functions-manifest-model.d.ts | 12 + .../config/api-report/functions-manifest.d.ts | 11 + packages/config/api-report/functions.d.ts | 9 + packages/config/api-report/inbucket.d.ts | 9 + packages/config/api-report/index.d.ts | 20 + packages/config/api-report/internal.d.ts | 14 + packages/config/api-report/io-browser.d.ts | 9 + packages/config/api-report/io.d.ts | 3374 +++++++++++++++++ packages/config/api-report/lib/env.d.ts | 36 + packages/config/api-report/lib/resolve.d.ts | 43 + packages/config/api-report/lib/schema.d.ts | 16 + .../config/api-report/lib/secret-paths.d.ts | 13 + packages/config/api-report/node.d.ts | 9 + packages/config/api-report/paths.d.ts | 38 + .../project-config/api-attributes.d.ts | 117 + .../project-config/hosted-sections.d.ts | 12 + .../project-config/project-config.d.ts | 374 ++ .../project-config/project-schema.d.ts | 34 + .../project-config/registry-auth.d.ts | 43 + .../project-config/registry-row.d.ts | 146 + .../api-report/project-config/registry.d.ts | 7 + packages/config/api-report/project.d.ts | 71 + .../config/api-report/promise-facade.d.ts | 33 + packages/config/api-report/realtime.d.ts | 6 + .../config/api-report/schema-metadata.d.ts | 1 + packages/config/api-report/sparse.d.ts | 137 + packages/config/api-report/storage.d.ts | 30 + packages/config/api-report/studio.d.ts | 7 + packages/config/api-report/workers.d.ts | 15 + packages/config/package.json | 41 +- packages/config/scripts/build.ts | 204 +- packages/config/src/api-report.unit.test.ts | 90 + .../config/src/entrypoint-purity.unit.test.ts | 57 +- .../src/monorepo-import-contract.unit.test.ts | 9 +- .../src/project-config/project-schema.ts | 14 +- packages/config/tsconfig.api-report.json | 14 + packages/config/tsconfig.build.json | 31 + pnpm-lock.yaml | 3 + turbo.json | 9 +- 66 files changed, 7922 insertions(+), 28 deletions(-) create mode 100644 packages/config/api-report/analytics.d.ts create mode 100644 packages/config/api-report/api.d.ts create mode 100644 packages/config/api-report/auth/captcha.d.ts create mode 100644 packages/config/api-report/auth/email.d.ts create mode 100644 packages/config/api-report/auth/hooks.d.ts create mode 100644 packages/config/api-report/auth/index.d.ts create mode 100644 packages/config/api-report/auth/mfa.d.ts create mode 100644 packages/config/api-report/auth/providers.d.ts create mode 100644 packages/config/api-report/auth/rate_limit.d.ts create mode 100644 packages/config/api-report/auth/sessions.d.ts create mode 100644 packages/config/api-report/auth/sms.d.ts create mode 100644 packages/config/api-report/auth/third_party.d.ts create mode 100644 packages/config/api-report/auth/web3.d.ts create mode 100644 packages/config/api-report/base.d.ts create mode 100644 packages/config/api-report/bun.d.ts create mode 100644 packages/config/api-report/cli-config.layer.d.ts create mode 100644 packages/config/api-report/cli-config.service.d.ts create mode 100644 packages/config/api-report/config-document.d.ts create mode 100644 packages/config/api-report/config-format.d.ts create mode 100644 packages/config/api-report/db.d.ts create mode 100644 packages/config/api-report/edge_runtime.d.ts create mode 100644 packages/config/api-report/effect.d.ts create mode 100644 packages/config/api-report/errors.d.ts create mode 100644 packages/config/api-report/experimental.d.ts create mode 100644 packages/config/api-report/functions-manifest-model.d.ts create mode 100644 packages/config/api-report/functions-manifest.d.ts create mode 100644 packages/config/api-report/functions.d.ts create mode 100644 packages/config/api-report/inbucket.d.ts create mode 100644 packages/config/api-report/index.d.ts create mode 100644 packages/config/api-report/internal.d.ts create mode 100644 packages/config/api-report/io-browser.d.ts create mode 100644 packages/config/api-report/io.d.ts create mode 100644 packages/config/api-report/lib/env.d.ts create mode 100644 packages/config/api-report/lib/resolve.d.ts create mode 100644 packages/config/api-report/lib/schema.d.ts create mode 100644 packages/config/api-report/lib/secret-paths.d.ts create mode 100644 packages/config/api-report/node.d.ts create mode 100644 packages/config/api-report/paths.d.ts create mode 100644 packages/config/api-report/project-config/api-attributes.d.ts create mode 100644 packages/config/api-report/project-config/hosted-sections.d.ts create mode 100644 packages/config/api-report/project-config/project-config.d.ts create mode 100644 packages/config/api-report/project-config/project-schema.d.ts create mode 100644 packages/config/api-report/project-config/registry-auth.d.ts create mode 100644 packages/config/api-report/project-config/registry-row.d.ts create mode 100644 packages/config/api-report/project-config/registry.d.ts create mode 100644 packages/config/api-report/project.d.ts create mode 100644 packages/config/api-report/promise-facade.d.ts create mode 100644 packages/config/api-report/realtime.d.ts create mode 100644 packages/config/api-report/schema-metadata.d.ts create mode 100644 packages/config/api-report/sparse.d.ts create mode 100644 packages/config/api-report/storage.d.ts create mode 100644 packages/config/api-report/studio.d.ts create mode 100644 packages/config/api-report/workers.d.ts create mode 100644 packages/config/src/api-report.unit.test.ts create mode 100644 packages/config/tsconfig.api-report.json create mode 100644 packages/config/tsconfig.build.json diff --git a/.oxfmtrc.json b/.oxfmtrc.json index 0e2a3a42c0..ea399a230a 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -5,6 +5,7 @@ "apps/cli-e2e/fixtures/", "apps/docs/content/docs/commands/", "apps/docs/public/", - "**/testdata/" + "**/testdata/", + "**/api-report/" ] } diff --git a/.oxlintrc.json b/.oxlintrc.json index 89de4dd693..01efbbc24a 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -10,6 +10,7 @@ "apps/cli-e2e/fixtures", "**/testdata", "**/dist", + "**/api-report", "**/coverage", "**/.next", "**/.source" diff --git a/knip.json b/knip.json index 0dd065b891..130ff84456 100644 --- a/knip.json +++ b/knip.json @@ -34,6 +34,10 @@ "entry": ["src/**/*.test.ts"], "ignoreDependencies": ["undici"] }, + "packages/config": { + "entry": ["src/**/*.test.ts"], + "ignore": ["api-report/**"] + }, "packages/process-compose": { "entry": ["src/**/*.test.ts", "tests/**/*.ts"] }, diff --git a/packages/config/api-report/analytics.d.ts b/packages/config/api-report/analytics.d.ts new file mode 100644 index 0000000000..34806114d3 --- /dev/null +++ b/packages/config/api-report/analytics.d.ts @@ -0,0 +1,10 @@ +import { Schema } from "effect"; +export declare const analytics: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly backend: Schema.withDecodingDefaultKey, never>; + readonly vector_port: Schema.optionalKey; + readonly gcp_project_id: Schema.optionalKey; + readonly gcp_project_number: Schema.optionalKey; + readonly gcp_jwt_path: Schema.optionalKey; +}>, never>; diff --git a/packages/config/api-report/api.d.ts b/packages/config/api-report/api.d.ts new file mode 100644 index 0000000000..e1273b53c7 --- /dev/null +++ b/packages/config/api-report/api.d.ts @@ -0,0 +1,15 @@ +import { Schema } from "effect"; +export declare const api: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly schemas: Schema.withDecodingDefaultKey, never>; + readonly extra_search_path: Schema.withDecodingDefaultKey, never>; + readonly max_rows: Schema.withDecodingDefaultKey; + readonly auto_expose_new_tables: Schema.optionalKey; + readonly tls: Schema.withDecodingDefaultKey; + readonly cert_path: Schema.optionalKey; + readonly key_path: Schema.optionalKey; + }>, never>; + readonly external_url: Schema.optionalKey; +}>, never>; diff --git a/packages/config/api-report/auth/captcha.d.ts b/packages/config/api-report/auth/captcha.d.ts new file mode 100644 index 0000000000..a3d71214e2 --- /dev/null +++ b/packages/config/api-report/auth/captcha.d.ts @@ -0,0 +1,6 @@ +import { Schema } from "effect"; +export declare const captcha: Schema.withDecodingDefaultKey; + readonly provider: Schema.optionalKey>; + readonly secret: Schema.optionalKey; +}>, never>; diff --git a/packages/config/api-report/auth/email.d.ts b/packages/config/api-report/auth/email.d.ts new file mode 100644 index 0000000000..11473a107c --- /dev/null +++ b/packages/config/api-report/auth/email.d.ts @@ -0,0 +1,28 @@ +import { Schema } from "effect"; +export declare const email: Schema.withDecodingDefaultKey; + readonly double_confirm_changes: Schema.withDecodingDefaultKey; + readonly enable_confirmations: Schema.withDecodingDefaultKey; + readonly secure_password_change: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + readonly otp_length: Schema.withDecodingDefaultKey; + readonly otp_expiry: Schema.withDecodingDefaultKey; + readonly smtp: Schema.optionalKey; + readonly host: Schema.optionalKey; + readonly port: Schema.optionalKey; + readonly user: Schema.optionalKey; + readonly pass: Schema.optionalKey; + readonly admin_email: Schema.optionalKey; + readonly sender_name: Schema.optionalKey; + }>, never>>; + readonly template: Schema.withDecodingDefault; + readonly content_path: Schema.withDecodingDefaultKey; + }>, never>>, never>; + readonly notification: Schema.withDecodingDefault; + readonly subject: Schema.withDecodingDefaultKey; + readonly content_path: Schema.withDecodingDefaultKey; + }>, never>>, never>; +}>, never>; diff --git a/packages/config/api-report/auth/hooks.d.ts b/packages/config/api-report/auth/hooks.d.ts new file mode 100644 index 0000000000..3498f24cdf --- /dev/null +++ b/packages/config/api-report/auth/hooks.d.ts @@ -0,0 +1,33 @@ +import { Schema } from "effect"; +export declare const hook: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly password_verification_attempt: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly custom_access_token: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly send_sms: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly send_email: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly before_user_created: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; +}>, never>; diff --git a/packages/config/api-report/auth/index.d.ts b/packages/config/api-report/auth/index.d.ts new file mode 100644 index 0000000000..028b544f82 --- /dev/null +++ b/packages/config/api-report/auth/index.d.ts @@ -0,0 +1,361 @@ +import { Schema } from "effect"; +export declare const auth: Schema.withDecodingDefaultKey; + readonly site_url: Schema.withDecodingDefaultKey; + readonly additional_redirect_urls: Schema.withDecodingDefaultKey, never>; + readonly jwt_expiry: Schema.withDecodingDefaultKey; + readonly jwt_issuer: Schema.optionalKey; + readonly signing_keys_path: Schema.optionalKey; + readonly enable_refresh_token_rotation: Schema.withDecodingDefaultKey; + readonly refresh_token_reuse_interval: Schema.withDecodingDefaultKey; + readonly enable_manual_linking: Schema.withDecodingDefaultKey; + readonly enable_signup: Schema.withDecodingDefaultKey; + readonly enable_anonymous_sign_ins: Schema.withDecodingDefaultKey; + readonly minimum_password_length: Schema.withDecodingDefaultKey; + readonly password_requirements: Schema.withDecodingDefaultKey, never>; + readonly publishable_key: Schema.optionalKey; + readonly secret_key: Schema.optionalKey; + readonly jwt_secret: Schema.optionalKey; + readonly anon_key: Schema.optionalKey; + readonly service_role_key: Schema.optionalKey; + readonly rate_limit: Schema.withDecodingDefaultKey; + readonly sms_sent: Schema.withDecodingDefaultKey; + readonly anonymous_users: Schema.withDecodingDefaultKey; + readonly token_refresh: Schema.withDecodingDefaultKey; + readonly sign_in_sign_ups: Schema.withDecodingDefaultKey; + readonly token_verifications: Schema.withDecodingDefaultKey; + readonly web3: Schema.withDecodingDefaultKey; + }>, never>; + readonly captcha: Schema.optionalKey; + readonly provider: Schema.optionalKey>; + readonly secret: Schema.optionalKey; + }>, never>>; + readonly hook: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly password_verification_attempt: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly custom_access_token: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly send_sms: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly send_email: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly before_user_created: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + }>, never>; + readonly mfa: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + }>, never>; + readonly phone: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + readonly otp_length: Schema.withDecodingDefaultKey; + readonly template: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + }>, never>; + readonly web_authn: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + }>, never>; + readonly max_enrolled_factors: Schema.withDecodingDefaultKey; + }>, never>; + readonly sessions: Schema.optionalKey; + readonly inactivity_timeout: Schema.optionalKey; + }>, never>>; + readonly email: Schema.withDecodingDefaultKey; + readonly double_confirm_changes: Schema.withDecodingDefaultKey; + readonly enable_confirmations: Schema.withDecodingDefaultKey; + readonly secure_password_change: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + readonly otp_length: Schema.withDecodingDefaultKey; + readonly otp_expiry: Schema.withDecodingDefaultKey; + readonly smtp: Schema.optionalKey; + readonly host: Schema.optionalKey; + readonly port: Schema.optionalKey; + readonly user: Schema.optionalKey; + readonly pass: Schema.optionalKey; + readonly admin_email: Schema.optionalKey; + readonly sender_name: Schema.optionalKey; + }>, never>>; + readonly template: Schema.withDecodingDefault; + readonly content_path: Schema.withDecodingDefaultKey; + }>, never>>, never>; + readonly notification: Schema.withDecodingDefault; + readonly subject: Schema.withDecodingDefaultKey; + readonly content_path: Schema.withDecodingDefaultKey; + }>, never>>, never>; + }>, never>; + readonly sms: Schema.withDecodingDefaultKey; + readonly enable_confirmations: Schema.withDecodingDefaultKey; + readonly template: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + readonly twilio: Schema.withDecodingDefaultKey; + readonly account_sid: Schema.withDecodingDefaultKey; + readonly message_service_sid: Schema.withDecodingDefaultKey; + readonly auth_token: Schema.optionalKey; + }>, never>; + readonly twilio_verify: Schema.withDecodingDefaultKey; + readonly account_sid: Schema.optionalKey; + readonly message_service_sid: Schema.optionalKey; + readonly auth_token: Schema.optionalKey; + }>, never>; + readonly messagebird: Schema.withDecodingDefaultKey; + readonly originator: Schema.optionalKey; + readonly access_key: Schema.optionalKey; + }>, never>; + readonly textlocal: Schema.withDecodingDefaultKey; + readonly sender: Schema.optionalKey; + readonly api_key: Schema.optionalKey; + }>, never>; + readonly vonage: Schema.withDecodingDefaultKey; + readonly from: Schema.optionalKey; + readonly api_key: Schema.optionalKey; + readonly api_secret: Schema.optionalKey; + }>, never>; + readonly test_otp: Schema.optionalKey>; + }>, never>; + readonly external: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly azure: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly bitbucket: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly discord: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly facebook: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly github: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly gitlab: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly google: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly kakao: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly keycloak: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly linkedin_oidc: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly notion: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly twitch: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly twitter: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly x: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly slack_oidc: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly spotify: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly workos: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly zoom: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + }>, never>; + readonly web3: Schema.withDecodingDefaultKey; + }>, never>; + readonly ethereum: Schema.withDecodingDefaultKey; + }>, never>; + }>, never>; + readonly oauth_server: Schema.withDecodingDefaultKey; + readonly authorization_url_path: Schema.withDecodingDefaultKey; + readonly allow_dynamic_registration: Schema.withDecodingDefaultKey; + }>, never>; + readonly third_party: Schema.withDecodingDefaultKey; + readonly project_id: Schema.optionalKey; + }>, never>; + readonly auth0: Schema.withDecodingDefaultKey; + readonly tenant: Schema.optionalKey; + readonly tenant_region: Schema.optionalKey; + }>, never>; + readonly aws_cognito: Schema.withDecodingDefaultKey; + readonly user_pool_id: Schema.optionalKey; + readonly user_pool_region: Schema.optionalKey; + }>, never>; + readonly clerk: Schema.withDecodingDefaultKey; + readonly domain: Schema.optionalKey; + }>, never>; + readonly workos: Schema.withDecodingDefaultKey; + readonly issuer_url: Schema.optionalKey; + }>, never>; + }>, never>; +}>, never>; diff --git a/packages/config/api-report/auth/mfa.d.ts b/packages/config/api-report/auth/mfa.d.ts new file mode 100644 index 0000000000..4ca7caeeff --- /dev/null +++ b/packages/config/api-report/auth/mfa.d.ts @@ -0,0 +1,19 @@ +import { Schema } from "effect"; +export declare const mfa: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + }>, never>; + readonly phone: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + readonly otp_length: Schema.withDecodingDefaultKey; + readonly template: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + }>, never>; + readonly web_authn: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + }>, never>; + readonly max_enrolled_factors: Schema.withDecodingDefaultKey; +}>, never>; diff --git a/packages/config/api-report/auth/providers.d.ts b/packages/config/api-report/auth/providers.d.ts new file mode 100644 index 0000000000..dd8ff38964 --- /dev/null +++ b/packages/config/api-report/auth/providers.d.ts @@ -0,0 +1,183 @@ +import { Schema } from "effect"; +/** + * Go's deprecated `linkedin`/`slack` provider ids (`pkg/config/config.go:1418- + * 1423`) are intentionally NOT modeled here — only their `_oidc` replacements + * (`linkedin_oidc`, `slack_oidc`) are, matching Go's `(e external) validate()`, + * which unconditionally deletes the deprecated keys before anything decodes + * them. `io.ts`'s `normalizeDeprecatedExternalProviders` strips a config's + * `linkedin`/`slack` table (warning on stderr when it was `enabled`, same as + * Go) before this schema ever sees it. + */ +export declare const external: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly azure: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly bitbucket: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly discord: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly facebook: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly github: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly gitlab: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly google: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly kakao: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly keycloak: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly linkedin_oidc: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly notion: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly twitch: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly twitter: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly x: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly slack_oidc: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly spotify: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly workos: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly zoom: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; +}>, never>; diff --git a/packages/config/api-report/auth/rate_limit.d.ts b/packages/config/api-report/auth/rate_limit.d.ts new file mode 100644 index 0000000000..564eca1092 --- /dev/null +++ b/packages/config/api-report/auth/rate_limit.d.ts @@ -0,0 +1,10 @@ +import { Schema } from "effect"; +export declare const rate_limit: Schema.withDecodingDefaultKey; + readonly sms_sent: Schema.withDecodingDefaultKey; + readonly anonymous_users: Schema.withDecodingDefaultKey; + readonly token_refresh: Schema.withDecodingDefaultKey; + readonly sign_in_sign_ups: Schema.withDecodingDefaultKey; + readonly token_verifications: Schema.withDecodingDefaultKey; + readonly web3: Schema.withDecodingDefaultKey; +}>, never>; diff --git a/packages/config/api-report/auth/sessions.d.ts b/packages/config/api-report/auth/sessions.d.ts new file mode 100644 index 0000000000..fc3794247c --- /dev/null +++ b/packages/config/api-report/auth/sessions.d.ts @@ -0,0 +1,5 @@ +import { Schema } from "effect"; +export declare const sessions: Schema.withDecodingDefaultKey; + readonly inactivity_timeout: Schema.optionalKey; +}>, never>; diff --git a/packages/config/api-report/auth/sms.d.ts b/packages/config/api-report/auth/sms.d.ts new file mode 100644 index 0000000000..f6b2dbaf21 --- /dev/null +++ b/packages/config/api-report/auth/sms.d.ts @@ -0,0 +1,36 @@ +import { Schema } from "effect"; +export declare const sms: Schema.withDecodingDefaultKey; + readonly enable_confirmations: Schema.withDecodingDefaultKey; + readonly template: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + readonly twilio: Schema.withDecodingDefaultKey; + readonly account_sid: Schema.withDecodingDefaultKey; + readonly message_service_sid: Schema.withDecodingDefaultKey; + readonly auth_token: Schema.optionalKey; + }>, never>; + readonly twilio_verify: Schema.withDecodingDefaultKey; + readonly account_sid: Schema.optionalKey; + readonly message_service_sid: Schema.optionalKey; + readonly auth_token: Schema.optionalKey; + }>, never>; + readonly messagebird: Schema.withDecodingDefaultKey; + readonly originator: Schema.optionalKey; + readonly access_key: Schema.optionalKey; + }>, never>; + readonly textlocal: Schema.withDecodingDefaultKey; + readonly sender: Schema.optionalKey; + readonly api_key: Schema.optionalKey; + }>, never>; + readonly vonage: Schema.withDecodingDefaultKey; + readonly from: Schema.optionalKey; + readonly api_key: Schema.optionalKey; + readonly api_secret: Schema.optionalKey; + }>, never>; + readonly test_otp: Schema.optionalKey>; +}>, never>; diff --git a/packages/config/api-report/auth/third_party.d.ts b/packages/config/api-report/auth/third_party.d.ts new file mode 100644 index 0000000000..03805089a4 --- /dev/null +++ b/packages/config/api-report/auth/third_party.d.ts @@ -0,0 +1,25 @@ +import { Schema } from "effect"; +export declare const third_party: Schema.withDecodingDefaultKey; + readonly project_id: Schema.optionalKey; + }>, never>; + readonly auth0: Schema.withDecodingDefaultKey; + readonly tenant: Schema.optionalKey; + readonly tenant_region: Schema.optionalKey; + }>, never>; + readonly aws_cognito: Schema.withDecodingDefaultKey; + readonly user_pool_id: Schema.optionalKey; + readonly user_pool_region: Schema.optionalKey; + }>, never>; + readonly clerk: Schema.withDecodingDefaultKey; + readonly domain: Schema.optionalKey; + }>, never>; + readonly workos: Schema.withDecodingDefaultKey; + readonly issuer_url: Schema.optionalKey; + }>, never>; +}>, never>; diff --git a/packages/config/api-report/auth/web3.d.ts b/packages/config/api-report/auth/web3.d.ts new file mode 100644 index 0000000000..97dca31015 --- /dev/null +++ b/packages/config/api-report/auth/web3.d.ts @@ -0,0 +1,9 @@ +import { Schema } from "effect"; +export declare const web3: Schema.withDecodingDefaultKey; + }>, never>; + readonly ethereum: Schema.withDecodingDefaultKey; + }>, never>; +}>, never>; diff --git a/packages/config/api-report/base.d.ts b/packages/config/api-report/base.d.ts new file mode 100644 index 0000000000..00ccac4bd0 --- /dev/null +++ b/packages/config/api-report/base.d.ts @@ -0,0 +1,1621 @@ +import { Schema } from "effect"; +/** + * Exported separately (not inlined into {@link CliConfigSchema}) so + * `packages/config/src/io.ts` can decode it on its own with + * `disableChecks: true`. Go's `Config.Validate` only ever checks + * `remotes.*.project_id` format for every remote block + * (`apps/cli-go/pkg/config/config.go:996-1001`, "Since remote config is merged + * to base, we only need to validate the project_id field") — every other + * business-rule check (`Auth.External.validate()`, `Auth.Sms.validate()`, + * etc.) runs exactly once, against the merged effective config + * (`config.go:1136-1152`), never iterated over `c.Remotes[*]`. Decoding this + * schema normally (checks enabled) would apply those same business-rule + * `.check()`s — embedded in `auth`/`db`/etc. — to every remote regardless of + * selection, rejecting configs Go accepts (e.g. an unselected + * `[remotes.prod.auth.external.github] enabled = true` stub with no secret). + */ +export declare const RemotesSchema: Schema.$Record; + readonly analytics: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly backend: Schema.withDecodingDefaultKey, never>; + readonly vector_port: Schema.optionalKey; + readonly gcp_project_id: Schema.optionalKey; + readonly gcp_project_number: Schema.optionalKey; + readonly gcp_jwt_path: Schema.optionalKey; + }>, never>; + readonly api: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly schemas: Schema.withDecodingDefaultKey, never>; + readonly extra_search_path: Schema.withDecodingDefaultKey, never>; + readonly max_rows: Schema.withDecodingDefaultKey; + readonly auto_expose_new_tables: Schema.optionalKey; + readonly tls: Schema.withDecodingDefaultKey; + readonly cert_path: Schema.optionalKey; + readonly key_path: Schema.optionalKey; + }>, never>; + readonly external_url: Schema.optionalKey; + }>, never>; + readonly auth: Schema.withDecodingDefaultKey; + readonly site_url: Schema.withDecodingDefaultKey; + readonly additional_redirect_urls: Schema.withDecodingDefaultKey, never>; + readonly jwt_expiry: Schema.withDecodingDefaultKey; + readonly jwt_issuer: Schema.optionalKey; + readonly signing_keys_path: Schema.optionalKey; + readonly enable_refresh_token_rotation: Schema.withDecodingDefaultKey; + readonly refresh_token_reuse_interval: Schema.withDecodingDefaultKey; + readonly enable_manual_linking: Schema.withDecodingDefaultKey; + readonly enable_signup: Schema.withDecodingDefaultKey; + readonly enable_anonymous_sign_ins: Schema.withDecodingDefaultKey; + readonly minimum_password_length: Schema.withDecodingDefaultKey; + readonly password_requirements: Schema.withDecodingDefaultKey, never>; + readonly publishable_key: Schema.optionalKey; + readonly secret_key: Schema.optionalKey; + readonly jwt_secret: Schema.optionalKey; + readonly anon_key: Schema.optionalKey; + readonly service_role_key: Schema.optionalKey; + readonly rate_limit: Schema.withDecodingDefaultKey; + readonly sms_sent: Schema.withDecodingDefaultKey; + readonly anonymous_users: Schema.withDecodingDefaultKey; + readonly token_refresh: Schema.withDecodingDefaultKey; + readonly sign_in_sign_ups: Schema.withDecodingDefaultKey; + readonly token_verifications: Schema.withDecodingDefaultKey; + readonly web3: Schema.withDecodingDefaultKey; + }>, never>; + readonly captcha: Schema.optionalKey; + readonly provider: Schema.optionalKey>; + readonly secret: Schema.optionalKey; + }>, never>>; + readonly hook: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly password_verification_attempt: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly custom_access_token: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly send_sms: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly send_email: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly before_user_created: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + }>, never>; + readonly mfa: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + }>, never>; + readonly phone: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + readonly otp_length: Schema.withDecodingDefaultKey; + readonly template: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + }>, never>; + readonly web_authn: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + }>, never>; + readonly max_enrolled_factors: Schema.withDecodingDefaultKey; + }>, never>; + readonly sessions: Schema.optionalKey; + readonly inactivity_timeout: Schema.optionalKey; + }>, never>>; + readonly email: Schema.withDecodingDefaultKey; + readonly double_confirm_changes: Schema.withDecodingDefaultKey; + readonly enable_confirmations: Schema.withDecodingDefaultKey; + readonly secure_password_change: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + readonly otp_length: Schema.withDecodingDefaultKey; + readonly otp_expiry: Schema.withDecodingDefaultKey; + readonly smtp: Schema.optionalKey; + readonly host: Schema.optionalKey; + readonly port: Schema.optionalKey; + readonly user: Schema.optionalKey; + readonly pass: Schema.optionalKey; + readonly admin_email: Schema.optionalKey; + readonly sender_name: Schema.optionalKey; + }>, never>>; + readonly template: Schema.withDecodingDefault; + readonly content_path: Schema.withDecodingDefaultKey; + }>, never>>, never>; + readonly notification: Schema.withDecodingDefault; + readonly subject: Schema.withDecodingDefaultKey; + readonly content_path: Schema.withDecodingDefaultKey; + }>, never>>, never>; + }>, never>; + readonly sms: Schema.withDecodingDefaultKey; + readonly enable_confirmations: Schema.withDecodingDefaultKey; + readonly template: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + readonly twilio: Schema.withDecodingDefaultKey; + readonly account_sid: Schema.withDecodingDefaultKey; + readonly message_service_sid: Schema.withDecodingDefaultKey; + readonly auth_token: Schema.optionalKey; + }>, never>; + readonly twilio_verify: Schema.withDecodingDefaultKey; + readonly account_sid: Schema.optionalKey; + readonly message_service_sid: Schema.optionalKey; + readonly auth_token: Schema.optionalKey; + }>, never>; + readonly messagebird: Schema.withDecodingDefaultKey; + readonly originator: Schema.optionalKey; + readonly access_key: Schema.optionalKey; + }>, never>; + readonly textlocal: Schema.withDecodingDefaultKey; + readonly sender: Schema.optionalKey; + readonly api_key: Schema.optionalKey; + }>, never>; + readonly vonage: Schema.withDecodingDefaultKey; + readonly from: Schema.optionalKey; + readonly api_key: Schema.optionalKey; + readonly api_secret: Schema.optionalKey; + }>, never>; + readonly test_otp: Schema.optionalKey>; + }>, never>; + readonly external: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly azure: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly bitbucket: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly discord: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly facebook: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly github: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly gitlab: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly google: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly kakao: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly keycloak: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly linkedin_oidc: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly notion: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly twitch: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly twitter: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly x: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly slack_oidc: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly spotify: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly workos: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly zoom: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + }>, never>; + readonly web3: Schema.withDecodingDefaultKey; + }>, never>; + readonly ethereum: Schema.withDecodingDefaultKey; + }>, never>; + }>, never>; + readonly oauth_server: Schema.withDecodingDefaultKey; + readonly authorization_url_path: Schema.withDecodingDefaultKey; + readonly allow_dynamic_registration: Schema.withDecodingDefaultKey; + }>, never>; + readonly third_party: Schema.withDecodingDefaultKey; + readonly project_id: Schema.optionalKey; + }>, never>; + readonly auth0: Schema.withDecodingDefaultKey; + readonly tenant: Schema.optionalKey; + readonly tenant_region: Schema.optionalKey; + }>, never>; + readonly aws_cognito: Schema.withDecodingDefaultKey; + readonly user_pool_id: Schema.optionalKey; + readonly user_pool_region: Schema.optionalKey; + }>, never>; + readonly clerk: Schema.withDecodingDefaultKey; + readonly domain: Schema.optionalKey; + }>, never>; + readonly workos: Schema.withDecodingDefaultKey; + readonly issuer_url: Schema.optionalKey; + }>, never>; + }>, never>; + }>, never>; + readonly db: Schema.withDecodingDefaultKey; + readonly shadow_port: Schema.withDecodingDefaultKey; + readonly health_timeout: Schema.withDecodingDefaultKey; + readonly major_version: Schema.withDecodingDefaultKey; + readonly pooler: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly pool_mode: Schema.withDecodingDefaultKey, never>; + readonly default_pool_size: Schema.withDecodingDefaultKey; + readonly max_client_conn: Schema.withDecodingDefaultKey; + }>, never>; + readonly migrations: Schema.withDecodingDefaultKey; + readonly schema_paths: Schema.withDecodingDefaultKey, never>; + }>, never>; + readonly seed: Schema.withDecodingDefaultKey; + readonly sql_paths: Schema.withDecodingDefaultKey, never>; + }>, never>; + readonly settings: Schema.optionalKey; + readonly logical_decoding_work_mem: Schema.optionalKey; + readonly maintenance_work_mem: Schema.optionalKey; + readonly max_connections: Schema.optionalKey; + readonly max_locks_per_transaction: Schema.optionalKey; + readonly max_parallel_maintenance_workers: Schema.optionalKey; + readonly max_parallel_workers: Schema.optionalKey; + readonly max_parallel_workers_per_gather: Schema.optionalKey; + readonly max_replication_slots: Schema.optionalKey; + readonly max_slot_wal_keep_size: Schema.optionalKey; + readonly max_standby_archive_delay: Schema.optionalKey; + readonly max_standby_streaming_delay: Schema.optionalKey; + readonly max_wal_size: Schema.optionalKey; + readonly max_wal_senders: Schema.optionalKey; + readonly max_worker_processes: Schema.optionalKey; + readonly session_replication_role: Schema.optionalKey>; + readonly shared_buffers: Schema.optionalKey; + readonly statement_timeout: Schema.optionalKey; + readonly track_activity_query_size: Schema.optionalKey; + readonly track_commit_timestamp: Schema.optionalKey; + readonly wal_keep_size: Schema.optionalKey; + readonly wal_sender_timeout: Schema.optionalKey; + readonly work_mem: Schema.optionalKey; + }>, never>>; + readonly network_restrictions: Schema.withDecodingDefaultKey; + readonly allowed_cidrs: Schema.withDecodingDefaultKey, never>; + readonly allowed_cidrs_v6: Schema.withDecodingDefaultKey, never>; + }>, never>; + readonly ssl_enforcement: Schema.optionalKey; + }>, never>>; + readonly vault: Schema.optionalKey>; + }>, never>; + readonly edge_runtime: Schema.withDecodingDefaultKey; + readonly policy: Schema.withDecodingDefaultKey, never>; + readonly inspector_port: Schema.withDecodingDefaultKey; + readonly deno_version: Schema.withDecodingDefaultKey; + readonly secrets: Schema.optionalKey>; + }>, never>; + readonly functions: Schema.withDecodingDefault; + readonly verify_jwt: Schema.withDecodingDefaultKey; + readonly import_map: Schema.withDecodingDefaultKey; + readonly entrypoint: Schema.withDecodingDefaultKey; + readonly static_files: Schema.withDecodingDefaultKey, never>; + readonly env: Schema.withDecodingDefaultKey, never>; + }>, never>>, never>; + readonly local_smtp: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly smtp_port: Schema.optionalKey; + readonly pop3_port: Schema.optionalKey; + readonly admin_email: Schema.optionalKey; + readonly sender_name: Schema.optionalKey; + }>, never>; + readonly realtime: Schema.withDecodingDefaultKey; + readonly ip_version: Schema.withDecodingDefaultKey, never>; + readonly max_header_length: Schema.withDecodingDefaultKey; + }>, never>; + readonly storage: Schema.withDecodingDefaultKey; + readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; + readonly image_transformation: Schema.optionalKey; + }>, never>>; + readonly buckets: Schema.optionalKey; + readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; + readonly allowed_mime_types: Schema.withDecodingDefaultKey, never>; + readonly objects_path: Schema.withDecodingDefaultKey; + }>, never>>>; + readonly s3_protocol: Schema.withDecodingDefaultKey; + }>, never>; + readonly analytics: Schema.withDecodingDefaultKey; + readonly max_namespaces: Schema.withDecodingDefaultKey; + readonly max_tables: Schema.withDecodingDefaultKey; + readonly max_catalogs: Schema.withDecodingDefaultKey; + readonly buckets: Schema.withDecodingDefault, never>>, never>; + }>, never>; + readonly vector: Schema.withDecodingDefaultKey; + readonly max_buckets: Schema.withDecodingDefaultKey; + readonly max_indexes: Schema.withDecodingDefaultKey; + readonly buckets: Schema.withDecodingDefault, never>>, never>; + }>, never>; + }>, never>; + readonly studio: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly api_url: Schema.withDecodingDefaultKey; + readonly openai_api_key: Schema.optionalKey; + }>, never>; + readonly workers: Schema.withDecodingDefault; + readonly size: Schema.optionalKey; + readonly instances: Schema.optionalKey; + readonly source: Schema.optionalKey; + }>>, never>; + readonly experimental: Schema.withDecodingDefaultKey; + readonly s3_host: Schema.optionalKey; + readonly s3_region: Schema.optionalKey; + readonly s3_access_key: Schema.optionalKey; + readonly s3_secret_key: Schema.optionalKey; + readonly webhooks: Schema.optionalKey; + }>, never>>; + readonly pgdelta: Schema.optionalKey; + readonly declarative_schema_path: Schema.optionalKey; + readonly format_options: Schema.optionalKey; + }>, never>>; + readonly inspect: Schema.optionalKey; + readonly name: Schema.optionalKey; + readonly pass: Schema.optionalKey; + readonly fail: Schema.optionalKey; + }>, never>>, never>; + }>, never>>; + }>, never>; +}>, never>>; +export declare const CliConfigSchema: Schema.Struct<{ + readonly project_id: Schema.optionalKey; + readonly analytics: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly backend: Schema.withDecodingDefaultKey, never>; + readonly vector_port: Schema.optionalKey; + readonly gcp_project_id: Schema.optionalKey; + readonly gcp_project_number: Schema.optionalKey; + readonly gcp_jwt_path: Schema.optionalKey; + }>, never>; + readonly api: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly schemas: Schema.withDecodingDefaultKey, never>; + readonly extra_search_path: Schema.withDecodingDefaultKey, never>; + readonly max_rows: Schema.withDecodingDefaultKey; + readonly auto_expose_new_tables: Schema.optionalKey; + readonly tls: Schema.withDecodingDefaultKey; + readonly cert_path: Schema.optionalKey; + readonly key_path: Schema.optionalKey; + }>, never>; + readonly external_url: Schema.optionalKey; + }>, never>; + readonly auth: Schema.withDecodingDefaultKey; + readonly site_url: Schema.withDecodingDefaultKey; + readonly additional_redirect_urls: Schema.withDecodingDefaultKey, never>; + readonly jwt_expiry: Schema.withDecodingDefaultKey; + readonly jwt_issuer: Schema.optionalKey; + readonly signing_keys_path: Schema.optionalKey; + readonly enable_refresh_token_rotation: Schema.withDecodingDefaultKey; + readonly refresh_token_reuse_interval: Schema.withDecodingDefaultKey; + readonly enable_manual_linking: Schema.withDecodingDefaultKey; + readonly enable_signup: Schema.withDecodingDefaultKey; + readonly enable_anonymous_sign_ins: Schema.withDecodingDefaultKey; + readonly minimum_password_length: Schema.withDecodingDefaultKey; + readonly password_requirements: Schema.withDecodingDefaultKey, never>; + readonly publishable_key: Schema.optionalKey; + readonly secret_key: Schema.optionalKey; + readonly jwt_secret: Schema.optionalKey; + readonly anon_key: Schema.optionalKey; + readonly service_role_key: Schema.optionalKey; + readonly rate_limit: Schema.withDecodingDefaultKey; + readonly sms_sent: Schema.withDecodingDefaultKey; + readonly anonymous_users: Schema.withDecodingDefaultKey; + readonly token_refresh: Schema.withDecodingDefaultKey; + readonly sign_in_sign_ups: Schema.withDecodingDefaultKey; + readonly token_verifications: Schema.withDecodingDefaultKey; + readonly web3: Schema.withDecodingDefaultKey; + }>, never>; + readonly captcha: Schema.optionalKey; + readonly provider: Schema.optionalKey>; + readonly secret: Schema.optionalKey; + }>, never>>; + readonly hook: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly password_verification_attempt: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly custom_access_token: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly send_sms: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly send_email: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly before_user_created: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + }>, never>; + readonly mfa: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + }>, never>; + readonly phone: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + readonly otp_length: Schema.withDecodingDefaultKey; + readonly template: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + }>, never>; + readonly web_authn: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + }>, never>; + readonly max_enrolled_factors: Schema.withDecodingDefaultKey; + }>, never>; + readonly sessions: Schema.optionalKey; + readonly inactivity_timeout: Schema.optionalKey; + }>, never>>; + readonly email: Schema.withDecodingDefaultKey; + readonly double_confirm_changes: Schema.withDecodingDefaultKey; + readonly enable_confirmations: Schema.withDecodingDefaultKey; + readonly secure_password_change: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + readonly otp_length: Schema.withDecodingDefaultKey; + readonly otp_expiry: Schema.withDecodingDefaultKey; + readonly smtp: Schema.optionalKey; + readonly host: Schema.optionalKey; + readonly port: Schema.optionalKey; + readonly user: Schema.optionalKey; + readonly pass: Schema.optionalKey; + readonly admin_email: Schema.optionalKey; + readonly sender_name: Schema.optionalKey; + }>, never>>; + readonly template: Schema.withDecodingDefault; + readonly content_path: Schema.withDecodingDefaultKey; + }>, never>>, never>; + readonly notification: Schema.withDecodingDefault; + readonly subject: Schema.withDecodingDefaultKey; + readonly content_path: Schema.withDecodingDefaultKey; + }>, never>>, never>; + }>, never>; + readonly sms: Schema.withDecodingDefaultKey; + readonly enable_confirmations: Schema.withDecodingDefaultKey; + readonly template: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + readonly twilio: Schema.withDecodingDefaultKey; + readonly account_sid: Schema.withDecodingDefaultKey; + readonly message_service_sid: Schema.withDecodingDefaultKey; + readonly auth_token: Schema.optionalKey; + }>, never>; + readonly twilio_verify: Schema.withDecodingDefaultKey; + readonly account_sid: Schema.optionalKey; + readonly message_service_sid: Schema.optionalKey; + readonly auth_token: Schema.optionalKey; + }>, never>; + readonly messagebird: Schema.withDecodingDefaultKey; + readonly originator: Schema.optionalKey; + readonly access_key: Schema.optionalKey; + }>, never>; + readonly textlocal: Schema.withDecodingDefaultKey; + readonly sender: Schema.optionalKey; + readonly api_key: Schema.optionalKey; + }>, never>; + readonly vonage: Schema.withDecodingDefaultKey; + readonly from: Schema.optionalKey; + readonly api_key: Schema.optionalKey; + readonly api_secret: Schema.optionalKey; + }>, never>; + readonly test_otp: Schema.optionalKey>; + }>, never>; + readonly external: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly azure: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly bitbucket: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly discord: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly facebook: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly github: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly gitlab: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly google: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly kakao: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly keycloak: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly linkedin_oidc: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly notion: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly twitch: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly twitter: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly x: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly slack_oidc: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly spotify: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly workos: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly zoom: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + }>, never>; + readonly web3: Schema.withDecodingDefaultKey; + }>, never>; + readonly ethereum: Schema.withDecodingDefaultKey; + }>, never>; + }>, never>; + readonly oauth_server: Schema.withDecodingDefaultKey; + readonly authorization_url_path: Schema.withDecodingDefaultKey; + readonly allow_dynamic_registration: Schema.withDecodingDefaultKey; + }>, never>; + readonly third_party: Schema.withDecodingDefaultKey; + readonly project_id: Schema.optionalKey; + }>, never>; + readonly auth0: Schema.withDecodingDefaultKey; + readonly tenant: Schema.optionalKey; + readonly tenant_region: Schema.optionalKey; + }>, never>; + readonly aws_cognito: Schema.withDecodingDefaultKey; + readonly user_pool_id: Schema.optionalKey; + readonly user_pool_region: Schema.optionalKey; + }>, never>; + readonly clerk: Schema.withDecodingDefaultKey; + readonly domain: Schema.optionalKey; + }>, never>; + readonly workos: Schema.withDecodingDefaultKey; + readonly issuer_url: Schema.optionalKey; + }>, never>; + }>, never>; + }>, never>; + readonly db: Schema.withDecodingDefaultKey; + readonly shadow_port: Schema.withDecodingDefaultKey; + readonly health_timeout: Schema.withDecodingDefaultKey; + readonly major_version: Schema.withDecodingDefaultKey; + readonly pooler: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly pool_mode: Schema.withDecodingDefaultKey, never>; + readonly default_pool_size: Schema.withDecodingDefaultKey; + readonly max_client_conn: Schema.withDecodingDefaultKey; + }>, never>; + readonly migrations: Schema.withDecodingDefaultKey; + readonly schema_paths: Schema.withDecodingDefaultKey, never>; + }>, never>; + readonly seed: Schema.withDecodingDefaultKey; + readonly sql_paths: Schema.withDecodingDefaultKey, never>; + }>, never>; + readonly settings: Schema.optionalKey; + readonly logical_decoding_work_mem: Schema.optionalKey; + readonly maintenance_work_mem: Schema.optionalKey; + readonly max_connections: Schema.optionalKey; + readonly max_locks_per_transaction: Schema.optionalKey; + readonly max_parallel_maintenance_workers: Schema.optionalKey; + readonly max_parallel_workers: Schema.optionalKey; + readonly max_parallel_workers_per_gather: Schema.optionalKey; + readonly max_replication_slots: Schema.optionalKey; + readonly max_slot_wal_keep_size: Schema.optionalKey; + readonly max_standby_archive_delay: Schema.optionalKey; + readonly max_standby_streaming_delay: Schema.optionalKey; + readonly max_wal_size: Schema.optionalKey; + readonly max_wal_senders: Schema.optionalKey; + readonly max_worker_processes: Schema.optionalKey; + readonly session_replication_role: Schema.optionalKey>; + readonly shared_buffers: Schema.optionalKey; + readonly statement_timeout: Schema.optionalKey; + readonly track_activity_query_size: Schema.optionalKey; + readonly track_commit_timestamp: Schema.optionalKey; + readonly wal_keep_size: Schema.optionalKey; + readonly wal_sender_timeout: Schema.optionalKey; + readonly work_mem: Schema.optionalKey; + }>, never>>; + readonly network_restrictions: Schema.withDecodingDefaultKey; + readonly allowed_cidrs: Schema.withDecodingDefaultKey, never>; + readonly allowed_cidrs_v6: Schema.withDecodingDefaultKey, never>; + }>, never>; + readonly ssl_enforcement: Schema.optionalKey; + }>, never>>; + readonly vault: Schema.optionalKey>; + }>, never>; + readonly edge_runtime: Schema.withDecodingDefaultKey; + readonly policy: Schema.withDecodingDefaultKey, never>; + readonly inspector_port: Schema.withDecodingDefaultKey; + readonly deno_version: Schema.withDecodingDefaultKey; + readonly secrets: Schema.optionalKey>; + }>, never>; + readonly functions: Schema.withDecodingDefault; + readonly verify_jwt: Schema.withDecodingDefaultKey; + readonly import_map: Schema.withDecodingDefaultKey; + readonly entrypoint: Schema.withDecodingDefaultKey; + readonly static_files: Schema.withDecodingDefaultKey, never>; + readonly env: Schema.withDecodingDefaultKey, never>; + }>, never>>, never>; + readonly local_smtp: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly smtp_port: Schema.optionalKey; + readonly pop3_port: Schema.optionalKey; + readonly admin_email: Schema.optionalKey; + readonly sender_name: Schema.optionalKey; + }>, never>; + readonly realtime: Schema.withDecodingDefaultKey; + readonly ip_version: Schema.withDecodingDefaultKey, never>; + readonly max_header_length: Schema.withDecodingDefaultKey; + }>, never>; + readonly storage: Schema.withDecodingDefaultKey; + readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; + readonly image_transformation: Schema.optionalKey; + }>, never>>; + readonly buckets: Schema.optionalKey; + readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; + readonly allowed_mime_types: Schema.withDecodingDefaultKey, never>; + readonly objects_path: Schema.withDecodingDefaultKey; + }>, never>>>; + readonly s3_protocol: Schema.withDecodingDefaultKey; + }>, never>; + readonly analytics: Schema.withDecodingDefaultKey; + readonly max_namespaces: Schema.withDecodingDefaultKey; + readonly max_tables: Schema.withDecodingDefaultKey; + readonly max_catalogs: Schema.withDecodingDefaultKey; + readonly buckets: Schema.withDecodingDefault, never>>, never>; + }>, never>; + readonly vector: Schema.withDecodingDefaultKey; + readonly max_buckets: Schema.withDecodingDefaultKey; + readonly max_indexes: Schema.withDecodingDefaultKey; + readonly buckets: Schema.withDecodingDefault, never>>, never>; + }>, never>; + }>, never>; + readonly studio: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly api_url: Schema.withDecodingDefaultKey; + readonly openai_api_key: Schema.optionalKey; + }>, never>; + readonly workers: Schema.withDecodingDefault; + readonly size: Schema.optionalKey; + readonly instances: Schema.optionalKey; + readonly source: Schema.optionalKey; + }>>, never>; + readonly experimental: Schema.withDecodingDefaultKey; + readonly s3_host: Schema.optionalKey; + readonly s3_region: Schema.optionalKey; + readonly s3_access_key: Schema.optionalKey; + readonly s3_secret_key: Schema.optionalKey; + readonly webhooks: Schema.optionalKey; + }>, never>>; + readonly pgdelta: Schema.optionalKey; + readonly declarative_schema_path: Schema.optionalKey; + readonly format_options: Schema.optionalKey; + }>, never>>; + readonly inspect: Schema.optionalKey; + readonly name: Schema.optionalKey; + readonly pass: Schema.optionalKey; + readonly fail: Schema.optionalKey; + }>, never>>, never>; + }>, never>>; + }>, never>; + readonly remotes: Schema.withDecodingDefault; + readonly analytics: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly backend: Schema.withDecodingDefaultKey, never>; + readonly vector_port: Schema.optionalKey; + readonly gcp_project_id: Schema.optionalKey; + readonly gcp_project_number: Schema.optionalKey; + readonly gcp_jwt_path: Schema.optionalKey; + }>, never>; + readonly api: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly schemas: Schema.withDecodingDefaultKey, never>; + readonly extra_search_path: Schema.withDecodingDefaultKey, never>; + readonly max_rows: Schema.withDecodingDefaultKey; + readonly auto_expose_new_tables: Schema.optionalKey; + readonly tls: Schema.withDecodingDefaultKey; + readonly cert_path: Schema.optionalKey; + readonly key_path: Schema.optionalKey; + }>, never>; + readonly external_url: Schema.optionalKey; + }>, never>; + readonly auth: Schema.withDecodingDefaultKey; + readonly site_url: Schema.withDecodingDefaultKey; + readonly additional_redirect_urls: Schema.withDecodingDefaultKey, never>; + readonly jwt_expiry: Schema.withDecodingDefaultKey; + readonly jwt_issuer: Schema.optionalKey; + readonly signing_keys_path: Schema.optionalKey; + readonly enable_refresh_token_rotation: Schema.withDecodingDefaultKey; + readonly refresh_token_reuse_interval: Schema.withDecodingDefaultKey; + readonly enable_manual_linking: Schema.withDecodingDefaultKey; + readonly enable_signup: Schema.withDecodingDefaultKey; + readonly enable_anonymous_sign_ins: Schema.withDecodingDefaultKey; + readonly minimum_password_length: Schema.withDecodingDefaultKey; + readonly password_requirements: Schema.withDecodingDefaultKey, never>; + readonly publishable_key: Schema.optionalKey; + readonly secret_key: Schema.optionalKey; + readonly jwt_secret: Schema.optionalKey; + readonly anon_key: Schema.optionalKey; + readonly service_role_key: Schema.optionalKey; + readonly rate_limit: Schema.withDecodingDefaultKey; + readonly sms_sent: Schema.withDecodingDefaultKey; + readonly anonymous_users: Schema.withDecodingDefaultKey; + readonly token_refresh: Schema.withDecodingDefaultKey; + readonly sign_in_sign_ups: Schema.withDecodingDefaultKey; + readonly token_verifications: Schema.withDecodingDefaultKey; + readonly web3: Schema.withDecodingDefaultKey; + }>, never>; + readonly captcha: Schema.optionalKey; + readonly provider: Schema.optionalKey>; + readonly secret: Schema.optionalKey; + }>, never>>; + readonly hook: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly password_verification_attempt: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly custom_access_token: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly send_sms: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly send_email: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + readonly before_user_created: Schema.withDecodingDefaultKey; + readonly uri: Schema.optionalKey; + readonly secrets: Schema.optionalKey; + }>, never>; + }>, never>; + readonly mfa: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + }>, never>; + readonly phone: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + readonly otp_length: Schema.withDecodingDefaultKey; + readonly template: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + }>, never>; + readonly web_authn: Schema.withDecodingDefaultKey; + readonly verify_enabled: Schema.withDecodingDefaultKey; + }>, never>; + readonly max_enrolled_factors: Schema.withDecodingDefaultKey; + }>, never>; + readonly sessions: Schema.optionalKey; + readonly inactivity_timeout: Schema.optionalKey; + }>, never>>; + readonly email: Schema.withDecodingDefaultKey; + readonly double_confirm_changes: Schema.withDecodingDefaultKey; + readonly enable_confirmations: Schema.withDecodingDefaultKey; + readonly secure_password_change: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + readonly otp_length: Schema.withDecodingDefaultKey; + readonly otp_expiry: Schema.withDecodingDefaultKey; + readonly smtp: Schema.optionalKey; + readonly host: Schema.optionalKey; + readonly port: Schema.optionalKey; + readonly user: Schema.optionalKey; + readonly pass: Schema.optionalKey; + readonly admin_email: Schema.optionalKey; + readonly sender_name: Schema.optionalKey; + }>, never>>; + readonly template: Schema.withDecodingDefault; + readonly content_path: Schema.withDecodingDefaultKey; + }>, never>>, never>; + readonly notification: Schema.withDecodingDefault; + readonly subject: Schema.withDecodingDefaultKey; + readonly content_path: Schema.withDecodingDefaultKey; + }>, never>>, never>; + }>, never>; + readonly sms: Schema.withDecodingDefaultKey; + readonly enable_confirmations: Schema.withDecodingDefaultKey; + readonly template: Schema.withDecodingDefaultKey; + readonly max_frequency: Schema.withDecodingDefaultKey; + readonly twilio: Schema.withDecodingDefaultKey; + readonly account_sid: Schema.withDecodingDefaultKey; + readonly message_service_sid: Schema.withDecodingDefaultKey; + readonly auth_token: Schema.optionalKey; + }>, never>; + readonly twilio_verify: Schema.withDecodingDefaultKey; + readonly account_sid: Schema.optionalKey; + readonly message_service_sid: Schema.optionalKey; + readonly auth_token: Schema.optionalKey; + }>, never>; + readonly messagebird: Schema.withDecodingDefaultKey; + readonly originator: Schema.optionalKey; + readonly access_key: Schema.optionalKey; + }>, never>; + readonly textlocal: Schema.withDecodingDefaultKey; + readonly sender: Schema.optionalKey; + readonly api_key: Schema.optionalKey; + }>, never>; + readonly vonage: Schema.withDecodingDefaultKey; + readonly from: Schema.optionalKey; + readonly api_key: Schema.optionalKey; + readonly api_secret: Schema.optionalKey; + }>, never>; + readonly test_otp: Schema.optionalKey>; + }>, never>; + readonly external: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly azure: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly bitbucket: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly discord: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly facebook: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly github: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly gitlab: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly google: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly kakao: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly keycloak: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly linkedin_oidc: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly notion: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly twitch: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly twitter: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly x: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly slack_oidc: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly spotify: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly workos: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + readonly zoom: Schema.withDecodingDefaultKey; + readonly client_id: Schema.withDecodingDefaultKey; + readonly secret: Schema.optionalKey; + readonly url: Schema.withDecodingDefaultKey; + readonly redirect_uri: Schema.withDecodingDefaultKey; + readonly skip_nonce_check: Schema.withDecodingDefaultKey; + readonly email_optional: Schema.withDecodingDefaultKey; + }>, never>; + }>, never>; + readonly web3: Schema.withDecodingDefaultKey; + }>, never>; + readonly ethereum: Schema.withDecodingDefaultKey; + }>, never>; + }>, never>; + readonly oauth_server: Schema.withDecodingDefaultKey; + readonly authorization_url_path: Schema.withDecodingDefaultKey; + readonly allow_dynamic_registration: Schema.withDecodingDefaultKey; + }>, never>; + readonly third_party: Schema.withDecodingDefaultKey; + readonly project_id: Schema.optionalKey; + }>, never>; + readonly auth0: Schema.withDecodingDefaultKey; + readonly tenant: Schema.optionalKey; + readonly tenant_region: Schema.optionalKey; + }>, never>; + readonly aws_cognito: Schema.withDecodingDefaultKey; + readonly user_pool_id: Schema.optionalKey; + readonly user_pool_region: Schema.optionalKey; + }>, never>; + readonly clerk: Schema.withDecodingDefaultKey; + readonly domain: Schema.optionalKey; + }>, never>; + readonly workos: Schema.withDecodingDefaultKey; + readonly issuer_url: Schema.optionalKey; + }>, never>; + }>, never>; + }>, never>; + readonly db: Schema.withDecodingDefaultKey; + readonly shadow_port: Schema.withDecodingDefaultKey; + readonly health_timeout: Schema.withDecodingDefaultKey; + readonly major_version: Schema.withDecodingDefaultKey; + readonly pooler: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly pool_mode: Schema.withDecodingDefaultKey, never>; + readonly default_pool_size: Schema.withDecodingDefaultKey; + readonly max_client_conn: Schema.withDecodingDefaultKey; + }>, never>; + readonly migrations: Schema.withDecodingDefaultKey; + readonly schema_paths: Schema.withDecodingDefaultKey, never>; + }>, never>; + readonly seed: Schema.withDecodingDefaultKey; + readonly sql_paths: Schema.withDecodingDefaultKey, never>; + }>, never>; + readonly settings: Schema.optionalKey; + readonly logical_decoding_work_mem: Schema.optionalKey; + readonly maintenance_work_mem: Schema.optionalKey; + readonly max_connections: Schema.optionalKey; + readonly max_locks_per_transaction: Schema.optionalKey; + readonly max_parallel_maintenance_workers: Schema.optionalKey; + readonly max_parallel_workers: Schema.optionalKey; + readonly max_parallel_workers_per_gather: Schema.optionalKey; + readonly max_replication_slots: Schema.optionalKey; + readonly max_slot_wal_keep_size: Schema.optionalKey; + readonly max_standby_archive_delay: Schema.optionalKey; + readonly max_standby_streaming_delay: Schema.optionalKey; + readonly max_wal_size: Schema.optionalKey; + readonly max_wal_senders: Schema.optionalKey; + readonly max_worker_processes: Schema.optionalKey; + readonly session_replication_role: Schema.optionalKey>; + readonly shared_buffers: Schema.optionalKey; + readonly statement_timeout: Schema.optionalKey; + readonly track_activity_query_size: Schema.optionalKey; + readonly track_commit_timestamp: Schema.optionalKey; + readonly wal_keep_size: Schema.optionalKey; + readonly wal_sender_timeout: Schema.optionalKey; + readonly work_mem: Schema.optionalKey; + }>, never>>; + readonly network_restrictions: Schema.withDecodingDefaultKey; + readonly allowed_cidrs: Schema.withDecodingDefaultKey, never>; + readonly allowed_cidrs_v6: Schema.withDecodingDefaultKey, never>; + }>, never>; + readonly ssl_enforcement: Schema.optionalKey; + }>, never>>; + readonly vault: Schema.optionalKey>; + }>, never>; + readonly edge_runtime: Schema.withDecodingDefaultKey; + readonly policy: Schema.withDecodingDefaultKey, never>; + readonly inspector_port: Schema.withDecodingDefaultKey; + readonly deno_version: Schema.withDecodingDefaultKey; + readonly secrets: Schema.optionalKey>; + }>, never>; + readonly functions: Schema.withDecodingDefault; + readonly verify_jwt: Schema.withDecodingDefaultKey; + readonly import_map: Schema.withDecodingDefaultKey; + readonly entrypoint: Schema.withDecodingDefaultKey; + readonly static_files: Schema.withDecodingDefaultKey, never>; + readonly env: Schema.withDecodingDefaultKey, never>; + }>, never>>, never>; + readonly local_smtp: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly smtp_port: Schema.optionalKey; + readonly pop3_port: Schema.optionalKey; + readonly admin_email: Schema.optionalKey; + readonly sender_name: Schema.optionalKey; + }>, never>; + readonly realtime: Schema.withDecodingDefaultKey; + readonly ip_version: Schema.withDecodingDefaultKey, never>; + readonly max_header_length: Schema.withDecodingDefaultKey; + }>, never>; + readonly storage: Schema.withDecodingDefaultKey; + readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; + readonly image_transformation: Schema.optionalKey; + }>, never>>; + readonly buckets: Schema.optionalKey; + readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; + readonly allowed_mime_types: Schema.withDecodingDefaultKey, never>; + readonly objects_path: Schema.withDecodingDefaultKey; + }>, never>>>; + readonly s3_protocol: Schema.withDecodingDefaultKey; + }>, never>; + readonly analytics: Schema.withDecodingDefaultKey; + readonly max_namespaces: Schema.withDecodingDefaultKey; + readonly max_tables: Schema.withDecodingDefaultKey; + readonly max_catalogs: Schema.withDecodingDefaultKey; + readonly buckets: Schema.withDecodingDefault, never>>, never>; + }>, never>; + readonly vector: Schema.withDecodingDefaultKey; + readonly max_buckets: Schema.withDecodingDefaultKey; + readonly max_indexes: Schema.withDecodingDefaultKey; + readonly buckets: Schema.withDecodingDefault, never>>, never>; + }>, never>; + }>, never>; + readonly studio: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly api_url: Schema.withDecodingDefaultKey; + readonly openai_api_key: Schema.optionalKey; + }>, never>; + readonly workers: Schema.withDecodingDefault; + readonly size: Schema.optionalKey; + readonly instances: Schema.optionalKey; + readonly source: Schema.optionalKey; + }>>, never>; + readonly experimental: Schema.withDecodingDefaultKey; + readonly s3_host: Schema.optionalKey; + readonly s3_region: Schema.optionalKey; + readonly s3_access_key: Schema.optionalKey; + readonly s3_secret_key: Schema.optionalKey; + readonly webhooks: Schema.optionalKey; + }>, never>>; + readonly pgdelta: Schema.optionalKey; + readonly declarative_schema_path: Schema.optionalKey; + readonly format_options: Schema.optionalKey; + }>, never>>; + readonly inspect: Schema.optionalKey; + readonly name: Schema.optionalKey; + readonly pass: Schema.optionalKey; + readonly fail: Schema.optionalKey; + }>, never>>, never>; + }>, never>>; + }>, never>; + }>, never>>, never>; +}>; +export declare function toCliConfigJsonSchema(): { + $schema: string; + $defs?: import("effect/JsonSchema").Definitions | undefined; +}; +export type CliConfig = typeof CliConfigSchema.Type; +export type CliConfigJson = typeof CliConfigSchema.Encoded; diff --git a/packages/config/api-report/bun.d.ts b/packages/config/api-report/bun.d.ts new file mode 100644 index 0000000000..ef8acf8a4c --- /dev/null +++ b/packages/config/api-report/bun.d.ts @@ -0,0 +1,9 @@ +export declare const loadCliConfig: (cwd: string, options?: import("./config-document.ts").LoadCliConfigOptions) => Promise; +export declare const findCliProjectRoot: (cwd: string) => Promise; +export declare const findCliProjectPaths: (cwd: string) => Promise; +export declare const loadCliConfigFile: (path: string) => Promise; +export declare const loadCliProjectEnvironment: (options: import("./project.ts").LoadCliProjectEnvironmentOptions) => Promise; +export declare const saveCliConfig: (options: import("./config-document.ts").SaveCliConfigOptions) => Promise; +export declare const inferFunctionsManifest: (cwd: string) => Promise; +export type { CliConfigIo } from "./promise-facade.ts"; +export * from "./index.ts"; diff --git a/packages/config/api-report/cli-config.layer.d.ts b/packages/config/api-report/cli-config.layer.d.ts new file mode 100644 index 0000000000..d6196598a0 --- /dev/null +++ b/packages/config/api-report/cli-config.layer.d.ts @@ -0,0 +1,3 @@ +import { FileSystem, Layer, Path } from "effect"; +import { CliConfigStore } from "./cli-config.service.ts"; +export declare const cliConfigStoreLayer: Layer.Layer; diff --git a/packages/config/api-report/cli-config.service.d.ts b/packages/config/api-report/cli-config.service.d.ts new file mode 100644 index 0000000000..e6481b24f8 --- /dev/null +++ b/packages/config/api-report/cli-config.service.d.ts @@ -0,0 +1,23 @@ +import type { Effect } from "effect"; +import { Context } from "effect"; +import type { PlatformError } from "effect/PlatformError"; +import type { LoadedCliConfig, LoadCliConfigOptions, SaveCliConfigOptions } from "./config-document.ts"; +import type { CliConfigParseError, CliProjectEnvParseError, DuplicateRemoteProjectIdError, InvalidRemoteProjectIdError } from "./errors.ts"; +/** + * Every error a `load`/`loadFile`/`save` rejection can carry: this package's + * own tagged failures (a malformed config document, a duplicate or + * malformed `[remotes.*]` block, a malformed `.env`/`.env.local` file) plus + * `PlatformError`, the single tagged wrapper Effect's `FileSystem` service + * uses for every host/OS failure (`effect/PlatformError`). A Promise-based + * consumer (`@supabase/config/io`) can distinguish these via `instanceof`. + */ +type CliConfigStoreError = CliConfigParseError | DuplicateRemoteProjectIdError | InvalidRemoteProjectIdError | CliProjectEnvParseError | PlatformError; +interface CliConfigStoreShape { + readonly load: (cwd: string, options?: LoadCliConfigOptions) => Effect.Effect; + readonly loadFile: (path: string) => Effect.Effect; + readonly save: (options: SaveCliConfigOptions) => Effect.Effect; +} +declare const CliConfigStore_base: Context.ServiceClass; +export declare class CliConfigStore extends CliConfigStore_base { +} +export {}; diff --git a/packages/config/api-report/config-document.d.ts b/packages/config/api-report/config-document.d.ts new file mode 100644 index 0000000000..3dfb05807e --- /dev/null +++ b/packages/config/api-report/config-document.d.ts @@ -0,0 +1,128 @@ +import { type CliConfig } from "./base.ts"; +import type { ConfigFormat } from "./config-format.ts"; +import type { CliProjectEnvironment } from "./project.ts"; +/** Shared with `io.ts`'s `getSchemaRef`, which reads this key back off a raw document. */ +export declare const cliConfigSchemaKey = "$schema"; +export type CliConfigValueSource = "environment" | "local" | "remote"; +export interface CliConfigValueOrigin { + readonly path: ReadonlyArray; + readonly source: CliConfigValueSource; +} +export interface LoadedCliConfig { + readonly path: string; + readonly format: ConfigFormat; + readonly config: CliConfig; + readonly schemaRef?: string; + readonly ignoredPaths: ReadonlyArray; + /** + * The raw, post-`env()`-interpolation document the `config` was decoded from, + * with any matching `[remotes.*]` override already merged in (see + * {@link LoadCliConfigOptions.projectRef}). Lets callers inspect key + * presence — which the decoded `config` loses because the schema defaults + * optional sections — without re-reading the file. Present whenever the file + * parsed to an object. + */ + readonly document?: Record; + /** + * Name of the `[remotes.]` block whose subtree was merged over the base + * config because its `project_id` matched the requested `projectRef`. + * `undefined` when no `projectRef` was requested or none matched. + */ + readonly appliedRemote?: string; + /** + * The top-level `auth.external.{linkedin,slack}` sub-objects that were stripped from + * {@link document} before it was returned (provider id → the removed object), keyed by + * provider id. Empty when neither deprecated block was present. See + * `normalizeDeprecatedExternalProviders`'s doc comment for why a caller doing its own + * Go-parity scan over `document` (e.g. a decrypt-or-abort secret check) may need to fold + * this back in — Go's decode-time decrypt hook sees these blocks before its later + * validate-time deletion, so `document` alone under-reports what Go would have decrypted. + * Present (possibly `{}`) whenever {@link document} is; absent from `saveCliConfig`'s + * result, which has no document to strip from. + */ + readonly removedDeprecatedExternalProviders?: Readonly>; + /** The source that supplied each explicitly configured effective leaf value. */ + readonly valueOrigins?: ReadonlyArray; +} +export declare const cliConfigValueSourceAt: (loaded: Pick, path: ReadonlyArray) => CliConfigValueSource | undefined; +/** + * When `projectRef` is set, the matching `[remotes.]` block (the one + * whose `project_id` equals it) is merged over the base config before decode, + * mirroring Go's `config.Load` with `Config.ProjectId` set + * (`apps/cli-go/pkg/config/config.go:503-562`). Omitting it loads the base + * config verbatim (no merge), so existing callers are unaffected. Go's + * duplicate-`project_id`/project-ref-format checks across every + * `[remotes.*]` block (`config.go:594-602,996-1001`) run unconditionally on + * every config load in Go, not only when a caller ends up selecting a + * remote — but here they only run when {@link InternalLoadCliConfigOptions.goViperCompat} + * is `true`, regardless of whether `projectRef` is set, so non-Go-parity + * callers that never select a remote (and never opt into Go parity) aren't + * broken by an unrelated duplicate/malformed `[remotes.*]` block. + */ +export interface LoadCliConfigOptions { + readonly projectRef?: string; + /** + * Pre-resolved project environment used to interpolate `env()` references. + * When omitted, the environment is resolved internally from `.env`/`.env.local` + * layered over `process.env` (the default for most callers). Callers that need + * Go-accurate, environment-specific resolution (e.g. `functions serve`, which + * also reads `.env.` files) resolve it themselves and pass it in + * so loading does not re-read those files or depend on `process.env` mutation. + */ + readonly cliProjectEnv?: CliProjectEnvironment; + /** See {@link FindCliProjectPathsOptions.search}. */ + readonly search?: boolean; + /** + * Skip the `config.json`-over-`config.toml` preference below and only ever + * load `config.toml`. Go's `Config.Load`/`NewPathBuilder` + * (`apps/cli-go/pkg/config/utils.go:43-48`) has no concept of a JSON project + * config file — it always resolves `supabase/config.toml` and treats a + * missing file as defaults — so Go-parity callers (the legacy `status`/`stop` + * ports) must set this to avoid picking up a stray `config.json` that Go + * would never see. + */ + readonly tomlOnly?: boolean; +} +/** + * Not covered by semver — exported from `@supabase/config/internal` only. See + * that module's header for why. + */ +export interface InternalLoadCliConfigOptions extends LoadCliConfigOptions { + /** + * Opt into the Go/viper-parity decode+validation semantics this loader + * otherwise omits, so only the Go-parity legacy shell (and shared modules + * invoked exclusively by it) pays for them. Defaults to `false` = pre-PR-#5765 + * behavior, which `next/`, `packages/stack`, and the functions manifest rely + * on. When `true`, mirrors Go's `config.Load` exactly: + * - runs the unconditional duplicate-`project_id` and project-ref-format + * checks across every `[remotes.*]` block (`config.go:594-602,996-1001`), + * even when no `projectRef` is requested; + * - warns on stderr for deprecated `auth.external.{linkedin,slack}` blocks + * (`config.go:1418-1423`) — the block is stripped from the decoded config + * either way, since the schema ignores excess properties; + * - matches `env(...)` references case-agnostically (`^env\((.*)\)$`) + * rather than the strict SCREAMING_SNAKE_CASE form; + * - splits a comma-separated string into a `[]string`-typed field (Go's + * `mapstructure.StringToSliceHookFunc(",")`, `config.go:775-784`), not + * just an `env()`-substituted one. + */ + readonly goViperCompat?: boolean; +} +export interface SaveCliConfigOptions { + readonly cwd: string; + readonly config: CliConfig; + readonly format?: ConfigFormat; + readonly schemaRef?: string; +} +/** + * Shared with `io.ts`, which uses it to inspect raw (pre-decode) config + * documents while resolving `[remotes.*]` overrides and stripping deprecated + * sections. + */ +export declare function isObject(value: unknown): value is Record; +export declare function encodeCliConfigToJson(config: CliConfig): string; +export declare function encodeCliConfigToToml(config: CliConfig): string; +/** Shared with `io.ts`'s `saveCliConfig`, which needs the `schemaRef`-carrying variant. */ +export declare function encodeCliConfigToJsonDocument(config: CliConfig, schemaRef: string | undefined): string; +/** Shared with `io.ts`'s `saveCliConfig`, which needs the `schemaRef`-carrying variant. */ +export declare function encodeCliConfigToTomlDocument(config: CliConfig, schemaRef: string | undefined): string; diff --git a/packages/config/api-report/config-format.d.ts b/packages/config/api-report/config-format.d.ts new file mode 100644 index 0000000000..4fe89b4fa0 --- /dev/null +++ b/packages/config/api-report/config-format.d.ts @@ -0,0 +1,11 @@ +/** + * Leaf module with zero imports of its own. `errors.ts` — this package's + * most primitive module — needs `ConfigFormat` for {@link CliConfigParseError}, + * so this type lives here rather than in `config-document.ts`, which itself + * imports from `project.ts`, which imports from `errors.ts`. Defining + * `ConfigFormat` in `config-document.ts` would create an + * `errors.ts` → `config-document.ts` → `project.ts` → `errors.ts` import + * cycle (benign at runtime today, but a live constraint for declaration + * emit). + */ +export type ConfigFormat = "json" | "toml"; diff --git a/packages/config/api-report/db.d.ts b/packages/config/api-report/db.d.ts new file mode 100644 index 0000000000..31639d7251 --- /dev/null +++ b/packages/config/api-report/db.d.ts @@ -0,0 +1,56 @@ +import { Schema } from "effect"; +export declare const db: Schema.withDecodingDefaultKey; + readonly shadow_port: Schema.withDecodingDefaultKey; + readonly health_timeout: Schema.withDecodingDefaultKey; + readonly major_version: Schema.withDecodingDefaultKey; + readonly pooler: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly pool_mode: Schema.withDecodingDefaultKey, never>; + readonly default_pool_size: Schema.withDecodingDefaultKey; + readonly max_client_conn: Schema.withDecodingDefaultKey; + }>, never>; + readonly migrations: Schema.withDecodingDefaultKey; + readonly schema_paths: Schema.withDecodingDefaultKey, never>; + }>, never>; + readonly seed: Schema.withDecodingDefaultKey; + readonly sql_paths: Schema.withDecodingDefaultKey, never>; + }>, never>; + readonly settings: Schema.optionalKey; + readonly logical_decoding_work_mem: Schema.optionalKey; + readonly maintenance_work_mem: Schema.optionalKey; + readonly max_connections: Schema.optionalKey; + readonly max_locks_per_transaction: Schema.optionalKey; + readonly max_parallel_maintenance_workers: Schema.optionalKey; + readonly max_parallel_workers: Schema.optionalKey; + readonly max_parallel_workers_per_gather: Schema.optionalKey; + readonly max_replication_slots: Schema.optionalKey; + readonly max_slot_wal_keep_size: Schema.optionalKey; + readonly max_standby_archive_delay: Schema.optionalKey; + readonly max_standby_streaming_delay: Schema.optionalKey; + readonly max_wal_size: Schema.optionalKey; + readonly max_wal_senders: Schema.optionalKey; + readonly max_worker_processes: Schema.optionalKey; + readonly session_replication_role: Schema.optionalKey>; + readonly shared_buffers: Schema.optionalKey; + readonly statement_timeout: Schema.optionalKey; + readonly track_activity_query_size: Schema.optionalKey; + readonly track_commit_timestamp: Schema.optionalKey; + readonly wal_keep_size: Schema.optionalKey; + readonly wal_sender_timeout: Schema.optionalKey; + readonly work_mem: Schema.optionalKey; + }>, never>>; + readonly network_restrictions: Schema.withDecodingDefaultKey; + readonly allowed_cidrs: Schema.withDecodingDefaultKey, never>; + readonly allowed_cidrs_v6: Schema.withDecodingDefaultKey, never>; + }>, never>; + readonly ssl_enforcement: Schema.optionalKey; + }>, never>>; + readonly vault: Schema.optionalKey>; +}>, never>; diff --git a/packages/config/api-report/edge_runtime.d.ts b/packages/config/api-report/edge_runtime.d.ts new file mode 100644 index 0000000000..df980f8f36 --- /dev/null +++ b/packages/config/api-report/edge_runtime.d.ts @@ -0,0 +1,8 @@ +import { Schema } from "effect"; +export declare const edge_runtime: Schema.withDecodingDefaultKey; + readonly policy: Schema.withDecodingDefaultKey, never>; + readonly inspector_port: Schema.withDecodingDefaultKey; + readonly deno_version: Schema.withDecodingDefaultKey; + readonly secrets: Schema.optionalKey>; +}>, never>; diff --git a/packages/config/api-report/effect.d.ts b/packages/config/api-report/effect.d.ts new file mode 100644 index 0000000000..62bd52d45d --- /dev/null +++ b/packages/config/api-report/effect.d.ts @@ -0,0 +1,39 @@ +export * from "./index.ts"; +import type { Effect } from "effect"; +import type { LoadCliConfigOptions } from "./config-document.ts"; +import type { ResolvedCliConfigValue, ResolveCliConfigOptions } from "./lib/resolve.ts"; +import * as io from "./io.ts"; +import type { CliProjectEnvironment } from "./project.ts"; +export { configJsonPath, configTomlPath, saveCliConfig } from "./io.ts"; +/** + * Narrowed to the public `LoadCliConfigOptions` (no `goViperCompat`). The + * underlying implementation in `./io.ts` is typed against the wider + * `InternalLoadCliConfigOptions` (a strict superset — one additional optional + * field), so assigning it here is a safe, cast-free narrowing: a function + * accepting the wider options type is assignable to a variable typed to + * accept only the narrower one. `@supabase/config/internal` re-exports this + * same runtime function typed to additionally show `goViperCompat`. + */ +export declare const loadCliConfig: (cwd: string, options?: LoadCliConfigOptions) => ReturnType; +/** See {@link loadCliConfig}'s doc comment for the narrowing rationale. */ +export declare const loadCliConfigFile: (filePath: string, options?: LoadCliConfigOptions) => ReturnType; +export { inferFunctionsManifest } from "./functions-manifest.ts"; +export { loadDotEnvFile, loadCliProjectEnvironment } from "./project.ts"; +/** + * Explicit named exports take precedence over `export * from "./index.ts"` + * above for a shared name (ESM re-export resolution), so these Effect-typed + * variants deliberately shadow `./index.ts`'s plain sync + * `resolveCliConfigValue`/`resolveCliConfigSubtree` on this subpath — the + * Effect-typed variant wins on `./effect`; the sync variant lives on `.`. + * + * Narrowed to the public `ResolveCliConfigOptions` (no `goViperCompat`) for + * the same reason as {@link loadCliConfig} above; `@supabase/config/internal` + * re-exports these same runtime functions typed to additionally show + * `goViperCompat`. + */ +export declare const resolveCliConfigValue: (value: T, cliProjectEnv: Pick, configPath: string, options?: ResolveCliConfigOptions) => Effect.Effect>; +/** See {@link resolveCliConfigValue}'s doc comment for the shadowing and narrowing rationale. */ +export declare const resolveCliConfigSubtree: (value: T, cliProjectEnv: Pick, pathPrefix: string, options?: ResolveCliConfigOptions) => Effect.Effect>; +export { findCliProjectPaths, findCliProjectRoot } from "./paths.ts"; +export { cliConfigStoreLayer } from "./cli-config.layer.ts"; +export { CliConfigStore } from "./cli-config.service.ts"; diff --git a/packages/config/api-report/errors.d.ts b/packages/config/api-report/errors.d.ts new file mode 100644 index 0000000000..6a60d6c6d2 --- /dev/null +++ b/packages/config/api-report/errors.d.ts @@ -0,0 +1,162 @@ +import type { ConfigFormat } from "./config-format.ts"; +declare const CliConfigParseError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { + readonly _tag: "CliConfigParseError"; +} & Readonly; +export declare class CliConfigParseError extends CliConfigParseError_base<{ + readonly path: string; + readonly format: ConfigFormat; + readonly cause: unknown; + /** + * The pre-schema-decode `edge_runtime` subtree (post env-interpolation and + * `[remotes.*]` merge) — present only when the failure happened during + * *schema* decode (`Schema.decodeUnknownSync`), not during raw TOML/JSON + * parsing. `Schema.decodeUnknownSync` is all-or-nothing: a single invalid + * field anywhere in the document discards the entire decode, unlike Go's + * `viper`+`mapstructure` decode (`apps/cli-go/pkg/config/config.go:749`), + * which mutates the target struct field-by-field and keeps whatever + * independently decoded before hitting an unrelated error. Callers that + * need Go's tolerance for a single subtree (e.g. `secrets set` recovering + * `edge_runtime.secrets` when an unrelated field like `analytics.port` is + * malformed) can re-decode this subtree against the full schema themselves. + * Only `edge_runtime` is retained, not the whole document — several callers + * of `loadCliConfig` don't catch `CliConfigParseError` at all, so + * this error can propagate with whatever is attached here, and no caller + * needs anything outside `edge_runtime` today. Every `edge_runtime.secrets` + * value is wrapped in `Redacted` (mirroring `secret()`'s `x-secret` + * treatment elsewhere in this package) so an uncaught error can't + * accidentally leak a resolved secret into a log or trace; callers must + * unwrap via `Redacted.value` before re-decoding. `undefined` when the + * document never parsed at all — that class has no recoverable structure in + * either implementation. + */ + readonly document?: { + readonly edge_runtime?: unknown; + }; + /** + * Name of the `[remotes.]` block whose subtree was merged over the + * base document before the decode that produced this error, when a + * `projectRef` was supplied and one matched. Mirrors `appliedRemote` on + * {@link LoadedCliConfig} for the success path. Go's `loadFromFile` + * prints `Loading config override: [remotes.]` to stderr + * unconditionally, *before* `mapstructure` decode ever runs + * (`apps/cli-go/pkg/config/config.go:604-609`) — so the notice is still due + * even when the subsequent decode fails. Callers that tolerate a + * schema-decode failure and keep going (e.g. `secrets set`) must surface + * this themselves; callers that let the error propagate get no such + * notice from Go either, since `c.load(v)` fails before `Run` prints + * anything else. `undefined` when no `projectRef` was requested or none + * matched — same as the raw-parse-failure case, where remote merging never + * runs at all. + */ + readonly appliedRemote?: string; +}> { +} +/** + * Renders `detail` under the shared {@link ProjectConfigParseError} message + * convention: `": "`, or `": at data.attributes.: + * "` when `apiPath` is given and non-empty. Every construction site + * (`./project-config/project-config.ts`, `./project-config/registry-row.ts`, + * `./project-config/registry.ts`) builds its message through this helper so + * the "at data.attributes...." rendering stays identical everywhere an + * `apiPath` is known. + */ +export declare function formatProjectConfigParseErrorMessage(detail: string, apiPath?: ReadonlyArray): string; +/** + * {@link ProjectConfigParseError} is, by construction, always the same + * underlying situation: this package's mirrored schema/registry + * (`./project-config/api-attributes.ts`, `./project-config/registry*.ts`) is + * behind what the Management API actually sent. There is therefore exactly + * one remediation, attached as `suggestion` at every construction site: + * upgrade first (a newer package version may already map or leniently accept + * the offending shape), then report if it persists. + */ +export declare const PROJECT_CONFIG_PARSE_ERROR_SUGGESTION = "Try upgrading the Supabase CLI to the latest version. If the error persists on the latest version, report it at https://github.com/supabase/cli/issues."; +declare const ProjectConfigParseError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { + readonly _tag: "ProjectConfigParseError"; +} & Readonly; +/** + * A Management API v2 project-config response failed to map into a + * `ProjectConfig`: the envelope/attributes shape didn't decode, or a + * registry-mapped field carried a value of the wrong type. `message` is a + * human-readable summary built via {@link formatProjectConfigParseErrorMessage} + * at every construction site; `detail` optionally carries a fuller, + * multi-issue rendering (currently only populated for a schema decode + * failure, via `SchemaIssue.makeFormatterDefault()`); `suggestion` is always + * {@link PROJECT_CONFIG_PARSE_ERROR_SUGGESTION}. Unknown keys never cause + * this on their own — the mapping decode is lenient toward + * API-ahead-of-package skew by design (ADR 0019, rule 2) — with one + * documented trade: an own `data` or `attributes` key found on what was + * actually meant to be a bare-attributes payload is indistinguishable from a + * real envelope and is treated as one (`unwrapApiResponse`'s docstring in + * `./project-config/project-config.ts`), so a section genuinely named either + * of those two words would trigger envelope validation instead of being + * tolerated as an unmapped key. + */ +export declare class ProjectConfigParseError extends ProjectConfigParseError_base<{ + readonly message: string; + /** + * What actually went wrong, as a closed union telemetry can branch on: + * `"api_response"` (the default when absent) — the Management API payload + * itself failed to decode or map; `"caller_misuse"` — the CALLER handed + * this package's own API an invalid argument (a `toProjectConfig` source + * carrying neither/both keys or not an object at all, a non-object + * `attachApiResponse` operand). Misuse is a programming error in the + * consumer: the upgrade `suggestion` does not apply to it, and it must not + * be reported as an external platform failure. + */ + readonly reason?: "api_response" | "caller_misuse"; + /** + * Path under v2 `data.attributes` of the offending value; `undefined` when + * the response envelope/attributes shape itself failed to decode. + */ + readonly apiPath?: ReadonlyArray; + readonly cause: unknown; + /** Fuller, multi-issue detail beyond `message`'s single-issue summary. */ + readonly detail?: string; + readonly suggestion?: string; +}> { +} +declare const CliProjectEnvParseError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { + readonly _tag: "CliProjectEnvParseError"; +} & Readonly; +export declare class CliProjectEnvParseError extends CliProjectEnvParseError_base<{ + readonly path: string; + readonly line: number; +}> { +} +declare const MissingCliConfigValueError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { + readonly _tag: "MissingCliConfigValueError"; +} & Readonly; +export declare class MissingCliConfigValueError extends MissingCliConfigValueError_base<{ + readonly configPath: string; +}> { +} +declare const DuplicateRemoteProjectIdError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { + readonly _tag: "DuplicateRemoteProjectIdError"; +} & Readonly; +/** + * Two `[remotes.*]` blocks declare the same `project_id` as the requested + * `projectRef`. Mirrors Go's `loadFromFile` guard + * (`apps/cli-go/pkg/config/config.go:508-509`); `message` matches the Go string + * verbatim so callers can surface it without rewrapping. + */ +export declare class DuplicateRemoteProjectIdError extends DuplicateRemoteProjectIdError_base<{ + readonly message: string; +}> { +} +declare const InvalidRemoteProjectIdError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { + readonly _tag: "InvalidRemoteProjectIdError"; +} & Readonly; +/** + * A `[remotes.]` block's `project_id` is not a valid 20-lowercase-letter + * project ref. Mirrors Go's `Config.Validate` (`apps/cli-go/pkg/config/config.go: + * 558,996-1001`), which checks every remote's `project_id` against `refPattern` + * on every config load — regardless of whether that remote ends up selected — + * so this fails before Docker/API access, same as Go. `message` matches the Go + * string verbatim so callers can surface it without rewrapping. + */ +export declare class InvalidRemoteProjectIdError extends InvalidRemoteProjectIdError_base<{ + readonly message: string; +}> { +} +export {}; diff --git a/packages/config/api-report/experimental.d.ts b/packages/config/api-report/experimental.d.ts new file mode 100644 index 0000000000..e9fcf4a742 --- /dev/null +++ b/packages/config/api-report/experimental.d.ts @@ -0,0 +1,24 @@ +import { Schema } from "effect"; +export declare const experimental: Schema.withDecodingDefaultKey; + readonly s3_host: Schema.optionalKey; + readonly s3_region: Schema.optionalKey; + readonly s3_access_key: Schema.optionalKey; + readonly s3_secret_key: Schema.optionalKey; + readonly webhooks: Schema.optionalKey; + }>, never>>; + readonly pgdelta: Schema.optionalKey; + readonly declarative_schema_path: Schema.optionalKey; + readonly format_options: Schema.optionalKey; + }>, never>>; + readonly inspect: Schema.optionalKey; + readonly name: Schema.optionalKey; + readonly pass: Schema.optionalKey; + readonly fail: Schema.optionalKey; + }>, never>>, never>; + }>, never>>; +}>, never>; diff --git a/packages/config/api-report/functions-manifest-model.d.ts b/packages/config/api-report/functions-manifest-model.d.ts new file mode 100644 index 0000000000..d57a361f5a --- /dev/null +++ b/packages/config/api-report/functions-manifest-model.d.ts @@ -0,0 +1,12 @@ +export declare const edgeFunctionsDirectoryName = "functions"; +export declare const edgeFunctionEntrypointFileName = "index.ts"; +export declare const edgeFunctionDenoConfigFileName = "deno.json"; +export interface ResolvedFunctionConfig { + readonly enabled: boolean; + readonly verify_jwt: boolean; + readonly import_map: string; + readonly entrypoint: string; + readonly static_files: ReadonlyArray; + readonly env: Readonly>; +} +export type FunctionsManifest = Readonly>; diff --git a/packages/config/api-report/functions-manifest.d.ts b/packages/config/api-report/functions-manifest.d.ts new file mode 100644 index 0000000000..07f5bde98f --- /dev/null +++ b/packages/config/api-report/functions-manifest.d.ts @@ -0,0 +1,11 @@ +import { Effect, FileSystem, Path } from "effect"; +import { type CliConfig } from "./base.ts"; +import { type ResolvedFunctionConfig } from "./functions-manifest-model.ts"; +interface InferFunctionsManifestOptions { + readonly cwd: string; + readonly config?: CliConfig; + /** Forwarded to {@link findCliProjectPaths}'s own `search` option — see its doc comment. */ + readonly search?: boolean; +} +export declare const inferFunctionsManifest: (options: InferFunctionsManifestOptions) => Effect.Effect, import("./errors.ts").CliConfigParseError | import("./errors.ts").CliProjectEnvParseError | import("./errors.ts").DuplicateRemoteProjectIdError | import("./errors.ts").InvalidRemoteProjectIdError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>; +export {}; diff --git a/packages/config/api-report/functions.d.ts b/packages/config/api-report/functions.d.ts new file mode 100644 index 0000000000..c2d5357303 --- /dev/null +++ b/packages/config/api-report/functions.d.ts @@ -0,0 +1,9 @@ +import { Schema } from "effect"; +export declare const functions: Schema.withDecodingDefault; + readonly verify_jwt: Schema.withDecodingDefaultKey; + readonly import_map: Schema.withDecodingDefaultKey; + readonly entrypoint: Schema.withDecodingDefaultKey; + readonly static_files: Schema.withDecodingDefaultKey, never>; + readonly env: Schema.withDecodingDefaultKey, never>; +}>, never>>, never>; diff --git a/packages/config/api-report/inbucket.d.ts b/packages/config/api-report/inbucket.d.ts new file mode 100644 index 0000000000..66f6d27bdb --- /dev/null +++ b/packages/config/api-report/inbucket.d.ts @@ -0,0 +1,9 @@ +import { Schema } from "effect"; +export declare const inbucket: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly smtp_port: Schema.optionalKey; + readonly pop3_port: Schema.optionalKey; + readonly admin_email: Schema.optionalKey; + readonly sender_name: Schema.optionalKey; +}>, never>; diff --git a/packages/config/api-report/index.d.ts b/packages/config/api-report/index.d.ts new file mode 100644 index 0000000000..dc1c4a312b --- /dev/null +++ b/packages/config/api-report/index.d.ts @@ -0,0 +1,20 @@ +/** + * Pure, browser/edge-safe entrypoint. Must never export an Effect-returning + * function, nor pull `@effect/platform-*` or `node:`/`bun:` modules into its + * transitive graph. Effect-core `FileSystem`/`Path` TAG references reachable + * from this graph are fine — they're inert without a platform layer provided. + * File IO and Effect-native services live at `@supabase/config/io` and + * `@supabase/config/effect`. + */ +export { CliConfigSchema, toCliConfigJsonSchema, type CliConfig, type CliConfigJson, } from "./base.ts"; +export { CliConfigParseError, CliProjectEnvParseError, DuplicateRemoteProjectIdError, InvalidRemoteProjectIdError, MissingCliConfigValueError, ProjectConfigParseError, } from "./errors.ts"; +export type { ConfigFormat } from "./config-format.ts"; +export { type LoadedCliConfig, type LoadCliConfigOptions, type CliConfigValueOrigin, type CliConfigValueSource, type SaveCliConfigOptions, encodeCliConfigToJson, encodeCliConfigToToml, cliConfigValueSourceAt, } from "./config-document.ts"; +export { edgeFunctionDenoConfigFileName, edgeFunctionEntrypointFileName, edgeFunctionsDirectoryName, type FunctionsManifest, type ResolvedFunctionConfig, } from "./functions-manifest-model.ts"; +export type { LoadCliProjectEnvironmentOptions, CliProjectEnvironment } from "./project.ts"; +export { type ResolvedCliConfigValue, type ResolveCliConfigOptions, resolveCliConfigValue, resolveCliConfigSubtree, } from "./lib/resolve.ts"; +export type { CliProjectPaths } from "./paths.ts"; +export { CLI_CONFIG_SCHEMA_URL } from "./schema-metadata.ts"; +export { type EffectiveConfig, type SparseCliConfig, getDefaultCliConfig, omitDefaultValues, subtractCliConfig, } from "./sparse.ts"; +export { type CliConfigWithRawPresence, type ProjectConfig, type ReadonlyJsonValue, type ToProjectConfigSource, attachApiResponse, comparableProjectConfigPaths, fromApiProjectConfig, fromConfigDocument, isComparableProjectConfigPath, toProjectConfig, unmappedApiFields, } from "./project-config/project-config.ts"; +export { ProjectConfigSchema, toProjectConfigJsonSchema } from "./project-config/project-schema.ts"; diff --git a/packages/config/api-report/internal.d.ts b/packages/config/api-report/internal.d.ts new file mode 100644 index 0000000000..5a5dd59967 --- /dev/null +++ b/packages/config/api-report/internal.d.ts @@ -0,0 +1,14 @@ +/** + * NOT covered by semver. This subpath exists solely for `apps/cli`'s own use + * and its contract-guard tests — every export here (its existence, its shape, + * its behavior) can change or vanish in any release without notice. External + * consumers must use `.`, `./effect`, or `./io` instead. + */ +export { ENV_CAPTURE_REGEX } from "./lib/env.ts"; +export { AUTH_HOOK_NAMES, unmappedSecretApiPaths } from "./project-config/registry-auth.ts"; +export { projectConfigMappingRows } from "./project-config/registry.ts"; +export { type ProjectConfigMappingRow } from "./project-config/registry-row.ts"; +export { type ProjectConfigApiAttributes } from "./project-config/api-attributes.ts"; +export { type InternalLoadCliConfigOptions } from "./config-document.ts"; +export { type InternalResolveCliConfigOptions, resolveCliConfigValue, resolveCliConfigSubtree, } from "./project.ts"; +export { loadCliConfig, loadCliConfigFile } from "./io.ts"; diff --git a/packages/config/api-report/io-browser.d.ts b/packages/config/api-report/io-browser.d.ts new file mode 100644 index 0000000000..ef8acf8a4c --- /dev/null +++ b/packages/config/api-report/io-browser.d.ts @@ -0,0 +1,9 @@ +export declare const loadCliConfig: (cwd: string, options?: import("./config-document.ts").LoadCliConfigOptions) => Promise; +export declare const findCliProjectRoot: (cwd: string) => Promise; +export declare const findCliProjectPaths: (cwd: string) => Promise; +export declare const loadCliConfigFile: (path: string) => Promise; +export declare const loadCliProjectEnvironment: (options: import("./project.ts").LoadCliProjectEnvironmentOptions) => Promise; +export declare const saveCliConfig: (options: import("./config-document.ts").SaveCliConfigOptions) => Promise; +export declare const inferFunctionsManifest: (cwd: string) => Promise; +export type { CliConfigIo } from "./promise-facade.ts"; +export * from "./index.ts"; diff --git a/packages/config/api-report/io.d.ts b/packages/config/api-report/io.d.ts new file mode 100644 index 0000000000..6840a686ba --- /dev/null +++ b/packages/config/api-report/io.d.ts @@ -0,0 +1,3374 @@ +import { Effect, FileSystem, Path } from "effect"; +import { type InternalLoadCliConfigOptions, type CliConfigValueSource, type SaveCliConfigOptions } from "./config-document.ts"; +import type { ConfigFormat } from "./config-format.ts"; +import { DuplicateRemoteProjectIdError, InvalidRemoteProjectIdError, CliConfigParseError } from "./errors.ts"; +export declare const configJsonPath: (cwd: string) => Effect.Effect; +export declare const configTomlPath: (cwd: string) => Effect.Effect; +export declare const loadCliConfigFile: (filePath: string, options?: InternalLoadCliConfigOptions | undefined) => Effect.Effect<{ + path: string; + format: "json" | "toml"; + config: { + readonly project_id?: string | undefined; + readonly analytics: { + readonly enabled: boolean; + readonly port: number; + readonly backend: string; + readonly vector_port?: number | undefined; + readonly gcp_project_id?: string | undefined; + readonly gcp_project_number?: string | undefined; + readonly gcp_jwt_path?: string | undefined; + }; + readonly api: { + readonly enabled: boolean; + readonly port: number; + readonly schemas: readonly string[]; + readonly extra_search_path: readonly string[]; + readonly max_rows: number; + readonly auto_expose_new_tables?: boolean | undefined; + readonly tls: { + readonly enabled: boolean; + readonly cert_path?: string | undefined; + readonly key_path?: string | undefined; + }; + readonly external_url?: string | undefined; + }; + readonly auth: { + readonly enabled: boolean; + readonly site_url: string; + readonly additional_redirect_urls: readonly string[]; + readonly jwt_expiry: number; + readonly jwt_issuer?: string | undefined; + readonly signing_keys_path?: string | undefined; + readonly enable_refresh_token_rotation: boolean; + readonly refresh_token_reuse_interval: number; + readonly enable_manual_linking: boolean; + readonly enable_signup: boolean; + readonly enable_anonymous_sign_ins: boolean; + readonly minimum_password_length: number; + readonly password_requirements: string; + readonly publishable_key?: string | undefined; + readonly secret_key?: string | undefined; + readonly jwt_secret?: string | undefined; + readonly anon_key?: string | undefined; + readonly service_role_key?: string | undefined; + readonly rate_limit: { + readonly email_sent: number; + readonly sms_sent: number; + readonly anonymous_users: number; + readonly token_refresh: number; + readonly sign_in_sign_ups: number; + readonly token_verifications: number; + readonly web3: number; + }; + readonly captcha?: { + readonly enabled: boolean; + readonly provider?: string | undefined; + readonly secret?: string | undefined; + } | undefined; + readonly hook: { + readonly mfa_verification_attempt: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly password_verification_attempt: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly custom_access_token: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly send_sms: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly send_email: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly before_user_created: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + }; + readonly mfa: { + readonly totp: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + }; + readonly phone: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + readonly otp_length: number; + readonly template: string; + readonly max_frequency: string; + }; + readonly web_authn: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + }; + readonly max_enrolled_factors: number; + }; + readonly sessions?: { + readonly timebox?: string | undefined; + readonly inactivity_timeout?: string | undefined; + } | undefined; + readonly email: { + readonly enable_signup: boolean; + readonly double_confirm_changes: boolean; + readonly enable_confirmations: boolean; + readonly secure_password_change: boolean; + readonly max_frequency: string; + readonly otp_length: number; + readonly otp_expiry: number; + readonly smtp?: { + readonly enabled: boolean; + readonly host?: string | undefined; + readonly port?: number | undefined; + readonly user?: string | undefined; + readonly pass?: string | undefined; + readonly admin_email?: string | undefined; + readonly sender_name?: string | undefined; + } | undefined; + readonly template: { + readonly [x: string]: { + readonly subject: string; + readonly content_path: string; + }; + }; + readonly notification: { + readonly [x: string]: { + readonly enabled: boolean; + readonly subject: string; + readonly content_path: string; + }; + }; + }; + readonly sms: { + readonly enable_signup: boolean; + readonly enable_confirmations: boolean; + readonly template: string; + readonly max_frequency: string; + readonly twilio: { + readonly enabled: boolean; + readonly account_sid: string; + readonly message_service_sid: string; + readonly auth_token?: string | undefined; + }; + readonly twilio_verify: { + readonly enabled: boolean; + readonly account_sid?: string | undefined; + readonly message_service_sid?: string | undefined; + readonly auth_token?: string | undefined; + }; + readonly messagebird: { + readonly enabled: boolean; + readonly originator?: string | undefined; + readonly access_key?: string | undefined; + }; + readonly textlocal: { + readonly enabled: boolean; + readonly sender?: string | undefined; + readonly api_key?: string | undefined; + }; + readonly vonage: { + readonly enabled: boolean; + readonly from?: string | undefined; + readonly api_key?: string | undefined; + readonly api_secret?: string | undefined; + }; + readonly test_otp?: { + readonly [x: string]: string; + } | undefined; + }; + readonly external: { + readonly apple: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly azure: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly bitbucket: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly discord: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly facebook: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly github: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly gitlab: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly google: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly kakao: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly keycloak: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly linkedin_oidc: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly notion: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly twitch: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly twitter: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly x: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly slack_oidc: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly spotify: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly workos: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly zoom: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + }; + readonly web3: { + readonly solana: { + readonly enabled: boolean; + }; + readonly ethereum: { + readonly enabled: boolean; + }; + }; + readonly oauth_server: { + readonly enabled: boolean; + readonly authorization_url_path: string; + readonly allow_dynamic_registration: boolean; + }; + readonly third_party: { + readonly firebase: { + readonly enabled: boolean; + readonly project_id?: string | undefined; + }; + readonly auth0: { + readonly enabled: boolean; + readonly tenant?: string | undefined; + readonly tenant_region?: string | undefined; + }; + readonly aws_cognito: { + readonly enabled: boolean; + readonly user_pool_id?: string | undefined; + readonly user_pool_region?: string | undefined; + }; + readonly clerk: { + readonly enabled: boolean; + readonly domain?: string | undefined; + }; + readonly workos: { + readonly enabled: boolean; + readonly issuer_url?: string | undefined; + }; + }; + }; + readonly db: { + readonly port: number; + readonly shadow_port: number; + readonly health_timeout: string; + readonly major_version: number; + readonly pooler: { + readonly enabled: boolean; + readonly port: number; + readonly pool_mode: string; + readonly default_pool_size: number; + readonly max_client_conn: number; + }; + readonly migrations: { + readonly enabled: boolean; + readonly schema_paths: readonly string[]; + }; + readonly seed: { + readonly enabled: boolean; + readonly sql_paths: readonly string[]; + }; + readonly settings?: { + readonly effective_cache_size?: string | undefined; + readonly logical_decoding_work_mem?: string | undefined; + readonly maintenance_work_mem?: string | undefined; + readonly max_connections?: number | undefined; + readonly max_locks_per_transaction?: number | undefined; + readonly max_parallel_maintenance_workers?: number | undefined; + readonly max_parallel_workers?: number | undefined; + readonly max_parallel_workers_per_gather?: number | undefined; + readonly max_replication_slots?: number | undefined; + readonly max_slot_wal_keep_size?: string | undefined; + readonly max_standby_archive_delay?: string | undefined; + readonly max_standby_streaming_delay?: string | undefined; + readonly max_wal_size?: string | undefined; + readonly max_wal_senders?: number | undefined; + readonly max_worker_processes?: number | undefined; + readonly session_replication_role?: string | undefined; + readonly shared_buffers?: string | undefined; + readonly statement_timeout?: string | undefined; + readonly track_activity_query_size?: string | undefined; + readonly track_commit_timestamp?: boolean | undefined; + readonly wal_keep_size?: string | undefined; + readonly wal_sender_timeout?: string | undefined; + readonly work_mem?: string | undefined; + } | undefined; + readonly network_restrictions: { + readonly enabled: boolean; + readonly allowed_cidrs: readonly string[]; + readonly allowed_cidrs_v6: readonly string[]; + }; + readonly ssl_enforcement?: { + readonly enabled: boolean; + } | undefined; + readonly vault?: { + readonly [x: string]: string; + } | undefined; + }; + readonly edge_runtime: { + readonly enabled: boolean; + readonly policy: string; + readonly inspector_port: number; + readonly deno_version: number; + readonly secrets?: { + readonly [x: string]: string; + } | undefined; + }; + readonly functions: { + readonly [x: string]: { + readonly enabled: boolean; + readonly verify_jwt: boolean; + readonly import_map: string; + readonly entrypoint: string; + readonly static_files: readonly string[]; + readonly env: { + readonly [x: string]: string; + }; + }; + }; + readonly local_smtp: { + readonly enabled: boolean; + readonly port: number; + readonly smtp_port?: number | undefined; + readonly pop3_port?: number | undefined; + readonly admin_email?: string | undefined; + readonly sender_name?: string | undefined; + }; + readonly realtime: { + readonly enabled: boolean; + readonly ip_version: string; + readonly max_header_length: number; + }; + readonly storage: { + readonly enabled: boolean; + readonly file_size_limit: string; + readonly image_transformation?: { + readonly enabled: boolean; + } | undefined; + readonly buckets?: { + readonly [x: string]: { + readonly public: boolean; + readonly file_size_limit: string; + readonly allowed_mime_types: readonly string[]; + readonly objects_path: string; + }; + } | undefined; + readonly s3_protocol: { + readonly enabled: boolean; + }; + readonly analytics: { + readonly enabled: boolean; + readonly max_namespaces: number; + readonly max_tables: number; + readonly max_catalogs: number; + readonly buckets: { + readonly [x: string]: {}; + }; + }; + readonly vector: { + readonly enabled: boolean; + readonly max_buckets: number; + readonly max_indexes: number; + readonly buckets: { + readonly [x: string]: {}; + }; + }; + }; + readonly studio: { + readonly enabled: boolean; + readonly port: number; + readonly api_url: string; + readonly openai_api_key?: string | undefined; + }; + readonly workers: { + readonly [x: string]: { + readonly runtime?: string | undefined; + readonly size?: string | undefined; + readonly instances?: number | undefined; + readonly source?: string | undefined; + }; + }; + readonly experimental: { + readonly orioledb_version?: string | undefined; + readonly s3_host?: string | undefined; + readonly s3_region?: string | undefined; + readonly s3_access_key?: string | undefined; + readonly s3_secret_key?: string | undefined; + readonly webhooks?: { + readonly enabled: boolean; + } | undefined; + readonly pgdelta?: { + readonly enabled: boolean; + readonly declarative_schema_path?: string | undefined; + readonly format_options?: string | undefined; + } | undefined; + readonly inspect?: { + readonly rules: readonly { + readonly query?: string | undefined; + readonly name?: string | undefined; + readonly pass?: string | undefined; + readonly fail?: string | undefined; + }[]; + } | undefined; + }; + readonly remotes: { + readonly [x: string]: { + readonly project_id: string; + readonly analytics: { + readonly enabled: boolean; + readonly port: number; + readonly backend: string; + readonly vector_port?: number | undefined; + readonly gcp_project_id?: string | undefined; + readonly gcp_project_number?: string | undefined; + readonly gcp_jwt_path?: string | undefined; + }; + readonly api: { + readonly enabled: boolean; + readonly port: number; + readonly schemas: readonly string[]; + readonly extra_search_path: readonly string[]; + readonly max_rows: number; + readonly auto_expose_new_tables?: boolean | undefined; + readonly tls: { + readonly enabled: boolean; + readonly cert_path?: string | undefined; + readonly key_path?: string | undefined; + }; + readonly external_url?: string | undefined; + }; + readonly auth: { + readonly enabled: boolean; + readonly site_url: string; + readonly additional_redirect_urls: readonly string[]; + readonly jwt_expiry: number; + readonly jwt_issuer?: string | undefined; + readonly signing_keys_path?: string | undefined; + readonly enable_refresh_token_rotation: boolean; + readonly refresh_token_reuse_interval: number; + readonly enable_manual_linking: boolean; + readonly enable_signup: boolean; + readonly enable_anonymous_sign_ins: boolean; + readonly minimum_password_length: number; + readonly password_requirements: string; + readonly publishable_key?: string | undefined; + readonly secret_key?: string | undefined; + readonly jwt_secret?: string | undefined; + readonly anon_key?: string | undefined; + readonly service_role_key?: string | undefined; + readonly rate_limit: { + readonly email_sent: number; + readonly sms_sent: number; + readonly anonymous_users: number; + readonly token_refresh: number; + readonly sign_in_sign_ups: number; + readonly token_verifications: number; + readonly web3: number; + }; + readonly captcha?: { + readonly enabled: boolean; + readonly provider?: string | undefined; + readonly secret?: string | undefined; + } | undefined; + readonly hook: { + readonly mfa_verification_attempt: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly password_verification_attempt: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly custom_access_token: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly send_sms: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly send_email: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly before_user_created: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + }; + readonly mfa: { + readonly totp: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + }; + readonly phone: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + readonly otp_length: number; + readonly template: string; + readonly max_frequency: string; + }; + readonly web_authn: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + }; + readonly max_enrolled_factors: number; + }; + readonly sessions?: { + readonly timebox?: string | undefined; + readonly inactivity_timeout?: string | undefined; + } | undefined; + readonly email: { + readonly enable_signup: boolean; + readonly double_confirm_changes: boolean; + readonly enable_confirmations: boolean; + readonly secure_password_change: boolean; + readonly max_frequency: string; + readonly otp_length: number; + readonly otp_expiry: number; + readonly smtp?: { + readonly enabled: boolean; + readonly host?: string | undefined; + readonly port?: number | undefined; + readonly user?: string | undefined; + readonly pass?: string | undefined; + readonly admin_email?: string | undefined; + readonly sender_name?: string | undefined; + } | undefined; + readonly template: { + readonly [x: string]: { + readonly subject: string; + readonly content_path: string; + }; + }; + readonly notification: { + readonly [x: string]: { + readonly enabled: boolean; + readonly subject: string; + readonly content_path: string; + }; + }; + }; + readonly sms: { + readonly enable_signup: boolean; + readonly enable_confirmations: boolean; + readonly template: string; + readonly max_frequency: string; + readonly twilio: { + readonly enabled: boolean; + readonly account_sid: string; + readonly message_service_sid: string; + readonly auth_token?: string | undefined; + }; + readonly twilio_verify: { + readonly enabled: boolean; + readonly account_sid?: string | undefined; + readonly message_service_sid?: string | undefined; + readonly auth_token?: string | undefined; + }; + readonly messagebird: { + readonly enabled: boolean; + readonly originator?: string | undefined; + readonly access_key?: string | undefined; + }; + readonly textlocal: { + readonly enabled: boolean; + readonly sender?: string | undefined; + readonly api_key?: string | undefined; + }; + readonly vonage: { + readonly enabled: boolean; + readonly from?: string | undefined; + readonly api_key?: string | undefined; + readonly api_secret?: string | undefined; + }; + readonly test_otp?: { + readonly [x: string]: string; + } | undefined; + }; + readonly external: { + readonly apple: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly azure: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly bitbucket: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly discord: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly facebook: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly github: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly gitlab: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly google: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly kakao: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly keycloak: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly linkedin_oidc: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly notion: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly twitch: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly twitter: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly x: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly slack_oidc: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly spotify: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly workos: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly zoom: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + }; + readonly web3: { + readonly solana: { + readonly enabled: boolean; + }; + readonly ethereum: { + readonly enabled: boolean; + }; + }; + readonly oauth_server: { + readonly enabled: boolean; + readonly authorization_url_path: string; + readonly allow_dynamic_registration: boolean; + }; + readonly third_party: { + readonly firebase: { + readonly enabled: boolean; + readonly project_id?: string | undefined; + }; + readonly auth0: { + readonly enabled: boolean; + readonly tenant?: string | undefined; + readonly tenant_region?: string | undefined; + }; + readonly aws_cognito: { + readonly enabled: boolean; + readonly user_pool_id?: string | undefined; + readonly user_pool_region?: string | undefined; + }; + readonly clerk: { + readonly enabled: boolean; + readonly domain?: string | undefined; + }; + readonly workos: { + readonly enabled: boolean; + readonly issuer_url?: string | undefined; + }; + }; + }; + readonly db: { + readonly port: number; + readonly shadow_port: number; + readonly health_timeout: string; + readonly major_version: number; + readonly pooler: { + readonly enabled: boolean; + readonly port: number; + readonly pool_mode: string; + readonly default_pool_size: number; + readonly max_client_conn: number; + }; + readonly migrations: { + readonly enabled: boolean; + readonly schema_paths: readonly string[]; + }; + readonly seed: { + readonly enabled: boolean; + readonly sql_paths: readonly string[]; + }; + readonly settings?: { + readonly effective_cache_size?: string | undefined; + readonly logical_decoding_work_mem?: string | undefined; + readonly maintenance_work_mem?: string | undefined; + readonly max_connections?: number | undefined; + readonly max_locks_per_transaction?: number | undefined; + readonly max_parallel_maintenance_workers?: number | undefined; + readonly max_parallel_workers?: number | undefined; + readonly max_parallel_workers_per_gather?: number | undefined; + readonly max_replication_slots?: number | undefined; + readonly max_slot_wal_keep_size?: string | undefined; + readonly max_standby_archive_delay?: string | undefined; + readonly max_standby_streaming_delay?: string | undefined; + readonly max_wal_size?: string | undefined; + readonly max_wal_senders?: number | undefined; + readonly max_worker_processes?: number | undefined; + readonly session_replication_role?: string | undefined; + readonly shared_buffers?: string | undefined; + readonly statement_timeout?: string | undefined; + readonly track_activity_query_size?: string | undefined; + readonly track_commit_timestamp?: boolean | undefined; + readonly wal_keep_size?: string | undefined; + readonly wal_sender_timeout?: string | undefined; + readonly work_mem?: string | undefined; + } | undefined; + readonly network_restrictions: { + readonly enabled: boolean; + readonly allowed_cidrs: readonly string[]; + readonly allowed_cidrs_v6: readonly string[]; + }; + readonly ssl_enforcement?: { + readonly enabled: boolean; + } | undefined; + readonly vault?: { + readonly [x: string]: string; + } | undefined; + }; + readonly edge_runtime: { + readonly enabled: boolean; + readonly policy: string; + readonly inspector_port: number; + readonly deno_version: number; + readonly secrets?: { + readonly [x: string]: string; + } | undefined; + }; + readonly functions: { + readonly [x: string]: { + readonly enabled: boolean; + readonly verify_jwt: boolean; + readonly import_map: string; + readonly entrypoint: string; + readonly static_files: readonly string[]; + readonly env: { + readonly [x: string]: string; + }; + }; + }; + readonly local_smtp: { + readonly enabled: boolean; + readonly port: number; + readonly smtp_port?: number | undefined; + readonly pop3_port?: number | undefined; + readonly admin_email?: string | undefined; + readonly sender_name?: string | undefined; + }; + readonly realtime: { + readonly enabled: boolean; + readonly ip_version: string; + readonly max_header_length: number; + }; + readonly storage: { + readonly enabled: boolean; + readonly file_size_limit: string; + readonly image_transformation?: { + readonly enabled: boolean; + } | undefined; + readonly buckets?: { + readonly [x: string]: { + readonly public: boolean; + readonly file_size_limit: string; + readonly allowed_mime_types: readonly string[]; + readonly objects_path: string; + }; + } | undefined; + readonly s3_protocol: { + readonly enabled: boolean; + }; + readonly analytics: { + readonly enabled: boolean; + readonly max_namespaces: number; + readonly max_tables: number; + readonly max_catalogs: number; + readonly buckets: { + readonly [x: string]: {}; + }; + }; + readonly vector: { + readonly enabled: boolean; + readonly max_buckets: number; + readonly max_indexes: number; + readonly buckets: { + readonly [x: string]: {}; + }; + }; + }; + readonly studio: { + readonly enabled: boolean; + readonly port: number; + readonly api_url: string; + readonly openai_api_key?: string | undefined; + }; + readonly workers: { + readonly [x: string]: { + readonly runtime?: string | undefined; + readonly size?: string | undefined; + readonly instances?: number | undefined; + readonly source?: string | undefined; + }; + }; + readonly experimental: { + readonly orioledb_version?: string | undefined; + readonly s3_host?: string | undefined; + readonly s3_region?: string | undefined; + readonly s3_access_key?: string | undefined; + readonly s3_secret_key?: string | undefined; + readonly webhooks?: { + readonly enabled: boolean; + } | undefined; + readonly pgdelta?: { + readonly enabled: boolean; + readonly declarative_schema_path?: string | undefined; + readonly format_options?: string | undefined; + } | undefined; + readonly inspect?: { + readonly rules: readonly { + readonly query?: string | undefined; + readonly name?: string | undefined; + readonly pass?: string | undefined; + readonly fail?: string | undefined; + }[]; + } | undefined; + }; + }; + }; + }; + schemaRef: string | undefined; + ignoredPaths: never[]; + document: Record | undefined; + appliedRemote: string | undefined; + removedDeprecatedExternalProviders: Readonly>; + valueOrigins: { + path: string[]; + source: CliConfigValueSource; + }[]; +}, CliConfigParseError | import("./errors.ts").CliProjectEnvParseError | DuplicateRemoteProjectIdError | InvalidRemoteProjectIdError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>; +export declare const loadCliConfig: (cwd: string, options?: InternalLoadCliConfigOptions | undefined) => Effect.Effect<{ + path: string; + format: "json" | "toml"; + config: { + readonly project_id?: string | undefined; + readonly analytics: { + readonly enabled: boolean; + readonly port: number; + readonly backend: string; + readonly vector_port?: number | undefined; + readonly gcp_project_id?: string | undefined; + readonly gcp_project_number?: string | undefined; + readonly gcp_jwt_path?: string | undefined; + }; + readonly api: { + readonly enabled: boolean; + readonly port: number; + readonly schemas: readonly string[]; + readonly extra_search_path: readonly string[]; + readonly max_rows: number; + readonly auto_expose_new_tables?: boolean | undefined; + readonly tls: { + readonly enabled: boolean; + readonly cert_path?: string | undefined; + readonly key_path?: string | undefined; + }; + readonly external_url?: string | undefined; + }; + readonly auth: { + readonly enabled: boolean; + readonly site_url: string; + readonly additional_redirect_urls: readonly string[]; + readonly jwt_expiry: number; + readonly jwt_issuer?: string | undefined; + readonly signing_keys_path?: string | undefined; + readonly enable_refresh_token_rotation: boolean; + readonly refresh_token_reuse_interval: number; + readonly enable_manual_linking: boolean; + readonly enable_signup: boolean; + readonly enable_anonymous_sign_ins: boolean; + readonly minimum_password_length: number; + readonly password_requirements: string; + readonly publishable_key?: string | undefined; + readonly secret_key?: string | undefined; + readonly jwt_secret?: string | undefined; + readonly anon_key?: string | undefined; + readonly service_role_key?: string | undefined; + readonly rate_limit: { + readonly email_sent: number; + readonly sms_sent: number; + readonly anonymous_users: number; + readonly token_refresh: number; + readonly sign_in_sign_ups: number; + readonly token_verifications: number; + readonly web3: number; + }; + readonly captcha?: { + readonly enabled: boolean; + readonly provider?: string | undefined; + readonly secret?: string | undefined; + } | undefined; + readonly hook: { + readonly mfa_verification_attempt: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly password_verification_attempt: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly custom_access_token: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly send_sms: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly send_email: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly before_user_created: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + }; + readonly mfa: { + readonly totp: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + }; + readonly phone: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + readonly otp_length: number; + readonly template: string; + readonly max_frequency: string; + }; + readonly web_authn: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + }; + readonly max_enrolled_factors: number; + }; + readonly sessions?: { + readonly timebox?: string | undefined; + readonly inactivity_timeout?: string | undefined; + } | undefined; + readonly email: { + readonly enable_signup: boolean; + readonly double_confirm_changes: boolean; + readonly enable_confirmations: boolean; + readonly secure_password_change: boolean; + readonly max_frequency: string; + readonly otp_length: number; + readonly otp_expiry: number; + readonly smtp?: { + readonly enabled: boolean; + readonly host?: string | undefined; + readonly port?: number | undefined; + readonly user?: string | undefined; + readonly pass?: string | undefined; + readonly admin_email?: string | undefined; + readonly sender_name?: string | undefined; + } | undefined; + readonly template: { + readonly [x: string]: { + readonly subject: string; + readonly content_path: string; + }; + }; + readonly notification: { + readonly [x: string]: { + readonly enabled: boolean; + readonly subject: string; + readonly content_path: string; + }; + }; + }; + readonly sms: { + readonly enable_signup: boolean; + readonly enable_confirmations: boolean; + readonly template: string; + readonly max_frequency: string; + readonly twilio: { + readonly enabled: boolean; + readonly account_sid: string; + readonly message_service_sid: string; + readonly auth_token?: string | undefined; + }; + readonly twilio_verify: { + readonly enabled: boolean; + readonly account_sid?: string | undefined; + readonly message_service_sid?: string | undefined; + readonly auth_token?: string | undefined; + }; + readonly messagebird: { + readonly enabled: boolean; + readonly originator?: string | undefined; + readonly access_key?: string | undefined; + }; + readonly textlocal: { + readonly enabled: boolean; + readonly sender?: string | undefined; + readonly api_key?: string | undefined; + }; + readonly vonage: { + readonly enabled: boolean; + readonly from?: string | undefined; + readonly api_key?: string | undefined; + readonly api_secret?: string | undefined; + }; + readonly test_otp?: { + readonly [x: string]: string; + } | undefined; + }; + readonly external: { + readonly apple: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly azure: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly bitbucket: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly discord: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly facebook: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly github: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly gitlab: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly google: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly kakao: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly keycloak: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly linkedin_oidc: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly notion: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly twitch: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly twitter: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly x: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly slack_oidc: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly spotify: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly workos: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly zoom: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + }; + readonly web3: { + readonly solana: { + readonly enabled: boolean; + }; + readonly ethereum: { + readonly enabled: boolean; + }; + }; + readonly oauth_server: { + readonly enabled: boolean; + readonly authorization_url_path: string; + readonly allow_dynamic_registration: boolean; + }; + readonly third_party: { + readonly firebase: { + readonly enabled: boolean; + readonly project_id?: string | undefined; + }; + readonly auth0: { + readonly enabled: boolean; + readonly tenant?: string | undefined; + readonly tenant_region?: string | undefined; + }; + readonly aws_cognito: { + readonly enabled: boolean; + readonly user_pool_id?: string | undefined; + readonly user_pool_region?: string | undefined; + }; + readonly clerk: { + readonly enabled: boolean; + readonly domain?: string | undefined; + }; + readonly workos: { + readonly enabled: boolean; + readonly issuer_url?: string | undefined; + }; + }; + }; + readonly db: { + readonly port: number; + readonly shadow_port: number; + readonly health_timeout: string; + readonly major_version: number; + readonly pooler: { + readonly enabled: boolean; + readonly port: number; + readonly pool_mode: string; + readonly default_pool_size: number; + readonly max_client_conn: number; + }; + readonly migrations: { + readonly enabled: boolean; + readonly schema_paths: readonly string[]; + }; + readonly seed: { + readonly enabled: boolean; + readonly sql_paths: readonly string[]; + }; + readonly settings?: { + readonly effective_cache_size?: string | undefined; + readonly logical_decoding_work_mem?: string | undefined; + readonly maintenance_work_mem?: string | undefined; + readonly max_connections?: number | undefined; + readonly max_locks_per_transaction?: number | undefined; + readonly max_parallel_maintenance_workers?: number | undefined; + readonly max_parallel_workers?: number | undefined; + readonly max_parallel_workers_per_gather?: number | undefined; + readonly max_replication_slots?: number | undefined; + readonly max_slot_wal_keep_size?: string | undefined; + readonly max_standby_archive_delay?: string | undefined; + readonly max_standby_streaming_delay?: string | undefined; + readonly max_wal_size?: string | undefined; + readonly max_wal_senders?: number | undefined; + readonly max_worker_processes?: number | undefined; + readonly session_replication_role?: string | undefined; + readonly shared_buffers?: string | undefined; + readonly statement_timeout?: string | undefined; + readonly track_activity_query_size?: string | undefined; + readonly track_commit_timestamp?: boolean | undefined; + readonly wal_keep_size?: string | undefined; + readonly wal_sender_timeout?: string | undefined; + readonly work_mem?: string | undefined; + } | undefined; + readonly network_restrictions: { + readonly enabled: boolean; + readonly allowed_cidrs: readonly string[]; + readonly allowed_cidrs_v6: readonly string[]; + }; + readonly ssl_enforcement?: { + readonly enabled: boolean; + } | undefined; + readonly vault?: { + readonly [x: string]: string; + } | undefined; + }; + readonly edge_runtime: { + readonly enabled: boolean; + readonly policy: string; + readonly inspector_port: number; + readonly deno_version: number; + readonly secrets?: { + readonly [x: string]: string; + } | undefined; + }; + readonly functions: { + readonly [x: string]: { + readonly enabled: boolean; + readonly verify_jwt: boolean; + readonly import_map: string; + readonly entrypoint: string; + readonly static_files: readonly string[]; + readonly env: { + readonly [x: string]: string; + }; + }; + }; + readonly local_smtp: { + readonly enabled: boolean; + readonly port: number; + readonly smtp_port?: number | undefined; + readonly pop3_port?: number | undefined; + readonly admin_email?: string | undefined; + readonly sender_name?: string | undefined; + }; + readonly realtime: { + readonly enabled: boolean; + readonly ip_version: string; + readonly max_header_length: number; + }; + readonly storage: { + readonly enabled: boolean; + readonly file_size_limit: string; + readonly image_transformation?: { + readonly enabled: boolean; + } | undefined; + readonly buckets?: { + readonly [x: string]: { + readonly public: boolean; + readonly file_size_limit: string; + readonly allowed_mime_types: readonly string[]; + readonly objects_path: string; + }; + } | undefined; + readonly s3_protocol: { + readonly enabled: boolean; + }; + readonly analytics: { + readonly enabled: boolean; + readonly max_namespaces: number; + readonly max_tables: number; + readonly max_catalogs: number; + readonly buckets: { + readonly [x: string]: {}; + }; + }; + readonly vector: { + readonly enabled: boolean; + readonly max_buckets: number; + readonly max_indexes: number; + readonly buckets: { + readonly [x: string]: {}; + }; + }; + }; + readonly studio: { + readonly enabled: boolean; + readonly port: number; + readonly api_url: string; + readonly openai_api_key?: string | undefined; + }; + readonly workers: { + readonly [x: string]: { + readonly runtime?: string | undefined; + readonly size?: string | undefined; + readonly instances?: number | undefined; + readonly source?: string | undefined; + }; + }; + readonly experimental: { + readonly orioledb_version?: string | undefined; + readonly s3_host?: string | undefined; + readonly s3_region?: string | undefined; + readonly s3_access_key?: string | undefined; + readonly s3_secret_key?: string | undefined; + readonly webhooks?: { + readonly enabled: boolean; + } | undefined; + readonly pgdelta?: { + readonly enabled: boolean; + readonly declarative_schema_path?: string | undefined; + readonly format_options?: string | undefined; + } | undefined; + readonly inspect?: { + readonly rules: readonly { + readonly query?: string | undefined; + readonly name?: string | undefined; + readonly pass?: string | undefined; + readonly fail?: string | undefined; + }[]; + } | undefined; + }; + readonly remotes: { + readonly [x: string]: { + readonly project_id: string; + readonly analytics: { + readonly enabled: boolean; + readonly port: number; + readonly backend: string; + readonly vector_port?: number | undefined; + readonly gcp_project_id?: string | undefined; + readonly gcp_project_number?: string | undefined; + readonly gcp_jwt_path?: string | undefined; + }; + readonly api: { + readonly enabled: boolean; + readonly port: number; + readonly schemas: readonly string[]; + readonly extra_search_path: readonly string[]; + readonly max_rows: number; + readonly auto_expose_new_tables?: boolean | undefined; + readonly tls: { + readonly enabled: boolean; + readonly cert_path?: string | undefined; + readonly key_path?: string | undefined; + }; + readonly external_url?: string | undefined; + }; + readonly auth: { + readonly enabled: boolean; + readonly site_url: string; + readonly additional_redirect_urls: readonly string[]; + readonly jwt_expiry: number; + readonly jwt_issuer?: string | undefined; + readonly signing_keys_path?: string | undefined; + readonly enable_refresh_token_rotation: boolean; + readonly refresh_token_reuse_interval: number; + readonly enable_manual_linking: boolean; + readonly enable_signup: boolean; + readonly enable_anonymous_sign_ins: boolean; + readonly minimum_password_length: number; + readonly password_requirements: string; + readonly publishable_key?: string | undefined; + readonly secret_key?: string | undefined; + readonly jwt_secret?: string | undefined; + readonly anon_key?: string | undefined; + readonly service_role_key?: string | undefined; + readonly rate_limit: { + readonly email_sent: number; + readonly sms_sent: number; + readonly anonymous_users: number; + readonly token_refresh: number; + readonly sign_in_sign_ups: number; + readonly token_verifications: number; + readonly web3: number; + }; + readonly captcha?: { + readonly enabled: boolean; + readonly provider?: string | undefined; + readonly secret?: string | undefined; + } | undefined; + readonly hook: { + readonly mfa_verification_attempt: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly password_verification_attempt: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly custom_access_token: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly send_sms: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly send_email: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly before_user_created: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + }; + readonly mfa: { + readonly totp: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + }; + readonly phone: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + readonly otp_length: number; + readonly template: string; + readonly max_frequency: string; + }; + readonly web_authn: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + }; + readonly max_enrolled_factors: number; + }; + readonly sessions?: { + readonly timebox?: string | undefined; + readonly inactivity_timeout?: string | undefined; + } | undefined; + readonly email: { + readonly enable_signup: boolean; + readonly double_confirm_changes: boolean; + readonly enable_confirmations: boolean; + readonly secure_password_change: boolean; + readonly max_frequency: string; + readonly otp_length: number; + readonly otp_expiry: number; + readonly smtp?: { + readonly enabled: boolean; + readonly host?: string | undefined; + readonly port?: number | undefined; + readonly user?: string | undefined; + readonly pass?: string | undefined; + readonly admin_email?: string | undefined; + readonly sender_name?: string | undefined; + } | undefined; + readonly template: { + readonly [x: string]: { + readonly subject: string; + readonly content_path: string; + }; + }; + readonly notification: { + readonly [x: string]: { + readonly enabled: boolean; + readonly subject: string; + readonly content_path: string; + }; + }; + }; + readonly sms: { + readonly enable_signup: boolean; + readonly enable_confirmations: boolean; + readonly template: string; + readonly max_frequency: string; + readonly twilio: { + readonly enabled: boolean; + readonly account_sid: string; + readonly message_service_sid: string; + readonly auth_token?: string | undefined; + }; + readonly twilio_verify: { + readonly enabled: boolean; + readonly account_sid?: string | undefined; + readonly message_service_sid?: string | undefined; + readonly auth_token?: string | undefined; + }; + readonly messagebird: { + readonly enabled: boolean; + readonly originator?: string | undefined; + readonly access_key?: string | undefined; + }; + readonly textlocal: { + readonly enabled: boolean; + readonly sender?: string | undefined; + readonly api_key?: string | undefined; + }; + readonly vonage: { + readonly enabled: boolean; + readonly from?: string | undefined; + readonly api_key?: string | undefined; + readonly api_secret?: string | undefined; + }; + readonly test_otp?: { + readonly [x: string]: string; + } | undefined; + }; + readonly external: { + readonly apple: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly azure: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly bitbucket: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly discord: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly facebook: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly github: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly gitlab: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly google: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly kakao: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly keycloak: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly linkedin_oidc: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly notion: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly twitch: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly twitter: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly x: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly slack_oidc: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly spotify: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly workos: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly zoom: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + }; + readonly web3: { + readonly solana: { + readonly enabled: boolean; + }; + readonly ethereum: { + readonly enabled: boolean; + }; + }; + readonly oauth_server: { + readonly enabled: boolean; + readonly authorization_url_path: string; + readonly allow_dynamic_registration: boolean; + }; + readonly third_party: { + readonly firebase: { + readonly enabled: boolean; + readonly project_id?: string | undefined; + }; + readonly auth0: { + readonly enabled: boolean; + readonly tenant?: string | undefined; + readonly tenant_region?: string | undefined; + }; + readonly aws_cognito: { + readonly enabled: boolean; + readonly user_pool_id?: string | undefined; + readonly user_pool_region?: string | undefined; + }; + readonly clerk: { + readonly enabled: boolean; + readonly domain?: string | undefined; + }; + readonly workos: { + readonly enabled: boolean; + readonly issuer_url?: string | undefined; + }; + }; + }; + readonly db: { + readonly port: number; + readonly shadow_port: number; + readonly health_timeout: string; + readonly major_version: number; + readonly pooler: { + readonly enabled: boolean; + readonly port: number; + readonly pool_mode: string; + readonly default_pool_size: number; + readonly max_client_conn: number; + }; + readonly migrations: { + readonly enabled: boolean; + readonly schema_paths: readonly string[]; + }; + readonly seed: { + readonly enabled: boolean; + readonly sql_paths: readonly string[]; + }; + readonly settings?: { + readonly effective_cache_size?: string | undefined; + readonly logical_decoding_work_mem?: string | undefined; + readonly maintenance_work_mem?: string | undefined; + readonly max_connections?: number | undefined; + readonly max_locks_per_transaction?: number | undefined; + readonly max_parallel_maintenance_workers?: number | undefined; + readonly max_parallel_workers?: number | undefined; + readonly max_parallel_workers_per_gather?: number | undefined; + readonly max_replication_slots?: number | undefined; + readonly max_slot_wal_keep_size?: string | undefined; + readonly max_standby_archive_delay?: string | undefined; + readonly max_standby_streaming_delay?: string | undefined; + readonly max_wal_size?: string | undefined; + readonly max_wal_senders?: number | undefined; + readonly max_worker_processes?: number | undefined; + readonly session_replication_role?: string | undefined; + readonly shared_buffers?: string | undefined; + readonly statement_timeout?: string | undefined; + readonly track_activity_query_size?: string | undefined; + readonly track_commit_timestamp?: boolean | undefined; + readonly wal_keep_size?: string | undefined; + readonly wal_sender_timeout?: string | undefined; + readonly work_mem?: string | undefined; + } | undefined; + readonly network_restrictions: { + readonly enabled: boolean; + readonly allowed_cidrs: readonly string[]; + readonly allowed_cidrs_v6: readonly string[]; + }; + readonly ssl_enforcement?: { + readonly enabled: boolean; + } | undefined; + readonly vault?: { + readonly [x: string]: string; + } | undefined; + }; + readonly edge_runtime: { + readonly enabled: boolean; + readonly policy: string; + readonly inspector_port: number; + readonly deno_version: number; + readonly secrets?: { + readonly [x: string]: string; + } | undefined; + }; + readonly functions: { + readonly [x: string]: { + readonly enabled: boolean; + readonly verify_jwt: boolean; + readonly import_map: string; + readonly entrypoint: string; + readonly static_files: readonly string[]; + readonly env: { + readonly [x: string]: string; + }; + }; + }; + readonly local_smtp: { + readonly enabled: boolean; + readonly port: number; + readonly smtp_port?: number | undefined; + readonly pop3_port?: number | undefined; + readonly admin_email?: string | undefined; + readonly sender_name?: string | undefined; + }; + readonly realtime: { + readonly enabled: boolean; + readonly ip_version: string; + readonly max_header_length: number; + }; + readonly storage: { + readonly enabled: boolean; + readonly file_size_limit: string; + readonly image_transformation?: { + readonly enabled: boolean; + } | undefined; + readonly buckets?: { + readonly [x: string]: { + readonly public: boolean; + readonly file_size_limit: string; + readonly allowed_mime_types: readonly string[]; + readonly objects_path: string; + }; + } | undefined; + readonly s3_protocol: { + readonly enabled: boolean; + }; + readonly analytics: { + readonly enabled: boolean; + readonly max_namespaces: number; + readonly max_tables: number; + readonly max_catalogs: number; + readonly buckets: { + readonly [x: string]: {}; + }; + }; + readonly vector: { + readonly enabled: boolean; + readonly max_buckets: number; + readonly max_indexes: number; + readonly buckets: { + readonly [x: string]: {}; + }; + }; + }; + readonly studio: { + readonly enabled: boolean; + readonly port: number; + readonly api_url: string; + readonly openai_api_key?: string | undefined; + }; + readonly workers: { + readonly [x: string]: { + readonly runtime?: string | undefined; + readonly size?: string | undefined; + readonly instances?: number | undefined; + readonly source?: string | undefined; + }; + }; + readonly experimental: { + readonly orioledb_version?: string | undefined; + readonly s3_host?: string | undefined; + readonly s3_region?: string | undefined; + readonly s3_access_key?: string | undefined; + readonly s3_secret_key?: string | undefined; + readonly webhooks?: { + readonly enabled: boolean; + } | undefined; + readonly pgdelta?: { + readonly enabled: boolean; + readonly declarative_schema_path?: string | undefined; + readonly format_options?: string | undefined; + } | undefined; + readonly inspect?: { + readonly rules: readonly { + readonly query?: string | undefined; + readonly name?: string | undefined; + readonly pass?: string | undefined; + readonly fail?: string | undefined; + }[]; + } | undefined; + }; + }; + }; + }; + schemaRef: string | undefined; + document: Record | undefined; + appliedRemote: string | undefined; + removedDeprecatedExternalProviders: Readonly>; + valueOrigins: { + path: string[]; + source: CliConfigValueSource; + }[]; + ignoredPaths: string[]; +} | null, CliConfigParseError | import("./errors.ts").CliProjectEnvParseError | DuplicateRemoteProjectIdError | InvalidRemoteProjectIdError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>; +export declare const saveCliConfig: (options: SaveCliConfigOptions) => Effect.Effect<{ + path: string; + format: ConfigFormat; + config: { + readonly project_id?: string | undefined; + readonly analytics: { + readonly enabled: boolean; + readonly port: number; + readonly backend: string; + readonly vector_port?: number | undefined; + readonly gcp_project_id?: string | undefined; + readonly gcp_project_number?: string | undefined; + readonly gcp_jwt_path?: string | undefined; + }; + readonly api: { + readonly enabled: boolean; + readonly port: number; + readonly schemas: readonly string[]; + readonly extra_search_path: readonly string[]; + readonly max_rows: number; + readonly auto_expose_new_tables?: boolean | undefined; + readonly tls: { + readonly enabled: boolean; + readonly cert_path?: string | undefined; + readonly key_path?: string | undefined; + }; + readonly external_url?: string | undefined; + }; + readonly auth: { + readonly enabled: boolean; + readonly site_url: string; + readonly additional_redirect_urls: readonly string[]; + readonly jwt_expiry: number; + readonly jwt_issuer?: string | undefined; + readonly signing_keys_path?: string | undefined; + readonly enable_refresh_token_rotation: boolean; + readonly refresh_token_reuse_interval: number; + readonly enable_manual_linking: boolean; + readonly enable_signup: boolean; + readonly enable_anonymous_sign_ins: boolean; + readonly minimum_password_length: number; + readonly password_requirements: string; + readonly publishable_key?: string | undefined; + readonly secret_key?: string | undefined; + readonly jwt_secret?: string | undefined; + readonly anon_key?: string | undefined; + readonly service_role_key?: string | undefined; + readonly rate_limit: { + readonly email_sent: number; + readonly sms_sent: number; + readonly anonymous_users: number; + readonly token_refresh: number; + readonly sign_in_sign_ups: number; + readonly token_verifications: number; + readonly web3: number; + }; + readonly captcha?: { + readonly enabled: boolean; + readonly provider?: string | undefined; + readonly secret?: string | undefined; + } | undefined; + readonly hook: { + readonly mfa_verification_attempt: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly password_verification_attempt: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly custom_access_token: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly send_sms: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly send_email: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly before_user_created: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + }; + readonly mfa: { + readonly totp: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + }; + readonly phone: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + readonly otp_length: number; + readonly template: string; + readonly max_frequency: string; + }; + readonly web_authn: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + }; + readonly max_enrolled_factors: number; + }; + readonly sessions?: { + readonly timebox?: string | undefined; + readonly inactivity_timeout?: string | undefined; + } | undefined; + readonly email: { + readonly enable_signup: boolean; + readonly double_confirm_changes: boolean; + readonly enable_confirmations: boolean; + readonly secure_password_change: boolean; + readonly max_frequency: string; + readonly otp_length: number; + readonly otp_expiry: number; + readonly smtp?: { + readonly enabled: boolean; + readonly host?: string | undefined; + readonly port?: number | undefined; + readonly user?: string | undefined; + readonly pass?: string | undefined; + readonly admin_email?: string | undefined; + readonly sender_name?: string | undefined; + } | undefined; + readonly template: { + readonly [x: string]: { + readonly subject: string; + readonly content_path: string; + }; + }; + readonly notification: { + readonly [x: string]: { + readonly enabled: boolean; + readonly subject: string; + readonly content_path: string; + }; + }; + }; + readonly sms: { + readonly enable_signup: boolean; + readonly enable_confirmations: boolean; + readonly template: string; + readonly max_frequency: string; + readonly twilio: { + readonly enabled: boolean; + readonly account_sid: string; + readonly message_service_sid: string; + readonly auth_token?: string | undefined; + }; + readonly twilio_verify: { + readonly enabled: boolean; + readonly account_sid?: string | undefined; + readonly message_service_sid?: string | undefined; + readonly auth_token?: string | undefined; + }; + readonly messagebird: { + readonly enabled: boolean; + readonly originator?: string | undefined; + readonly access_key?: string | undefined; + }; + readonly textlocal: { + readonly enabled: boolean; + readonly sender?: string | undefined; + readonly api_key?: string | undefined; + }; + readonly vonage: { + readonly enabled: boolean; + readonly from?: string | undefined; + readonly api_key?: string | undefined; + readonly api_secret?: string | undefined; + }; + readonly test_otp?: { + readonly [x: string]: string; + } | undefined; + }; + readonly external: { + readonly apple: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly azure: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly bitbucket: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly discord: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly facebook: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly github: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly gitlab: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly google: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly kakao: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly keycloak: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly linkedin_oidc: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly notion: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly twitch: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly twitter: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly x: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly slack_oidc: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly spotify: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly workos: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly zoom: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + }; + readonly web3: { + readonly solana: { + readonly enabled: boolean; + }; + readonly ethereum: { + readonly enabled: boolean; + }; + }; + readonly oauth_server: { + readonly enabled: boolean; + readonly authorization_url_path: string; + readonly allow_dynamic_registration: boolean; + }; + readonly third_party: { + readonly firebase: { + readonly enabled: boolean; + readonly project_id?: string | undefined; + }; + readonly auth0: { + readonly enabled: boolean; + readonly tenant?: string | undefined; + readonly tenant_region?: string | undefined; + }; + readonly aws_cognito: { + readonly enabled: boolean; + readonly user_pool_id?: string | undefined; + readonly user_pool_region?: string | undefined; + }; + readonly clerk: { + readonly enabled: boolean; + readonly domain?: string | undefined; + }; + readonly workos: { + readonly enabled: boolean; + readonly issuer_url?: string | undefined; + }; + }; + }; + readonly db: { + readonly port: number; + readonly shadow_port: number; + readonly health_timeout: string; + readonly major_version: number; + readonly pooler: { + readonly enabled: boolean; + readonly port: number; + readonly pool_mode: string; + readonly default_pool_size: number; + readonly max_client_conn: number; + }; + readonly migrations: { + readonly enabled: boolean; + readonly schema_paths: readonly string[]; + }; + readonly seed: { + readonly enabled: boolean; + readonly sql_paths: readonly string[]; + }; + readonly settings?: { + readonly effective_cache_size?: string | undefined; + readonly logical_decoding_work_mem?: string | undefined; + readonly maintenance_work_mem?: string | undefined; + readonly max_connections?: number | undefined; + readonly max_locks_per_transaction?: number | undefined; + readonly max_parallel_maintenance_workers?: number | undefined; + readonly max_parallel_workers?: number | undefined; + readonly max_parallel_workers_per_gather?: number | undefined; + readonly max_replication_slots?: number | undefined; + readonly max_slot_wal_keep_size?: string | undefined; + readonly max_standby_archive_delay?: string | undefined; + readonly max_standby_streaming_delay?: string | undefined; + readonly max_wal_size?: string | undefined; + readonly max_wal_senders?: number | undefined; + readonly max_worker_processes?: number | undefined; + readonly session_replication_role?: string | undefined; + readonly shared_buffers?: string | undefined; + readonly statement_timeout?: string | undefined; + readonly track_activity_query_size?: string | undefined; + readonly track_commit_timestamp?: boolean | undefined; + readonly wal_keep_size?: string | undefined; + readonly wal_sender_timeout?: string | undefined; + readonly work_mem?: string | undefined; + } | undefined; + readonly network_restrictions: { + readonly enabled: boolean; + readonly allowed_cidrs: readonly string[]; + readonly allowed_cidrs_v6: readonly string[]; + }; + readonly ssl_enforcement?: { + readonly enabled: boolean; + } | undefined; + readonly vault?: { + readonly [x: string]: string; + } | undefined; + }; + readonly edge_runtime: { + readonly enabled: boolean; + readonly policy: string; + readonly inspector_port: number; + readonly deno_version: number; + readonly secrets?: { + readonly [x: string]: string; + } | undefined; + }; + readonly functions: { + readonly [x: string]: { + readonly enabled: boolean; + readonly verify_jwt: boolean; + readonly import_map: string; + readonly entrypoint: string; + readonly static_files: readonly string[]; + readonly env: { + readonly [x: string]: string; + }; + }; + }; + readonly local_smtp: { + readonly enabled: boolean; + readonly port: number; + readonly smtp_port?: number | undefined; + readonly pop3_port?: number | undefined; + readonly admin_email?: string | undefined; + readonly sender_name?: string | undefined; + }; + readonly realtime: { + readonly enabled: boolean; + readonly ip_version: string; + readonly max_header_length: number; + }; + readonly storage: { + readonly enabled: boolean; + readonly file_size_limit: string; + readonly image_transformation?: { + readonly enabled: boolean; + } | undefined; + readonly buckets?: { + readonly [x: string]: { + readonly public: boolean; + readonly file_size_limit: string; + readonly allowed_mime_types: readonly string[]; + readonly objects_path: string; + }; + } | undefined; + readonly s3_protocol: { + readonly enabled: boolean; + }; + readonly analytics: { + readonly enabled: boolean; + readonly max_namespaces: number; + readonly max_tables: number; + readonly max_catalogs: number; + readonly buckets: { + readonly [x: string]: {}; + }; + }; + readonly vector: { + readonly enabled: boolean; + readonly max_buckets: number; + readonly max_indexes: number; + readonly buckets: { + readonly [x: string]: {}; + }; + }; + }; + readonly studio: { + readonly enabled: boolean; + readonly port: number; + readonly api_url: string; + readonly openai_api_key?: string | undefined; + }; + readonly workers: { + readonly [x: string]: { + readonly runtime?: string | undefined; + readonly size?: string | undefined; + readonly instances?: number | undefined; + readonly source?: string | undefined; + }; + }; + readonly experimental: { + readonly orioledb_version?: string | undefined; + readonly s3_host?: string | undefined; + readonly s3_region?: string | undefined; + readonly s3_access_key?: string | undefined; + readonly s3_secret_key?: string | undefined; + readonly webhooks?: { + readonly enabled: boolean; + } | undefined; + readonly pgdelta?: { + readonly enabled: boolean; + readonly declarative_schema_path?: string | undefined; + readonly format_options?: string | undefined; + } | undefined; + readonly inspect?: { + readonly rules: readonly { + readonly query?: string | undefined; + readonly name?: string | undefined; + readonly pass?: string | undefined; + readonly fail?: string | undefined; + }[]; + } | undefined; + }; + readonly remotes: { + readonly [x: string]: { + readonly project_id: string; + readonly analytics: { + readonly enabled: boolean; + readonly port: number; + readonly backend: string; + readonly vector_port?: number | undefined; + readonly gcp_project_id?: string | undefined; + readonly gcp_project_number?: string | undefined; + readonly gcp_jwt_path?: string | undefined; + }; + readonly api: { + readonly enabled: boolean; + readonly port: number; + readonly schemas: readonly string[]; + readonly extra_search_path: readonly string[]; + readonly max_rows: number; + readonly auto_expose_new_tables?: boolean | undefined; + readonly tls: { + readonly enabled: boolean; + readonly cert_path?: string | undefined; + readonly key_path?: string | undefined; + }; + readonly external_url?: string | undefined; + }; + readonly auth: { + readonly enabled: boolean; + readonly site_url: string; + readonly additional_redirect_urls: readonly string[]; + readonly jwt_expiry: number; + readonly jwt_issuer?: string | undefined; + readonly signing_keys_path?: string | undefined; + readonly enable_refresh_token_rotation: boolean; + readonly refresh_token_reuse_interval: number; + readonly enable_manual_linking: boolean; + readonly enable_signup: boolean; + readonly enable_anonymous_sign_ins: boolean; + readonly minimum_password_length: number; + readonly password_requirements: string; + readonly publishable_key?: string | undefined; + readonly secret_key?: string | undefined; + readonly jwt_secret?: string | undefined; + readonly anon_key?: string | undefined; + readonly service_role_key?: string | undefined; + readonly rate_limit: { + readonly email_sent: number; + readonly sms_sent: number; + readonly anonymous_users: number; + readonly token_refresh: number; + readonly sign_in_sign_ups: number; + readonly token_verifications: number; + readonly web3: number; + }; + readonly captcha?: { + readonly enabled: boolean; + readonly provider?: string | undefined; + readonly secret?: string | undefined; + } | undefined; + readonly hook: { + readonly mfa_verification_attempt: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly password_verification_attempt: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly custom_access_token: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly send_sms: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly send_email: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + readonly before_user_created: { + readonly enabled: boolean; + readonly uri?: string | undefined; + readonly secrets?: string | undefined; + }; + }; + readonly mfa: { + readonly totp: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + }; + readonly phone: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + readonly otp_length: number; + readonly template: string; + readonly max_frequency: string; + }; + readonly web_authn: { + readonly enroll_enabled: boolean; + readonly verify_enabled: boolean; + }; + readonly max_enrolled_factors: number; + }; + readonly sessions?: { + readonly timebox?: string | undefined; + readonly inactivity_timeout?: string | undefined; + } | undefined; + readonly email: { + readonly enable_signup: boolean; + readonly double_confirm_changes: boolean; + readonly enable_confirmations: boolean; + readonly secure_password_change: boolean; + readonly max_frequency: string; + readonly otp_length: number; + readonly otp_expiry: number; + readonly smtp?: { + readonly enabled: boolean; + readonly host?: string | undefined; + readonly port?: number | undefined; + readonly user?: string | undefined; + readonly pass?: string | undefined; + readonly admin_email?: string | undefined; + readonly sender_name?: string | undefined; + } | undefined; + readonly template: { + readonly [x: string]: { + readonly subject: string; + readonly content_path: string; + }; + }; + readonly notification: { + readonly [x: string]: { + readonly enabled: boolean; + readonly subject: string; + readonly content_path: string; + }; + }; + }; + readonly sms: { + readonly enable_signup: boolean; + readonly enable_confirmations: boolean; + readonly template: string; + readonly max_frequency: string; + readonly twilio: { + readonly enabled: boolean; + readonly account_sid: string; + readonly message_service_sid: string; + readonly auth_token?: string | undefined; + }; + readonly twilio_verify: { + readonly enabled: boolean; + readonly account_sid?: string | undefined; + readonly message_service_sid?: string | undefined; + readonly auth_token?: string | undefined; + }; + readonly messagebird: { + readonly enabled: boolean; + readonly originator?: string | undefined; + readonly access_key?: string | undefined; + }; + readonly textlocal: { + readonly enabled: boolean; + readonly sender?: string | undefined; + readonly api_key?: string | undefined; + }; + readonly vonage: { + readonly enabled: boolean; + readonly from?: string | undefined; + readonly api_key?: string | undefined; + readonly api_secret?: string | undefined; + }; + readonly test_otp?: { + readonly [x: string]: string; + } | undefined; + }; + readonly external: { + readonly apple: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly azure: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly bitbucket: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly discord: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly facebook: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly github: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly gitlab: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly google: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly kakao: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly keycloak: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly linkedin_oidc: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly notion: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly twitch: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly twitter: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly x: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly slack_oidc: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly spotify: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly workos: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + readonly zoom: { + readonly enabled: boolean; + readonly client_id: string; + readonly secret?: string | undefined; + readonly url: string; + readonly redirect_uri: string; + readonly skip_nonce_check: boolean; + readonly email_optional: boolean; + }; + }; + readonly web3: { + readonly solana: { + readonly enabled: boolean; + }; + readonly ethereum: { + readonly enabled: boolean; + }; + }; + readonly oauth_server: { + readonly enabled: boolean; + readonly authorization_url_path: string; + readonly allow_dynamic_registration: boolean; + }; + readonly third_party: { + readonly firebase: { + readonly enabled: boolean; + readonly project_id?: string | undefined; + }; + readonly auth0: { + readonly enabled: boolean; + readonly tenant?: string | undefined; + readonly tenant_region?: string | undefined; + }; + readonly aws_cognito: { + readonly enabled: boolean; + readonly user_pool_id?: string | undefined; + readonly user_pool_region?: string | undefined; + }; + readonly clerk: { + readonly enabled: boolean; + readonly domain?: string | undefined; + }; + readonly workos: { + readonly enabled: boolean; + readonly issuer_url?: string | undefined; + }; + }; + }; + readonly db: { + readonly port: number; + readonly shadow_port: number; + readonly health_timeout: string; + readonly major_version: number; + readonly pooler: { + readonly enabled: boolean; + readonly port: number; + readonly pool_mode: string; + readonly default_pool_size: number; + readonly max_client_conn: number; + }; + readonly migrations: { + readonly enabled: boolean; + readonly schema_paths: readonly string[]; + }; + readonly seed: { + readonly enabled: boolean; + readonly sql_paths: readonly string[]; + }; + readonly settings?: { + readonly effective_cache_size?: string | undefined; + readonly logical_decoding_work_mem?: string | undefined; + readonly maintenance_work_mem?: string | undefined; + readonly max_connections?: number | undefined; + readonly max_locks_per_transaction?: number | undefined; + readonly max_parallel_maintenance_workers?: number | undefined; + readonly max_parallel_workers?: number | undefined; + readonly max_parallel_workers_per_gather?: number | undefined; + readonly max_replication_slots?: number | undefined; + readonly max_slot_wal_keep_size?: string | undefined; + readonly max_standby_archive_delay?: string | undefined; + readonly max_standby_streaming_delay?: string | undefined; + readonly max_wal_size?: string | undefined; + readonly max_wal_senders?: number | undefined; + readonly max_worker_processes?: number | undefined; + readonly session_replication_role?: string | undefined; + readonly shared_buffers?: string | undefined; + readonly statement_timeout?: string | undefined; + readonly track_activity_query_size?: string | undefined; + readonly track_commit_timestamp?: boolean | undefined; + readonly wal_keep_size?: string | undefined; + readonly wal_sender_timeout?: string | undefined; + readonly work_mem?: string | undefined; + } | undefined; + readonly network_restrictions: { + readonly enabled: boolean; + readonly allowed_cidrs: readonly string[]; + readonly allowed_cidrs_v6: readonly string[]; + }; + readonly ssl_enforcement?: { + readonly enabled: boolean; + } | undefined; + readonly vault?: { + readonly [x: string]: string; + } | undefined; + }; + readonly edge_runtime: { + readonly enabled: boolean; + readonly policy: string; + readonly inspector_port: number; + readonly deno_version: number; + readonly secrets?: { + readonly [x: string]: string; + } | undefined; + }; + readonly functions: { + readonly [x: string]: { + readonly enabled: boolean; + readonly verify_jwt: boolean; + readonly import_map: string; + readonly entrypoint: string; + readonly static_files: readonly string[]; + readonly env: { + readonly [x: string]: string; + }; + }; + }; + readonly local_smtp: { + readonly enabled: boolean; + readonly port: number; + readonly smtp_port?: number | undefined; + readonly pop3_port?: number | undefined; + readonly admin_email?: string | undefined; + readonly sender_name?: string | undefined; + }; + readonly realtime: { + readonly enabled: boolean; + readonly ip_version: string; + readonly max_header_length: number; + }; + readonly storage: { + readonly enabled: boolean; + readonly file_size_limit: string; + readonly image_transformation?: { + readonly enabled: boolean; + } | undefined; + readonly buckets?: { + readonly [x: string]: { + readonly public: boolean; + readonly file_size_limit: string; + readonly allowed_mime_types: readonly string[]; + readonly objects_path: string; + }; + } | undefined; + readonly s3_protocol: { + readonly enabled: boolean; + }; + readonly analytics: { + readonly enabled: boolean; + readonly max_namespaces: number; + readonly max_tables: number; + readonly max_catalogs: number; + readonly buckets: { + readonly [x: string]: {}; + }; + }; + readonly vector: { + readonly enabled: boolean; + readonly max_buckets: number; + readonly max_indexes: number; + readonly buckets: { + readonly [x: string]: {}; + }; + }; + }; + readonly studio: { + readonly enabled: boolean; + readonly port: number; + readonly api_url: string; + readonly openai_api_key?: string | undefined; + }; + readonly workers: { + readonly [x: string]: { + readonly runtime?: string | undefined; + readonly size?: string | undefined; + readonly instances?: number | undefined; + readonly source?: string | undefined; + }; + }; + readonly experimental: { + readonly orioledb_version?: string | undefined; + readonly s3_host?: string | undefined; + readonly s3_region?: string | undefined; + readonly s3_access_key?: string | undefined; + readonly s3_secret_key?: string | undefined; + readonly webhooks?: { + readonly enabled: boolean; + } | undefined; + readonly pgdelta?: { + readonly enabled: boolean; + readonly declarative_schema_path?: string | undefined; + readonly format_options?: string | undefined; + } | undefined; + readonly inspect?: { + readonly rules: readonly { + readonly query?: string | undefined; + readonly name?: string | undefined; + readonly pass?: string | undefined; + readonly fail?: string | undefined; + }[]; + } | undefined; + }; + }; + }; + }; + schemaRef: string | undefined; + ignoredPaths: never[]; +}, CliConfigParseError | import("./errors.ts").CliProjectEnvParseError | DuplicateRemoteProjectIdError | InvalidRemoteProjectIdError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>; diff --git a/packages/config/api-report/lib/env.d.ts b/packages/config/api-report/lib/env.d.ts new file mode 100644 index 0000000000..542770900e --- /dev/null +++ b/packages/config/api-report/lib/env.d.ts @@ -0,0 +1,36 @@ +import { Schema, SchemaAST } from "effect"; +export declare const ENV_PATTERN = "^env\\((.*)\\)$"; +export declare const ENV_CAPTURE_REGEX: RegExp; +export declare const ENV_CAPTURE_REGEX_STRICT: RegExp; +export declare function isEnvReference(value: string, goViperCompat: boolean): boolean; +interface EnvAnnotations extends Schema.Annotations.Documentation { + readonly secret?: true; +} +export declare const env: (annotations?: EnvAnnotations) => Schema.String; +interface SecretAnnotations extends Schema.Annotations.Documentation { +} +export declare const secret: (annotations?: SecretAnnotations) => Schema.String; +/** + * Pre-decode env() substitution + schema-aware coercion. + * + * Walks the raw parsed document and the schema AST in parallel. For every + * string leaf matching `env(VAR)`: + * 1. Substitutes `env[VAR]` if set AND non-empty, else preserves the + * literal verbatim (Go-parity with + * `apps/cli-go/pkg/config/decode_hooks.go:14-21`, which gates on + * `len(env) > 0` — a set-but-empty var, e.g. a dotenv `KEY=` line, + * leaves the `env(KEY)` literal untouched just like an unset one). + * 2. If the schema at that path expects Number or Boolean, coerces the + * substituted string to the expected primitive — mirroring Go's + * mapstructure chain where `LoadEnvHook` returns a string that the next + * hook converts to the target type. + * + * Returns a new structure; does not mutate the input. + */ +export declare function interpolateEnvReferencesAgainstSchema(document: unknown, env: Readonly>, schema: { + readonly ast: SchemaAST.AST; +}, options?: { + readonly goViperCompat?: boolean; + readonly onResolvedEnv?: (path: ReadonlyArray) => void; +}): unknown; +export {}; diff --git a/packages/config/api-report/lib/resolve.d.ts b/packages/config/api-report/lib/resolve.d.ts new file mode 100644 index 0000000000..cfe1104f76 --- /dev/null +++ b/packages/config/api-report/lib/resolve.d.ts @@ -0,0 +1,43 @@ +import { Redacted } from "effect"; +import type { CliProjectEnvironment } from "../project.ts"; +type ResolvedString = string | Redacted.Redacted; +export type ResolvedCliConfigValue = T extends string ? ResolvedString : T extends ReadonlyArray ? ReadonlyArray> : T extends Array ? Array> : T extends Record ? { + readonly [K in keyof T]: ResolvedCliConfigValue; +} & { + readonly [key: string]: ResolvedCliConfigValue; +} : T extends object ? { + readonly [K in keyof T]: ResolvedCliConfigValue; +} : T; +/** + * Currently empty: this package's one `resolveCliConfigValue`/ + * `resolveCliConfigSubtree` option (`goViperCompat`) is internal-only — see + * {@link InternalResolveCliConfigOptions} in `../project.ts`, exported from + * `@supabase/config/internal`. Kept as a named type (rather than removed + * entirely) so the public sync resolvers below have a stable options + * parameter to extend if a public knob is ever added. + */ +export interface ResolveCliConfigOptions { +} +export declare function toPathSegments(path: string): ReadonlyArray; +/** + * Shared by the plain sync resolvers below and `../project.ts`'s + * Effect-typed `resolveCliConfigValue`/`resolveCliConfigSubtree` (which wrap + * this in `Effect.sync` and additionally accept the internal-only + * `goViperCompat` option). + */ +export declare function resolveCliConfigValueAtPath(value: unknown, cliProjectEnv: Pick, path: ReadonlyArray, goViperCompat: boolean): unknown; +/** + * Plain synchronous counterpart of `../project.ts`'s Effect-typed + * `resolveCliConfigValue`, exported from `.` under the same name — `./effect` + * re-exports the Effect-typed variant explicitly, which wins over this one's + * star re-export through `./index.ts` (see `../effect.ts`'s doc comment). + * + * `cliProjectEnv` only needs `.values` (`Pick`) — + * a caller that already has a project's env values but not the full + * `CliProjectEnvironment` shape (e.g. `paths`/`loadedPaths`/`sources`) can pass + * `{ values }` directly instead of threading through the whole loaded object. + */ +export declare function resolveCliConfigValue(value: T, cliProjectEnv: Pick, configPath: string, _options?: ResolveCliConfigOptions): ResolvedCliConfigValue; +/** See {@link resolveCliConfigValue}'s doc comment for why `cliProjectEnv` only needs `.values`. */ +export declare function resolveCliConfigSubtree(value: T, cliProjectEnv: Pick, pathPrefix: string, _options?: ResolveCliConfigOptions): ResolvedCliConfigValue; +export {}; diff --git a/packages/config/api-report/lib/schema.d.ts b/packages/config/api-report/lib/schema.d.ts new file mode 100644 index 0000000000..6df4879aec --- /dev/null +++ b/packages/config/api-report/lib/schema.d.ts @@ -0,0 +1,16 @@ +import { Schema } from "effect"; +interface LinkMetadata { + readonly name: string; + readonly link: string; +} +declare module "effect/Schema" { + namespace Annotations { + interface Augment { + readonly tags?: ReadonlyArray | undefined; + readonly links?: ReadonlyArray | undefined; + readonly ["x-secret"]?: boolean | undefined; + } + } +} +export declare const stringEnum: >(values: Values, annotations?: Schema.Annotations.Documentation) => Schema.Literals; +export {}; diff --git a/packages/config/api-report/lib/secret-paths.d.ts b/packages/config/api-report/lib/secret-paths.d.ts new file mode 100644 index 0000000000..756cda43fc --- /dev/null +++ b/packages/config/api-report/lib/secret-paths.d.ts @@ -0,0 +1,13 @@ +/** + * Derived from `CliConfigSchema` once, at module load — the schema's + * annotations are the single source of truth for which paths are secret; no + * hand-maintained list exists alongside it. A pattern segment is either a + * literal key or `"*"` (a dynamic `Schema.Record` key, e.g. `db.vault.*`, + * `edge_runtime.secrets.*`, `remotes.*.auth.jwt_secret`). Exported (beyond + * {@link isSecretPath}) so `../project-config/project-config.unit.test.ts` + * can build an exhaustive secret-strip probe from the same source of truth, + * rather than a second hand-picked field list. + */ +export declare const secretPathPatterns: (readonly string[])[]; +/** Whether `path` (root-relative segments into {@link CliConfigSchema}) names an `x-secret` leaf. */ +export declare function isSecretPath(path: ReadonlyArray): boolean; diff --git a/packages/config/api-report/node.d.ts b/packages/config/api-report/node.d.ts new file mode 100644 index 0000000000..ef8acf8a4c --- /dev/null +++ b/packages/config/api-report/node.d.ts @@ -0,0 +1,9 @@ +export declare const loadCliConfig: (cwd: string, options?: import("./config-document.ts").LoadCliConfigOptions) => Promise; +export declare const findCliProjectRoot: (cwd: string) => Promise; +export declare const findCliProjectPaths: (cwd: string) => Promise; +export declare const loadCliConfigFile: (path: string) => Promise; +export declare const loadCliProjectEnvironment: (options: import("./project.ts").LoadCliProjectEnvironmentOptions) => Promise; +export declare const saveCliConfig: (options: import("./config-document.ts").SaveCliConfigOptions) => Promise; +export declare const inferFunctionsManifest: (cwd: string) => Promise; +export type { CliConfigIo } from "./promise-facade.ts"; +export * from "./index.ts"; diff --git a/packages/config/api-report/paths.d.ts b/packages/config/api-report/paths.d.ts new file mode 100644 index 0000000000..23b424b346 --- /dev/null +++ b/packages/config/api-report/paths.d.ts @@ -0,0 +1,38 @@ +import { Effect, FileSystem, Path } from "effect"; +export interface CliProjectPaths { + readonly projectRoot: string; + readonly supabaseDir: string; + readonly configPath: string; + readonly envPath: string; + readonly envLocalPath: string; +} +export interface FindCliProjectPathsOptions { + /** + * When `false`, only `cwd` itself is checked for `supabase/config.{json,toml}` — + * no ancestor climb. Go's own resolution never searches twice: an explicit + * `--workdir`/`SUPABASE_WORKDIR` is used exactly as given (`ChangeWorkDir`, + * `apps/cli-go/internal/utils/misc.go:238-257`), and once `os.Chdir`'d there, + * `config.toml` is read as a plain relative path with no further ancestor + * search (`NewPathBuilder`, `pkg/config/utils.go:43-48`). Ancestor climbing in + * Go only ever happens once, as the *default* when workdir is unset + * (`getProjectRoot`, `internal/utils/misc.go:216-231`). + * + * Callers that already hold an authoritative, Go-equivalent project root + * (e.g. the legacy `stop`/`status` ports' `cliSettings.workdir`, which mirrors + * `ChangeWorkDir`'s own explicit-vs-default resolution) should pass `false` + * here to avoid a second, un-Go-like ancestor search that could otherwise + * pick up an unrelated ancestor project's config. + * + * Defaults to `true` (the original ancestor-search behavior), so existing + * callers are unaffected. + */ + readonly search?: boolean; +} +export declare const findCliProjectPaths: (cwd: string, options?: FindCliProjectPathsOptions | undefined) => Effect.Effect<{ + projectRoot: string; + supabaseDir: string; + configPath: string; + envPath: string; + envLocalPath: string; +} | null, never, FileSystem.FileSystem | Path.Path>; +export declare const findCliProjectRoot: (cwd: string) => Effect.Effect; diff --git a/packages/config/api-report/project-config/api-attributes.d.ts b/packages/config/api-report/project-config/api-attributes.d.ts new file mode 100644 index 0000000000..0f42aabe44 --- /dev/null +++ b/packages/config/api-report/project-config/api-attributes.d.ts @@ -0,0 +1,117 @@ +import { Schema } from "effect"; +export declare const ProjectConfigApiAttributesSchema: Schema.Struct<{ + readonly database: Schema.optionalKey; + readonly ssl_enforced: Schema.optionalKey; + readonly network_restrictions: Schema.optionalKey; + readonly status: Schema.optionalKey; + readonly allowed_cidrs: Schema.optionalKey; + readonly type: Schema.optionalKey; + }>>>; + readonly updated_at: Schema.optionalKey; + readonly applied_at: Schema.optionalKey; + }>>; + readonly postgres_settings: Schema.optionalKey; + readonly logical_decoding_work_mem: Schema.optionalKey; + readonly log_autovacuum_min_duration: Schema.optionalKey; + readonly log_checkpoints: Schema.optionalKey; + readonly log_connections: Schema.optionalKey; + readonly log_disconnections: Schema.optionalKey; + readonly log_duration: Schema.optionalKey; + readonly log_lock_waits: Schema.optionalKey; + readonly log_recovery_conflict_waits: Schema.optionalKey; + readonly log_replication_commands: Schema.optionalKey; + readonly log_startup_progress_interval: Schema.optionalKey; + readonly log_temp_files: Schema.optionalKey; + readonly maintenance_work_mem: Schema.optionalKey; + readonly track_activity_query_size: Schema.optionalKey; + readonly max_connections: Schema.optionalKey; + readonly max_locks_per_transaction: Schema.optionalKey; + readonly max_logical_replication_workers: Schema.optionalKey; + readonly max_parallel_maintenance_workers: Schema.optionalKey; + readonly max_parallel_workers: Schema.optionalKey; + readonly max_parallel_workers_per_gather: Schema.optionalKey; + readonly max_replication_slots: Schema.optionalKey; + readonly max_slot_wal_keep_size: Schema.optionalKey; + readonly max_standby_archive_delay: Schema.optionalKey; + readonly max_standby_streaming_delay: Schema.optionalKey; + readonly max_sync_workers_per_subscription: Schema.optionalKey; + readonly max_wal_size: Schema.optionalKey; + readonly max_wal_senders: Schema.optionalKey; + readonly max_worker_processes: Schema.optionalKey; + readonly session_replication_role: Schema.optionalKey; + readonly shared_buffers: Schema.optionalKey; + readonly statement_timeout: Schema.optionalKey; + readonly track_commit_timestamp: Schema.optionalKey; + readonly wal_keep_size: Schema.optionalKey; + readonly wal_sender_timeout: Schema.optionalKey; + readonly work_mem: Schema.optionalKey; + readonly checkpoint_timeout: Schema.optionalKey; + readonly hot_standby_feedback: Schema.optionalKey; + readonly cron_log_statement: Schema.optionalKey; + }>>; + }>>; + readonly pooler: Schema.optionalKey; + readonly ignore_startup_parameters: Schema.optionalKey; + readonly server_idle_timeout: Schema.optionalKey; + readonly server_lifetime: Schema.optionalKey; + readonly query_wait_timeout: Schema.optionalKey; + readonly reserve_pool_size: Schema.optionalKey; + readonly default_pool_size: Schema.optionalKey; + readonly max_client_conn: Schema.optionalKey; + }>>; + readonly auth: Schema.optionalKey>>; + readonly api: Schema.optionalKey; + readonly db_extra_search_path: Schema.optionalKey; + readonly max_rows: Schema.optionalKey; + readonly db_pool_acquisition_timeout: Schema.optionalKey; + readonly db_pool: Schema.optionalKey; + }>>; + readonly realtime: Schema.optionalKey; + readonly max_concurrent_users: Schema.optionalKey; + readonly max_events_per_second: Schema.optionalKey; + readonly max_bytes_per_second: Schema.optionalKey; + readonly max_channels_per_client: Schema.optionalKey; + readonly max_joins_per_second: Schema.optionalKey; + readonly max_presence_events_per_second: Schema.optionalKey; + readonly max_payload_size_in_kb: Schema.optionalKey; + readonly presence_enabled: Schema.optionalKey; + readonly suspend: Schema.optionalKey; + readonly connection_pool: Schema.optionalKey; + readonly postgres_changes_pool: Schema.optionalKey; + }>>; + readonly storage: Schema.optionalKey; + readonly features: Schema.optionalKey; + }>>; + readonly s3_protocol: Schema.optionalKey; + }>>; + readonly purge_cache: Schema.optionalKey; + readonly iceberg_catalog: Schema.optionalKey; + readonly max_namespaces: Schema.optionalKey; + readonly max_tables: Schema.optionalKey; + readonly max_catalogs: Schema.optionalKey; + }>>; + readonly vector_buckets: Schema.optionalKey; + readonly max_buckets: Schema.optionalKey; + readonly max_indexes: Schema.optionalKey; + }>>; + }>>; + readonly capabilities: Schema.optionalKey; + readonly upstream_target: Schema.optionalKey; + readonly migration_version: Schema.optionalKey; + readonly database_pool_mode: Schema.optionalKey; + }>>; +}>; +export type ProjectConfigApiAttributes = typeof ProjectConfigApiAttributesSchema.Type; diff --git a/packages/config/api-report/project-config/hosted-sections.d.ts b/packages/config/api-report/project-config/hosted-sections.d.ts new file mode 100644 index 0000000000..d169f94ae8 --- /dev/null +++ b/packages/config/api-report/project-config/hosted-sections.d.ts @@ -0,0 +1,12 @@ +/** + * The seven {@link CliConfig} (`../base.ts`) section keys a hosted + * project-config API response can speak for — the vocabulary ceiling for + * {@link ProjectConfig} (`./project-config.ts`)'s compile-time type and + * {@link ProjectConfigSchema} (`./project-schema.ts`)'s runtime derivation. + * Owned here rather than duplicated in either consumer, per this repo's + * policy of moving a shared constant to its correct owner instead of + * hand-keeping two copies in sync. + */ +export declare const HOSTED_SECTION_KEYS: readonly ["api", "auth", "db", "realtime", "storage", "workers", "experimental"]; +/** The seven keys {@link ProjectConfig}/{@link ProjectConfigSchema} can carry. */ +export type HostedSectionKey = (typeof HOSTED_SECTION_KEYS)[number]; diff --git a/packages/config/api-report/project-config/project-config.d.ts b/packages/config/api-report/project-config/project-config.d.ts new file mode 100644 index 0000000000..1a58b4fe57 --- /dev/null +++ b/packages/config/api-report/project-config/project-config.d.ts @@ -0,0 +1,374 @@ +import type { CliConfig } from "../base.ts"; +import { type DeepPartial, type EffectiveConfig } from "../sparse.ts"; +import { type HostedSectionKey } from "./hosted-sections.ts"; +/** + * A deeply-readonly JSON value — the shape of everything under + * `_apiResponse`, which holds (a clone of) a parsed Management API JSON + * payload and is recursively frozen at attach time. Typed recursively + * readonly so no narrowing path reaches a mutable view: with plain `unknown` + * values, `Array.isArray(...)` would narrow to a mutable array whose + * `.push` compiles and then throws against the frozen runtime value. (A + * programmatic `attachApiResponse` caller can technically hand over + * non-JSON structured-cloneable values — Dates, Maps; those step outside + * this type by their own choice, exactly like any other consumer-side + * assertion.) One narrowing caveat no user-space type can close: the lib's + * own `Array.isArray` guard is typed `arg is any[]`, so narrowing through it + * yields a MUTABLE array view (microsoft/TypeScript#17002) whose `.push` + * compiles and then throws against the frozen value — narrow with a + * readonly-preserving guard (`(v): v is ReadonlyArray => + * Array.isArray(v)`) instead. + */ +export type ReadonlyJsonValue = string | number | boolean | null | ReadonlyArray | { + readonly [key: string]: ReadonlyJsonValue; +}; +/** + * The hosted-project subset of {@link CliConfig}: the sections a Management + * API project-config response can speak for (`api`, `auth`, `db`, + * `realtime`, `storage`, `workers`, `experimental`) — never the local-only + * sections (`studio`, service ports, `edge_runtime`, `analytics`, + * `[remotes.*]`, …) that only make sense for a checkout on disk + * (`docs/cli-config-loading.md`'s vocabulary). + * + * Deliberately sparse (`DeepPartial`), not a fully-materialized `CliConfig` + * with schema defaults filled in: an API response never mentions a section + * or field it doesn't manage, and a `ProjectConfig` that flooded in schema + * defaults for everything it didn't report would fabricate drift against a + * local document that genuinely differs only where the API actually speaks + * (CLI-2230's design rule). Sparseness is also what makes a `ProjectConfig` + * usable as an operand of `subtractCliConfig`/`omitDefaultValues` + * (`../sparse.ts`): those helpers take an {@link EffectiveConfig}, and a + * `ProjectConfig` (minus `_apiResponse`, which those walks never see — see + * below) is structurally assignable to it, since `EffectiveConfig` is + * `DeepPartial>` and every key `ProjectConfig` + * can carry is one of `CliConfig`'s non-`remotes` keys. + * + * `_apiResponse` follows ADR 0019: present only on a value built by + * {@link fromApiProjectConfig} (never on one built by + * {@link fromConfigDocument}), holding a deep-cloned, deep-frozen copy of the + * raw, pre-mapping `data.attributes` object (frozen/cloned rather than + * aliasing the caller's object: neither this package nor a caller can + * accidentally mutate it after the fact). It is attached as a non-enumerable + * property at runtime (rule 1), so it is invisible to every *serializer* — + * `JSON.stringify`, object spread, `Object.assign`, `structuredClone` — and + * to the structural walks in `../sparse.ts`, and is therefore never + * persisted to a config file. Invisible to serializers is not invisible to + * every possible inspection, though: a debug inspector that deliberately + * shows non-enumerable own properties (e.g. Bun's `console.log`) still + * prints it. Never log an API-sourced `ProjectConfig` directly — the raw + * attributes can include an HMAC digest of a secret value. A caller that + * loses `_apiResponse` across a spread/`structuredClone`/state-store + * round-trip can re-attach it via {@link attachApiResponse}. + * + * The seven hosted-section keys above are a vocabulary-level ceiling, not a + * per-field guarantee: they name every section a project-config response + * *could* speak for, not how much of each section a given operand actually + * does. `fromConfigDocument`'s operand (a `CliConfig`/`EffectiveConfig`) can + * genuinely carry any field in any of the seven. `fromApiProjectConfig`'s + * operand speaks for far fewer — `realtime` maps zero rows today (every field + * is local dev-server tuning with no hosted counterpart, `./registry.ts`'s + * comment on `realtime`), and `workers`/`experimental` have no v2 + * project-config API counterpart at all, so an API-sourced `ProjectConfig` + * never carries those two keys regardless of what the remote project has + * configured. A comparison consumer (CLI-2156) must restrict its comparison + * to the fields both operands actually speak for, never treat one operand's + * whole-section presence/absence as drift against the other's — that + * granularity gap is not only whole-section: several record-entry and + * optional-substruct fields the registry maps *unconditionally* (every + * mailer template/notification row, `email.smtp.enabled`, every + * `db.settings.*` row, `sessions.timebox`/`inactivity_timeout`, + * `captcha.enabled`, …) appear on an API-sourced `ProjectConfig` even when a + * local document never declared that sub-section at all, since the mapping + * has no "the local document is silent here" signal to withhold on. Use + * {@link comparableProjectConfigPaths}/{@link isComparableProjectConfigPath} + * to restrict a comparison to exactly the fields `fromApiProjectConfig` can + * actually speak for, rather than hand-maintaining an equivalent field list. + * The gap runs the other direction too: `auth.oauth_server`, and + * `storage.analytics`/`storage.vector` when disabled, ARE comparable paths + * (`fromApiProjectConfig` maps them) that `fromConfigDocument` can be + * silent on entirely, since push cannot communicate that state at all — see + * ADR 0021's "unmanaged-by-push containers" family — so the same + * both-operands-speak-for restriction applies symmetrically, not only for + * the API arm's unconditional fields above. + * + * Per ADR 0021, a `ProjectConfig` value is NOT a verbatim projection of + * whichever operand produced it — both {@link fromConfigDocument} and + * {@link fromApiProjectConfig} canonicalize toward the state a `config push` + * would actually converge on (SMS-provider push precedence, disabled-sentinel + * pruning of gated siblings, duration/byte-size re-quantization, and more — + * see that ADR for the full enumeration). A `ProjectConfig` built from a + * document is therefore not a faithful rendering of what the user wrote in + * their config file; see {@link fromConfigDocument}'s own docstring. + */ +export type ProjectConfig = DeepPartial> & { + readonly _apiResponse?: { + readonly [key: string]: ReadonlyJsonValue; + }; +}; +/** + * A `{ config, document }` pair {@link fromConfigDocument} accepts as an + * alternative to a bare {@link EffectiveConfig} (human review round on PR + * #6339, thread 1): `document` is the raw, pre-decode document object + * (`LoadedCliConfig.document`, `../config-document.ts` — post-`env()`, + * remotes-merged, retained precisely so a caller can inspect key presence a + * decoded value loses to schema defaults) and unlocks raw-presence masking + * ({@link applyRawPresenceMask}) a bare `EffectiveConfig` operand cannot, + * since decode has already erased the distinction between "the file + * declared this with a default value" and "the file never mentioned this at + * all". `LoadedCliConfig` is structurally assignable to this interface + * WITHOUT a cast — its `config: CliConfig` fits `EffectiveConfig` (a + * `CliConfig` is one), its `document?: Record` matches + * exactly. Declared independently rather than importing `LoadedCliConfig` + * by name: not for pure-runtime-graph reasons (`config-document.ts` is + * already reachable from this package's pure entrypoint, and this very file + * already imports `isObject` from it), but so `fromConfigDocument`'s public + * contract doesn't couple its parameter shape to the loader's own type name + * — this type is local-checkout-side on its own terms (ADR 0020's `Cli*` + * convention), independent of which loader happens to produce a matching + * shape. + */ +export interface CliConfigWithRawPresence { + readonly config: EffectiveConfig; + readonly document?: Record; +} +/** + * Projects a {@link CliConfig} document (or any {@link EffectiveConfig} + * operand — a full `CliConfig` is one) down to its hosted-section subset. + * Copies each hosted section deeply and only when own-present on `config`, + * omitting every `x-secret` leaf ({@link copyHostedValueWithoutSecrets}) and + * canonicalizing every field a registry row's `normalizeDocument` covers + * ({@link applyDocumentNormalizations}) — parity with + * {@link fromApiProjectConfig}'s own secret omission and canonical + * duration/byte-size spellings, so the same logical hosted config compares + * equal regardless of which side produced it, and so this function never + * leaks a document's plaintext secrets onto a value that will sit next to + * an API-sourced `ProjectConfig` in a diff. The returned value is always a + * fresh copy — safe to call even when `config` is frozen (e.g. + * {@link getDefaultCliConfig}'s memo). Never attaches `_apiResponse`; that + * only happens in {@link fromApiProjectConfig}. Throws + * {@link ProjectConfigParseError} if a value at a normalized path is + * malformed in a way `normalizeDocument` cannot tolerate — in practice this + * should not happen, since every `normalizeDocument` implementation returns + * its input verbatim rather than throwing. + * + * NOT a verbatim projection of `config` (ADR 0021): beyond secret omission + * and per-field canonicalization, this function also applies + * {@link applySmsProviderPrecedence} (a document enabling several SMS + * providers converges on only the push-selected one staying `enabled`) and + * {@link applyDisabledSentinels} (a disabled section/entry drops the sibling + * fields the legacy push does not manage while it is off). The result + * predicts what the hosted config will look like AFTER pushing `config`, not + * `config`'s own declared hosted-section values — do not render it to a user + * as "your local config". + * + * The convergence prediction is exact for a genuinely sparse `config` — one + * that only carries the keys the caller means to speak for. It holds only + * "exact modulo schema defaults" for a fully-materialized decoded document + * passed BARE (the common case, since a full `CliConfig` is a valid + * operand): decode cannot recover whether the raw file actually wrote a key + * or merely inherited its schema default, a distinction the legacy push + * pipeline DOES read (e.g. it emits only the external providers the raw + * file declared, never every provider a decoded document defaults to). + * + * **This limit has a first-class remedy**: pass a {@link + * CliConfigWithRawPresence} pair instead of a bare `config` — this is the + * RECOMMENDED form whenever a `document` is available (i.e. whenever the + * config came from `loadCliConfig` rather than being constructed in-memory, + * e.g. `getDefaultCliConfig()`'s memo). With `document` present, this + * function additionally applies {@link applyRawPresenceMask}, mirroring the + * legacy push pipeline's own raw-presence gates + * (`apps/cli/src/legacy/commands/config/push/push.raw-presence.ts`) exactly, + * closing the gap for the fields those gates cover. Without `document`, this + * function's behavior is unchanged, and a caller diffing its output against + * a remote `ProjectConfig` should still first strip schema defaults with + * `omitDefaultValues` and intersect to the fields both operands actually + * speak for — see ADR 0021's "Limits" section for the verified boundary, + * which fields the presence mask covers, and the residual drift categories + * that remain deferred to CLI-2266 even with a `document` supplied. + * `@supabase/config/io`'s `loadCliConfig` supplies a `document`; + * `saveCliConfig`'s returned `LoadedCliConfig` does NOT (there is no raw + * file being re-read on a save) — passing that result here silently falls + * back to the un-remedied, bare-`config` behavior. + */ +export declare function fromConfigDocument(config: EffectiveConfig): ProjectConfig; +export declare function fromConfigDocument(loaded: CliConfigWithRawPresence): ProjectConfig; +export declare function fromConfigDocument(source: EffectiveConfig | CliConfigWithRawPresence): ProjectConfig; +/** + * DOCUMENT-arm only: at most one SMS provider can be live on the platform — + * the push switch selects the FIRST enabled provider in its fixed order and + * sends only that one (`switch (true)`, auth.sync.ts:2498-2539), so a + * document enabling several providers converges, after any push, on a hosted + * state where only the first is enabled. Later `enabled: true` flags flip to + * `false` here, and the entry sweep in {@link applyDisabledSentinels} (which + * runs next) prunes their siblings — matching what `fromApiProjectConfig` + * reports for that hosted state. The API arm never needs this: its five + * flags all derive from the single `sms_provider` discriminator. + */ +export declare const SMS_PROVIDER_PUSH_PRECEDENCE: readonly ["twilio", "twilio_verify", "messagebird", "textlocal", "vonage"]; +/** + * Fields the legacy push does not manage while their section's toggle is off + * — it writes only the disable sentinel for each of these (Data API: only + * `db_schema: ""`, api.sync.ts:130-145; network restrictions: whole flow + * skipped, db.sync.ts:148-150; SMTP: only `smtp_host: ""`, + * auth.sync.ts:2384-2397; storage Iceberg/Vector: whole feature omitted, + * storage.sync.ts:287-299; captcha provider/secret only when enabled, + * :2315-2324; hook URI/secrets only when enabled, :2551-2565; SMS provider + * credentials only for the selected provider, :2498-2539; whole Auth/Storage + * sections gated on their own `enabled`, :1224-1226 / storage.sync.ts's + * subset gating) — so projecting the (usually schema-filled or + * platform-retained) siblings would fabricate drift between representations + * of the same disabled state. Applied to BOTH normalizers' outputs: the + * mapped shape is identical on the document and API arms, so one pass keeps + * the two symmetric by construction. + */ +export declare const DISABLED_SENTINEL_PRUNES: ReadonlyArray<{ + readonly containerPath: ReadonlyArray; + /** Keys to drop when `enabled === false`; absent = drop every key but `enabled`. */ + readonly dropKeys?: ReadonlyArray; +}>; +/** Record-shaped containers whose per-entry `enabled: false` keeps only the flag. */ +export declare const DISABLED_SENTINEL_ENTRY_SWEEPS: ReadonlyArray<{ + readonly containerPath: ReadonlyArray; + /** Restrict the sweep to these entry keys (a container mixing records and scalars). */ + readonly entryKeys?: ReadonlyArray; +}>; +/** + * Maps a Management API v2 project-config response into a {@link + * ProjectConfig}, per ADR 0019: (1) unwraps whichever of the three envelope + * shapes `input` is, (2) decodes the unwrapped attributes leniently — an + * API-ahead-of-package field never fails this decode, only a genuinely + * malformed mapped field does — (3) walks the mapping registry + * (`./registry.ts`) to populate the typed sections, and (4) attaches a + * deep-cloned, deep-frozen copy of the raw, unwrapped attributes as a + * non-enumerable `_apiResponse` ({@link attachFrozenApiResponse}) so + * `unmappedApiFields` and forward-compatible consumers can still reach + * whatever the registry didn't map. Throws {@link ProjectConfigParseError} + * when `input` isn't an object, when the envelope is malformed, or when + * decoding/mapping a value fails. + * + * Also NOT a verbatim projection of the response (ADR 0021): a `null` on a + * gating boolean canonicalizes to `enabled: false` rather than being skipped + * (`gatedBoolRow`/the SMTP host anchor, `./registry-auth.ts`), the + * same {@link applyDisabledSentinels} pruning `fromConfigDocument` applies + * runs here too, and an out-of-domain value on a mapped field (e.g. a + * negative `storage.file_size_limit`) throws rather than canonicalizing to a + * wrong value. This makes an API-sourced and a document-sourced + * `ProjectConfig` comparable for the same hosted state, at the cost of this + * function's output also not being a byte-for-byte echo of what the API + * reported. + */ +export declare function fromApiProjectConfig(input: unknown): ProjectConfig; +/** + * Re-attaches `_apiResponse` to `config` after a caller's own spread, + * `structuredClone`, or state-store round-trip already dropped it — ADR + * 0019 rule 1 promises the attach step exists precisely because those + * operations are non-enumerable-property-blind by design, and a consumer + * that legitimately needs to carry the raw attributes across such a + * boundary (a state store, a serialized cache entry it then rehydrates) must + * be able to restore them explicitly rather than losing `unmappedApiFields` + * access permanently. Returns a NEW object: a shallow copy of `config`'s own + * enumerable properties, plus `rawAttributes` attached via the same + * clone-and-freeze path {@link fromApiProjectConfig} uses internally + * ({@link attachFrozenApiResponse}) — never mutates `config` in place. Throws + * {@link ProjectConfigParseError} when `config` is not an object, matching + * {@link toProjectConfig}'s own strictness — a non-object `config` used to + * silently substitute `{}`, discarding whatever the caller actually passed + * instead of surfacing the misuse. + */ +export declare function attachApiResponse(config: ProjectConfig, rawAttributes: Record): ProjectConfig; +/** + * Either operand `toProjectConfig` accepts: a local {@link EffectiveConfig} + * — or a {@link CliConfigWithRawPresence} pair, the RECOMMENDED form + * whenever a `document` is available (see {@link fromConfigDocument}'s own + * docstring) — to project down to the hosted subset, or a raw, + * not-yet-decoded Management API v2 project-config response (in any of the + * three envelope shapes {@link fromApiProjectConfig} accepts) to map. + */ +export type ToProjectConfigSource = { + readonly cliConfig: EffectiveConfig | CliConfigWithRawPresence; +} | { + readonly apiResponse: unknown; +}; +/** + * Thin dispatcher over the two normalizers above: routes to + * {@link fromApiProjectConfig} when `source` carries an own `apiResponse` + * property, otherwise to {@link fromConfigDocument} when it carries an own + * `cliConfig` property. A full `CliConfig` fits the `cliConfig` arm + * directly, since `CliConfig` is assignable to {@link EffectiveConfig}. + * Throws {@link ProjectConfigParseError} when `source` carries neither own + * key or both — `{}` and `{ cliConfig: x, apiResponse: y }` are equally + * meaningless dispatch requests, and failing loudly here beats a raw + * `TypeError` from reaching into a property that isn't there. + */ +export declare function toProjectConfig(source: ToProjectConfigSource): ProjectConfig; +/** + * The subtree of `config._apiResponse` that {@link projectConfigMappingRows} + * does not map — `{}` when `config` carries no `_apiResponse` at all + * (file-sourced config, or a `ProjectConfig` that was never built from an API + * response), which per ADR 0019 rule 1 does NOT mean "fully mapped". + * Registry-derived, not a second hand-maintained field list (ADR 0019 rule + * 5): a path is "mapped" when some row's `apiPath` or `alsoConsumes` names it + * exactly, including every `isSecret` row (deliberately omitted, but known) + * and every `unmappedSecretApiPaths` entry (deliberately omitted despite + * having no row at all). Empty objects are pruned from the result, so a + * subtree that is entirely mapped never shows up as `{}` noise. + * + * Reports at REGISTRY `apiPath` granularity, not full recursive fidelity: a + * key nested INSIDE a consumed subtree — including inside an element of a + * consumed array, e.g. an unexpected `comment` field on a + * `database.network_restrictions.allowed_cidrs` entry — is not itemized here + * either, since the whole subtree at that `apiPath` is already "known" to + * this registry version (`consumedApiPathKeys`'s own docstring). This is + * never lossy for the CALLER, only for this report: `_apiResponse` still + * carries every such key verbatim, so a consumer that needs full recursive + * fidelity reads it directly instead of relying on this helper. + * + * The result can include the HMAC digest the API reports for a secret-typed + * key neither a row nor `unmappedSecretApiPaths` knows about yet — a future + * GoTrue secret, say, added on the platform side before this package's + * `isSecret` rows catch up. Callers must not render this result blindly — an + * HMAC digest is not a value a user should see echoed back at them. Throws + * {@link ProjectConfigParseError} if `_apiResponse` is nested more than 64 + * levels deep, or if `config` is not a plain object (`reason: + * "caller_misuse"`). + */ +export declare function unmappedApiFields(config: ProjectConfig): { + readonly [key: string]: ReadonlyJsonValue; +}; +/** + * The deduped `configPath`s of every non-`isSecret` row in + * {@link projectConfigMappingRows}, in registry order — the fields + * `fromApiProjectConfig` can actually speak for. Exists so a diff consumer + * (CLI-2156/Studio) never hand-maintains an equivalent field list: as rows + * are added, removed, or renamed, this set moves with them automatically. + * Excludes secret rows (an API-sourced value for one is never populated, so + * it can never meaningfully participate in a comparison) and every field + * with no row at all (`realtime` in full, `workers`/`experimental`, and + * every "Deliberately unmapped" field the sibling registries document). + * + * This ONLY remedies the whole-SECTION-granularity gap (e.g. `realtime` in + * full never showing up as phantom drift just because it has zero rows). It + * does NOT remedy the finer, per-path granularity gap this file's own + * {@link ProjectConfig} docstring describes: `["auth", "email", "smtp", + * "enabled"]` IS a member of this list (`isComparableProjectConfigPath` + * returns `true` for it) and yet still fabricates drift against a document + * operand that never declared `[auth.email.smtp]` at all, because + * `subtractCliConfig`'s baseline has no `smtp` key to compare against and + * therefore keeps the API side's value verbatim (pinned by + * `project-config.unit.test.ts`'s "does NOT rescue a diff against a document + * operand that never declared the sub-section at all" test). A caller doing + * that comparison must additionally intersect with what the document-side + * operand actually declared — or accept that every field a row maps + * unconditionally will read as a remote-only statement whenever the document + * side is silent on it, never as neutral "no opinion". + */ +export declare const comparableProjectConfigPaths: ReadonlyArray>; +/** + * Whether `path` is a member of {@link comparableProjectConfigPaths} — or a + * DESCENDANT of one: a row that maps a container (e.g. `sms.test_otp`'s + * record) yields diff leaves like `["auth","sms","test_otp",""]` from + * a leaf-path traversal, and those entries are exactly as comparable as the + * mapped container itself. A bare PREFIX of a mapped path (e.g. + * `["auth","sms"]`) is still not comparable — it names a section, not a + * mapped value. + */ +export declare function isComparableProjectConfigPath(path: ReadonlyArray): boolean; diff --git a/packages/config/api-report/project-config/project-schema.d.ts b/packages/config/api-report/project-config/project-schema.d.ts new file mode 100644 index 0000000000..85e3e5d761 --- /dev/null +++ b/packages/config/api-report/project-config/project-schema.d.ts @@ -0,0 +1,34 @@ +import type { StandardSchemaV1 } from "@standard-schema/spec"; +import { Schema } from "effect"; +import type { ProjectConfig } from "./project-config.ts"; +/** + * The runtime shape {@link projectConfigAst} validates: {@link ProjectConfig} + * minus `_apiResponse`, which — being non-enumerable and never serialized — + * has no runtime representation for a schema to check. `Schema.make` performs + * no structural verification against this annotation (the same trust-the- + * caller contract as effect's own `Json: Codec = make(SchemaAST.Json)` + * precedent); the type-level pin in `./project-schema.unit.test.ts` cross- + * checks this exact type expression against `ProjectConfig` independently, so + * a future edit to either side that silently drifts fails to compile there. + */ +type ProjectConfigSchemaType = Omit; +/** + * Runtime validation for {@link ProjectConfig} — both an Effect-native schema + * (decode/encode, `.ast`, …) and a spec-compliant Standard Schema + * (`~standard`), since {@link Schema.toStandardSchemaV1} augments and returns + * the SAME object rather than wrapping it in a second value. + * + * Annotated explicitly (rather than left inferred) because the inferred type + * names `StandardSchemaV1` from `@standard-schema/spec` — a package reachable + * only transitively through `effect` under pnpm's strict `node_modules` + * isolation — which tsc's declaration emit refuses to synthesize into + * `project-schema.d.ts` as non-portable. Explicitly importing the type here + * pins `@standard-schema/spec` as a direct dependency instead. + */ +export declare const ProjectConfigSchema: StandardSchemaV1 & Schema.Codec; +/** JSON Schema (draft 2020-12) rendering of {@link ProjectConfigSchema}, mirroring `../base.ts`'s `toCliConfigJsonSchema`. */ +export declare function toProjectConfigJsonSchema(): { + $schema: string; + $defs?: import("effect/JsonSchema").Definitions | undefined; +}; +export {}; diff --git a/packages/config/api-report/project-config/registry-auth.d.ts b/packages/config/api-report/project-config/registry-auth.d.ts new file mode 100644 index 0000000000..6b46edc970 --- /dev/null +++ b/packages/config/api-report/project-config/registry-auth.d.ts @@ -0,0 +1,43 @@ +import { type ProjectConfigMappingRow } from "./registry-row.ts"; +export declare const AUTH_HOOK_NAMES: readonly ["mfa_verification_attempt", "password_verification_attempt", "custom_access_token", "send_sms", "send_email", "before_user_created"]; +export declare const authMappingRows: ReadonlyArray; +/** + * API-side GoTrue keys shaped like a secret (suffix `_secret`, `_secrets`, + * `_auth_token`, `_api_secret`, `_access_key`, or `_api_key`) that have no + * registry row at all, verified exhaustively against the generated + * Management API v1 auth-config contract + * (`packages/api/src/generated/contracts.ts`'s `V1GetAuthServiceConfigOutput` + * — the authority for this registry's key set, not the legacy hand-mined + * `auth.sync.ts` interface, which is missing `external_slack` and + * `nimbus_oauth` entirely) (CLI-2230's `unmappedApiFields` secret-leak + * finding). Every OTHER secret-shaped GoTrue key already has an `isSecret` + * row above and is therefore already excluded from `unmappedApiFields` on + * its own merit; this list exists only for the ones that don't, so an HMAC + * digest can't leak into that report just because this registry hasn't grown + * a row for the field yet. `walkUnmapped` (`./project-config.ts`) treats + * every path here as consumed, same as a row's `apiPath`/`alsoConsumes`. + * + * `sms_vonage_api_key` is deliberately excluded despite the `_api_key` + * suffix: it is NOT `x-secret` on the config side (`../auth/sms.ts:286-292` + * has no `secret()` wrapper on it — `smsCredentialRows`'s comment) and + * already has an ordinary `stringRow`. + * + * Three orphans found, none with a config-schema counterpart at all: + * - `external_figma_secret`: `figma` is a GoTrue provider with no + * config-schema counterpart at all (`externalProviderRows`'s comment + * above), so it never gets a row of its own, secret or otherwise. + * - `external_slack_secret`: distinct from the mapped `slack_oidc` provider + * (`EXTERNAL_PROVIDERS`) — plain `slack` has no config-schema counterpart + * either. + * - `hook_after_user_created_secrets`: distinct from the mapped + * `before_user_created` hook (`AUTH_HOOK_NAMES`) — there is no + * `hook.after_user_created` config-schema section to target. + * - `nimbus_oauth_client_secret`: there is no `nimbus`-named external + * provider in the config schema at all. + * + * Guarded against regrowing a fourth orphan by + * `apps/cli/src/shared/config/project-config-auth-contract.unit.test.ts`, + * which walks the same generated contract's full key set, not just this + * hand-maintained list. + */ +export declare const unmappedSecretApiPaths: ReadonlyArray>; diff --git a/packages/config/api-report/project-config/registry-row.d.ts b/packages/config/api-report/project-config/registry-row.d.ts new file mode 100644 index 0000000000..d31e498da3 --- /dev/null +++ b/packages/config/api-report/project-config/registry-row.d.ts @@ -0,0 +1,146 @@ +/** + * Registry-driven mapping between the Management API v2 project-config + * resource (`data.attributes`) and the hosted subset of `CliConfig` — one + * table of rows so the pull-direction normalizer (`fromApiProjectConfig`) and + * the future push-direction `*ToUpdateBody` mappers derive from a single + * source of truth (CLI-2230). Rows are data, not behavior: the assembly + * engine lives in `project-config.ts`, and `unmappedApiFields` derives its + * mapped-path set from these same rows. + * + * Null convention: the legacy push-direction apply (`config-sync/*.sync.ts`) + * merges remote values into a local document, so it maps API `null` to a + * zero value (`valOrDefault`). This registry produces a *standalone sparse* + * config instead, where "no value" must stay absent: the engine skips a row + * whose API value is `undefined` (key not reported) — unless the row declares + * `alsoConsumes` and a consumed sibling IS present, in which case the + * transform runs with `undefined` so it can still validate the sibling — and + * skips `null` unless the row declares a `transform`; a transform receives + * `null` and decides (e.g. `smtp_host: null` still means "SMTP disabled"). + */ +export interface ProjectConfigMappingRow { + /** Path segments into the hosted subset of `CliConfig`, e.g. `["api", "max_rows"]`. */ + readonly configPath: ReadonlyArray; + /** + * Path segments under v2 `data.attributes`, e.g. `["api", "db_schema"]` or + * `["auth", "site_url"]`. Several rows may share one `apiPath` when a + * single API field feeds multiple config fields (e.g. `api.db_schema` + * drives both `api.schemas` and the derived `api.enabled`). + */ + readonly apiPath: ReadonlyArray; + /** + * Maps the API-reported value to the config-side value; identity when + * absent. Receives the full decoded attributes object as a second argument + * for the rare row that combines sibling fields (declare those siblings in + * {@link alsoConsumes}). Returning `undefined` omits the field from the + * mapped output (e.g. an API enum member the config schema cannot + * represent). Narrowing failures throw `ProjectConfigParseError` via the + * `expect*` helpers. + */ + readonly transform?: (value: unknown, attributes: Record) => unknown; + /** + * Additional `data.attributes` paths this row's `transform` reads beyond + * `apiPath` (e.g. Apple/Google `external_*_additional_client_ids`, folded + * into `client_id`). Listed so `unmappedApiFields` counts them as mapped. + */ + readonly alsoConsumes?: ReadonlyArray>; + /** + * Canonicalizes a DOCUMENT-sourced value at `configPath` so a value pulled + * from the API and the same logical value spelled locally converge on one + * representation (CLI-2230's duration/byte-size finding) — e.g. a document + * duration of `"24h"` and an API-derived `"24h0m0s"` denote the same + * duration but compare unequal textually unless one side is normalized. + * Applied by `fromConfigDocument` only, at `configPath`, after the + * secret-omitting copy; never applied by `fromApiProjectConfig` (its output + * is already canonical). Must return the canonical value, the input + * verbatim when it cannot be parsed (a document value has already passed + * schema validation, so this must never throw), or `undefined` to REMOVE + * the field — unmanaged absence, for a value the push wrapper would omit + * entirely (e.g. an empty `test_otp` map); the engine prunes containers + * the removal empties. + */ + readonly normalizeDocument?: (value: unknown) => unknown; + /** + * Push-direction inverse (config value → API body value). Unused by + * `toProjectConfig` — carried so a future push mapper can derive from this + * registry instead of a second hand-maintained table. Absence does NOT mean + * identity: several rows have no faithful config→API inverse yet (every + * duration row, since `"1m0s"` must push as `60`; `BytesSize` strings; + * `email.smtp.port`'s number→string; `sms.test_otp`'s record→env string; + * the SMS provider selection rows). Absence means "not derived for this row + * yet" — zero rows currently define one; a push mapper must treat a missing + * `inverse` as unsupported for that row, never fall back to identity. Push + * derivation lands with the push-mapper work (CLI-2230 follow-up). + */ + readonly inverse?: (value: unknown) => unknown; + /** + * `x-secret` field: the API reports an HMAC digest of the value, never the + * plaintext, so the mapping omits the value entirely and pull flows must + * source it from the local document (ADR 0019, rule 5). The path still + * counts as mapped for `unmappedApiFields`. + */ + readonly isSecret?: boolean; + /** + * Unit/semantics note, e.g. `"csv → string[]"` or `"seconds → duration + * string"`. Documentation-only — never read at runtime. + */ + readonly unit?: string; +} +/** + * Narrowing helpers for `transform` implementations. The non-auth attribute + * sections are schema-typed before rows run, so these mostly guard the `auth` + * record (typed `Record` by the API) and document each row's + * expectation at its use site. + */ +export declare function expectString(value: unknown, apiPath: ReadonlyArray): string; +/** + * Narrows to a finite integer. The generated API contract types these fields + * `isInt`; the lenient mirror deliberately drops that check so API-ahead skew + * never fails the decode (ADR 0019 rule 2), so the rows for integer-typed + * config fields re-assert it here — a fractional value on an integer field is + * a malformed platform response, not tolerable skew. Only the session-hour + * durations stay on {@link expectNumber}: the contract types them as plain + * numbers and fractional hours are meaningful (the renderer rounds); every + * other numeric field — including the `*_max_frequency` seconds — is + * `isInt()` in the contract and narrows here. + */ +export declare function expectInteger(value: unknown, apiPath: ReadonlyArray): number; +/** + * Narrows to a finite number within `[min, max]` — for fields whose + * downstream formatter is only defined on a bounded range. The session-hour + * durations are the motivating case: the generated contract only requires + * them finite, but a huge-but-finite hours value overflows the nanosecond + * conversion into `"InfinityhNaNmNaNs"`, and a merely-large one stringifies + * in exponent notation (`"1e+22h0m0s"`) that no duration parser reads. + */ +export declare function expectNumberBetween(value: unknown, apiPath: ReadonlyArray, min: number, max: number): number; +export declare function expectBoolean(value: unknown, apiPath: ReadonlyArray): boolean; +/** + * Clamps a signed API integer to the unsigned domain the config schema + * expects. Replicates the legacy shell's `intToUint` + * (`apps/cli/src/legacy/shared/legacy-size-units.ts`), applied by the sync + * mappers to every uint-typed field pulled from the API. + */ +export declare function clampToUint(value: number): number; +/** + * Splits an API comma-separated list field into the string array the config + * schema holds. Replicates the legacy shell's `legacyStrToArr` + per-element + * trim as applied in `config-sync/api.sync.ts:92-93` (`db_schema`, + * `db_extra_search_path`). The `auth.sync.ts:1265` `uri_allow_list` site uses + * `legacyStrToArr` without the trim; trimming there too is a deliberate, + * benign normalization — push-direction bodies are built with `join(",")`, so + * round-tripped data never carries the spaces the trim would remove. + */ +export declare function splitCommaSeparated(value: string): ReadonlyArray; +/** + * DOCUMENT-side canonicalization for the three CSV-backed array rows + * (`api.schemas`, `api.extra_search_path`, `auth.additional_redirect_urls`): + * the push mapper joins the array with `","` (auth.sync.ts:2294, + * api.sync.ts:138,140) and the pull direction re-splits with + * {@link splitCommaSeparated}, so an element containing a literal comma (or + * padded with whitespace) round-trips into a DIFFERENT array — replaying + * join-then-split makes the document projection converge on the value that + * actually exists hosted after a push, same as the whole-second duration + * flooring. Non-array/non-string-element values stay verbatim (a document + * value has already passed schema validation; never throw here). + */ +export declare function canonicalizeCommaJoinedArray(value: unknown): unknown; diff --git a/packages/config/api-report/project-config/registry.d.ts b/packages/config/api-report/project-config/registry.d.ts new file mode 100644 index 0000000000..0421bf47be --- /dev/null +++ b/packages/config/api-report/project-config/registry.d.ts @@ -0,0 +1,7 @@ +import { type ProjectConfigMappingRow } from "./registry-row.ts"; +/** + * The full API↔`CliConfig` mapping table: this file's non-auth rows plus + * `./registry-auth.ts`'s auth rows. `fromApiProjectConfig`/ + * `unmappedApiFields` (`./project-config.ts`) are the only consumers. + */ +export declare const projectConfigMappingRows: ReadonlyArray; diff --git a/packages/config/api-report/project.d.ts b/packages/config/api-report/project.d.ts new file mode 100644 index 0000000000..389da8c67f --- /dev/null +++ b/packages/config/api-report/project.d.ts @@ -0,0 +1,71 @@ +import { Effect, FileSystem } from "effect"; +import { CliProjectEnvParseError } from "./errors.ts"; +import { type ResolvedCliConfigValue, type ResolveCliConfigOptions } from "./lib/resolve.ts"; +import { type CliProjectPaths } from "./paths.ts"; +export interface CliProjectEnvironment { + readonly paths: CliProjectPaths; + readonly values: Readonly>; + readonly loadedPaths: ReadonlyArray; + readonly sources: Readonly>; +} +/** Parse one explicit dotenv file without applying ambient or project-local precedence. */ +export declare const loadDotEnvFile: (path: string) => Effect.Effect, CliProjectEnvParseError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem>; +export interface LoadCliProjectEnvironmentOptions { + readonly cwd: string; + readonly baseEnv?: Readonly>; + /** See {@link FindCliProjectPathsOptions.search}. */ + readonly search?: boolean; + /** + * Skip reading/parsing `paths.envLocalPath` (`supabase/.env.local`) + * entirely. Mirrors Go's `loadDefaultEnv` (`apps/cli-go/pkg/config/ + * config.go:1243-1250`), which omits `.env.local` from its candidate + * filename list whenever `SUPABASE_ENV=test` — so a malformed or + * intentionally non-test `.env.local` is invisible to Go in that mode and + * must not fail config loading here either. Defaults to `false` so + * existing callers that don't have a `SUPABASE_ENV` gate of their own + * (`next/`, `secrets set`) are unaffected. + */ + readonly skipEnvLocal?: boolean; +} +/** + * Not covered by semver — exported from `@supabase/config/internal` only. See + * that module's header for why. + */ +export interface InternalResolveCliConfigOptions extends ResolveCliConfigOptions { + /** + * Opt into Go/viper-parity `env()` matching (case-agnostic + * `^env\((.*)\)$`). Defaults to `false`, which uses the pre-PR-#5765 strict + * SCREAMING_SNAKE_CASE matcher (`ENV_CAPTURE_REGEX_STRICT`). Only the + * Go-parity legacy shell sets this to `true`. + */ + readonly goViperCompat?: boolean; +} +export declare const loadCliProjectEnvironment: (options: LoadCliProjectEnvironmentOptions) => Effect.Effect<{ + paths: { + projectRoot: string; + supabaseDir: string; + configPath: string; + envPath: string; + envLocalPath: string; + }; + values: Record; + loadedPaths: string[]; + sources: Record; +} | null, CliProjectEnvParseError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | import("effect/Path").Path>; +/** + * Effect-typed counterpart of `./lib/resolve.ts`'s plain sync + * `resolveCliConfigValue`, additionally accepting the internal-only + * `goViperCompat` option (see {@link InternalResolveCliConfigOptions}). + * `../effect.ts` re-exports this explicitly, which wins over the sync + * version's star re-export through `./index.ts` (see that module's doc + * comment on the deliberate shadowing) — `@supabase/config/internal` + * re-exports this same function typed to show `goViperCompat`. + * + * `cliProjectEnv` only needs `.values` (`Pick`) — + * a caller that already has a project's env values but not the full + * `CliProjectEnvironment` shape (e.g. `paths`/`loadedPaths`/`sources`) can pass + * `{ values }` directly instead of threading through the whole loaded object. + */ +export declare function resolveCliConfigValue(value: T, cliProjectEnv: Pick, configPath: string, options?: InternalResolveCliConfigOptions): Effect.Effect>; +/** See {@link resolveCliConfigValue}'s doc comment for why `cliProjectEnv` only needs `.values`. */ +export declare function resolveCliConfigSubtree(value: T, cliProjectEnv: Pick, pathPrefix: string, options?: InternalResolveCliConfigOptions): Effect.Effect>; diff --git a/packages/config/api-report/promise-facade.d.ts b/packages/config/api-report/promise-facade.d.ts new file mode 100644 index 0000000000..8bee44f4ba --- /dev/null +++ b/packages/config/api-report/promise-facade.d.ts @@ -0,0 +1,33 @@ +import type { FileSystem, Path } from "effect"; +import { Layer } from "effect"; +import type { LoadedCliConfig, LoadCliConfigOptions, SaveCliConfigOptions } from "./config-document.ts"; +import type { FunctionsManifest } from "./functions-manifest-model.ts"; +import type { CliProjectPaths } from "./paths.ts"; +import type { LoadCliProjectEnvironmentOptions, CliProjectEnvironment } from "./project.ts"; +/** + * Names deliberately mirror `@supabase/config/effect` one-to-one — the + * subpath itself (`/io` vs `/effect`) conveys Promise-vs-Effect, not the + * member names. + * + * A rejection from `loadCliConfig`, `loadCliConfigFile`, or `saveCliConfig` + * can carry any of `CliConfigStoreError`'s members (`cli-config.service.ts`): + * this package's own `CliConfigParseError` / `DuplicateRemoteProjectIdError` / + * `InvalidRemoteProjectIdError` / `CliProjectEnvParseError`, or `PlatformError` + * for a host/OS failure — distinguish via `instanceof`. + */ +export interface CliConfigIo { + readonly loadCliConfig: (cwd: string, options?: LoadCliConfigOptions) => Promise; + readonly findCliProjectRoot: (cwd: string) => Promise; + readonly findCliProjectPaths: (cwd: string) => Promise; + readonly loadCliConfigFile: (path: string) => Promise; + readonly loadCliProjectEnvironment: (options: LoadCliProjectEnvironmentOptions) => Promise; + readonly saveCliConfig: (options: SaveCliConfigOptions) => Promise; + readonly inferFunctionsManifest: (cwd: string) => Promise; +} +/** + * Builds the Promise-based `@supabase/config/io` facade over a given platform + * layer. `Layer`'s `ROut` is declared contravariant (`in ROut`), so a + * platform layer providing a superset of `FileSystem | Path` (e.g. + * `BunServices.layer` / `NodeServices.layer`) is assignable here. + */ +export declare function makeCliConfigIo(platformLayer: Layer.Layer): CliConfigIo; diff --git a/packages/config/api-report/realtime.d.ts b/packages/config/api-report/realtime.d.ts new file mode 100644 index 0000000000..acdcd82b30 --- /dev/null +++ b/packages/config/api-report/realtime.d.ts @@ -0,0 +1,6 @@ +import { Schema } from "effect"; +export declare const realtime: Schema.withDecodingDefaultKey; + readonly ip_version: Schema.withDecodingDefaultKey, never>; + readonly max_header_length: Schema.withDecodingDefaultKey; +}>, never>; diff --git a/packages/config/api-report/schema-metadata.d.ts b/packages/config/api-report/schema-metadata.d.ts new file mode 100644 index 0000000000..117d2bf958 --- /dev/null +++ b/packages/config/api-report/schema-metadata.d.ts @@ -0,0 +1 @@ +export declare const CLI_CONFIG_SCHEMA_URL = "https://supabase.com/docs/cli/config.schema.json"; diff --git a/packages/config/api-report/sparse.d.ts b/packages/config/api-report/sparse.d.ts new file mode 100644 index 0000000000..22cddd26db --- /dev/null +++ b/packages/config/api-report/sparse.d.ts @@ -0,0 +1,137 @@ +import { type CliConfig } from "./base.ts"; +/** + * Sparse config subtraction — see `docs/adr/0018-sparse-config-subtraction.md`. + * + * A sparse config is a partial overlay containing only the values that differ + * from some baseline. In the primary case — subtracting the default config + * ({@link omitDefaultValues}) — the result is itself a valid config document: + * re-decoding refills exactly what was removed, so it denotes the same + * effective config under the current schema's defaults. Subtracting any other + * baseline (e.g. a remote block against the merged base config) yields an + * overlay meaningful only relative to that baseline. Arrays are compared + * wholesale (order-sensitive) and never subtracted element-wise, so a sparse + * value is always either an entire array or an object subtree of kept leaves — + * hence arrays survive `DeepPartial` unchanged below. + */ +export type DeepPartial = T extends ReadonlyArray ? T : T extends object ? { + readonly [K in keyof T]?: DeepPartial; +} : T; +export type SparseCliConfig = DeepPartial; +/** + * The family-neutral operand shape of the comparison core: a deeply partial + * root scope of {@link CliConfig}, without the nested `remotes` record. Every + * key an operand carries must hold its fully-resolved *effective* value; an + * absent key means the operand doesn't speak for that field — never that the + * field is at its default. Both config families fit: on the local side a full + * {@link CliConfig} document or a branch's merged effective config, and on + * the hosted side the sparse `ProjectConfig` subset produced by + * `toProjectConfig` — a Management API response never mentions local-only + * sections, so its operands are inherently partial. Keeping `remotes` out of + * the contract means neither operand has to fabricate one to type-check. + * (Replaces the former fully-materialized `BaseCliConfig` operand; see ADR + * 0018's addendum for the CLI-2230 ruling.) + */ +export type EffectiveConfig = DeepPartial>; +/** + * The default config: a {@link CliConfig} in which every value carries its + * schema-declared default. Derived by decoding `{}` through + * {@link CliConfigSchema} — the schema's `default` annotations and decoding + * defaults are the single source of truth, so there is no hand-maintained + * defaults table to drift. Fields declared `optionalKey` without a default + * (e.g. `project_id`, `api.external_url`) are absent. + * + * Memoized (and the memo shared with callers) rather than computed at module + * load, so importing the package doesn't pay for a full schema decode. The + * memo is deeply frozen before it is shared: it doubles as the module-wide + * subtraction baseline, so a caller mutation would silently corrupt every + * later {@link omitDefaultValues} result. + */ +export declare function getDefaultCliConfig(): CliConfig; +/** + * Recursively freezes `value` and returns it. Exported (not re-exported from + * `./index.ts` — this stays an internal cross-module helper, per CLI-2230's + * `_apiResponse` clone-and-freeze finding) so `./project-config/ + * project-config.ts` can freeze the cloned raw attributes it attaches, using + * the same freezing behavior {@link getDefaultCliConfig}'s memo relies on. + * + * Guarded against revisiting an already-frozen (or otherwise already-seen) + * object with a `WeakSet`, as defense in depth: {@link getDefaultCliConfig}'s + * memo is a decoded schema default, genuinely acyclic by construction, but + * `./project-config/project-config.ts`'s caller is a cloned Management API + * response — untrusted input — and that caller now bounds depth and cycles + * itself before ever calling this (`assertRawAttributesDepthWithinBound`). + * This guard exists so `deepFreeze` stays safe to call directly against + * arbitrary input even if that upstream bound is ever bypassed or forgotten, + * not because this function's own callers currently need it. + */ +export declare function deepFreeze(value: T): T; +/** + * Defines `key` as an own data property. Record keys come from user config + * files, and both smol-toml and `JSON.parse` produce an own `__proto__` key + * (a valid function name or remote label) that a plain `target[key] = value` + * assignment would feed to the legacy prototype setter, silently dropping the + * entry — or, for object values, swapping the target's prototype. + */ +export declare function setOwnProperty(target: Record, key: string, value: unknown): void; +/** + * The untyped subtraction walk: returns `value − baseline`, or `undefined` + * when nothing survives. Values strictly deep-equal (order-sensitive) to the + * baseline's are removed; objects recurse per key and are dropped once empty; + * arrays are removed wholesale on equality, never subtracted element-wise. A + * key with no counterpart in the baseline is kept verbatim — which is exactly + * how `remotes` and other record entries pass through untouched when the + * baseline is the default config. Symmetrically, a baseline-only key is + * ignored by design: subtraction reports what `value` declares, and in + * overlay semantics absence means *inherit*, so a missing key is not a + * removal. + * + * Shared with `io.ts`, which subtracts *encoded* documents before writing + * minimal config files; the typed entry points below operate on decoded + * {@link CliConfig} values, the only shape where "equals the default" is + * well-defined. + */ +export declare function subtractValue(value: unknown, baseline: unknown): unknown; +/** + * Returns the sparse config `config − baseline`. Directional: a value equal to + * the baseline's is removed even when it differs from the schema default, and + * a value differing from the baseline's is kept even when it equals the schema + * default. + * + * Operands must be *effective* wherever they speak: every key present must + * carry its fully-resolved value (a decode of a complete document, or of a + * raw-merged one — or the hosted values a Management API response reports), + * while an absent key is simply outside the comparison, per the absence rules + * above. A standalone-decoded `[remotes.*]` block is NOT a valid operand: + * decoding a sparse fragment materializes global defaults in every section it + * omitted, where the block meant to inherit from the base config, so the + * overlay would pin the branch to global defaults wherever the base overrides + * a field the block omits. To sparsify a branch's config (a `[remotes.*]` + * block declares overrides for a specific persistent Supabase branch, bound + * to it by `project_id`), subtract its merged effective config — the raw + * remote subtree merged over the raw base document *before* decoding, exactly + * as `io.ts`'s `mergeRemoteSubtree` does so remote schema defaults never leak + * in — against the base effective config, never the default config; see ADR + * 0018 for why the default-config baseline silently changes what the branch + * resolves to. + */ +export declare function subtractCliConfig(config: EffectiveConfig, baseline: EffectiveConfig): SparseCliConfig; +/** + * Returns the sparse config `config − default config`: only the values that + * differ from their schema defaults, per {@link subtractCliConfig}'s + * semantics. The result is itself a valid config document — re-decoding + * refills the removed defaults, yielding the same effective config. `remotes` + * blocks (per-persistent-branch overrides) pass through untouched (the + * default config has none), and undefaulted `optionalKey` fields always + * survive when present. + * + * The result is sparse at the root scope only: record-keyed entries + * (`functions.*`, `remotes.*`) survive whole, with every per-entry decoding + * default materialized — decoding fills them in, and the default config's + * empty records offer no per-entry baseline to subtract. This cancels out in + * a diff (both sides carry the same materialized defaults), but a consumer + * rendering the result directly must strip entry-level defaults itself. For a + * remote block that is necessarily the consumer's job — its correct baseline + * is the merged base config (ADR 0018); for function entries, `io.ts`'s + * `stripFunctionRecordDefaults` is the encoded-path precedent. + */ +export declare function omitDefaultValues(config: EffectiveConfig): SparseCliConfig; diff --git a/packages/config/api-report/storage.d.ts b/packages/config/api-report/storage.d.ts new file mode 100644 index 0000000000..a200d923be --- /dev/null +++ b/packages/config/api-report/storage.d.ts @@ -0,0 +1,30 @@ +import { Schema } from "effect"; +export declare const storage: Schema.withDecodingDefaultKey; + readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; + readonly image_transformation: Schema.optionalKey; + }>, never>>; + readonly buckets: Schema.optionalKey; + readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; + readonly allowed_mime_types: Schema.withDecodingDefaultKey, never>; + readonly objects_path: Schema.withDecodingDefaultKey; + }>, never>>>; + readonly s3_protocol: Schema.withDecodingDefaultKey; + }>, never>; + readonly analytics: Schema.withDecodingDefaultKey; + readonly max_namespaces: Schema.withDecodingDefaultKey; + readonly max_tables: Schema.withDecodingDefaultKey; + readonly max_catalogs: Schema.withDecodingDefaultKey; + readonly buckets: Schema.withDecodingDefault, never>>, never>; + }>, never>; + readonly vector: Schema.withDecodingDefaultKey; + readonly max_buckets: Schema.withDecodingDefaultKey; + readonly max_indexes: Schema.withDecodingDefaultKey; + readonly buckets: Schema.withDecodingDefault, never>>, never>; + }>, never>; +}>, never>; diff --git a/packages/config/api-report/studio.d.ts b/packages/config/api-report/studio.d.ts new file mode 100644 index 0000000000..50e04955ba --- /dev/null +++ b/packages/config/api-report/studio.d.ts @@ -0,0 +1,7 @@ +import { Schema } from "effect"; +export declare const studio: Schema.withDecodingDefaultKey; + readonly port: Schema.withDecodingDefaultKey; + readonly api_url: Schema.withDecodingDefaultKey; + readonly openai_api_key: Schema.optionalKey; +}>, never>; diff --git a/packages/config/api-report/workers.d.ts b/packages/config/api-report/workers.d.ts new file mode 100644 index 0000000000..29e695eb95 --- /dev/null +++ b/packages/config/api-report/workers.d.ts @@ -0,0 +1,15 @@ +import { Schema } from "effect"; +/** + * `[workers]` — one `[workers.]` table per worker, mirroring the + * `[functions.]` convention in the same file. + * + * Workers live at `supabase/workers//`; one whose code lives somewhere + * else entirely uses its own `source`, which is anchored to the project root and + * so can leave `supabase/`. + */ +export declare const workers: Schema.withDecodingDefault; + readonly size: Schema.optionalKey; + readonly instances: Schema.optionalKey; + readonly source: Schema.optionalKey; +}>>, never>; diff --git a/packages/config/package.json b/packages/config/package.json index b74f24d36a..b6e70923cd 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -2,17 +2,45 @@ "name": "@supabase/config", "version": "0.1.0", "private": true, + "files": [ + "src", + "!src/**/*.test.ts", + "dist", + "docs" + ], "type": "module", + "sideEffects": false, "exports": { - ".": "./src/index.ts", - "./internal": "./src/internal.ts", + ".": { + "types": "./dist/index.d.ts", + "bun": "./src/index.ts", + "default": "./dist/index.js" + }, + "./internal": { + "types": "./dist/internal.d.ts", + "bun": "./src/internal.ts", + "default": "./dist/internal.js" + }, "./io": { "bun": "./src/bun.ts", - "node": "./src/node.ts", - "browser": "./src/io-browser.ts", - "default": "./src/node.ts" + "node": { + "types": "./dist/node.d.ts", + "default": "./dist/node.js" + }, + "browser": { + "types": "./dist/io-browser.d.ts", + "default": "./dist/io-browser.js" + }, + "default": { + "types": "./dist/node.d.ts", + "default": "./dist/node.js" + } + }, + "./effect": { + "types": "./dist/effect.d.ts", + "bun": "./src/effect.ts", + "default": "./dist/effect.js" }, - "./effect": "./src/effect.ts", "./schema.json": "./dist/schema.json", "./project-schema.json": "./dist/project-schema.json" }, @@ -24,6 +52,7 @@ "test:unit:run": "bun --bun vitest run --project unit --coverage.reportsDirectory=coverage/unit" }, "dependencies": { + "@standard-schema/spec": "^1.1.0", "dedent": "^1.7.2", "smol-toml": "^1.8.0" }, diff --git a/packages/config/scripts/build.ts b/packages/config/scripts/build.ts index a234ac550d..488fc8806d 100644 --- a/packages/config/scripts/build.ts +++ b/packages/config/scripts/build.ts @@ -1,7 +1,20 @@ -import { mkdir } from "node:fs/promises"; +import { copyFile, mkdir, mkdtemp, realpath, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; import { toCliConfigJsonSchema } from "../src/base.ts"; import { toProjectConfigJsonSchema } from "../src/project-config/project-schema.ts"; +const packageRoot = path.resolve(import.meta.dir, ".."); +const repoRoot = path.resolve(packageRoot, "../.."); + +async function runCommand(cmd: readonly string[], cwd: string = packageRoot): Promise { + const child = Bun.spawn([...cmd], { cwd, stdout: "inherit", stderr: "inherit" }); + const exitCode = await child.exited; + if (exitCode !== 0) { + throw new Error(`\`${cmd.join(" ")}\` failed with exit code ${exitCode}`); + } +} + async function renderJsonSchema(outputPath: string, json: unknown): Promise { const schema = `${JSON.stringify(json, null, 2)}\n`; @@ -26,5 +39,194 @@ async function renderJsonSchema(outputPath: string, json: unknown): Promise { + const distIndexPath = await realpath(path.join(packageRoot, "dist", "index.js")); + // `mkdtemp` can return a path through a symlinked prefix (e.g. macOS's + // `/var` -> `/private/var`) that Bun's bundler resolves to its canonical + // form internally when computing the probe entry's own directory — compute + // the relative specifier against that same canonical form, or a + // `path.relative` mismatch silently produces a specifier with one too many + // `../` segments. + const probeDir = await realpath( + await mkdtemp(path.join(tmpdir(), "supabase-config-tree-shake-")), + ); + + try { + const probeEntry = path.join(probeDir, "probe.js"); + const relativeSpecifier = path.relative(probeDir, distIndexPath).split(path.sep).join("/"); + const specifier = relativeSpecifier.startsWith(".") + ? relativeSpecifier + : `./${relativeSpecifier}`; + await Bun.write(probeEntry, `export { CliConfigSchema } from "${specifier}";\n`); + + const result = await Bun.build({ + entrypoints: [probeEntry], + target: "browser", + minify: false, + }); + + if (!result.success) { + const messages = result.logs.map((log) => log.message).join("\n"); + throw new Error(`tree-shake probe failed to bundle:\n${messages}`); + } + + const [output] = result.outputs; + if (!output) { + throw new Error("tree-shake probe produced no bundle output"); + } + const code = await output.text(); + + // Only appears in `src/project-config/registry*.ts` (verified by + // grepping `dist/`) — a real API attribute path segment, never used by + // `CliConfigSchema`'s own field names (`base.ts`/`api.ts` use `schemas`, + // not `db_schema`). + const REGISTRY_ONLY_MARKER = "db_schema"; + // Only appears in `src/api.ts`, reachable through `CliConfigSchema` — + // proof the probe still bundled real, non-empty content. + const SCHEMA_MARKER = "Enable the local PostgREST service."; + + if (code.includes(REGISTRY_ONLY_MARKER)) { + throw new Error( + `tree-shake probe failed: bundling only { CliConfigSchema } from dist/index.js still pulled in ` + + `registry-only code (found marker ${JSON.stringify(REGISTRY_ONLY_MARKER)} from ` + + `project-config/registry.ts). "sideEffects": false is not holding for this package — ` + + `investigate before trusting the tree-shaking claim.`, + ); + } + if (!code.includes(SCHEMA_MARKER)) { + throw new Error( + `tree-shake probe failed: expected schema marker ${JSON.stringify(SCHEMA_MARKER)} is missing from ` + + `the bundle output — the probe isn't actually exercising CliConfigSchema.`, + ); + } + + console.log( + `[build] tree-shake probe OK (${code.length} bytes; registry-only marker absent, schema marker present).`, + ); + } finally { + await rm(probeDir, { recursive: true, force: true }); + } +} + +/** + * Regenerates a declarations-only build (`tsconfig.api-report.json`, no + * `.d.ts.map`/`.js`) into a scratch dir and mirrors it into the checked-in + * `api-report/` (CLI-2234 enforcement layer 4). `src/api-report.unit.test.ts` + * regenerates the same way and diffs against this mirror, so any type-surface + * change becomes a reviewable `git diff` instead of a silent drift. + */ +async function syncApiReport(): Promise { + const apiReportDir = path.join(packageRoot, "api-report"); + const scratchDir = await mkdtemp(path.join(tmpdir(), "supabase-config-api-report-")); + + try { + await runCommand([ + "pnpm", + "exec", + "tsc", + "-p", + "tsconfig.api-report.json", + "--outDir", + scratchDir, + ]); + + await rm(apiReportDir, { recursive: true, force: true }); + await mkdir(apiReportDir, { recursive: true }); + + const glob = new Bun.Glob("**/*.d.ts"); + let count = 0; + for await (const relativePath of glob.scan({ cwd: scratchDir })) { + const dest = path.join(apiReportDir, relativePath); + await mkdir(path.dirname(dest), { recursive: true }); + await copyFile(path.join(scratchDir, relativePath), dest); + count++; + } + + console.log(`[build] synced ${count} .d.ts files into api-report/`); + } finally { + await rm(scratchDir, { recursive: true, force: true }); + } +} + +/** + * The real CLI-2232 acceptance check: proves every exports-map subpath + * actually resolves compiled `dist/` output end-to-end for a real Node + * consumer — not just that `tsc` produced files. Runs from `apps/cli` + * (the one in-repo workspace that depends on `@supabase/config`) so the + * top-level bare specifier resolves through pnpm's real `node_modules` link, + * exactly like an external consumer would. + */ +async function runNodeSmokeTest(): Promise { + const nodePath = Bun.which("node"); + if (!nodePath) { + console.error( + "[build] `node` executable not found on PATH; skipping the Node-consumer smoke test. This " + + "step exists specifically to catch a broken `exports` map / dist resolution for real Node " + + "consumers (CLI-2232) — install Node (mise provides it) and re-run `pnpm build` before " + + "trusting this package's dist output.", + ); + return; + } + + const smokeScript = [ + 'import assert from "node:assert/strict";', + 'import { createRequire } from "node:module";', + "", + 'import { CliConfigSchema, ProjectConfigSchema, toProjectConfigJsonSchema, toCliConfigJsonSchema } from "@supabase/config";', + "assert.ok(CliConfigSchema, \"CliConfigSchema missing from '.'\");", + "assert.ok(ProjectConfigSchema, \"ProjectConfigSchema missing from '.'\");", + "assert.ok(toProjectConfigJsonSchema, \"toProjectConfigJsonSchema missing from '.'\");", + 'assert.equal(typeof toCliConfigJsonSchema(), "object", "toCliConfigJsonSchema() did not return an object");', + "", + 'const effectMod = await import("@supabase/config/effect");', + "assert.ok(effectMod.loadCliConfig, \"loadCliConfig missing from './effect'\");", + "", + 'const ioMod = await import("@supabase/config/io");', + "assert.ok(ioMod.loadCliConfig, \"loadCliConfig missing from './io'\");", + "assert.ok(ioMod.inferFunctionsManifest, \"inferFunctionsManifest missing from './io'\");", + "", + 'const internalMod = await import("@supabase/config/internal");', + "assert.ok(internalMod.projectConfigMappingRows, \"projectConfigMappingRows missing from './internal'\");", + "", + "const require = createRequire(import.meta.url);", + 'const schemaJson = require("@supabase/config/schema.json");', + 'const projectSchemaJson = require("@supabase/config/project-schema.json");', + 'assert.equal(typeof schemaJson, "object", "schema.json did not resolve to an object");', + 'assert.equal(typeof projectSchemaJson, "object", "project-schema.json did not resolve to an object");', + "", + 'console.log("[build] node smoke test: every entrypoint resolved through the node condition");', + ].join("\n"); + + await runCommand( + [nodePath, "--input-type=module", "-e", smokeScript], + path.join(repoRoot, "apps/cli"), + ); +} + +console.log("[build] compiling TypeScript project (tsconfig.build.json)..."); +await runCommand(["pnpm", "exec", "tsc", "-p", "tsconfig.build.json"]); + +console.log("[build] rendering JSON Schema artifacts..."); await renderJsonSchema("./dist/schema.json", toCliConfigJsonSchema()); await renderJsonSchema("./dist/project-schema.json", toProjectConfigJsonSchema()); + +console.log("[build] verifying the sideEffects:false tree-shaking claim..."); +await verifyTreeShaking(); + +console.log("[build] syncing api-report/ from a declarations-only compile..."); +await syncApiReport(); + +console.log("[build] running the Node-consumer smoke test..."); +await runNodeSmokeTest(); + +console.log("[build] done."); diff --git a/packages/config/src/api-report.unit.test.ts b/packages/config/src/api-report.unit.test.ts new file mode 100644 index 0000000000..0545376be0 --- /dev/null +++ b/packages/config/src/api-report.unit.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, test } from "vitest"; +import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +// CLI-2234 enforcement layer 4: `packages/config/api-report/` is a checked-in +// mirror of this package's compiled `.d.ts` surface (synced by +// `scripts/build.ts`'s `syncApiReport`, via `tsconfig.api-report.json`). This +// regenerates that same declarations-only build into a temp dir with the +// exact same config and diffs it against the checked-in mirror, so a +// type-signature change anywhere in `src/` shows up as a reviewable +// `api-report/` diff instead of passing silently. + +const srcDir = dirname(fileURLToPath(import.meta.url)); +const packageRoot = join(srcDir, ".."); +const apiReportDir = join(packageRoot, "api-report"); + +const FAILURE_MESSAGE = + "type surface changed — run `pnpm --filter @supabase/config build` and review+commit the api-report/ diff"; + +async function listDeclarationFiles(root: string): Promise { + const glob = new Bun.Glob("**/*.d.ts"); + const relativePaths: string[] = []; + for await (const relativePath of glob.scan({ cwd: root })) { + relativePaths.push(relativePath); + } + return relativePaths.sort(); +} + +/** + * `bun --bun vitest` (this package's mandated test runner, per `AGENTS.md`) + * prepends a synthetic `node` shim directory (`/tmp/bun-node-*`, `node` -> + * `bun`) to `PATH` for the whole process tree, so any nested + * `#!/usr/bin/env node` script resolves to Bun instead of real Node. `pnpm`'s + * own launcher is exactly such a script, and its corepack wrapper needs + * `node:sqlite`, which Bun's Node-compat layer doesn't implement — so + * spawning `pnpm` unmodified from inside this test fails before it ever + * reaches `tsc`. Stripping that shim directory back out restores real `node` + * resolution for the spawned `pnpm` subprocess. + */ +function pnpmSpawnEnv(): Record { + const path = process.env.PATH ?? ""; + const sanitizedPath = path + .split(":") + .filter((segment) => !segment.includes("/bun-node-")) + .join(":"); + return { ...process.env, PATH: sanitizedPath }; +} + +describe("api-report/ mirrors the compiled declaration surface", () => { + test("a fresh declarations-only build matches the checked-in api-report/ mirror", async () => { + const scratchDir = await mkdtemp(join(tmpdir(), "supabase-config-api-report-test-")); + + try { + const tsc = Bun.spawn( + ["pnpm", "exec", "tsc", "-p", "tsconfig.api-report.json", "--outDir", scratchDir], + { cwd: packageRoot, env: pnpmSpawnEnv(), stdout: "pipe", stderr: "pipe" }, + ); + const [exitCode, stdout, stderr] = await Promise.all([ + tsc.exited, + new Response(tsc.stdout).text(), + new Response(tsc.stderr).text(), + ]); + expect(exitCode, `tsc failed:\n${stdout}\n${stderr}`).toBe(0); + + const [freshFiles, checkedInFiles] = await Promise.all([ + listDeclarationFiles(scratchDir), + listDeclarationFiles(apiReportDir), + ]); + + expect(freshFiles, FAILURE_MESSAGE).toEqual(checkedInFiles); + + const mismatches: string[] = []; + for (const relativePath of freshFiles) { + const [fresh, checkedIn] = await Promise.all([ + readFile(join(scratchDir, relativePath), "utf8"), + readFile(join(apiReportDir, relativePath), "utf8"), + ]); + if (fresh !== checkedIn) { + mismatches.push(relativePath); + } + } + + expect(mismatches, FAILURE_MESSAGE).toEqual([]); + } finally { + await rm(scratchDir, { recursive: true, force: true }); + } + }, 20_000); +}); diff --git a/packages/config/src/entrypoint-purity.unit.test.ts b/packages/config/src/entrypoint-purity.unit.test.ts index 1588d37520..6d00a61bbc 100644 --- a/packages/config/src/entrypoint-purity.unit.test.ts +++ b/packages/config/src/entrypoint-purity.unit.test.ts @@ -19,12 +19,29 @@ import * as internalEntrypoint from "./internal.ts"; const srcDir = dirname(fileURLToPath(import.meta.url)); const packageRoot = join(srcDir, ".."); + +interface DistConditions { + readonly types: string; + readonly default: string; +} + +interface TypesBunDefaultExport { + readonly types: string; + readonly bun: string; + readonly default: string; +} + const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")) as { readonly exports: { - readonly ".": string; - readonly "./internal": string; - readonly "./io": Readonly>; - readonly "./effect": string; + readonly ".": TypesBunDefaultExport; + readonly "./internal": TypesBunDefaultExport; + readonly "./io": { + readonly bun: string; + readonly node: DistConditions; + readonly browser: DistConditions; + readonly default: DistConditions; + }; + readonly "./effect": TypesBunDefaultExport; readonly "./schema.json": string; readonly "./project-schema.json": string; }; @@ -498,19 +515,33 @@ describe("package.json exports map", () => { expect(Object.keys(ioExports)).toEqual(["bun", "node", "browser", "default"]); }); - test("every ./io condition target file exists on disk", () => { - const ioExports = packageJson.exports["./io"]; - for (const target of Object.values(ioExports)) { - expect(() => readFileSync(join(packageRoot, target))).not.toThrow(); + test("'types' is the first key in every conditional export object (CLI-2232)", () => { + const conditionObjects = [ + packageJson.exports["."], + packageJson.exports["./effect"], + packageJson.exports["./internal"], + packageJson.exports["./io"].node, + packageJson.exports["./io"].browser, + packageJson.exports["./io"].default, + ]; + for (const conditions of conditionObjects) { + expect(Object.keys(conditions)[0]).toBe("types"); } }); - test("the '.', './effect', and './internal' export targets exist on disk", () => { - // `./schema.json`/`./project-schema.json` are build outputs - // (`dist/schema.json`/`dist/project-schema.json`) and intentionally - // skipped here — they only exist after running `pnpm run build`. + // The `types`/`default` conditions of `.`/`./effect`/`./internal`/`./io` + // (node, browser, default) all point at `dist/` build outputs, which only + // exist after `pnpm run build` — intentionally NOT checked here so this + // test stays build-independent. `src/api-report.unit.test.ts` and + // `scripts/build.ts`'s tree-shake/Node-consumer smoke test own dist + // correctness instead (CLI-2232). + test("the ./io bun condition target exists on disk (its only src target)", () => { + expect(() => readFileSync(join(packageRoot, packageJson.exports["./io"].bun))).not.toThrow(); + }); + + test("the '.', './effect', and './internal' bun condition targets exist on disk", () => { for (const key of [".", "./effect", "./internal"] as const) { - const target = packageJson.exports[key]; + const target = packageJson.exports[key].bun; expect(() => readFileSync(join(packageRoot, target))).not.toThrow(); } }); diff --git a/packages/config/src/monorepo-import-contract.unit.test.ts b/packages/config/src/monorepo-import-contract.unit.test.ts index 7fdb6baf43..1168068c0b 100644 --- a/packages/config/src/monorepo-import-contract.unit.test.ts +++ b/packages/config/src/monorepo-import-contract.unit.test.ts @@ -15,8 +15,9 @@ import { fileURLToPath } from "node:url"; // two forbidden specifiers can't appear by accident. The forbidden strings // below are built by concatenation so this file's own source can never // self-match (on top of the directory exclusion below, which already keeps -// this package's `src/` — where those specifier strings legitimately appear -// in test fixtures — out of the walk). +// this whole package — where those specifier strings legitimately appear in +// doc comments, generated `api-report/` declarations, and the build script's +// own Node-consumer smoke-test source string — out of the walk). // const srcDir = dirname(fileURLToPath(import.meta.url)); const repoRoot = join(srcDir, "..", "..", ".."); @@ -26,14 +27,14 @@ const forbiddenIoSpecifier = `${configPackageName}/io`; const forbiddenDeepImportPrefix = `${configPackageName}/src/`; const EXCLUDED_DIR_NAMES = new Set(["node_modules", "dist", ".repos"]); -const thisPackageSrcDir = srcDir; +const thisPackageDir = join(srcDir, ".."); function collectTsFiles(dir: string, into: string[]): void { for (const entry of readdirSync(dir, { withFileTypes: true })) { const fullPath = join(dir, entry.name); if (entry.isDirectory()) { - if (EXCLUDED_DIR_NAMES.has(entry.name) || fullPath === thisPackageSrcDir) { + if (EXCLUDED_DIR_NAMES.has(entry.name) || fullPath === thisPackageDir) { continue; } collectTsFiles(fullPath, into); diff --git a/packages/config/src/project-config/project-schema.ts b/packages/config/src/project-config/project-schema.ts index 545f78e603..7ade12fac7 100644 --- a/packages/config/src/project-config/project-schema.ts +++ b/packages/config/src/project-config/project-schema.ts @@ -1,3 +1,4 @@ +import type { StandardSchemaV1 } from "@standard-schema/spec"; import { Schema, SchemaAST } from "effect"; import { CliConfigSchema } from "../base.ts"; import { HOSTED_SECTION_KEYS } from "./hosted-sections.ts"; @@ -208,8 +209,19 @@ type ProjectConfigSchemaType = Omit; * (decode/encode, `.ast`, …) and a spec-compliant Standard Schema * (`~standard`), since {@link Schema.toStandardSchemaV1} augments and returns * the SAME object rather than wrapping it in a second value. + * + * Annotated explicitly (rather than left inferred) because the inferred type + * names `StandardSchemaV1` from `@standard-schema/spec` — a package reachable + * only transitively through `effect` under pnpm's strict `node_modules` + * isolation — which tsc's declaration emit refuses to synthesize into + * `project-schema.d.ts` as non-portable. Explicitly importing the type here + * pins `@standard-schema/spec` as a direct dependency instead. */ -export const ProjectConfigSchema = Schema.toStandardSchemaV1( +export const ProjectConfigSchema: StandardSchemaV1< + ProjectConfigSchemaType, + ProjectConfigSchemaType +> & + Schema.Codec = Schema.toStandardSchemaV1( Schema.make>(projectConfigAst), ); diff --git a/packages/config/tsconfig.api-report.json b/packages/config/tsconfig.api-report.json new file mode 100644 index 0000000000..f6145b9a6e --- /dev/null +++ b/packages/config/tsconfig.api-report.json @@ -0,0 +1,14 @@ +{ + // Declaration-only companion to `tsconfig.build.json`, used exclusively to + // populate `api-report/` (see `scripts/build.ts` and + // `src/api-report.unit.test.ts`). A second minimal config — rather than + // overriding `declarationMap` on the CLI — because `declarationMap` is a + // boolean compiler option with no dedicated CLI negation flag; `--outDir` + // is a plain string override and stays safe to pass on the command line at + // each call site. + "extends": "./tsconfig.build.json", + "compilerOptions": { + "emitDeclarationOnly": true, + "declarationMap": false + } +} diff --git a/packages/config/tsconfig.build.json b/packages/config/tsconfig.build.json new file mode 100644 index 0000000000..73b1cfd0ae --- /dev/null +++ b/packages/config/tsconfig.build.json @@ -0,0 +1,31 @@ +{ + // Compiles the published `dist/` output for `pnpm build`. Extends the same + // `@tsconfig/bun` strictness baseline as `tsconfig.json` (dev/tests, run + // under Bun) but swaps its bundler-mode settings for a real Node-compatible + // ESM emit, so the two configs share strictness without fighting over + // `module`/`moduleResolution`/`types`/`noEmit`, which this file overrides. + // + // `"types": []` drops the ambient `bun` global types from `tsconfig.json` + // and doubles as the @types/bun leak audit CLI-2234 requires: if any + // compiled module reaches for a Bun-only global (`Bun.*`, `Bun.env`, ...), + // this compile fails on a missing name instead of silently type-checking + // against `bun-types`. + "extends": "@tsconfig/bun/tsconfig.json", + "compilerOptions": { + "lib": ["ESNext"], + "types": [], + "module": "nodenext", + "moduleResolution": "nodenext", + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true, + "noEmit": false, + "declaration": true, + // `dist/` ships alongside `src/` in the published tarball (see `files` in + // package.json), so declaration maps resolve back to real source. + "declarationMap": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 646b0b4476..8372b826ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -400,6 +400,9 @@ importers: packages/config: dependencies: + '@standard-schema/spec': + specifier: ^1.1.0 + version: 1.1.0 dedent: specifier: ^1.7.2 version: 1.7.2 diff --git a/turbo.json b/turbo.json index a258a26efc..706473fdd5 100644 --- a/turbo.json +++ b/turbo.json @@ -62,8 +62,13 @@ }, "@supabase/config#build": { "cache": true, - "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/.bun-version", "$TURBO_ROOT$/mise.lock"], - "outputs": ["dist/schema.json"] + "inputs": [ + "$TURBO_DEFAULT$", + "!api-report/**", + "$TURBO_ROOT$/.bun-version", + "$TURBO_ROOT$/mise.lock" + ], + "outputs": ["dist/**", "api-report/**"] }, "@supabase/api#generate": { "cache": false, From e1aac3678b87a6ddc4e9acf797ea2211126ed8d6 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 27 Aug 2026 17:33:23 +0100 Subject: [PATCH 4/9] docs(config): document the published contract and entrypoint surfaces (CLI-2234) --- packages/config/AGENTS.md | 50 ++++++- packages/config/README.md | 294 +++++++++++++++++++++++--------------- 2 files changed, 223 insertions(+), 121 deletions(-) diff --git a/packages/config/AGENTS.md b/packages/config/AGENTS.md index 08c4fa3fe3..0f76b35230 100644 --- a/packages/config/AGENTS.md +++ b/packages/config/AGENTS.md @@ -5,8 +5,8 @@ Supabase project configuration package built on Effect V4 Schema — owns the ca ## Entrypoints -Four entrypoints plus a generated artifact (see ADR 0009's 2026-08-24 decision for the full -rationale): +Four entrypoints plus two generated JSON Schema artifacts (see ADR 0009's 2026-08-24 decision for +the full rationale): - `@supabase/config` (`.`) — pure, browser/edge-safe surface. The `CliConfigSchema` and derived types, config encoding, sparse-config defaults, and error classes. No file IO, no @@ -25,8 +25,11 @@ rationale): `ProjectConfigMappingRow`, `ProjectConfigApiAttributes`, `ENV_CAPTURE_REGEX`). Unlike `./io`, `apps/cli` IS an expected consumer of this subpath. Anything here can change or vanish in any release. -- `@supabase/config/schema.json` — generated JSON Schema for `CliConfig` (a `dist/` build - output). +- `@supabase/config/schema.json` — generated JSON Schema (draft 2020-12) for `CliConfig` (a + `dist/` build output). +- `@supabase/config/project-schema.json` (CLI-2234) — generated JSON Schema (draft 2020-12) for + `ProjectConfig`, derived from `ProjectConfigSchema` (`src/project-config/project-schema.ts`); a + `dist/` build output alongside `schema.json`. ## Monorepo import rule @@ -42,7 +45,7 @@ rationale): tests only — a symbol that needs the internal-only `goViperCompat` typings, or the internal registry data, imports it from there; every other symbol in the same import statement stays on its public specifier (`.`/`./effect`). -- Never deep-import this package's internals (e.g. `@supabase/config/src/io.ts`). Only the five +- Never deep-import this package's internals (e.g. `@supabase/config/src/io.ts`). Only the six entrypoints above are supported import paths. ## Pure-graph invariant @@ -56,7 +59,42 @@ import graph against a hardcoded allowlist, pins both entrypoints' exact runtime asserts the package.json `exports` map shape. Any change that grows the pure graph or the export surface must update that test deliberately — it is not meant to be a silent pass. +## Build (CLI-2232) + +`pnpm --filter @supabase/config build` (or `pnpm run build` from this package) runs +`scripts/build.ts`, in order: + +1. Compiles `src/` to `dist/` (`tsc -p tsconfig.build.json`) — the `.js`/`.d.ts` output every + `dist`/`types`/`default` export condition points at. +2. Renders both generated JSON Schema artifacts (`dist/schema.json`, `dist/project-schema.json`) + from `toCliConfigJsonSchema()`/`toProjectConfigJsonSchema()`, formatted through `oxfmt`. +3. Runs a tree-shake probe: bundles a probe importing only `CliConfigSchema` from the compiled + `dist/index.js` for a `browser` target and asserts the output excludes registry-only code, + proving the package.json `sideEffects: false` claim against real compiled output rather than + merely asserting it. +4. Syncs `api-report/` — a declarations-only build (`tsconfig.api-report.json`) mirrored into the + checked-in `api-report/` directory. `src/api-report.unit.test.ts` regenerates the same build and + diffs it against that mirror, so any type-surface change anywhere in `src/` shows up as a + reviewable `api-report/` diff instead of passing silently — commit that diff whenever it appears. +5. Runs a Node-consumer smoke test (from `apps/cli`, the one in-repo workspace that depends on this + package through a real `node_modules` link) that imports every entrypoint and JSON artifact + through the `node` export condition, catching a broken `exports` map or dist resolution that a + `tsc`-only build wouldn't. + +`dist/` is gitignored and rebuilt on demand; `api-report/` is the one build output that is checked +in. Re-run the build and commit the resulting `api-report/` diff whenever a change touches this +package's public type surface. + ## Testing Run tests from this package with `bun --bun vitest run --project unit` (plain `node` vitest is -broken here). Always run the relevant unit tests for what you changed before considering a task done. +broken here). Always run the relevant unit tests for what you changed before considering a task +done. Besides ordinary behavioral coverage, three tests enforce this package's own contracts and +must stay green after any entrypoint or type-surface change: + +- `src/entrypoint-purity.unit.test.ts` — the pure-graph invariant above, plus pinned export-name + snapshots for `.`/`./effect`/`./internal` and the package.json `exports` map shape. +- `src/api-report.unit.test.ts` — the checked-in `api-report/` mirror described above. +- `src/monorepo-import-contract.unit.test.ts` — the "Monorepo import rule" above (no internal + `./io` consumer, no deep `@supabase/config/src/*` import), scanning `apps/` and `packages/` + while excluding this package's own directory. diff --git a/packages/config/README.md b/packages/config/README.md index e48a9817cf..880f895490 100644 --- a/packages/config/README.md +++ b/packages/config/README.md @@ -1,144 +1,204 @@ # @supabase/config -Supabase project configuration package built on Effect V4 Schema — owns the canonical `CliConfig` -document schema, config file loading/saving, and JSON Schema generation. +Supabase project configuration package built on Effect V4 Schema — the config-file document model +(`CliConfig`), the hosted-project subset (`ProjectConfig`), schema-backed parsing, validation, and +encoding, defaults and sparse-diff helpers, and the converters between the local and hosted +representations. It owns: -- the canonical `CliConfig` schema -- the `CliConfigStore` Effect service for config IO -- JSON Schema generation at `@supabase/config/schema.json` -- config file loading/saving for `supabase/config.json` -- backward-compatible TOML support for `supabase/config.toml` +- the canonical `CliConfig` schema for `supabase/config.toml`/`supabase/config.json` +- the `CliConfigStore` Effect service for config file IO, and a Promise-based facade over it +- the `ProjectConfig` hosted-project subset and its converters to/from a `CliConfig` document or a + Management API v2 project-config response +- `ProjectConfigSchema`, a runtime-validating companion to `ProjectConfig` +- JSON Schema generation for both shapes, at `@supabase/config/schema.json` and + `@supabase/config/project-schema.json` ## Naming - `CliConfig` — the config _document_ (`supabase/config.toml`/`.json`) — the full local superset the CLI reads and writes. -- `ProjectConfig` — the hosted-project subset: a sparse overlay of the hosted sections (api, auth, - db, realtime, storage, workers, experimental) describing what a Supabase project looks like on - the platform. Introduced by CLI-2230: produced by `toProjectConfig` from either a `CliConfig` - document or a Management API response — see "ProjectConfig mapping" below. +- `ProjectConfig` — the hosted-project subset: a sparse overlay of the hosted sections (`api`, + `auth`, `db`, `realtime`, `storage`, `workers`, `experimental`) describing what a Supabase + project looks like on the platform. Produced by `toProjectConfig` from either a `CliConfig` + document or a Management API response — see "ProjectConfig: producing and validating values" + below. - `CliSettings` — the CLI's own runtime settings; lives in `apps/cli`, not this package. Use the `Cli*` prefix for the local checkout side and a bare `Project*` name for the hosted -Supabase project. Helpers that operate on config values follow the config family regardless of -their inputs (`resolveCliConfigValue`, `MissingCliConfigValueError`). See -[ADR 0020](../../docs/adr/0020-config-naming-vocabulary.md) for the full decision record. +Supabase project. Config-value helpers follow the config family regardless of their inputs +(`resolveCliConfigValue`, `MissingCliConfigValueError`). See +[ADR 0020](../../docs/adr/0020-config-naming-vocabulary.md) and +[docs/cli-config-loading.md](./docs/cli-config-loading.md) for the full vocabulary. ## Entrypoints -- `@supabase/config` — pure, browser/edge-safe surface: the `CliConfig` schema and types, - config encoding, sparse-config defaults, and errors. No file IO, no Effect-returning functions. -- `@supabase/config/io` — Promise-based file-IO facade for non-Effect consumers. The bun/node - implementation is picked automatically via package.json exports conditions. Requires installing - exactly one of the optional platform peers — `@effect/platform-bun` under Bun, `@effect/platform-node` - under Node — and is unavailable in browser bundles (the `browser` condition resolves to a stub that - throws); use `@supabase/config` there instead. -- `@supabase/config/effect` — Effect-native superset of `@supabase/config`, adding the - `CliConfigStore` service, `cliConfigStoreLayer`, and other Effect programs (config - loading/saving, project env resolution, functions manifest inference). -- `@supabase/config/schema.json` — generated JSON Schema for `CliConfig`. - -## ProjectConfig mapping - -The hosted-project subset — `ProjectConfig` — and its normalizers live on the pure entrypoint -(`@supabase/config`), so the CLI and Studio share one implementation: - -- `toProjectConfig(source)` — thin dispatcher over the two normalizers; pass `{ cliConfig }` - or `{ apiResponse }`. Throws `ProjectConfigParseError` when `source` carries neither own key - or both. -- `fromConfigDocument(cliConfig)` — projection of a `CliConfig` document (or any - `EffectiveConfig`): keeps the hosted sections (`api`, `auth`, `db`, `realtime`, `storage`, - `workers`, `experimental`), drops local-only ones. Hosted sections are copied at field - granularity, omitting every `x-secret` leaf, and every duration/byte-size field a mapping - row canonicalizes (e.g. a document's `"24h"` becomes `"24h0m0s"`, matching what the API side - would emit for the same logical value) — parity with `fromApiProjectConfig`'s own secret - omission and canonical spellings. **Not a verbatim rendering of the document**: per - [ADR 0021](../../docs/adr/0021-projectconfig-convergence-semantics.md), the result also - applies SMS-provider push precedence and disabled-sentinel pruning, so it predicts what the - hosted config will look like _after_ pushing the document, not the document's own declared - values. **RECOMMENDED for a file-sourced config**: pass `{ config, document }` instead of a - bare `cliConfig` whenever a raw `document` is available (`LoadedCliConfig`'s own shape — - `@supabase/config/io`'s loaders return one, and it is structurally assignable here without a - cast). With `document`, the projection additionally mirrors the legacy push pipeline's own - raw-presence gates (a raw-absent `auth.captcha`, an un-raw-declared external provider, …), which - a bare `cliConfig` operand cannot — see ADR 0021's "Limits" section for exactly which fields - this closes and which residual gap remains even with `document` supplied. `@supabase/config/io`'s - `loadCliConfig` supplies a `document`; `saveCliConfig`'s returned `LoadedCliConfig` does NOT - (there is no raw file being re-read on a save), so passing that result straight into - `fromConfigDocument` silently falls back to the un-remedied, bare-`cliConfig` behavior. -- `fromApiProjectConfig(input)` — translation of a Management API v2 project-config response - (the full envelope, its `data` object, or bare `data.attributes`): registry-driven renames, boolean inversions, and - unit conversions; lenient toward API keys this package version doesn't know; secret fields - omitted (the API reports HMAC digests, never plaintext). Attaches a deep-cloned, deep-frozen - copy of the raw attributes as a non-enumerable `_apiResponse` — invisible to encodes and - structural walks, never persisted (ADR 0019). Also not a byte-for-byte echo of the response - (ADR 0021): a `null` on a gating boolean canonicalizes to `enabled: false`, and the same - disabled-sentinel pruning `fromConfigDocument` applies runs here too. Both normalizers throw - `ProjectConfigParseError` on malformed API input (a bad envelope, a mapped field of the wrong - type, or an unparseable schema-decode failure). -- `unmappedApiFields(projectConfig)` — the API fields this package version doesn't map, - derived from the same mapping registry. +| Entrypoint | Contents | Constraints | +| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `.` | `CliConfigSchema`/`ProjectConfigSchema` and their types, config encoding, sparse-config defaults, the `ProjectConfig` converters, error classes | Pure — browser/edge/Node/Bun-safe. No file IO, no Effect-returning function, no `@effect/platform-*`/`node:`/`bun:` module anywhere in its transitive import graph | +| `./io` | A Promise-based facade over the same file-IO/Effect programs as `./effect` | Resolved automatically via package.json export conditions (`bun`/`node`/`browser`/`default`). Requires one of the optional platform peers — `@effect/platform-bun` under Bun, `@effect/platform-node` under Node — installed at runtime; see "Installing" below for the failure mode when it's missing | +| `./effect` | Effect-native superset of `.`: `CliConfigStore`/`cliConfigStoreLayer`, config loading/saving, project-environment resolution, functions-manifest inference | Requires `effect`; requires a platform peer only for the file-IO programs, exactly like `./io` | +| `./internal` | `apps/cli`'s Go-parity typings (`goViperCompat`) and the internal API-mapping registry data | **Not covered by semver.** Exists solely for the Supabase CLI's own use and its contract-guard tests — any export here (its existence, shape, or behavior) can change or vanish in any release without notice | +| `./schema.json` | Generated JSON Schema for `CliConfig` | Draft 2020-12 — a language-agnostic contract for non-TypeScript consumers | +| `./project-schema.json` | Generated JSON Schema for `ProjectConfig` | Draft 2020-12 — a language-agnostic contract for non-TypeScript consumers | + +A few things worth calling out beyond the table: + +- **`./io`'s member names mirror `./effect`'s one-to-one** (`loadCliConfig`, `saveCliConfig`, + `loadCliConfigFile`, `findCliProjectRoot`, `findCliProjectPaths`, `loadCliProjectEnvironment`, + `inferFunctionsManifest`) — the subpath itself conveys Promise-vs-Effect, not the member name. + In a browser bundle, `./io` resolves to a stub whose exports throw a curated error only when + actually invoked (never at import time), directing you back to `.`. +- **`./effect` deliberately shadows two names from `.`.** `resolveCliConfigValue` and + `resolveCliConfigSubtree` exist on both `.` (plain, synchronous — throws instead of failing an + `Effect`) and `./effect` (Effect-typed). Because explicit named exports win over a star + re-export of the same name, importing from `./effect` always gets you the Effect-typed variant, + even though `./effect` also re-exports everything else from `.` verbatim. +- **`./internal` is genuinely unstable.** It is not merely undocumented — it is explicitly outside + this package's compatibility promise (see "Semver and the published contract" below). + +## Installing + +This package is not yet published (`private: true`; publishing is tracked separately). Once it +is, install it alongside the peers your runtime needs: + +```sh +npm install @supabase/config effect +``` + +`effect` is a required peer dependency. `@effect/platform-bun` and `@effect/platform-node` are +optional peers — install exactly one, matching your runtime, if you use `./io` or `./effect`'s +file-IO programs: + +| Consumer | Required peers | +| ---------------------------------------------- | --------------------------------- | +| Pure / browser / edge (`.` only, no file IO) | `effect` | +| Node (`./io` or `./effect`'s file-IO programs) | `effect`, `@effect/platform-node` | +| Bun (`./io` or `./effect`'s file-IO programs) | `effect`, `@effect/platform-bun` | + +Under the `node`/`bun` export conditions, the matching platform peer is imported eagerly at module +load. A missing peer surfaces as a raw module-resolution error (e.g. `Cannot find package +'@effect/platform-node'`) the first time something imports `./io` or `./effect` — not a curated +message — so install the peer for your runtime before importing either subpath. The `browser` +condition is the one exception: it needs no platform peer, since it resolves to a stub that throws +its own curated error only when invoked (see "Entrypoints" above). + +## ProjectConfig: producing and validating hosted-project values + +The hosted-project subset — `ProjectConfig` — and its converters live on the pure entrypoint +(`.`), so any TypeScript consumer can produce or compare `ProjectConfig` values without file IO or +Effect: + +- `toProjectConfig(source)` — thin dispatcher over the two normalizers below; pass `{ cliConfig }` + or `{ apiResponse }`. Throws `ProjectConfigParseError` when `source` carries neither key or both. +- `fromConfigDocument(cliConfig)` — projection of a `CliConfig` document (or any `EffectiveConfig`) + onto the hosted sections (`api`, `auth`, `db`, `realtime`, `storage`, `workers`, `experimental`), + omitting every `x-secret` leaf and canonicalizing duration/byte-size fields the same way the API + side would. **Not a verbatim rendering of the document** — see + [ADR 0021](../../docs/adr/0021-projectconfig-convergence-semantics.md) for the push-precedence + and sentinel-pruning semantics this applies. +- `fromApiProjectConfig(input)` — translation of a Management API v2 project-config response (the + full envelope, its `data` object, or bare `data.attributes`) via registry-driven renames, + boolean inversions, and unit conversions; lenient toward API keys this package version doesn't + yet know, and never reports a secret field's plaintext. Attaches a deep-frozen copy of the raw + attributes as a non-enumerable `_apiResponse` (invisible to encodes and structural walks, never + persisted — see [ADR 0019](../../docs/adr/0019-config-api-response-passthrough.md)). Throws + `ProjectConfigParseError` on malformed input. +- `unmappedApiFields(projectConfig)` — the API fields this package version doesn't map, derived + from the same mapping registry. - `attachApiResponse(projectConfig, rawAttributes)` — re-attaches `_apiResponse` after a spread/`structuredClone`/state-store round-trip already dropped it. - `comparableProjectConfigPaths` / `isComparableProjectConfigPath(path)` — the registry-derived field paths `fromApiProjectConfig` can actually speak for, so a diff consumer restricts its comparison instead of hand-maintaining an equivalent field list. -`ProjectConfig` is sparse by design: it carries only what its source actually said, so it -composes with `subtractCliConfig`/`omitDefaultValues` (operand type `EffectiveConfig`) without -fabricating drift from schema defaults. Diffing two independently-sourced `ProjectConfig`s (a -remote response against a local document, rather than either against schema defaults) still needs -restricting to `comparableProjectConfigPaths`/`isComparableProjectConfigPath` — and at LEAF-path -granularity: `isComparableProjectConfigPath` takes a full path like -`["auth", "email", "smtp", "enabled"]`, not a top-level section name, so filtering -`Object.entries(overlay)` (section names only) restricts nothing. - ```ts -import { - subtractCliConfig, - toProjectConfig, - isComparableProjectConfigPath, -} from "@supabase/config"; +import { fromApiProjectConfig, fromConfigDocument, toProjectConfig } from "@supabase/config"; const remote = toProjectConfig({ apiResponse }); // Management API v2 project-config response -// `loaded` here is whatever `@supabase/config/io`'s loader returned (a -// `LoadedCliConfig`) — passing it directly (not just `loaded.config`) is the -// RECOMMENDED form: it unlocks the raw-presence masking described above. -const local = toProjectConfig({ cliConfig: loaded }); - -// `overlay` is what `local` says that `remote` doesn't already agree with. -const overlay = subtractCliConfig(local, remote); - -// Restrict to individual LEAF paths — see the granularity note above. -function leafPaths( - value: unknown, - prefix: ReadonlyArray = [], -): ReadonlyArray> { - if (value !== null && typeof value === "object" && !Array.isArray(value)) { - return Object.entries(value as Record).flatMap(([key, child]) => - leafPaths(child, [...prefix, key]), - ); - } - return [prefix]; +const local = toProjectConfig({ cliConfig: someCliConfig }); +``` + +### `ProjectConfigSchema`: runtime validation as another option + +For a consumer that already holds a well-typed `ProjectConfig` (produced by the converters above), +that's the whole story. A consumer that instead receives untrusted or serialized data — a value +read back from storage, sent over the wire, or produced by a third party — can validate it against +`ProjectConfigSchema` instead: + +```ts +import { ProjectConfigSchema } from "@supabase/config"; + +const result = await ProjectConfigSchema["~standard"].validate(candidate); +if (result.issues) { + // reject `candidate` — see the Standard Schema v1 spec for the `issues` shape } +``` -const restrictedDrift = leafPaths(overlay).filter(isComparableProjectConfigPath); -// e.g. [["api", "schemas"], ["api", "max_rows"], ["auth", "site_url"]] — the fields `local` -// DECLARES that `remote` doesn't already agree with, restricted to what `fromApiProjectConfig` -// can actually speak for. +`ProjectConfigSchema` is a full Effect `Schema.Codec` (usable with `Schema.decodeUnknownEffect` +and friends) **and** a spec-compliant [Standard Schema v1](https://standardschema.dev/) object +(`~standard`) at the same time — `Schema.toStandardSchemaV1` augments and returns the same value +rather than wrapping it, so it works with any library that accepts a `~standard`-compatible +schema, not only Effect code. + +`ProjectConfigSchema` is derived from `CliConfigSchema`, never hand-declared, which gives it a +specific, narrower validation contract — what it does and does not promise: + +- **Hosted sections only** — the same seven sections `ProjectConfig` itself carries; nothing else + validates. +- **Deeply optional** — every key at every level is optional, mirroring `ProjectConfig`'s own + `DeepPartial` shape, so a sparse fragment like `{ auth: { email: { smtp: { enabled: true } } } }` + validates even without whatever sibling fields would otherwise be required. +- **`x-secret` leaves removed** — no secret-marked field exists in this schema at all, matching + the converters' own secret-omission behavior. +- **Cross-field checks stripped** — whole-struct business-rule refinements from the base schema + (e.g. "if `enabled`, then `host` is required") are removed, since a deliberately sparse overlay + can legitimately violate them. +- **Arrays are not deep-partialized** — an array field's element type is left untouched, matching + `ProjectConfig`'s own array handling. +- **Permissive, not closed** — never `additionalProperties: false`; an unrecognized own key (from + a schema version ahead of this package) is accepted, not rejected. +- **`_apiResponse` is out of scope** — it's a non-enumerable property, invisible to both decode and + validation. + +## `./io`'s error contract + +A rejected `loadCliConfig`, `loadCliConfigFile`, or `saveCliConfig` call from `./io` can reject +with any of: + +- `CliConfigParseError` — a malformed `supabase/config.toml`/`.json` +- `DuplicateRemoteProjectIdError` — two `[remotes.*]` blocks declare the same `project_id` +- `InvalidRemoteProjectIdError` — a `[remotes.*]` block's `project_id` isn't a valid project ref +- `CliProjectEnvParseError` — a malformed `.env`/`.env.local` file +- `PlatformError` (from `effect/PlatformError`) — a host/OS failure surfaced by the underlying + `FileSystem` service + +Every one of these is a plain class (an Effect `Data.TaggedError`), so a catch block can +distinguish them with `instanceof`: + +```ts +import { CliConfigParseError, DuplicateRemoteProjectIdError } from "@supabase/config"; +import { loadCliConfig } from "@supabase/config/io"; + +try { + const loaded = await loadCliConfig(process.cwd()); +} catch (error) { + if (error instanceof CliConfigParseError) { + // malformed config.toml/config.json + } else if (error instanceof DuplicateRemoteProjectIdError) { + // two [remotes.*] blocks claim the same project_id + } + throw error; +} ``` -This example computes **one direction** of a drift check: values the local document declares that -differ from the remote. It does not surface remote-only settings — a field the API maps -unconditionally (e.g. `auth.email.smtp.enabled`) where the local document never declared the -subsection produces no leaf in this overlay at all. Finding those needs the reverse subtraction -(`subtractCliConfig(remote, local)`) intersected with the paths the document-side operand actually -declares, per the comparison contract on the `ProjectConfig` docstring — the comparable-path set -only says which paths the API mapper can represent, not which ones a given document spoke for. A -complete two-sided drift computation is `config diff`'s job (CLI-2156); this example is its -building block, not a substitute. +## Semver and the published contract + +The runtime export surface of `.`, `./io`, and `./effect`, plus the two generated JSON Schema +artifacts (`./schema.json`, `./project-schema.json`), is this package's published contract. +`./internal` carries no such guarantee. See [AGENTS.md](./AGENTS.md) for how that contract is +enforced (export-surface snapshots and a checked-in API report). ## Usage @@ -168,13 +228,15 @@ For convenience entrypoints at the runtime edge: import { loadCliConfig } from "@supabase/config/io"; ``` -For lazy `env(NAME)` resolution, load project env separately and resolve only the value or subtree you need: +For lazy `env(NAME)` resolution, load project env separately and resolve only the value or subtree +you need: ```ts import { loadCliProjectEnvironment, resolveCliConfigSubtree } from "@supabase/config/effect"; ``` -When both `supabase/config.json` and `supabase/config.toml` exist in one project, JSON wins. Saves preserve the existing format when possible and default new config files to JSON. +When both `supabase/config.json` and `supabase/config.toml` exist in one project, JSON wins. Saves +preserve the existing format when possible and default new config files to JSON. ## Architecture Docs @@ -194,5 +256,7 @@ Package-local checks and development commands run from `packages/config`: ```sh pnpm types:check pnpm run test # Run tests -pnpm run build # Generate dist/schema.json +pnpm run build # Compile dist/, generate schema.json/project-schema.json, sync api-report/ ``` + +See [AGENTS.md](./AGENTS.md) for the build pipeline and contract-enforcement details. From 7b96a6172c85040296f052e8862212b42226a3c5 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 27 Aug 2026 18:45:02 +0100 Subject: [PATCH 5/9] fix(config): address publish-readiness review findings (CLI-2234, CLI-2232) --- .gitattributes | 7 + .../src/shared/functions/functions-config.ts | 10 +- .../output/normalize-error.unit.test.ts | 17 +- .../shared/telemetry/error-actionability.ts | 1 - apps/cli/tsconfig.json | 11 + packages/config/.npmignore | 11 + packages/config/AGENTS.md | 103 +++-- packages/config/LICENSE | 21 + packages/config/README.md | 166 ++++++- packages/config/api-report/effect.d.ts | 13 +- packages/config/api-report/errors.d.ts | 7 - packages/config/api-report/index.d.ts | 6 +- packages/config/api-report/internal.d.ts | 15 +- packages/config/api-report/lib/resolve.d.ts | 29 +- .../project-config/project-schema.d.ts | 102 ++++- packages/config/api-report/project.d.ts | 4 +- .../config/api-report/promise-facade.d.ts | 15 +- .../config/api-report/schema-metadata.d.ts | 2 + packages/config/docs/cli-config-loading.md | 32 +- packages/config/package.json | 36 +- .../scripts/build-artifacts.unit.test.ts | 113 +++++ packages/config/scripts/build.ts | 414 ++++++++++++++---- .../config/scripts/json-schema-postprocess.ts | 236 ++++++++++ .../json-schema-postprocess.unit.test.ts | 129 ++++++ packages/config/src/api-report.unit.test.ts | 57 ++- packages/config/src/effect.ts | 11 +- .../config/src/entrypoint-purity.unit.test.ts | 61 ++- packages/config/src/errors.ts | 4 - packages/config/src/index.ts | 4 +- packages/config/src/internal.ts | 19 +- packages/config/src/lib/resolve.ts | 45 +- packages/config/src/lib/resolve.unit.test.ts | 50 +++ .../src/monorepo-import-contract.unit.test.ts | 33 +- .../src/project-config/project-schema.ts | 115 ++--- .../project-schema.unit.test.ts | 88 ++++ packages/config/src/project.ts | 33 +- packages/config/src/promise-facade.ts | 15 +- packages/config/src/schema-metadata.ts | 2 + 38 files changed, 1663 insertions(+), 374 deletions(-) create mode 100644 .gitattributes create mode 100644 packages/config/.npmignore create mode 100644 packages/config/LICENSE create mode 100644 packages/config/scripts/build-artifacts.unit.test.ts create mode 100644 packages/config/scripts/json-schema-postprocess.ts create mode 100644 packages/config/scripts/json-schema-postprocess.unit.test.ts create mode 100644 packages/config/src/lib/resolve.unit.test.ts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..01cc9ae0c1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# `packages/config/api-report/` is a checked-in mirror of compiled `.d.ts` +# output, byte-diffed against a fresh regenerate by +# `packages/config/src/api-report.unit.test.ts` (CLI-2234). Windows autocrlf +# line-ending rewriting would break that comparison — treat these files as +# binary (no line-ending normalization) so they stay byte-identical across +# platforms. +packages/config/api-report/** -text diff --git a/apps/cli/src/shared/functions/functions-config.ts b/apps/cli/src/shared/functions/functions-config.ts index 3f59cd8b7f..0c578eda46 100644 --- a/apps/cli/src/shared/functions/functions-config.ts +++ b/apps/cli/src/shared/functions/functions-config.ts @@ -1,7 +1,7 @@ import { basename } from "node:path"; import { Effect, type FileSystem, type Path } from "effect"; import type { LoadedCliConfig } from "@supabase/config/effect"; -import { loadCliConfig } from "@supabase/config/internal"; +import { loadCliConfig } from "@supabase/config/effect"; import { normalizeProjectId } from "./functions-docker.ts"; /** @@ -62,10 +62,10 @@ export const loadFunctionsCliConfig = Effect.fnUntraced(function* (input: { readonly goConfigCompat: FunctionsGoConfigCompat | undefined; }) { if (input.goConfigCompat === undefined) { - const loaded = yield* loadCliConfig(input.projectRoot, { - ...(input.projectRef === undefined ? {} : { projectRef: input.projectRef }), - goViperCompat: false, - }); + const loaded = yield* loadCliConfig( + input.projectRoot, + input.projectRef === undefined ? {} : { projectRef: input.projectRef }, + ); return { loaded, projectEnvValues: undefined, diff --git a/apps/cli/src/shared/output/normalize-error.unit.test.ts b/apps/cli/src/shared/output/normalize-error.unit.test.ts index 68ac197786..6adcdb132f 100644 --- a/apps/cli/src/shared/output/normalize-error.unit.test.ts +++ b/apps/cli/src/shared/output/normalize-error.unit.test.ts @@ -1,11 +1,7 @@ import { describe, expect, test } from "vitest"; import { Cause } from "effect"; import { CliError, Command } from "effect/unstable/cli"; -import { - CliConfigParseError, - CliProjectEnvParseError, - MissingCliConfigValueError, -} from "@supabase/config"; +import { CliConfigParseError, CliProjectEnvParseError } from "@supabase/config"; import { legacyBranchesCommand } from "../../legacy/commands/branches/branches.command.ts"; import { legacyNetworkRestrictionsCommand } from "../../legacy/commands/network-restrictions/network-restrictions.command.ts"; import { CliProjectHomeNotDirectoryError } from "../../next/config/cli-project-home.service.ts"; @@ -214,17 +210,6 @@ describe("normalizeCliError", () => { }); }); - test("MissingCliConfigValueError falls back to its bare tag as both code and message", () => { - const error = new MissingCliConfigValueError({ - configPath: "project_id", - }); - - expect(normalizeCliError(error)).toEqual({ - code: "MissingCliConfigValueError", - message: "MissingCliConfigValueError", - }); - }); - test("CliProjectHomeNotDirectoryError surfaces its tag as code with its own message", () => { const error = new CliProjectHomeNotDirectoryError({ message: ".supabase could not be created: a file exists at that path", diff --git a/apps/cli/src/shared/telemetry/error-actionability.ts b/apps/cli/src/shared/telemetry/error-actionability.ts index 777a1a5f7b..0dc1c6d9ce 100644 --- a/apps/cli/src/shared/telemetry/error-actionability.ts +++ b/apps/cli/src/shared/telemetry/error-actionability.ts @@ -940,7 +940,6 @@ const externalActionabilityByTag: Record = { // @supabase/config CliConfigParseError: () => actionability.invalidConfig, CliProjectEnvParseError: () => actionability.invalidConfig, - MissingCliConfigValueError: () => actionability.invalidConfig, DuplicateRemoteProjectIdError: () => actionability.invalidConfig, InvalidRemoteProjectIdError: () => actionability.invalidConfig, // A Management API project-config response that fails to map is a platform diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index 50b81a2098..3fefb21ac8 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -1,4 +1,15 @@ { "extends": "@tsconfig/bun/tsconfig.json", + "compilerOptions": { + // Lets `tsc` resolve `@supabase/config`'s exports-map `bun` condition + // straight to its `src/*.ts` sources (self-typed, no `.d.ts` needed) + // instead of `dist/*.d.ts`, which requires that package to be built + // first — see `packages/config/package.json`'s exports map and + // `packages/config/AGENTS.md`'s "Build" section (CLI-2234). This also + // affects any OTHER dependency whose own exports map declares a `bun` + // condition (e.g. `@supabase/pg-topo`) — see this package's AGENTS.md/PR + // notes for a known collision that surfaces there. + "customConditions": ["bun"] + }, "exclude": ["supabase", "src/shared/workers/stacks"] } diff --git a/packages/config/.npmignore b/packages/config/.npmignore new file mode 100644 index 0000000000..769812e808 --- /dev/null +++ b/packages/config/.npmignore @@ -0,0 +1,11 @@ +# Without this file, `npm pack`/`npm publish` fall back to the root +# `.gitignore` for this whole directory — and its bare `dist` line prunes +# `packages/config/dist/` from npm's packlist WALK before `package.json`'s +# `files` array is ever consulted, silently shipping a tarball with zero +# `dist/**` files despite `dist` being explicitly listed there (CLI-2234). +# This file's mere presence is what fixes it: once an `.npmignore` exists, +# npm uses it instead of consulting `.gitignore`. `files` in package.json +# still governs what actually ships; the one line below just restates, for +# npm's own ignore pass, the same test-file exclusion `files`'s +# `!src/**/*.test.ts` negation already expresses. +*.test.ts diff --git a/packages/config/AGENTS.md b/packages/config/AGENTS.md index 0f76b35230..31fc753a3d 100644 --- a/packages/config/AGENTS.md +++ b/packages/config/AGENTS.md @@ -5,26 +5,35 @@ Supabase project configuration package built on Effect V4 Schema — owns the ca ## Entrypoints -Four entrypoints plus two generated JSON Schema artifacts (see ADR 0009's 2026-08-24 decision for -the full rationale): +Six supported import paths total (see ADR 0009's 2026-08-24 decision for the full rationale): four +module entrypoints (`.`, `./io`, `./effect`, `./internal`) plus two generated JSON Schema +artifacts (`./schema.json`, `./project-schema.json`). -- `@supabase/config` (`.`) — pure, browser/edge-safe surface. The `CliConfigSchema` and - derived types, config encoding, sparse-config defaults, and error classes. No file IO, no - Effect-returning function, no `@effect/platform-*`/`node:`/`bun:` module anywhere in its - transitive import graph. +- `@supabase/config` (`.`) — pure, browser/edge-safe surface. `CliConfigSchema`/`ProjectConfigSchema` + and their derived types, config encoding, sparse-config defaults, the `ProjectConfig` converters + (`toProjectConfig`, `fromConfigDocument`, `fromApiProjectConfig`, …), and error classes. No file + IO, no Effect-returning function, no `@effect/platform-*`/`node:`/`bun:` module anywhere in its + transitive import graph. `fromConfigDocument` also accepts a `CliConfigWithRawPresence` pair (a + `CliConfig` alongside which keys were actually present in the source document) — presence matters + because the schema defaults every optional section, so the decoded `CliConfig` alone can't tell + "explicitly set to the default" from "never set" (ADR 0021). Call `unmappedApiFields` after + `fromApiProjectConfig` if you care whether this package version understood the response. - `@supabase/config/io` — a Promise-based file-IO facade for **external, non-Effect Node/Bun consumers only**. Resolved via package.json exports conditions (`bun`/`node`/`browser`/`default`). Has zero internal consumers by design — nothing inside this monorepo should import it. - `@supabase/config/effect` — the Effect-native superset. Re-exports everything from `.` plus the Effect-returning config-loading/saving programs, `CliConfigStore`/`cliConfigStoreLayer`, - project-environment resolution, and functions-manifest inference. -- `@supabase/config/internal` (CLI-2234) — NOT covered by semver. Exists solely for `apps/cli`'s - own Go-parity call sites and contract-guard tests: the internal-only `goViperCompat` typings - (`InternalLoadCliConfigOptions`/`InternalResolveCliConfigOptions`) and the otherwise-internal - registry data (`AUTH_HOOK_NAMES`, `unmappedSecretApiPaths`, `projectConfigMappingRows`, - `ProjectConfigMappingRow`, `ProjectConfigApiAttributes`, `ENV_CAPTURE_REGEX`). Unlike `./io`, - `apps/cli` IS an expected consumer of this subpath. Anything here can change or vanish in any - release. + project-environment resolution, and `inferFunctionsManifest` (discovers and validates + `supabase/functions/*` on disk). +- `@supabase/config/internal` (CLI-2234) — NOT covered by semver, and only `apps/cli` may import it + (enforced by `src/monorepo-import-contract.unit.test.ts`). Exists solely for `apps/cli`'s own + Go-parity call sites and contract-guard tests: `loadCliConfig`/`resolveCliConfigValue`/ + `resolveCliConfigSubtree` — the SAME runtime functions `./effect` exports, re-typed here to + additionally accept the internal-only `goViperCompat` option (`InternalLoadCliConfigOptions`/ + `InternalResolveCliConfigOptions`) — plus the otherwise-internal registry data + (`AUTH_HOOK_NAMES`, `unmappedSecretApiPaths`, `projectConfigMappingRows`, + `ProjectConfigMappingRow`, `ProjectConfigApiAttributes`, `ENV_CAPTURE_REGEX`). Anything here can + change or vanish in any release. - `@supabase/config/schema.json` — generated JSON Schema (draft 2020-12) for `CliConfig` (a `dist/` build output). - `@supabase/config/project-schema.json` (CLI-2234) — generated JSON Schema (draft 2020-12) for @@ -44,7 +53,8 @@ the full rationale): - `@supabase/config/internal` is for `apps/cli`'s own Go-parity call sites and contract-guard tests only — a symbol that needs the internal-only `goViperCompat` typings, or the internal registry data, imports it from there; every other symbol in the same import statement stays on - its public specifier (`.`/`./effect`). + its public specifier (`.`/`./effect`). Enforced: every `@supabase/config/internal` occurrence + outside this package must be under `apps/cli/`. - Never deep-import this package's internals (e.g. `@supabase/config/src/io.ts`). Only the six entrypoints above are supported import paths. @@ -64,27 +74,50 @@ surface must update that test deliberately — it is not meant to be a silent pa `pnpm --filter @supabase/config build` (or `pnpm run build` from this package) runs `scripts/build.ts`, in order: -1. Compiles `src/` to `dist/` (`tsc -p tsconfig.build.json`) — the `.js`/`.d.ts` output every - `dist`/`types`/`default` export condition points at. +1. Removes any stale `dist/` (a rename that leaves an orphaned compiled module behind must not + ship), then compiles `src/` to `dist/` (`tsc -p tsconfig.build.json`) — the `.js`/`.d.ts` output + every `dist`/`types`/`default` export condition points at. 2. Renders both generated JSON Schema artifacts (`dist/schema.json`, `dist/project-schema.json`) - from `toCliConfigJsonSchema()`/`toProjectConfigJsonSchema()`, formatted through `oxfmt`. -3. Runs a tree-shake probe: bundles a probe importing only `CliConfigSchema` from the compiled + from `toCliConfigJsonSchema()`/`toProjectConfigJsonSchema()`, post-processed (via + `scripts/json-schema-postprocess.ts`) to collapse Effect's non-finite-number `anyOf` encoding + back to a plain `number`/`integer` node and to add `$id`/`title`/`description`, then formatted + through `oxfmt`. +3. Verifies every `types`/non-`bun` `default` target (plus both JSON artifacts) declared in + package.json's `exports` map actually exists on disk. +4. Runs a tree-shake probe: bundles a probe importing only `CliConfigSchema` from the compiled `dist/index.js` for a `browser` target and asserts the output excludes registry-only code, proving the package.json `sideEffects: false` claim against real compiled output rather than - merely asserting it. -4. Syncs `api-report/` — a declarations-only build (`tsconfig.api-report.json`) mirrored into the - checked-in `api-report/` directory. `src/api-report.unit.test.ts` regenerates the same build and - diffs it against that mirror, so any type-surface change anywhere in `src/` shows up as a - reviewable `api-report/` diff instead of passing silently — commit that diff whenever it appears. -5. Runs a Node-consumer smoke test (from `apps/cli`, the one in-repo workspace that depends on this - package through a real `node_modules` link) that imports every entrypoint and JSON artifact - through the `node` export condition, catching a broken `exports` map or dist resolution that a - `tsc`-only build wouldn't. + merely asserting it — plus a positive-control probe (bundling `projectConfigMappingRows` from + `dist/internal.js`) proving the registry-only marker is actually detectable by this bundling + method before trusting its absence elsewhere as meaningful. +5. Syncs `api-report/` — a declarations-only build (`tsconfig.api-report.json`) built into a + scratch directory and atomically swapped into the checked-in `api-report/` directory (never a + partial write). `src/api-report.unit.test.ts` regenerates the same build and diffs it against + that mirror, so any type-surface change anywhere in `src/` shows up as a reviewable + `api-report/` diff instead of passing silently — commit that diff whenever it appears. Run + `pnpm run api-report:update` for just this step (declaration emit + sync only) without the rest + of the build. +6. Runs a pack-and-install smoke test: `npm pack`s the real publish tarball (governed by `files`/ + `.npmignore` — the exact thing `npm publish` would ship), extracts it into a fresh, isolated + consumer project, symlinks in the real, already pnpm-resolved runtime deps (network-free), and + imports every entrypoint and JSON artifact through a real `node` process — catching `files`/ + `exports` drift a workspace-link smoke test or a `tsc`-only build would miss entirely. `dist/` is gitignored and rebuilt on demand; `api-report/` is the one build output that is checked in. Re-run the build and commit the resulting `api-report/` diff whenever a change touches this package's public type surface. +### Publishing the tarball (CLI-2234) + +A `.npmignore` file exists at this package's root — even though its own rules exclude almost +nothing `files` in package.json doesn't already exclude — because npm's packlist walk otherwise +falls back to the ROOT `.gitignore` for this whole directory, and that file's bare `dist` line +prunes `packages/config/dist/` from the walk entirely before `files` is ever consulted, silently +shipping a tarball with zero `dist/**` files. An `.npmignore`'s mere presence (regardless of +content) stops npm from consulting `.gitignore` at all; `files` still governs what actually ships. +Verify `npm pack --dry-run` and `pnpm pack --dry-run` produce equivalent content after touching +either file. + ## Testing Run tests from this package with `bun --bun vitest run --project unit` (plain `node` vitest is @@ -92,9 +125,15 @@ broken here). Always run the relevant unit tests for what you changed before con done. Besides ordinary behavioral coverage, three tests enforce this package's own contracts and must stay green after any entrypoint or type-surface change: -- `src/entrypoint-purity.unit.test.ts` — the pure-graph invariant above, plus pinned export-name +- `src/entrypoint-purity.unit.test.ts` — the pure-graph invariant above (also walked separately for + `src/io-browser.ts`, the `browser` condition target for `./io`), plus pinned export-name snapshots for `.`/`./effect`/`./internal` and the package.json `exports` map shape. - `src/api-report.unit.test.ts` — the checked-in `api-report/` mirror described above. -- `src/monorepo-import-contract.unit.test.ts` — the "Monorepo import rule" above (no internal - `./io` consumer, no deep `@supabase/config/src/*` import), scanning `apps/` and `packages/` - while excluding this package's own directory. +- `src/monorepo-import-contract.unit.test.ts` — the "Monorepo import rule" above: no internal + `./io` consumer, no deep `@supabase/config/src/*` import, and no `@supabase/config/internal` + import outside `apps/cli/` — scanning `apps/` and `packages/` while excluding this package's own + directory. +- `src/lib/resolve.unit.test.ts` — behavioral coverage of the public sync resolvers. +- `scripts/json-schema-postprocess.unit.test.ts` / `scripts/build-artifacts.unit.test.ts` — the + JSON Schema post-processing `renderJsonSchema` applies (non-finite-number `anyOf` collapse, + `$id`/`title`/`description`), the second against the real generated documents. diff --git a/packages/config/LICENSE b/packages/config/LICENSE new file mode 100644 index 0000000000..f1802dffa8 --- /dev/null +++ b/packages/config/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Supabase, Inc. and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/config/README.md b/packages/config/README.md index 880f895490..262b4f741a 100644 --- a/packages/config/README.md +++ b/packages/config/README.md @@ -28,20 +28,20 @@ It owns: Use the `Cli*` prefix for the local checkout side and a bare `Project*` name for the hosted Supabase project. Config-value helpers follow the config family regardless of their inputs -(`resolveCliConfigValue`, `MissingCliConfigValueError`). See -[ADR 0020](../../docs/adr/0020-config-naming-vocabulary.md) and -[docs/cli-config-loading.md](./docs/cli-config-loading.md) for the full vocabulary. +(`resolveCliConfigValue`). See +[ADR 0020](https://github.com/supabase/cli/blob/develop/docs/adr/0020-config-naming-vocabulary.md) +and [docs/cli-config-loading.md](./docs/cli-config-loading.md) for the full vocabulary. ## Entrypoints -| Entrypoint | Contents | Constraints | -| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `.` | `CliConfigSchema`/`ProjectConfigSchema` and their types, config encoding, sparse-config defaults, the `ProjectConfig` converters, error classes | Pure — browser/edge/Node/Bun-safe. No file IO, no Effect-returning function, no `@effect/platform-*`/`node:`/`bun:` module anywhere in its transitive import graph | -| `./io` | A Promise-based facade over the same file-IO/Effect programs as `./effect` | Resolved automatically via package.json export conditions (`bun`/`node`/`browser`/`default`). Requires one of the optional platform peers — `@effect/platform-bun` under Bun, `@effect/platform-node` under Node — installed at runtime; see "Installing" below for the failure mode when it's missing | -| `./effect` | Effect-native superset of `.`: `CliConfigStore`/`cliConfigStoreLayer`, config loading/saving, project-environment resolution, functions-manifest inference | Requires `effect`; requires a platform peer only for the file-IO programs, exactly like `./io` | -| `./internal` | `apps/cli`'s Go-parity typings (`goViperCompat`) and the internal API-mapping registry data | **Not covered by semver.** Exists solely for the Supabase CLI's own use and its contract-guard tests — any export here (its existence, shape, or behavior) can change or vanish in any release without notice | -| `./schema.json` | Generated JSON Schema for `CliConfig` | Draft 2020-12 — a language-agnostic contract for non-TypeScript consumers | -| `./project-schema.json` | Generated JSON Schema for `ProjectConfig` | Draft 2020-12 — a language-agnostic contract for non-TypeScript consumers | +| Entrypoint | Contents | Constraints | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `.` | `CliConfigSchema`/`ProjectConfigSchema` and their types, config encoding, sparse-config defaults, the `ProjectConfig` converters, error classes | Pure — browser/edge/Node/Bun-safe. No file IO, no Effect-returning function, no `@effect/platform-*`/`node:`/`bun:` module anywhere in its transitive import graph | +| `./io` | A Promise-based facade over the same file-IO/Effect programs as `./effect` | Resolved automatically via package.json export conditions (`bun`/`node`/`browser`/`default`). Requires one of the optional platform peers — `@effect/platform-bun` under Bun, `@effect/platform-node` under Node — installed at runtime; see "Installing" below for the failure mode when it's missing | +| `./effect` | Effect-native superset of `.`: `CliConfigStore`/`cliConfigStoreLayer`, config loading/saving, project-environment resolution, functions-manifest inference | Requires `effect`; requires a platform peer only for the file-IO programs, exactly like `./io` | +| `./internal` | `ENV_CAPTURE_REGEX`, `AUTH_HOOK_NAMES`, `unmappedSecretApiPaths`, `projectConfigMappingRows`, the `ProjectConfigMappingRow`/`ProjectConfigApiAttributes`/`InternalLoadCliConfigOptions` types, plus `loadCliConfig`/`resolveCliConfigValue`/`resolveCliConfigSubtree` re-typed to additionally accept the internal-only `goViperCompat` option — the SAME runtime functions `./effect` exports, not independent implementations | **Not covered by semver, and only `apps/cli` may import it** (enforced by `src/monorepo-import-contract.unit.test.ts`). Exists solely for the Supabase CLI's own use and its contract-guard tests — any export here (its existence, shape, or behavior) can change or vanish in any release without notice | +| `./schema.json` | Generated JSON Schema for `CliConfig` | Draft 2020-12 — a language-agnostic contract for non-TypeScript consumers | +| `./project-schema.json` | Generated JSON Schema for `ProjectConfig` | Draft 2020-12 — a language-agnostic contract for non-TypeScript consumers | A few things worth calling out beyond the table: @@ -51,22 +51,112 @@ A few things worth calling out beyond the table: In a browser bundle, `./io` resolves to a stub whose exports throw a curated error only when actually invoked (never at import time), directing you back to `.`. - **`./effect` deliberately shadows two names from `.`.** `resolveCliConfigValue` and - `resolveCliConfigSubtree` exist on both `.` (plain, synchronous — throws instead of failing an - `Effect`) and `./effect` (Effect-typed). Because explicit named exports win over a star - re-export of the same name, importing from `./effect` always gets you the Effect-typed variant, - even though `./effect` also re-exports everything else from `.` verbatim. + `resolveCliConfigSubtree` exist on both `.` (plain, synchronous) and `./effect` (Effect-typed). + Neither has a failure mode — an unresolved `env(NAME)` reference is preserved verbatim rather + than rejected or thrown. Because explicit named exports win over a star re-export of the same + name, importing from `./effect` always gets you the Effect-typed variant, even though `./effect` + also re-exports everything else from `.` verbatim. - **`./internal` is genuinely unstable.** It is not merely undocumented — it is explicitly outside this package's compatibility promise (see "Semver and the published contract" below). +Bundle size, measured against the full `.` surface: ~390 KB minified (~110 KB minified+gzipped), +most of which is `effect`'s own schema/validation engine — an app that already bundles `effect` +adds closer to ~115 KB minified for this package's own code on top. A consumer that only needs the +shape contract, not runtime validation, can use `@supabase/config/schema.json`/`project-schema.json` +with any JSON Schema validator instead of importing this package at all. + +### Exports at a glance (`.`) + +Every runtime and type export of the pure `.` entrypoint, grouped by category: + +**Schema/types** + +| Export | What it is | +| ------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | +| `CliConfigSchema` | The `CliConfig` Effect schema (decode/encode/validate). | +| `CliConfig` | The decoded `CliConfig` type. | +| `CliConfigJson` | The encoded (pre-decode) `CliConfig` JSON shape. | +| `ConfigFormat` | `"toml" \| "json"`. | +| `LoadedCliConfig` | The shape a successful `loadCliConfig`/`loadCliConfigFile`/`saveCliConfig` call returns. | +| `LoadCliConfigOptions` | Public options accepted by `loadCliConfig`/`loadCliConfigFile`. | +| `CliConfigValueOrigin` / `CliConfigValueSource` | Per-leaf provenance (`"local"`/`"remote"`/`"environment"`). | +| `SaveCliConfigOptions` | Options accepted by `saveCliConfig`. | +| `FunctionsManifest` / `ResolvedFunctionConfig` | The shape `inferFunctionsManifest` (`./effect`/`./io`) returns. | +| `LoadCliProjectEnvironmentOptions` / `CliProjectEnvironment` | Options for, and the merged env-map shape returned by, `loadCliProjectEnvironment`. | +| `CliProjectPaths` | The discovered project paths shape. | +| `ProjectConfig` | The hosted-project subset shape. | +| `ProjectConfigSchema` | Runtime-validating companion to `ProjectConfig` (see below). | +| `CliConfigWithRawPresence` | A `CliConfig` + raw-presence pair `fromConfigDocument` also accepts (ADR 0021). | +| `ReadonlyJsonValue` | A JSON-safe, deeply readonly value type. | +| `ToProjectConfigSource` | `toProjectConfig`'s discriminated `{ cliConfig }`/`{ apiResponse }` input. | + +**Env resolution & value provenance** + +| Export | What it is | +| --------------------------------------------------- | ------------------------------------------------------------------------------- | +| `resolveCliConfigValue` / `resolveCliConfigSubtree` | Resolve/redact `env(NAME)` leaves; plain sync here, Effect-typed on `./effect`. | +| `ResolvedCliConfigValue` | The resolved/redacted shape those two return. | +| `cliConfigValueSourceAt` | Looks up a `LoadedCliConfig.valueOrigins` entry for one path. | + +**Encoding** + +| Export | What it is | +| ------------------------------------------------- | ------------------------------------- | +| `encodeCliConfigToJson` / `encodeCliConfigToToml` | Serialize a `CliConfig` back to text. | + +**Defaults & sparse diff** + +| Export | What it is | +| ----------------------------------------- | --------------------------------------------------- | +| `getDefaultCliConfig` | The schema-derived default `CliConfig`. | +| `omitDefaultValues` / `subtractCliConfig` | Strip-default helpers over an `EffectiveConfig`. | +| `EffectiveConfig` / `SparseCliConfig` | The operand/result types for the two helpers above. | + +**ProjectConfig converters** + +| Export | What it is | +| ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `toProjectConfig` | Dispatcher over the two normalizers below. | +| `fromConfigDocument` | Projects a `CliConfig` (or `CliConfigWithRawPresence` pair) onto `ProjectConfig`. | +| `fromApiProjectConfig` | Translates a Management API v2 project-config response into `ProjectConfig`. | +| `attachApiResponse` | Re-attaches `_apiResponse` after a round-trip that dropped it. | +| `unmappedApiFields` | The API fields this package version doesn't map — call after `fromApiProjectConfig` if you care whether it understood the response. | +| `comparableProjectConfigPaths` / `isComparableProjectConfigPath` | Registry-derived field paths a diff consumer can safely compare. | + +**JSON Schema generators + URLs** + +| Export | What it is | +| ----------------------------------------------------- | --------------------------------------------------------- | +| `toCliConfigJsonSchema` / `toProjectConfigJsonSchema` | Render each shape's JSON Schema (draft 2020-12) document. | +| `CLI_CONFIG_SCHEMA_URL` / `PROJECT_CONFIG_SCHEMA_URL` | The `$id`/`$schema` URL for each generated document. | + +**Errors** + +| Export | What it is | +| --------------------------------------------------------------- | ----------------------------------------------------------------------- | +| `CliConfigParseError` | A malformed `supabase/config.toml`/`.json`. | +| `CliProjectEnvParseError` | A malformed `.env`/`.env.local` file. | +| `DuplicateRemoteProjectIdError` / `InvalidRemoteProjectIdError` | A `[remotes.*]` block problem. | +| `ProjectConfigParseError` | A Management API v2 response, or a caller argument, that failed to map. | + +**Constants** + +| Export | What it is | +| -------------------------------------------------------------------------------------------------- | ---------------------------------------- | +| `edgeFunctionDenoConfigFileName` / `edgeFunctionEntrypointFileName` / `edgeFunctionsDirectoryName` | Edge Functions on-disk layout filenames. | + ## Installing This package is not yet published (`private: true`; publishing is tracked separately). Once it is, install it alongside the peers your runtime needs: ```sh -npm install @supabase/config effect +npm install @supabase/config effect@rc ``` +This package requires Effect 4.x, currently only published under the `rc` dist-tag — `effect@latest` +still resolves to 3.x, which will not satisfy this package's peer range. + `effect` is a required peer dependency. `@effect/platform-bun` and `@effect/platform-node` are optional peers — install exactly one, matching your runtime, if you use `./io` or `./effect`'s file-IO programs: @@ -96,14 +186,14 @@ Effect: onto the hosted sections (`api`, `auth`, `db`, `realtime`, `storage`, `workers`, `experimental`), omitting every `x-secret` leaf and canonicalizing duration/byte-size fields the same way the API side would. **Not a verbatim rendering of the document** — see - [ADR 0021](../../docs/adr/0021-projectconfig-convergence-semantics.md) for the push-precedence + [ADR 0021](https://github.com/supabase/cli/blob/develop/docs/adr/0021-projectconfig-convergence-semantics.md) for the push-precedence and sentinel-pruning semantics this applies. - `fromApiProjectConfig(input)` — translation of a Management API v2 project-config response (the full envelope, its `data` object, or bare `data.attributes`) via registry-driven renames, boolean inversions, and unit conversions; lenient toward API keys this package version doesn't yet know, and never reports a secret field's plaintext. Attaches a deep-frozen copy of the raw attributes as a non-enumerable `_apiResponse` (invisible to encodes and structural walks, never - persisted — see [ADR 0019](../../docs/adr/0019-config-api-response-passthrough.md)). Throws + persisted — see [ADR 0019](https://github.com/supabase/cli/blob/develop/docs/adr/0019-config-api-response-passthrough.md)). Throws `ProjectConfigParseError` on malformed input. - `unmappedApiFields(projectConfig)` — the API fields this package version doesn't map, derived from the same mapping registry. @@ -136,6 +226,11 @@ if (result.issues) { } ``` +> **Caution:** a key that isn't one of the seven hosted sections (or a field this schema version +> doesn't yet model) does not fail validation — it is silently dropped from `result.value` rather +> than rejected or preserved. Keep using your own `candidate` afterward if you need the original, +> unfiltered value. + `ProjectConfigSchema` is a full Effect `Schema.Codec` (usable with `Schema.decodeUnknownEffect` and friends) **and** a spec-compliant [Standard Schema v1](https://standardschema.dev/) object (`~standard`) at the same time — `Schema.toStandardSchemaV1` augments and returns the same value @@ -164,6 +259,18 @@ specific, narrower validation contract — what it does and does not promise: ## `./io`'s error contract +`./io` re-exports this package's entire pure surface (`export * from "."`, the same way `./effect` +does) alongside its seven Promise-returning functions, so one import from `@supabase/config/io` is +enough — no separate import from `.` needed to also name an error class, `CliConfigSchema`, or the +two synchronous resolvers (the only non-Promise members `./io` exports). + +`loadCliConfig`, `findCliProjectRoot`, `findCliProjectPaths`, and `loadCliProjectEnvironment` +resolve to `null` — they never reject — when there is simply no project or config file to find. +Rejection always means something was found but couldn't be read or understood (malformed config, +malformed env file, an OS-level failure); it never means "missing". `loadCliConfigFile` and +`saveCliConfig` have no such "missing" case (they name an exact path), so they only ever resolve or +reject. + A rejected `loadCliConfig`, `loadCliConfigFile`, or `saveCliConfig` call from `./io` can reject with any of: @@ -175,14 +282,27 @@ with any of: `FileSystem` service Every one of these is a plain class (an Effect `Data.TaggedError`), so a catch block can -distinguish them with `instanceof`: +distinguish them with `instanceof`. Each carries structured fields instead of a prose message — +`error.message` is empty on every one of them except `ProjectConfigParseError` (the only class that +always sets it). Build user-facing text from the typed fields instead: `CliConfigParseError.path`/ +`.format`/`.cause`, `CliProjectEnvParseError.path`/`.line`, `DuplicateRemoteProjectIdError.message`/ +`InvalidRemoteProjectIdError.message` (these two DO set `message`, verbatim from Go), and +`ProjectConfigParseError.message`/`.reason`/`.apiPath`/`.detail`. A `CliConfigParseError`'s `.cause` +is typically a schema issue that itself carries line/column location info worth surfacing. ```ts -import { CliConfigParseError, DuplicateRemoteProjectIdError } from "@supabase/config"; -import { loadCliConfig } from "@supabase/config/io"; +import { + CliConfigParseError, + DuplicateRemoteProjectIdError, + loadCliConfig, +} from "@supabase/config/io"; try { const loaded = await loadCliConfig(process.cwd()); + if (loaded === null) { + // no supabase/config.toml or config.json in this project — not an error + return; + } } catch (error) { if (error instanceof CliConfigParseError) { // malformed config.toml/config.json @@ -197,7 +317,7 @@ try { The runtime export surface of `.`, `./io`, and `./effect`, plus the two generated JSON Schema artifacts (`./schema.json`, `./project-schema.json`), is this package's published contract. -`./internal` carries no such guarantee. See [AGENTS.md](./AGENTS.md) for how that contract is +`./internal` carries no such guarantee. See [AGENTS.md](https://github.com/supabase/cli/blob/develop/packages/config/AGENTS.md) for how that contract is enforced (export-surface snapshots and a checked-in API report). ## Usage @@ -259,4 +379,4 @@ pnpm run test # Run tests pnpm run build # Compile dist/, generate schema.json/project-schema.json, sync api-report/ ``` -See [AGENTS.md](./AGENTS.md) for the build pipeline and contract-enforcement details. +See [AGENTS.md](https://github.com/supabase/cli/blob/develop/packages/config/AGENTS.md) for the build pipeline and contract-enforcement details. diff --git a/packages/config/api-report/effect.d.ts b/packages/config/api-report/effect.d.ts index 62bd52d45d..8d86d05ba5 100644 --- a/packages/config/api-report/effect.d.ts +++ b/packages/config/api-report/effect.d.ts @@ -1,7 +1,7 @@ export * from "./index.ts"; import type { Effect } from "effect"; import type { LoadCliConfigOptions } from "./config-document.ts"; -import type { ResolvedCliConfigValue, ResolveCliConfigOptions } from "./lib/resolve.ts"; +import type { ResolvedCliConfigValue } from "./lib/resolve.ts"; import * as io from "./io.ts"; import type { CliProjectEnvironment } from "./project.ts"; export { configJsonPath, configTomlPath, saveCliConfig } from "./io.ts"; @@ -26,14 +26,13 @@ export { loadDotEnvFile, loadCliProjectEnvironment } from "./project.ts"; * `resolveCliConfigValue`/`resolveCliConfigSubtree` on this subpath — the * Effect-typed variant wins on `./effect`; the sync variant lives on `.`. * - * Narrowed to the public `ResolveCliConfigOptions` (no `goViperCompat`) for - * the same reason as {@link loadCliConfig} above; `@supabase/config/internal` - * re-exports these same runtime functions typed to additionally show - * `goViperCompat`. + * Narrowed to no options parameter (no `goViperCompat`) for the same reason + * as {@link loadCliConfig} above; `@supabase/config/internal` re-exports + * these same runtime functions typed to additionally show `goViperCompat`. */ -export declare const resolveCliConfigValue: (value: T, cliProjectEnv: Pick, configPath: string, options?: ResolveCliConfigOptions) => Effect.Effect>; +export declare const resolveCliConfigValue: (value: T, cliProjectEnv: Pick, configPath: string) => Effect.Effect>; /** See {@link resolveCliConfigValue}'s doc comment for the shadowing and narrowing rationale. */ -export declare const resolveCliConfigSubtree: (value: T, cliProjectEnv: Pick, pathPrefix: string, options?: ResolveCliConfigOptions) => Effect.Effect>; +export declare const resolveCliConfigSubtree: (value: T, cliProjectEnv: Pick, pathPrefix: string) => Effect.Effect>; export { findCliProjectPaths, findCliProjectRoot } from "./paths.ts"; export { cliConfigStoreLayer } from "./cli-config.layer.ts"; export { CliConfigStore } from "./cli-config.service.ts"; diff --git a/packages/config/api-report/errors.d.ts b/packages/config/api-report/errors.d.ts index 6a60d6c6d2..016a60e317 100644 --- a/packages/config/api-report/errors.d.ts +++ b/packages/config/api-report/errors.d.ts @@ -124,13 +124,6 @@ export declare class CliProjectEnvParseError extends CliProjectEnvParseError_bas readonly line: number; }> { } -declare const MissingCliConfigValueError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { - readonly _tag: "MissingCliConfigValueError"; -} & Readonly; -export declare class MissingCliConfigValueError extends MissingCliConfigValueError_base<{ - readonly configPath: string; -}> { -} declare const DuplicateRemoteProjectIdError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { readonly _tag: "DuplicateRemoteProjectIdError"; } & Readonly; diff --git a/packages/config/api-report/index.d.ts b/packages/config/api-report/index.d.ts index dc1c4a312b..21701d9e24 100644 --- a/packages/config/api-report/index.d.ts +++ b/packages/config/api-report/index.d.ts @@ -7,14 +7,14 @@ * `@supabase/config/effect`. */ export { CliConfigSchema, toCliConfigJsonSchema, type CliConfig, type CliConfigJson, } from "./base.ts"; -export { CliConfigParseError, CliProjectEnvParseError, DuplicateRemoteProjectIdError, InvalidRemoteProjectIdError, MissingCliConfigValueError, ProjectConfigParseError, } from "./errors.ts"; +export { CliConfigParseError, CliProjectEnvParseError, DuplicateRemoteProjectIdError, InvalidRemoteProjectIdError, ProjectConfigParseError, } from "./errors.ts"; export type { ConfigFormat } from "./config-format.ts"; export { type LoadedCliConfig, type LoadCliConfigOptions, type CliConfigValueOrigin, type CliConfigValueSource, type SaveCliConfigOptions, encodeCliConfigToJson, encodeCliConfigToToml, cliConfigValueSourceAt, } from "./config-document.ts"; export { edgeFunctionDenoConfigFileName, edgeFunctionEntrypointFileName, edgeFunctionsDirectoryName, type FunctionsManifest, type ResolvedFunctionConfig, } from "./functions-manifest-model.ts"; export type { LoadCliProjectEnvironmentOptions, CliProjectEnvironment } from "./project.ts"; -export { type ResolvedCliConfigValue, type ResolveCliConfigOptions, resolveCliConfigValue, resolveCliConfigSubtree, } from "./lib/resolve.ts"; +export { type ResolvedCliConfigValue, resolveCliConfigValue, resolveCliConfigSubtree, } from "./lib/resolve.ts"; export type { CliProjectPaths } from "./paths.ts"; -export { CLI_CONFIG_SCHEMA_URL } from "./schema-metadata.ts"; +export { CLI_CONFIG_SCHEMA_URL, PROJECT_CONFIG_SCHEMA_URL } from "./schema-metadata.ts"; export { type EffectiveConfig, type SparseCliConfig, getDefaultCliConfig, omitDefaultValues, subtractCliConfig, } from "./sparse.ts"; export { type CliConfigWithRawPresence, type ProjectConfig, type ReadonlyJsonValue, type ToProjectConfigSource, attachApiResponse, comparableProjectConfigPaths, fromApiProjectConfig, fromConfigDocument, isComparableProjectConfigPath, toProjectConfig, unmappedApiFields, } from "./project-config/project-config.ts"; export { ProjectConfigSchema, toProjectConfigJsonSchema } from "./project-config/project-schema.ts"; diff --git a/packages/config/api-report/internal.d.ts b/packages/config/api-report/internal.d.ts index 5a5dd59967..22bdbf08ee 100644 --- a/packages/config/api-report/internal.d.ts +++ b/packages/config/api-report/internal.d.ts @@ -2,7 +2,16 @@ * NOT covered by semver. This subpath exists solely for `apps/cli`'s own use * and its contract-guard tests — every export here (its existence, its shape, * its behavior) can change or vanish in any release without notice. External - * consumers must use `.`, `./effect`, or `./io` instead. + * consumers must use `.`, `./effect`, or `./io` instead; only `apps/cli` may + * import `@supabase/config/internal` (enforced by + * `src/monorepo-import-contract.unit.test.ts`). + * + * `loadCliConfig`/`resolveCliConfigValue`/`resolveCliConfigSubtree` below are + * the SAME runtime functions `./effect` exports, just re-typed here to widen + * their options parameter to the internal-only, Go-parity `goViperCompat` + * knob (`InternalLoadCliConfigOptions`/`InternalResolveCliConfigOptions`) — + * this module otherwise only re-exports types and registry data, not + * independent implementations. */ export { ENV_CAPTURE_REGEX } from "./lib/env.ts"; export { AUTH_HOOK_NAMES, unmappedSecretApiPaths } from "./project-config/registry-auth.ts"; @@ -10,5 +19,5 @@ export { projectConfigMappingRows } from "./project-config/registry.ts"; export { type ProjectConfigMappingRow } from "./project-config/registry-row.ts"; export { type ProjectConfigApiAttributes } from "./project-config/api-attributes.ts"; export { type InternalLoadCliConfigOptions } from "./config-document.ts"; -export { type InternalResolveCliConfigOptions, resolveCliConfigValue, resolveCliConfigSubtree, } from "./project.ts"; -export { loadCliConfig, loadCliConfigFile } from "./io.ts"; +export { resolveCliConfigValue, resolveCliConfigSubtree } from "./project.ts"; +export { loadCliConfig } from "./io.ts"; diff --git a/packages/config/api-report/lib/resolve.d.ts b/packages/config/api-report/lib/resolve.d.ts index cfe1104f76..0c6a805760 100644 --- a/packages/config/api-report/lib/resolve.d.ts +++ b/packages/config/api-report/lib/resolve.d.ts @@ -8,24 +8,22 @@ export type ResolvedCliConfigValue = T extends string ? ResolvedString : T ex } : T extends object ? { readonly [K in keyof T]: ResolvedCliConfigValue; } : T; -/** - * Currently empty: this package's one `resolveCliConfigValue`/ - * `resolveCliConfigSubtree` option (`goViperCompat`) is internal-only — see - * {@link InternalResolveCliConfigOptions} in `../project.ts`, exported from - * `@supabase/config/internal`. Kept as a named type (rather than removed - * entirely) so the public sync resolvers below have a stable options - * parameter to extend if a public knob is ever added. - */ -export interface ResolveCliConfigOptions { -} export declare function toPathSegments(path: string): ReadonlyArray; /** * Shared by the plain sync resolvers below and `../project.ts`'s * Effect-typed `resolveCliConfigValue`/`resolveCliConfigSubtree` (which wrap * this in `Effect.sync` and additionally accept the internal-only * `goViperCompat` option). + * + * Declared as an overload pair rather than a single generic signature: the + * body's `unknown`-typed implementation signature is what lets + * `interpolateValue`/`redactValue` (both genuinely `unknown -> unknown`, + * since the recursion branches on runtime shape, not on `T`) flow straight + * through to the return without an `as` cast — callers only ever see the + * generic overload above, which resolves `T` from the argument and returns + * `ResolvedCliConfigValue` directly. */ -export declare function resolveCliConfigValueAtPath(value: unknown, cliProjectEnv: Pick, path: ReadonlyArray, goViperCompat: boolean): unknown; +export declare function resolveCliConfigValueAtPath(value: T, cliProjectEnv: Pick, path: ReadonlyArray, goViperCompat: boolean): ResolvedCliConfigValue; /** * Plain synchronous counterpart of `../project.ts`'s Effect-typed * `resolveCliConfigValue`, exported from `.` under the same name — `./effect` @@ -36,8 +34,13 @@ export declare function resolveCliConfigValueAtPath(value: unknown, cliProjectEn * a caller that already has a project's env values but not the full * `CliProjectEnvironment` shape (e.g. `paths`/`loadedPaths`/`sources`) can pass * `{ values }` directly instead of threading through the whole loaded object. + * + * Has no options parameter: this package's one resolver knob (`goViperCompat`) + * is internal-only — see `InternalResolveCliConfigOptions` in `../project.ts`, + * exported from `@supabase/config/internal`. Adding a public knob later is a + * non-breaking, additive change. */ -export declare function resolveCliConfigValue(value: T, cliProjectEnv: Pick, configPath: string, _options?: ResolveCliConfigOptions): ResolvedCliConfigValue; +export declare function resolveCliConfigValue(value: T, cliProjectEnv: Pick, configPath: string): ResolvedCliConfigValue; /** See {@link resolveCliConfigValue}'s doc comment for why `cliProjectEnv` only needs `.values`. */ -export declare function resolveCliConfigSubtree(value: T, cliProjectEnv: Pick, pathPrefix: string, _options?: ResolveCliConfigOptions): ResolvedCliConfigValue; +export declare function resolveCliConfigSubtree(value: T, cliProjectEnv: Pick, pathPrefix: string): ResolvedCliConfigValue; export {}; diff --git a/packages/config/api-report/project-config/project-schema.d.ts b/packages/config/api-report/project-config/project-schema.d.ts index 85e3e5d761..2a51a62fca 100644 --- a/packages/config/api-report/project-config/project-schema.d.ts +++ b/packages/config/api-report/project-config/project-schema.d.ts @@ -1,3 +1,99 @@ +/** + * Runtime companion to {@link ProjectConfig} (`./project-config.ts`) — a + * schema that VALIDATES the same sparse hosted-section overlay + * `ProjectConfig` only describes at compile time. Derived from + * {@link CliConfigSchema} (`../base.ts`), never hand-declared, so the two can + * never independently drift: every leaf type, annotation, and leaf-level + * check traces back to the exact schema `base.ts` decodes a config document + * with. + * + * Derivation, in order: + * + * 1. {@link hostedSectionsStruct} picks the seven {@link HOSTED_SECTION_KEYS} + * fields off `CliConfigSchema.fields` and rebuilds a fresh `Schema.Struct` + * from them — the same field schemas `CliConfigSchema` itself embeds, not + * copies. + * 2. `SchemaAST.toType` strips every encoding/transformation (decoding + * defaults, `env()` deferred substitution, …), leaving the DECODED shape — + * exactly what `ProjectConfig` describes; a `ProjectConfig` value is never + * re-encoded. + * 3. {@link toDeepOptionalHostedAst} then recursively rebuilds the result: + * - In every `Objects` node (struct OR record), drops any + * `PropertySignature`/`IndexSignature` whose value AST carries the + * `x-secret` annotation (ADR 0019 rule 5 — `fromConfigDocument`/ + * `fromApiProjectConfig` never populate a secret leaf either), the same + * detection `../lib/secret-paths.ts`'s own walk uses. A container whose + * value type consists ENTIRELY of secret leaves (`db.vault`, a + * `Record`) ends up an empty `Objects` node this way + * (no surviving properties or index signatures) — `SchemaAST`'s own + * documented behavior for that shape is "accepts any value except + * `null`/`undefined`", the closest a schema can get to "this container + * held nothing but secrets, so nothing concrete is left to validate + * here" without special-casing an empty-object type JSON Schema has no + * way to express either. Two OTHER hosted-section leaves land on that + * same empty-`Objects` shape for an unrelated reason: + * `storage.analytics.buckets.*` and `storage.vector.buckets.*` are + * already `Schema.Struct({})` at the SOURCE level (`../storage.ts`) — + * genuinely empty structs, untouched by this walk's secret-stripping. + * - Wraps every SURVIVING property in `optionalKey` (via + * {@link toOptionalAst}), recursing into its type — mirroring + * `DeepPartial`'s `{ readonly [K in keyof T]?: DeepPartial }` + * mapped type (`../sparse.ts`) at every object level reached, and + * recursing the same way into index-signature VALUE types (matching + * `DeepPartial`'s recursion into a `Record`'s value type — + * `Record` deep-partializes to `Record>`, not `X` verbatim). + * - Leaves an `Arrays` node completely untouched, INCLUDING its element + * types: `DeepPartial` special-cases arrays to pass `T` through + * verbatim rather than partializing element types (`../sparse.ts`), and + * no `x-secret` leaf sits inside an array anywhere in this schema + * (`../lib/secret-paths.ts`'s own docstring), so there is nothing this + * walk would otherwise need to change there anyway. + * - Strips every `checks` array attached DIRECTLY to an `Objects` node — + * the cross-field business-rule refinements this repo attaches with + * `.check()` on a whole struct (`requiredWhenEnabled` in + * `../auth/email.ts`/`../auth/providers.ts`, `validateSmsProviderSwitch` + * in `../auth/sms.ts`) encode invariants a deliberately sparse overlay + * cannot generally satisfy — e.g. `{ auth: { email: { smtp: { enabled: + * true } } } }` with no `host` yet is a legal, if incomplete, + * `ProjectConfig` fragment, but `requiredWhenEnabled("host", ...)` would + * reject it. Every LEAF-level check survives untouched, since it lives + * on a non-`Objects` node — today that's only `workers.*.instances`'s + * `Schema.Number.check(isInt(), isGreaterThanOrEqualTo(0))` and the + * `[workers]` record's own key pattern (`Schema.isPattern(...)` on + * `workerName`, `../workers.ts`). There is no port-range (or other + * numeric-bound) leaf check anywhere in this schema today. + * - Recurses into `Union` members (e.g. `storage.file_size_limit`'s + * `Schema.Union([String, Number])`, and every `Schema.Literals`-backed + * enum, which V4 also compiles to a `Union`), so a secret-bearing or + * object-shaped member nested inside one would still be reached. Every + * other node kind (every leaf: `String`, `Number`, `Boolean`, + * `Literal`, …) is returned unchanged — there is nothing further to + * drop or partialize on a leaf. This module's own AST node kinds are + * enumerated explicitly, via each class's PUBLIC constructor, rather + * than through a generic `.recur()`-style mechanism: unlike + * `.repos/effect`'s vendored source, the installed `effect` release's + * own `AST#recur` is `@internal` (absent from its published `.d.ts`), + * so a truly generic fallback isn't available through the public API + * surface this package is allowed to depend on. + * `./project-schema.unit.test.ts`'s AST-walk exhaustiveness guard walks + * the derived AST and fails loudly if a node kind outside this + * enumerated set (or a reintroduced `Suspend`, deliberately unhandled + * here — see {@link toDeepOptionalHostedAst}) ever appears, rather than + * silently mishandling it. + * + * `_apiResponse` (ADR 0019) is deliberately NOT part of this schema: it's + * attached as a non-enumerable property that ordinary decode/validation can + * never see, so there is nothing here for a schema to describe. + * + * Never `additionalProperties: false` ({@link toProjectConfigJsonSchema} + * passes `{ additionalProperties: true }` to `Schema.toJsonSchemaDocument`, + * and `ProjectConfigSchema` itself is never decoded with + * `onExcessProperty: "error"`): a `ProjectConfig` value can carry extra own + * keys a given schema VERSION doesn't yet model (a registry-mapped field a + * future release adds), and JSON Schema's own default is permissive — this + * derivation matches that norm rather than rejecting anything unrecognized. + */ import type { StandardSchemaV1 } from "@standard-schema/spec"; import { Schema } from "effect"; import type { ProjectConfig } from "./project-config.ts"; @@ -23,7 +119,11 @@ type ProjectConfigSchemaType = Omit; * only transitively through `effect` under pnpm's strict `node_modules` * isolation — which tsc's declaration emit refuses to synthesize into * `project-schema.d.ts` as non-portable. Explicitly importing the type here - * pins `@standard-schema/spec` as a direct dependency instead. + * pins `@standard-schema/spec` as a direct dependency instead. Conscious + * exception to this repo's `as`-cast policy's spirit: `Schema.make`'s type + * parameter here is asserted, not verified, against `projectConfigAst` — see + * {@link ProjectConfigSchemaType}'s doc comment for the independent + * compile-time cross-check that catches drift instead. */ export declare const ProjectConfigSchema: StandardSchemaV1 & Schema.Codec; /** JSON Schema (draft 2020-12) rendering of {@link ProjectConfigSchema}, mirroring `../base.ts`'s `toCliConfigJsonSchema`. */ diff --git a/packages/config/api-report/project.d.ts b/packages/config/api-report/project.d.ts index 389da8c67f..c025d1d72f 100644 --- a/packages/config/api-report/project.d.ts +++ b/packages/config/api-report/project.d.ts @@ -1,6 +1,6 @@ import { Effect, FileSystem } from "effect"; import { CliProjectEnvParseError } from "./errors.ts"; -import { type ResolvedCliConfigValue, type ResolveCliConfigOptions } from "./lib/resolve.ts"; +import { type ResolvedCliConfigValue } from "./lib/resolve.ts"; import { type CliProjectPaths } from "./paths.ts"; export interface CliProjectEnvironment { readonly paths: CliProjectPaths; @@ -31,7 +31,7 @@ export interface LoadCliProjectEnvironmentOptions { * Not covered by semver — exported from `@supabase/config/internal` only. See * that module's header for why. */ -export interface InternalResolveCliConfigOptions extends ResolveCliConfigOptions { +export interface InternalResolveCliConfigOptions { /** * Opt into Go/viper-parity `env()` matching (case-agnostic * `^env\((.*)\)$`). Defaults to `false`, which uses the pre-PR-#5765 strict diff --git a/packages/config/api-report/promise-facade.d.ts b/packages/config/api-report/promise-facade.d.ts index 8bee44f4ba..64611cc7fb 100644 --- a/packages/config/api-report/promise-facade.d.ts +++ b/packages/config/api-report/promise-facade.d.ts @@ -10,10 +10,17 @@ import type { LoadCliProjectEnvironmentOptions, CliProjectEnvironment } from "./ * member names. * * A rejection from `loadCliConfig`, `loadCliConfigFile`, or `saveCliConfig` - * can carry any of `CliConfigStoreError`'s members (`cli-config.service.ts`): - * this package's own `CliConfigParseError` / `DuplicateRemoteProjectIdError` / - * `InvalidRemoteProjectIdError` / `CliProjectEnvParseError`, or `PlatformError` - * for a host/OS failure — distinguish via `instanceof`. + * can carry any of five typed failures — this package's own + * `CliConfigParseError`, `DuplicateRemoteProjectIdError`, + * `InvalidRemoteProjectIdError`, `CliProjectEnvParseError`, or `PlatformError` + * (from `effect/PlatformError`) for a host/OS failure — distinguish via + * `instanceof`. One exception: `saveCliConfig`'s atomic-write step maps a + * rename failure to a defect rather than one of these typed failures (see + * `io.ts`'s `writeFileAtomic`) — the returned promise still rejects, but with + * the raw, un-mapped failure, not an instance of any class listed above. This + * is a deliberate design choice (a rename failure after a successful write + * indicates something is wrong with the filesystem itself, not a recoverable + * config condition), not an oversight. */ export interface CliConfigIo { readonly loadCliConfig: (cwd: string, options?: LoadCliConfigOptions) => Promise; diff --git a/packages/config/api-report/schema-metadata.d.ts b/packages/config/api-report/schema-metadata.d.ts index 117d2bf958..ef19b8fb0e 100644 --- a/packages/config/api-report/schema-metadata.d.ts +++ b/packages/config/api-report/schema-metadata.d.ts @@ -1 +1,3 @@ export declare const CLI_CONFIG_SCHEMA_URL = "https://supabase.com/docs/cli/config.schema.json"; +/** Sibling of {@link CLI_CONFIG_SCHEMA_URL} for `ProjectConfigSchema`'s generated JSON Schema document (`toProjectConfigJsonSchema`) — same `/docs/cli/` path, same `apps/cli/scripts/generate-docs.ts` pathname-derivation convention. */ +export declare const PROJECT_CONFIG_SCHEMA_URL = "https://supabase.com/docs/cli/project-config.schema.json"; diff --git a/packages/config/docs/cli-config-loading.md b/packages/config/docs/cli-config-loading.md index 7cd6ddd2a8..c8994dbd4a 100644 --- a/packages/config/docs/cli-config-loading.md +++ b/packages/config/docs/cli-config-loading.md @@ -34,9 +34,8 @@ This document explains how the CLI's on-disk config document loading works, acro The `Cli*` prefix is a rule, not a per-name coincidence: it names the local checkout side — what the CLI reads, writes, or resolves about itself on disk. A bare `Project*` name is reserved for the hosted Supabase project. Value-helpers follow the config family regardless of their inputs, not the -shape of whatever they're passed — `resolveCliConfigValue` and `MissingCliConfigValueError` are -`Cli*`-named for this reason. See [ADR 0020](../../../docs/adr/0020-config-naming-vocabulary.md) -for the full decision record. +shape of whatever they're passed — `resolveCliConfigValue` is `Cli*`-named for this reason. See +[ADR 0020](../../../docs/adr/0020-config-naming-vocabulary.md) for the full decision record. Within `CliConfig` itself, `project_id` is overloaded by position: the root-scope `project_id` is a local identifier that defaults to the working directory name when running `supabase init` (see @@ -207,13 +206,14 @@ literal, unresolved `env(NAME)`. ## Lazy `env(NAME)` Resolution A caller can also resolve `env(NAME)` references explicitly, after config is loaded. The package -exposes two helpers, under the same names from both `.` (plain, synchronous — throws instead of -failing an `Effect`) and `@supabase/config/effect` (Effect-typed; the Effect-typed variant wins -when both are in scope via `@supabase/config/effect`, since explicit named exports take precedence -over a star re-export of the same name): +exposes two helpers, under the same names from both `.` (plain, synchronous) and +`@supabase/config/effect` (Effect-typed; the Effect-typed variant wins when both are in scope via +`@supabase/config/effect`, since explicit named exports take precedence over a star re-export of +the same name). Neither has a failure mode: an unresolved `env(NAME)` reference is preserved +verbatim rather than rejected or thrown (see "Lazy `env(NAME)` Resolution" behavior below). -- `resolveCliConfigValue(value, cliProjectEnv, configPath, options?)` -- `resolveCliConfigSubtree(value, cliProjectEnv, pathPrefix, options?)` +- `resolveCliConfigValue(value, cliProjectEnv, configPath)` +- `resolveCliConfigSubtree(value, cliProjectEnv, pathPrefix)` Resolution only applies to exact whole-string matches of the form: @@ -238,10 +238,11 @@ resolves and redacts leaves nested inside `[remotes.*]` blocks. An optional `goViperCompat` flag switches the `env(NAME)` matcher from the default, strict `SCREAMING_SNAKE_CASE`-only pattern to Go/viper's case-agnostic `^env\((.*)\)$` form; only the -Go-parity legacy shell sets it. `goViperCompat` is not part of the public `ResolveCliConfigOptions` -type on `.`/`./effect` — it is internal-only (CLI-2234), typed on `InternalResolveCliConfigOptions` -and exported from `@supabase/config/internal`, which `apps/cli`'s Go-parity call sites import from -instead. +Go-parity legacy shell sets it. The public `resolveCliConfigValue`/`resolveCliConfigSubtree` on +`.`/`./effect` take no options parameter at all (CLI-2234) — `goViperCompat` is internal-only, +typed on `InternalResolveCliConfigOptions` and exported from `@supabase/config/internal`, which +re-exports these same runtime functions typed to additionally accept it; `apps/cli`'s Go-parity +call sites import from there instead. Callers such as `functions serve`/`functions dev`, `secrets set`, and `start` call these resolvers on the subtrees they actually need (e.g. `auth`, `edge_runtime`, `functions`), so dormant @@ -249,9 +250,8 @@ config — like a disabled Twilio block whose `auth_token` is still `env(TWILIO_ that variable was never set — never has to resolve at load time, and no caller pays for resolving or redacting a subtree it doesn't use. -The package still exports a `MissingCliConfigValueError` class, and `apps/cli` classifies it for -telemetry, but neither resolver raises it today: an unresolved `env(NAME)` reference is returned -as a plain string, not a typed failure. +Neither resolver ever fails: an unresolved `env(NAME)` reference is returned as a plain string, +not a typed failure. ## Secret Handling diff --git a/packages/config/package.json b/packages/config/package.json index b6e70923cd..f242a7319d 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -2,6 +2,23 @@ "name": "@supabase/config", "version": "0.1.0", "private": true, + "description": "Supabase project configuration schema, parsing, and validation, built on Effect Schema.", + "keywords": [ + "config", + "effect", + "schema", + "supabase" + ], + "homepage": "https://github.com/supabase/cli#readme", + "bugs": { + "url": "https://github.com/supabase/cli/issues" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/supabase/cli.git", + "directory": "packages/config" + }, "files": [ "src", "!src/**/*.test.ts", @@ -12,13 +29,13 @@ "sideEffects": false, "exports": { ".": { - "types": "./dist/index.d.ts", "bun": "./src/index.ts", + "types": "./dist/index.d.ts", "default": "./dist/index.js" }, "./internal": { - "types": "./dist/internal.d.ts", "bun": "./src/internal.ts", + "types": "./dist/internal.d.ts", "default": "./dist/internal.js" }, "./io": { @@ -37,15 +54,19 @@ } }, "./effect": { - "types": "./dist/effect.d.ts", "bun": "./src/effect.ts", + "types": "./dist/effect.d.ts", "default": "./dist/effect.js" }, "./schema.json": "./dist/schema.json", "./project-schema.json": "./dist/project-schema.json" }, + "publishConfig": { + "access": "public" + }, "scripts": { "build": "bun run ./scripts/build.ts", + "api-report:update": "bun run ./scripts/build.ts --api-report-only", "types:check": "tsc --noEmit", "test": "pnpm run test:unit", "test:unit": "pnpm exec turbo run @supabase/config#test:unit:run --", @@ -67,9 +88,9 @@ "vitest": "catalog:" }, "peerDependencies": { - "@effect/platform-bun": "catalog:", - "@effect/platform-node": "catalog:", - "effect": "catalog:" + "@effect/platform-bun": ">=4.0.0-rc.111 <5", + "@effect/platform-node": ">=4.0.0-rc.111 <5", + "effect": ">=4.0.0-rc.111 <5" }, "peerDependenciesMeta": { "@effect/platform-bun": { @@ -78,5 +99,8 @@ "@effect/platform-node": { "optional": true } + }, + "engines": { + "node": ">=20" } } diff --git a/packages/config/scripts/build-artifacts.unit.test.ts b/packages/config/scripts/build-artifacts.unit.test.ts new file mode 100644 index 0000000000..479e6a6e46 --- /dev/null +++ b/packages/config/scripts/build-artifacts.unit.test.ts @@ -0,0 +1,113 @@ +import { describe, expect, test } from "vitest"; +import { CliConfigSchema, toCliConfigJsonSchema } from "../src/base.ts"; +import { + ProjectConfigSchema, + toProjectConfigJsonSchema, +} from "../src/project-config/project-schema.ts"; +import { CLI_CONFIG_SCHEMA_URL, PROJECT_CONFIG_SCHEMA_URL } from "../src/schema-metadata.ts"; +import { collapseNonFiniteNumberUnions, withSchemaMetadata } from "./json-schema-postprocess.ts"; + +// CLI-2234 group 6c: regression coverage for the exact post-processing +// `scripts/build.ts` applies to both `dist/schema.json` and +// `dist/project-schema.json` — generated in-memory here (no real build), via +// the same pure functions the build script itself calls, against the real +// `CliConfigSchema`/`ProjectConfigSchema`. + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function findAnyOfWithNonFiniteEnum(node: unknown, into: Array): void { + if (Array.isArray(node)) { + for (const item of node) { + findAnyOfWithNonFiniteEnum(item, into); + } + return; + } + if (!isRecord(node)) { + return; + } + const anyOf = node["anyOf"]; + if (Array.isArray(anyOf) && anyOf.length === 2) { + const hasNumber = anyOf.some((branch) => isRecord(branch) && branch["type"] === "number"); + const hasNonFiniteEnum = anyOf.some( + (branch) => + isRecord(branch) && + branch["type"] === "string" && + Array.isArray(branch["enum"]) && + branch["enum"].every( + (value) => typeof value === "string" && ["Infinity", "-Infinity", "NaN"].includes(value), + ), + ); + if (hasNumber && hasNonFiniteEnum) { + into.push(node); + } + } + for (const value of Object.values(node)) { + findAnyOfWithNonFiniteEnum(value, into); + } +} + +const cliDocument = withSchemaMetadata( + collapseNonFiniteNumberUnions(toCliConfigJsonSchema(), CliConfigSchema.ast) as Record< + string, + unknown + >, + { + id: CLI_CONFIG_SCHEMA_URL, + title: "Supabase CLI config (CliConfig)", + description: "test", + }, +); + +const projectDocument = withSchemaMetadata( + collapseNonFiniteNumberUnions(toProjectConfigJsonSchema(), ProjectConfigSchema.ast) as Record< + string, + unknown + >, + { + id: PROJECT_CONFIG_SCHEMA_URL, + title: "Supabase hosted project config (ProjectConfig)", + description: "test", + }, +); + +describe("generated JSON Schema artifacts, post-processed", () => { + test.each([ + ["schema.json", cliDocument], + ["project-schema.json", projectDocument], + ])("%s carries no anyOf-with-non-finite-enum pattern anywhere", (_name, document) => { + const matches: Array = []; + findAnyOfWithNonFiniteEnum(document, matches); + expect(matches).toEqual([]); + }); + + test("schema.json's api.max_rows carries both description and default", () => { + const properties = cliDocument["properties"]; + if (!isRecord(properties) || !isRecord(properties["api"])) { + throw new Error("expected properties.api to be an object"); + } + const apiProperties = properties["api"]["properties"]; + if (!isRecord(apiProperties) || !isRecord(apiProperties["max_rows"])) { + throw new Error("expected properties.api.properties.max_rows to be an object"); + } + const maxRows = apiProperties["max_rows"]; + expect(maxRows["type"]).toBe("number"); + expect(typeof maxRows["description"]).toBe("string"); + expect(maxRows["default"]).toBe(1000); + }); + + test.each([ + ["schema.json", cliDocument, CLI_CONFIG_SCHEMA_URL, "Supabase CLI config (CliConfig)"], + [ + "project-schema.json", + projectDocument, + PROJECT_CONFIG_SCHEMA_URL, + "Supabase hosted project config (ProjectConfig)", + ], + ])("%s carries $schema, $id, and title", (_name, document, id, title) => { + expect(document["$schema"]).toBe("https://json-schema.org/draft/2020-12/schema"); + expect(document["$id"]).toBe(id); + expect(document["title"]).toBe(title); + }); +}); diff --git a/packages/config/scripts/build.ts b/packages/config/scripts/build.ts index 488fc8806d..46e4bac5b9 100644 --- a/packages/config/scripts/build.ts +++ b/packages/config/scripts/build.ts @@ -1,11 +1,16 @@ -import { copyFile, mkdir, mkdtemp, realpath, rm } from "node:fs/promises"; +import { mkdir, mkdtemp, realpath, rename, rm, symlink } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; -import { toCliConfigJsonSchema } from "../src/base.ts"; -import { toProjectConfigJsonSchema } from "../src/project-config/project-schema.ts"; +import { CliConfigSchema, toCliConfigJsonSchema } from "../src/base.ts"; +import { + ProjectConfigSchema, + toProjectConfigJsonSchema, +} from "../src/project-config/project-schema.ts"; +import { CLI_CONFIG_SCHEMA_URL, PROJECT_CONFIG_SCHEMA_URL } from "../src/schema-metadata.ts"; +import { collapseNonFiniteNumberUnions, withSchemaMetadata } from "./json-schema-postprocess.ts"; const packageRoot = path.resolve(import.meta.dir, ".."); -const repoRoot = path.resolve(packageRoot, "../.."); +const apiReportOnly = process.argv.includes("--api-report-only"); async function runCommand(cmd: readonly string[], cwd: string = packageRoot): Promise { const child = Bun.spawn([...cmd], { cwd, stdout: "inherit", stderr: "inherit" }); @@ -15,7 +20,51 @@ async function runCommand(cmd: readonly string[], cwd: string = packageRoot): Pr } } -async function renderJsonSchema(outputPath: string, json: unknown): Promise { +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +/** Descends `root` at each of `path`, requiring an object at every intermediate step, throwing with a precise location otherwise. */ +function readStringAt(root: unknown, path: ReadonlyArray): string { + let current = root; + for (const [index, key] of path.entries()) { + if (!isRecord(current)) { + throw new Error( + `expected an object while reading ${path.slice(0, index).join(".")} (looking for "${key}"), got ${typeof current}`, + ); + } + current = current[key]; + } + if (typeof current !== "string") { + throw new Error(`expected a string at ${path.join(".")}, got ${typeof current}`); + } + return current; +} + +/** + * Runs a schema's rendered JSON Schema document through + * {@link collapseNonFiniteNumberUnions} and {@link withSchemaMetadata}, then + * writes it via {@link renderJsonSchema}. `collapseNonFiniteNumberUnions` + * returns `unknown` (it's a generic JSON-tree walk with no static shape + * guarantee); this narrows it back to an object via `isRecord` rather than an + * `as` cast — both `toCliConfigJsonSchema()`/`toProjectConfigJsonSchema()` + * always render a top-level object, so a non-object result here would mean + * the collapse walk itself is broken, worth failing loudly on. + */ +async function renderCollapsedJsonSchema( + outputPath: string, + document: unknown, + rootAst: Parameters[1], + metadata: Parameters[1], +): Promise { + const collapsed = collapseNonFiniteNumberUnions(document, rootAst); + if (!isRecord(collapsed)) { + throw new Error(`collapseNonFiniteNumberUnions did not return an object for ${outputPath}`); + } + await renderJsonSchema(outputPath, withSchemaMetadata(collapsed, metadata)); +} + +async function renderJsonSchema(outputPath: string, json: Record): Promise { const schema = `${JSON.stringify(json, null, 2)}\n`; const formatter = Bun.spawn(["bun", "x", "oxfmt", `--stdin-filepath=${outputPath}`], { @@ -47,10 +96,15 @@ async function renderJsonSchema(outputPath: string, json: unknown): Promise { const distIndexPath = await realpath(path.join(packageRoot, "dist", "index.js")); + const distInternalPath = await realpath(path.join(packageRoot, "dist", "internal.js")); // `mkdtemp` can return a path through a symlinked prefix (e.g. macOS's // `/var` -> `/private/var`) that Bun's bundler resolves to its canonical // form internally when computing the probe entry's own directory — compute @@ -62,38 +116,65 @@ async function verifyTreeShaking(): Promise { ); try { - const probeEntry = path.join(probeDir, "probe.js"); - const relativeSpecifier = path.relative(probeDir, distIndexPath).split(path.sep).join("/"); - const specifier = relativeSpecifier.startsWith(".") - ? relativeSpecifier - : `./${relativeSpecifier}`; - await Bun.write(probeEntry, `export { CliConfigSchema } from "${specifier}";\n`); - - const result = await Bun.build({ - entrypoints: [probeEntry], - target: "browser", - minify: false, - }); - - if (!result.success) { - const messages = result.logs.map((log) => log.message).join("\n"); - throw new Error(`tree-shake probe failed to bundle:\n${messages}`); + function relativeSpecifierFor(target: string): string { + const relative = path.relative(probeDir, target).split(path.sep).join("/"); + return relative.startsWith(".") ? relative : `./${relative}`; } - const [output] = result.outputs; - if (!output) { - throw new Error("tree-shake probe produced no bundle output"); + async function bundle(entryName: string, source: string): Promise { + const probeEntry = path.join(probeDir, entryName); + await Bun.write(probeEntry, source); + const result = await Bun.build({ + entrypoints: [probeEntry], + target: "browser", + minify: false, + }); + if (!result.success) { + const messages = result.logs.map((log) => log.message).join("\n"); + throw new Error(`tree-shake probe failed to bundle ${entryName}:\n${messages}`); + } + const [output] = result.outputs; + if (!output) { + throw new Error(`tree-shake probe produced no bundle output for ${entryName}`); + } + return output.text(); } - const code = await output.text(); // Only appears in `src/project-config/registry*.ts` (verified by // grepping `dist/`) — a real API attribute path segment, never used by // `CliConfigSchema`'s own field names (`base.ts`/`api.ts` use `schemas`, // not `db_schema`). const REGISTRY_ONLY_MARKER = "db_schema"; - // Only appears in `src/api.ts`, reachable through `CliConfigSchema` — - // proof the probe still bundled real, non-empty content. - const SCHEMA_MARKER = "Enable the local PostgREST service."; + // Derived at runtime from the actual `CliConfigSchema` annotation it + // names, rather than hardcoded prose that could silently drift from the + // real description text — `api.enabled`'s `description`, read off the + // real rendered JSON Schema document (the same source `dist/schema.json` + // is built from). + const SCHEMA_MARKER = readStringAt(toCliConfigJsonSchema(), [ + "properties", + "api", + "properties", + "enabled", + "description", + ]); + + const positiveControlCode = await bundle( + "positive-control.js", + `export { projectConfigMappingRows } from "${relativeSpecifierFor(distInternalPath)}";\n`, + ); + if (!positiveControlCode.includes(REGISTRY_ONLY_MARKER)) { + throw new Error( + `tree-shake probe's positive control failed: bundling { projectConfigMappingRows } from ` + + `dist/internal.js did not include marker ${JSON.stringify(REGISTRY_ONLY_MARKER)} — this ` + + `probe methodology can no longer detect the marker it's meant to prove absent below, so its ` + + `absence from the CliConfigSchema-only probe would be meaningless.`, + ); + } + + const code = await bundle( + "probe.js", + `export { CliConfigSchema } from "${relativeSpecifierFor(distIndexPath)}";\n`, + ); if (code.includes(REGISTRY_ONLY_MARKER)) { throw new Error( @@ -111,7 +192,7 @@ async function verifyTreeShaking(): Promise { } console.log( - `[build] tree-shake probe OK (${code.length} bytes; registry-only marker absent, schema marker present).`, + `[build] tree-shake probe OK (${code.length} bytes; registry-only marker absent, schema marker present, positive control passed).`, ); } finally { await rm(probeDir, { recursive: true, force: true }); @@ -120,14 +201,16 @@ async function verifyTreeShaking(): Promise { /** * Regenerates a declarations-only build (`tsconfig.api-report.json`, no - * `.d.ts.map`/`.js`) into a scratch dir and mirrors it into the checked-in - * `api-report/` (CLI-2234 enforcement layer 4). `src/api-report.unit.test.ts` - * regenerates the same way and diffs against this mirror, so any type-surface - * change becomes a reviewable `git diff` instead of a silent drift. + * `.d.ts.map`/`.js`) into a scratch dir INSIDE this package (so the final + * swap below is a same-filesystem, atomic `rename`) and swaps it into the + * checked-in `api-report/` (CLI-2234 enforcement layer 4). + * `src/api-report.unit.test.ts` regenerates the same way and diffs against + * this mirror, so any type-surface change becomes a reviewable `api-report/` + * diff instead of passing silently. */ async function syncApiReport(): Promise { const apiReportDir = path.join(packageRoot, "api-report"); - const scratchDir = await mkdtemp(path.join(tmpdir(), "supabase-config-api-report-")); + const scratchDir = await mkdtemp(path.join(packageRoot, ".api-report-scratch-")); try { await runCommand([ @@ -140,45 +223,45 @@ async function syncApiReport(): Promise { scratchDir, ]); - await rm(apiReportDir, { recursive: true, force: true }); - await mkdir(apiReportDir, { recursive: true }); - const glob = new Bun.Glob("**/*.d.ts"); let count = 0; - for await (const relativePath of glob.scan({ cwd: scratchDir })) { - const dest = path.join(apiReportDir, relativePath); - await mkdir(path.dirname(dest), { recursive: true }); - await copyFile(path.join(scratchDir, relativePath), dest); + for await (const _relativePath of glob.scan({ cwd: scratchDir })) { count++; } + if (count === 0) { + throw new Error( + "the declarations-only compile produced zero .d.ts files — refusing to swap an empty tree " + + "into api-report/", + ); + } - console.log(`[build] synced ${count} .d.ts files into api-report/`); + const staleDir = `${apiReportDir}.stale-${Date.now()}`; + const hadExistingApiReport = await Bun.file(path.join(apiReportDir, "index.d.ts")).exists(); + if (hadExistingApiReport) { + await rm(staleDir, { recursive: true, force: true }); + await rename(apiReportDir, staleDir); + } + await rename(scratchDir, apiReportDir); + if (hadExistingApiReport) { + await rm(staleDir, { recursive: true, force: true }); + } + + console.log(`[build] synced ${count} .d.ts files into api-report/ (atomic swap)`); } finally { await rm(scratchDir, { recursive: true, force: true }); } } -/** - * The real CLI-2232 acceptance check: proves every exports-map subpath - * actually resolves compiled `dist/` output end-to-end for a real Node - * consumer — not just that `tsc` produced files. Runs from `apps/cli` - * (the one in-repo workspace that depends on `@supabase/config`) so the - * top-level bare specifier resolves through pnpm's real `node_modules` link, - * exactly like an external consumer would. - */ -async function runNodeSmokeTest(): Promise { - const nodePath = Bun.which("node"); - if (!nodePath) { - console.error( - "[build] `node` executable not found on PATH; skipping the Node-consumer smoke test. This " + - "step exists specifically to catch a broken `exports` map / dist resolution for real Node " + - "consumers (CLI-2232) — install Node (mise provides it) and re-run `pnpm build` before " + - "trusting this package's dist output.", - ); - return; - } +const SMOKE_TEST_RUNTIME_DEPS = [ + "effect", + "@effect/platform-node", + "@standard-schema/spec", + "dedent", + "smol-toml", +] as const; - const smokeScript = [ +function buildSmokeTestScript(): string { + return [ 'import assert from "node:assert/strict";', 'import { createRequire } from "node:module";', "", @@ -204,29 +287,200 @@ async function runNodeSmokeTest(): Promise { 'assert.equal(typeof schemaJson, "object", "schema.json did not resolve to an object");', 'assert.equal(typeof projectSchemaJson, "object", "project-schema.json did not resolve to an object");', "", - 'console.log("[build] node smoke test: every entrypoint resolved through the node condition");', + 'console.log("[build] pack-and-install smoke test: every entrypoint resolved through a real npm-packed tarball install");', ].join("\n"); +} - await runCommand( - [nodePath, "--input-type=module", "-e", smokeScript], - path.join(repoRoot, "apps/cli"), - ); +/** + * The real CLI-2232/CLI-2234 acceptance check: packs the actual publish + * tarball (`npm pack`, governed by `files`/`.npmignore` — the exact thing + * `npm publish` would ship) and installs it into a fresh, isolated consumer + * project, then imports every entrypoint and JSON artifact through a real + * `node` process. This catches `files`/`exports` drift the previous + * workspace-link Node smoke test missed entirely (a workspace `pnpm` link + * resolves straight to this package's own directory, bypassing `files` + * filtering altogether). + * + * Deliberately extracts the tarball directly (`tar`) rather than `npm install + * `: the latter would additionally try to resolve + * `@supabase/config`'s own dependency tree (`effect`'s own `fast-check`/ + * `msgpackr`, `@effect/platform-node`'s `undici`/`mime`, …) from the npm + * registry over the network on every build. Every runtime dependency this + * smoke test actually needs is already resolved locally by pnpm — symlinking + * those real, already-resolved package directories in below — the same + * directories `packages/config/node_modules/*` itself points at — mirrors + * exactly how pnpm links every other workspace in this monorepo (Node + * resolves each symlink to its real path before walking further ancestor + * `node_modules` directories, so each linked package's own transitive deps, + * already resolved alongside it in the pnpm store, are found the same way). + * This keeps the check hermetic, fast, and network-free. + */ +async function runPackAndInstallSmokeTest(): Promise { + const npmPath = Bun.which("npm"); + const nodePath = Bun.which("node"); + const tarPath = Bun.which("tar"); + if (npmPath === null || nodePath === null || tarPath === null) { + const missing = [ + npmPath === null ? "npm" : null, + nodePath === null ? "node" : null, + tarPath === null ? "tar" : null, + ].filter((name) => name !== null); + throw new Error( + `the pack-and-install smoke test (CLI-2234) requires ${missing.join(", ")} on PATH — install ` + + "it (mise provides node/npm; tar ships with every supported OS) before running `pnpm build`.", + ); + } + + const scratchDir = await mkdtemp(path.join(tmpdir(), "supabase-config-pack-smoke-")); + try { + const packResult = Bun.spawn([npmPath, "pack", "--json", "--pack-destination", scratchDir], { + cwd: packageRoot, + stdout: "pipe", + stderr: "inherit", + }); + const [packExitCode, packStdout] = await Promise.all([ + packResult.exited, + new Response(packResult.stdout).text(), + ]); + if (packExitCode !== 0) { + throw new Error(`\`npm pack\` failed with exit code ${packExitCode}`); + } + const packEntries = JSON.parse(packStdout) as ReadonlyArray<{ readonly filename: string }>; + const [packEntry] = packEntries; + if (packEntry === undefined) { + throw new Error("`npm pack --json` produced no tarball entries"); + } + const tarballPath = path.join(scratchDir, packEntry.filename); + + const consumerDir = path.join(scratchDir, "consumer"); + const consumerConfigDir = path.join(consumerDir, "node_modules", "@supabase", "config"); + await mkdir(consumerConfigDir, { recursive: true }); + await runCommand([ + tarPath, + "-xzf", + tarballPath, + "-C", + consumerConfigDir, + "--strip-components=1", + ]); + + await Bun.write( + path.join(consumerDir, "package.json"), + `${JSON.stringify( + { name: "supabase-config-pack-smoke", version: "0.0.0", private: true, type: "module" }, + null, + 2, + )}\n`, + ); + + const consumerNodeModules = path.join(consumerDir, "node_modules"); + for (const name of SMOKE_TEST_RUNTIME_DEPS) { + const real = await realpath(path.join(packageRoot, "node_modules", name)); + const dest = path.join(consumerNodeModules, name); + await mkdir(path.dirname(dest), { recursive: true }); + await symlink(real, dest, "dir"); + } + + await runCommand([nodePath, "--input-type=module", "-e", buildSmokeTestScript()], consumerDir); + } finally { + await rm(scratchDir, { recursive: true, force: true }); + } } -console.log("[build] compiling TypeScript project (tsconfig.build.json)..."); -await runCommand(["pnpm", "exec", "tsc", "-p", "tsconfig.build.json"]); +interface ExportsMap { + readonly [subpath: string]: ExportsNode; +} +type ExportsNode = string | { readonly [condition: string]: ExportsNode }; -console.log("[build] rendering JSON Schema artifacts..."); -await renderJsonSchema("./dist/schema.json", toCliConfigJsonSchema()); -await renderJsonSchema("./dist/project-schema.json", toProjectConfigJsonSchema()); +function collectDistTargets(node: ExportsNode, into: Set): void { + if (typeof node === "string") { + if (node.startsWith("./dist/")) { + into.add(node); + } + return; + } + for (const [condition, value] of Object.entries(node)) { + // `bun` conditions point at `src/*.ts`, which trivially exists at every + // commit (it's source, not a build output) — nothing to verify here. + if (condition === "bun") { + continue; + } + collectDistTargets(value, into); + } +} -console.log("[build] verifying the sideEffects:false tree-shaking claim..."); -await verifyTreeShaking(); +/** CLI-2234: every `types`/`default`/JSON-artifact target the exports map declares must exist once the build finishes. */ +async function verifyExportsMapTargetsExist(): Promise { + const packageJson = JSON.parse(await Bun.file(path.join(packageRoot, "package.json")).text()) as { + readonly exports: ExportsMap; + }; -console.log("[build] syncing api-report/ from a declarations-only compile..."); -await syncApiReport(); + const targets = new Set(); + for (const node of Object.values(packageJson.exports)) { + collectDistTargets(node, targets); + } -console.log("[build] running the Node-consumer smoke test..."); -await runNodeSmokeTest(); + const missing: string[] = []; + for (const target of targets) { + if (!(await Bun.file(path.join(packageRoot, target)).exists())) { + missing.push(target); + } + } + if (missing.length > 0) { + throw new Error( + `the following dist targets declared in package.json's exports map are missing after the ` + + `build: ${missing.join(", ")}`, + ); + } + console.log(`[build] verified ${targets.size} exports-map dist targets exist on disk.`); +} -console.log("[build] done."); +if (apiReportOnly) { + console.log("[build] --api-report-only: syncing api-report/ from a declarations-only compile..."); + await syncApiReport(); + console.log("[build] done."); +} else { + console.log("[build] removing stale dist/ (stale modules from renames must not ship)..."); + await rm(path.join(packageRoot, "dist"), { recursive: true, force: true }); + + console.log("[build] compiling TypeScript project (tsconfig.build.json)..."); + await runCommand(["pnpm", "exec", "tsc", "-p", "tsconfig.build.json"]); + + console.log("[build] rendering JSON Schema artifacts..."); + await renderCollapsedJsonSchema( + "./dist/schema.json", + toCliConfigJsonSchema(), + CliConfigSchema.ast, + { + id: CLI_CONFIG_SCHEMA_URL, + title: "Supabase CLI config (CliConfig)", + description: + "The Supabase CLI's local project config document (supabase/config.toml or supabase/config.json).", + }, + ); + await renderCollapsedJsonSchema( + "./dist/project-schema.json", + toProjectConfigJsonSchema(), + ProjectConfigSchema.ast, + { + id: PROJECT_CONFIG_SCHEMA_URL, + title: "Supabase hosted project config (ProjectConfig)", + description: + "The sparse, hosted-project subset of CliConfig that a Supabase project manages.", + }, + ); + + console.log("[build] verifying every exports-map dist target exists..."); + await verifyExportsMapTargetsExist(); + + console.log("[build] verifying the sideEffects:false tree-shaking claim..."); + await verifyTreeShaking(); + + console.log("[build] syncing api-report/ from a declarations-only compile..."); + await syncApiReport(); + + console.log("[build] running the pack-and-install smoke test..."); + await runPackAndInstallSmokeTest(); + + console.log("[build] done."); +} diff --git a/packages/config/scripts/json-schema-postprocess.ts b/packages/config/scripts/json-schema-postprocess.ts new file mode 100644 index 0000000000..829d3e8a25 --- /dev/null +++ b/packages/config/scripts/json-schema-postprocess.ts @@ -0,0 +1,236 @@ +import { SchemaAST } from "effect"; + +/** + * Pure JSON Schema post-processing used by `build.ts`'s `renderJsonSchema` on + * both generated artifacts (`dist/schema.json`, `dist/project-schema.json`). + * Extracted to its own module (rather than inlined in `build.ts`) so + * `json-schema-postprocess.unit.test.ts` can exercise it directly against an + * in-memory document, without spawning the real build. + */ + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +const NON_FINITE_ENUM_VALUES: ReadonlySet = new Set(["Infinity", "-Infinity", "NaN"]); + +function isNonFiniteStringEnumNode(node: unknown): boolean { + if (!isRecord(node) || node["type"] !== "string") { + return false; + } + const values = node["enum"]; + return ( + Array.isArray(values) && + values.length > 0 && + values.every((value) => typeof value === "string" && NON_FINITE_ENUM_VALUES.has(value)) + ); +} + +function isPlainNumberNode(node: unknown): node is Record { + return isRecord(node) && node["type"] === "number"; +} + +interface RecoveredAnnotations { + readonly description?: string; + readonly default?: unknown; +} + +/** + * `description`/`default` for every `Schema.Number` leaf reachable from + * `ast`, keyed by dotted property path (`"*"` for a record/array element) — + * the only two annotations Effect's `Schema.toJsonSchemaDocument` silently + * drops when it splits a plain `Schema.Number` into the `anyOf` union + * {@link collapseNonFiniteNumberUnions} collapses back down (verified + * empirically against `api.max_rows`, which carries both). A `.check()`ed + * number (e.g. `workers.*.instances`'s `isInt()`) renders as a plain + * `"type": "integer"` node instead of this union, so it never reaches this + * map's consumer in the first place — collected here regardless, since nothing + * downstream keys off `_tag` other than `"Number"`. + */ +function collectNumberLeafAnnotations( + ast: SchemaAST.AST, + prefix: ReadonlyArray = [], + into: Map = new Map(), +): Map { + if (SchemaAST.isObjects(ast)) { + for (const property of ast.propertySignatures) { + collectNumberLeafAnnotations(property.type, [...prefix, String(property.name)], into); + } + for (const indexSignature of ast.indexSignatures) { + collectNumberLeafAnnotations(indexSignature.type, [...prefix, "*"], into); + } + } else if (SchemaAST.isArrays(ast)) { + for (const element of ast.elements) { + collectNumberLeafAnnotations(element, [...prefix, "*"], into); + } + for (const rest of ast.rest) { + collectNumberLeafAnnotations(rest, [...prefix, "*"], into); + } + } else if (SchemaAST.isUnion(ast)) { + for (const member of ast.types) { + collectNumberLeafAnnotations(member, prefix, into); + } + } else if (ast._tag === "Number") { + const description = ast.annotations?.["description"]; + const defaultValue = ast.annotations?.["default"]; + if (typeof description === "string" || defaultValue !== undefined) { + into.set(prefix.join("."), { + ...(typeof description === "string" ? { description } : {}), + ...(defaultValue !== undefined ? { default: defaultValue } : {}), + }); + } + } + return into; +} + +function tryCollapseNonFiniteNumberUnion( + node: Record, + path: ReadonlyArray, + annotationsByPath: ReadonlyMap, +): Record | undefined { + const anyOf = node["anyOf"]; + if (!Array.isArray(anyOf) || anyOf.length !== 2) { + return undefined; + } + const [first, second] = anyOf; + const numberNode = isPlainNumberNode(first) + ? first + : isPlainNumberNode(second) + ? second + : undefined; + const enumNode = isNonFiniteStringEnumNode(first) + ? first + : isNonFiniteStringEnumNode(second) + ? second + : undefined; + if (numberNode === undefined || enumNode === undefined) { + return undefined; + } + + const { anyOf: _anyOf, ...siblings } = node; + const merged: Record = { ...numberNode, ...siblings }; + const recovered = annotationsByPath.get(path.join(".")); + if (merged["description"] === undefined && recovered?.description !== undefined) { + merged["description"] = recovered.description; + } + if (merged["default"] === undefined && recovered?.default !== undefined) { + merged["default"] = recovered.default; + } + return merged; +} + +function collapseSchemaNode( + node: unknown, + path: ReadonlyArray, + annotationsByPath: ReadonlyMap, +): unknown { + if (!isRecord(node)) { + return node; + } + + const collapsed = tryCollapseNonFiniteNumberUnion(node, path, annotationsByPath); + if (collapsed !== undefined) { + return collapsed; + } + + const result: Record = { ...node }; + + const properties = node["properties"]; + if (isRecord(properties)) { + result["properties"] = Object.fromEntries( + Object.entries(properties).map(([name, child]) => [ + name, + collapseSchemaNode(child, [...path, name], annotationsByPath), + ]), + ); + } + + const patternProperties = node["patternProperties"]; + if (isRecord(patternProperties)) { + result["patternProperties"] = Object.fromEntries( + Object.entries(patternProperties).map(([pattern, child]) => [ + pattern, + collapseSchemaNode(child, [...path, "*"], annotationsByPath), + ]), + ); + } + + const additionalProperties = node["additionalProperties"]; + if (isRecord(additionalProperties)) { + result["additionalProperties"] = collapseSchemaNode( + additionalProperties, + [...path, "*"], + annotationsByPath, + ); + } + + const items = node["items"]; + if (items !== undefined) { + result["items"] = collapseSchemaNode(items, [...path, "*"], annotationsByPath); + } + + const prefixItems = node["prefixItems"]; + if (Array.isArray(prefixItems)) { + result["prefixItems"] = prefixItems.map((item) => + collapseSchemaNode(item, [...path, "*"], annotationsByPath), + ); + } + + for (const combinator of ["anyOf", "allOf", "oneOf"] as const) { + const branches = node[combinator]; + if (Array.isArray(branches)) { + result[combinator] = branches.map((branch) => + collapseSchemaNode(branch, path, annotationsByPath), + ); + } + } + + const defs = node["$defs"]; + if (isRecord(defs)) { + result["$defs"] = Object.fromEntries( + Object.entries(defs).map(([name, child]) => [ + name, + // `$defs` members don't correspond to a reachable property path off + // `ast` (they're keyed by ref name, not position) — pass `path` + // through unchanged. Neither generated document actually emits + // `$defs` today (no shared/recursive substructure), so this is inert. + collapseSchemaNode(child, path, annotationsByPath), + ]), + ); + } + + return result; +} + +/** + * Collapses every `anyOf: [{ type: "number", ... }, { type: "string", enum: + * [subset of "Infinity"/"-Infinity"/"NaN"] }]` node anywhere in `document` + * down to the plain `{ type: "number", ... }` branch, re-attaching that + * leaf's `description`/`default` from `rootAst` when the union node itself + * doesn't already carry them (Effect's `Schema.toJsonSchemaDocument` drops + * both when it renders a plain `Schema.Number` as this non-finite-safe + * union). `rootAst` must be the same schema `document` was rendered from. + */ +export function collapseNonFiniteNumberUnions(document: unknown, rootAst: SchemaAST.AST): unknown { + const annotationsByPath = collectNumberLeafAnnotations(rootAst); + return collapseSchemaNode(document, [], annotationsByPath); +} + +/** + * Injects `$id`/`title`/`description` right after `$schema`, ahead of the + * rest of the document's own keys — used by `build.ts` on both generated + * artifacts (CLI-2234). + */ +export function withSchemaMetadata( + document: Record, + metadata: { readonly id: string; readonly title: string; readonly description: string }, +): Record { + const { $schema, ...rest } = document; + return { + $schema, + $id: metadata.id, + title: metadata.title, + description: metadata.description, + ...rest, + }; +} diff --git a/packages/config/scripts/json-schema-postprocess.unit.test.ts b/packages/config/scripts/json-schema-postprocess.unit.test.ts new file mode 100644 index 0000000000..4db8f73550 --- /dev/null +++ b/packages/config/scripts/json-schema-postprocess.unit.test.ts @@ -0,0 +1,129 @@ +import { describe, expect, test } from "vitest"; +import { Schema } from "effect"; +import { collapseNonFiniteNumberUnions, withSchemaMetadata } from "./json-schema-postprocess.ts"; + +describe("collapseNonFiniteNumberUnions", () => { + test("collapses a top-level anyOf-with-non-finite-enum node to a plain number", () => { + const schema = Schema.Struct({ port: Schema.Number }); + const document = { + properties: { + port: { + anyOf: [{ type: "number" }, { type: "string", enum: ["Infinity", "-Infinity", "NaN"] }], + }, + }, + }; + + const result = collapseNonFiniteNumberUnions(document, schema.ast); + + expect(result).toEqual({ properties: { port: { type: "number" } } }); + }); + + test("re-attaches description/default from the source AST when missing on the union node", () => { + const schema = Schema.Struct({ + max_rows: Schema.Number.annotate({ description: "Row limit.", default: 1000 }), + }); + const document = { + properties: { + max_rows: { + anyOf: [{ type: "number" }, { type: "string", enum: ["Infinity", "-Infinity", "NaN"] }], + }, + }, + }; + + const result = collapseNonFiniteNumberUnions(document, schema.ast) as { + properties: { max_rows: Record }; + }; + + expect(result.properties.max_rows).toEqual({ + type: "number", + description: "Row limit.", + default: 1000, + }); + }); + + test("does not override description/default already present on the union node", () => { + const schema = Schema.Struct({ + max_rows: Schema.Number.annotate({ description: "Row limit.", default: 1000 }), + }); + const document = { + properties: { + max_rows: { + anyOf: [{ type: "number" }, { type: "string", enum: ["Infinity", "-Infinity", "NaN"] }], + description: "Overridden description.", + }, + }, + }; + + const result = collapseNonFiniteNumberUnions(document, schema.ast) as { + properties: { max_rows: Record }; + }; + + expect(result.properties.max_rows["description"]).toBe("Overridden description."); + expect(result.properties.max_rows["default"]).toBe(1000); + }); + + test("leaves an unrelated anyOf (e.g. object-or-null) untouched", () => { + const schema = Schema.Struct({ workers: Schema.Unknown }); + const document = { + properties: { + workers: { anyOf: [{ type: "object" }, { type: "null" }] }, + }, + }; + + const result = collapseNonFiniteNumberUnions(document, schema.ast); + + expect(result).toEqual(document); + }); + + test("descends through properties, patternProperties, items, and additionalProperties", () => { + const schema = Schema.Struct({ + list: Schema.Array(Schema.Number), + table: Schema.Record(Schema.String, Schema.Number), + }); + const nonFiniteNumberNode = { + anyOf: [{ type: "number" }, { type: "string", enum: ["Infinity", "-Infinity", "NaN"] }], + }; + const document = { + properties: { + list: { type: "array", items: nonFiniteNumberNode }, + table: { type: "object", patternProperties: { ".*": nonFiniteNumberNode } }, + }, + }; + + const result = collapseNonFiniteNumberUnions(document, schema.ast); + + expect(result).toEqual({ + properties: { + list: { type: "array", items: { type: "number" } }, + table: { type: "object", patternProperties: { ".*": { type: "number" } } }, + }, + }); + }); + + test("leaves a plain non-object document untouched", () => { + expect(collapseNonFiniteNumberUnions("not-a-schema-doc", Schema.String.ast)).toBe( + "not-a-schema-doc", + ); + }); +}); + +describe("withSchemaMetadata", () => { + test("inserts $id/title/description right after $schema, ahead of the rest of the document", () => { + const document = { $schema: "https://json-schema.org/draft/2020-12/schema", type: "object" }; + + const result = withSchemaMetadata(document, { + id: "https://example.com/schema.json", + title: "Example", + description: "An example schema.", + }); + + expect(Object.keys(result)).toEqual(["$schema", "$id", "title", "description", "type"]); + expect(result).toEqual({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.com/schema.json", + title: "Example", + description: "An example schema.", + type: "object", + }); + }); +}); diff --git a/packages/config/src/api-report.unit.test.ts b/packages/config/src/api-report.unit.test.ts index 0545376be0..ac8b16f294 100644 --- a/packages/config/src/api-report.unit.test.ts +++ b/packages/config/src/api-report.unit.test.ts @@ -28,34 +28,49 @@ async function listDeclarationFiles(root: string): Promise { return relativePaths.sort(); } -/** - * `bun --bun vitest` (this package's mandated test runner, per `AGENTS.md`) - * prepends a synthetic `node` shim directory (`/tmp/bun-node-*`, `node` -> - * `bun`) to `PATH` for the whole process tree, so any nested - * `#!/usr/bin/env node` script resolves to Bun instead of real Node. `pnpm`'s - * own launcher is exactly such a script, and its corepack wrapper needs - * `node:sqlite`, which Bun's Node-compat layer doesn't implement — so - * spawning `pnpm` unmodified from inside this test fails before it ever - * reaches `tsc`. Stripping that shim directory back out restores real `node` - * resolution for the spawned `pnpm` subprocess. - */ -function pnpmSpawnEnv(): Record { - const path = process.env.PATH ?? ""; - const sanitizedPath = path - .split(":") - .filter((segment) => !segment.includes("/bun-node-")) - .join(":"); - return { ...process.env, PATH: sanitizedPath }; +/** The first line at which `fresh`/`checkedIn` diverge, `undefined` when identical, for a precise mismatch message. */ +function firstDifferingLine(fresh: string, checkedIn: string): string | undefined { + const freshLines = fresh.split("\n"); + const checkedInLines = checkedIn.split("\n"); + const length = Math.max(freshLines.length, checkedInLines.length); + for (let index = 0; index < length; index++) { + if (freshLines[index] !== checkedInLines[index]) { + return ( + `line ${index + 1}: fresh=${JSON.stringify(freshLines[index])} ` + + `checked-in=${JSON.stringify(checkedInLines[index])}` + ); + } + } + return undefined; } +// This unit test spawns a real subprocess (`tsc`), a deliberate, narrow +// exception to this repo's usual "no subprocess in unit tests" default: the +// declarations-only compile it runs takes well under half a second, and it's +// the only thing that can actually guard the published type surface against +// silent drift (see this file's header comment). describe("api-report/ mirrors the compiled declaration surface", () => { test("a fresh declarations-only build matches the checked-in api-report/ mirror", async () => { const scratchDir = await mkdtemp(join(tmpdir(), "supabase-config-api-report-test-")); try { + // Spawns this package's own `node_modules/.bin/tsc` directly rather + // than `pnpm exec tsc`: `bun --bun vitest` (this package's mandated + // test runner, per `AGENTS.md`) prepends a synthetic `node` shim + // directory (`/tmp/bun-node-*`, `node` -> `bun`) to `PATH` for the + // whole process tree, so any nested `#!/usr/bin/env node` script + // resolves to Bun instead of real Node. `pnpm`'s own launcher is + // exactly such a script, and its corepack wrapper needs `node:sqlite`, + // which Bun's Node-compat layer doesn't implement — so spawning `pnpm` + // unmodified from inside this test used to fail before ever reaching + // `tsc`, requiring PATH surgery to work around it. Going straight to + // the installed `tsc` bin sidesteps `pnpm`'s launcher (and its + // `node:sqlite` dependency) entirely — `tsc` itself has no such + // dependency, so Bun's `node` shim resolving it is fine. + const tscBinPath = join(packageRoot, "node_modules", ".bin", "tsc"); const tsc = Bun.spawn( - ["pnpm", "exec", "tsc", "-p", "tsconfig.api-report.json", "--outDir", scratchDir], - { cwd: packageRoot, env: pnpmSpawnEnv(), stdout: "pipe", stderr: "pipe" }, + [tscBinPath, "-p", "tsconfig.api-report.json", "--outDir", scratchDir], + { cwd: packageRoot, stdout: "pipe", stderr: "pipe" }, ); const [exitCode, stdout, stderr] = await Promise.all([ tsc.exited, @@ -78,7 +93,7 @@ describe("api-report/ mirrors the compiled declaration surface", () => { readFile(join(apiReportDir, relativePath), "utf8"), ]); if (fresh !== checkedIn) { - mismatches.push(relativePath); + mismatches.push(`${relativePath} (${firstDifferingLine(fresh, checkedIn)})`); } } diff --git a/packages/config/src/effect.ts b/packages/config/src/effect.ts index 9354d2f86b..ac715ca58e 100644 --- a/packages/config/src/effect.ts +++ b/packages/config/src/effect.ts @@ -2,7 +2,7 @@ export * from "./index.ts"; import type { Effect } from "effect"; import type { LoadCliConfigOptions } from "./config-document.ts"; -import type { ResolvedCliConfigValue, ResolveCliConfigOptions } from "./lib/resolve.ts"; +import type { ResolvedCliConfigValue } from "./lib/resolve.ts"; import * as io from "./io.ts"; import type { CliProjectEnvironment } from "./project.ts"; import * as project from "./project.ts"; @@ -39,16 +39,14 @@ export { loadDotEnvFile, loadCliProjectEnvironment } from "./project.ts"; * `resolveCliConfigValue`/`resolveCliConfigSubtree` on this subpath — the * Effect-typed variant wins on `./effect`; the sync variant lives on `.`. * - * Narrowed to the public `ResolveCliConfigOptions` (no `goViperCompat`) for - * the same reason as {@link loadCliConfig} above; `@supabase/config/internal` - * re-exports these same runtime functions typed to additionally show - * `goViperCompat`. + * Narrowed to no options parameter (no `goViperCompat`) for the same reason + * as {@link loadCliConfig} above; `@supabase/config/internal` re-exports + * these same runtime functions typed to additionally show `goViperCompat`. */ export const resolveCliConfigValue: ( value: T, cliProjectEnv: Pick, configPath: string, - options?: ResolveCliConfigOptions, ) => Effect.Effect> = project.resolveCliConfigValue; /** See {@link resolveCliConfigValue}'s doc comment for the shadowing and narrowing rationale. */ @@ -56,7 +54,6 @@ export const resolveCliConfigSubtree: ( value: T, cliProjectEnv: Pick, pathPrefix: string, - options?: ResolveCliConfigOptions, ) => Effect.Effect> = project.resolveCliConfigSubtree; export { findCliProjectPaths, findCliProjectRoot } from "./paths.ts"; diff --git a/packages/config/src/entrypoint-purity.unit.test.ts b/packages/config/src/entrypoint-purity.unit.test.ts index 6d00a61bbc..96473d9105 100644 --- a/packages/config/src/entrypoint-purity.unit.test.ts +++ b/packages/config/src/entrypoint-purity.unit.test.ts @@ -356,6 +356,37 @@ describe("src/index.ts stays browser-safe", () => { }); }); +// CLI-2234 group 8c: `src/io-browser.ts` (the `browser` condition target for +// `@supabase/config/io`) must stay just as bundler-safe as `index.ts` itself +// — it only adds inert, throw-when-invoked stubs plus a type-only import from +// `promise-facade.ts` (erased at the specifier-scan level, same as every +// other `import type`/`export type` statement this walker already ignores) +// on top of `export * from "./index.ts"`. Reuses the exact same walker and +// browser-safe bare-specifier allowlist as the `index.ts` suite above. +const ioBrowserGraph = collectImportGraph(join(srcDir, "io-browser.ts")); +const expectedIoBrowserGraphFiles = [ + join(srcDir, "io-browser.ts"), + ...expectedPureGraphFiles, +].sort(); + +describe("src/io-browser.ts stays browser-safe", () => { + test("the traversal actually walked the real module graph", () => { + expect(ioBrowserGraph.visitedFiles.size).toBeGreaterThan(1); + expect(ioBrowserGraph.bareSpecifiers.has("effect")).toBe(true); + }); + + test("every bare import reachable from io-browser.ts is on the browser-safe allowlist", () => { + const disallowed = [...ioBrowserGraph.bareSpecifiers].filter( + (specifier) => !allowedBareSpecifier(specifier), + ); + expect(disallowed).toEqual([]); + }); + + test("the pure runtime graph is exactly index.ts's graph plus io-browser.ts itself", () => { + expect([...ioBrowserGraph.visitedFiles].sort()).toEqual(expectedIoBrowserGraphFiles); + }); +}); + describe("src/index.ts export surface", () => { test("pins the exact set of runtime export names", () => { expect(Object.keys(defaultEntrypoint).sort()).toMatchInlineSnapshot(` @@ -366,7 +397,7 @@ describe("src/index.ts export surface", () => { "CliProjectEnvParseError", "DuplicateRemoteProjectIdError", "InvalidRemoteProjectIdError", - "MissingCliConfigValueError", + "PROJECT_CONFIG_SCHEMA_URL", "ProjectConfigParseError", "ProjectConfigSchema", "attachApiResponse", @@ -405,7 +436,7 @@ describe("src/effect.ts is a superset of src/index.ts", () => { "CliProjectEnvParseError", "DuplicateRemoteProjectIdError", "InvalidRemoteProjectIdError", - "MissingCliConfigValueError", + "PROJECT_CONFIG_SCHEMA_URL", "ProjectConfigParseError", "ProjectConfigSchema", "attachApiResponse", @@ -482,7 +513,6 @@ describe("src/internal.ts export surface", () => { "AUTH_HOOK_NAMES", "ENV_CAPTURE_REGEX", "loadCliConfig", - "loadCliConfigFile", "projectConfigMappingRows", "resolveCliConfigSubtree", "resolveCliConfigValue", @@ -515,7 +545,15 @@ describe("package.json exports map", () => { expect(Object.keys(ioExports)).toEqual(["bun", "node", "browser", "default"]); }); - test("'types' is the first key in every conditional export object (CLI-2232)", () => { + // `.`/`./effect`/`./internal` lead with `bun` (CLI-2234): `tsc` under this + // repo's `customConditions: ["bun"]` must resolve straight to `src/*.ts` + // (self-typed, no separate `.d.ts` needed) instead of `dist/*.d.ts`, which + // requires `bun` to win the exports-map lookup ahead of `types` — see + // `apps/cli/tsconfig.json`'s `customConditions`. `types` only needs to + // precede `default` (the dist JS the `types` `.d.ts` describes), not be + // first outright, so a plain `nodenext` consumer (no `bun` condition + // requested) still resolves `types` -> `dist/*.d.ts` correctly. + test("'types' precedes 'default' in every conditional export object (CLI-2234)", () => { const conditionObjects = [ packageJson.exports["."], packageJson.exports["./effect"], @@ -525,10 +563,23 @@ describe("package.json exports map", () => { packageJson.exports["./io"].default, ]; for (const conditions of conditionObjects) { - expect(Object.keys(conditions)[0]).toBe("types"); + const keys = Object.keys(conditions); + expect(keys.indexOf("types")).toBeLessThan(keys.indexOf("default")); } }); + test("'.', './effect', and './internal' lead with the 'bun' condition (CLI-2234)", () => { + for (const key of [".", "./effect", "./internal"] as const) { + expect(Object.keys(packageJson.exports[key])[0]).toBe("bun"); + } + }); + + test("pins the exact top-level exports-map subpath set", () => { + expect(Object.keys(packageJson.exports).sort()).toEqual( + [".", "./internal", "./io", "./effect", "./schema.json", "./project-schema.json"].sort(), + ); + }); + // The `types`/`default` conditions of `.`/`./effect`/`./internal`/`./io` // (node, browser, default) all point at `dist/` build outputs, which only // exist after `pnpm run build` — intentionally NOT checked here so this diff --git a/packages/config/src/errors.ts b/packages/config/src/errors.ts index be2bdedbac..c235448665 100644 --- a/packages/config/src/errors.ts +++ b/packages/config/src/errors.ts @@ -134,10 +134,6 @@ export class CliProjectEnvParseError extends Data.TaggedError("CliProjectEnvPars readonly line: number; }> {} -export class MissingCliConfigValueError extends Data.TaggedError("MissingCliConfigValueError")<{ - readonly configPath: string; -}> {} - /** * Two `[remotes.*]` blocks declare the same `project_id` as the requested * `projectRef`. Mirrors Go's `loadFromFile` guard diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 115fb3dde6..d5d5bc6cbf 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -17,7 +17,6 @@ export { CliProjectEnvParseError, DuplicateRemoteProjectIdError, InvalidRemoteProjectIdError, - MissingCliConfigValueError, ProjectConfigParseError, } from "./errors.ts"; export type { ConfigFormat } from "./config-format.ts"; @@ -41,12 +40,11 @@ export { export type { LoadCliProjectEnvironmentOptions, CliProjectEnvironment } from "./project.ts"; export { type ResolvedCliConfigValue, - type ResolveCliConfigOptions, resolveCliConfigValue, resolveCliConfigSubtree, } from "./lib/resolve.ts"; export type { CliProjectPaths } from "./paths.ts"; -export { CLI_CONFIG_SCHEMA_URL } from "./schema-metadata.ts"; +export { CLI_CONFIG_SCHEMA_URL, PROJECT_CONFIG_SCHEMA_URL } from "./schema-metadata.ts"; export { type EffectiveConfig, type SparseCliConfig, diff --git a/packages/config/src/internal.ts b/packages/config/src/internal.ts index 5af9f06c2b..22bdbf08ee 100644 --- a/packages/config/src/internal.ts +++ b/packages/config/src/internal.ts @@ -2,7 +2,16 @@ * NOT covered by semver. This subpath exists solely for `apps/cli`'s own use * and its contract-guard tests — every export here (its existence, its shape, * its behavior) can change or vanish in any release without notice. External - * consumers must use `.`, `./effect`, or `./io` instead. + * consumers must use `.`, `./effect`, or `./io` instead; only `apps/cli` may + * import `@supabase/config/internal` (enforced by + * `src/monorepo-import-contract.unit.test.ts`). + * + * `loadCliConfig`/`resolveCliConfigValue`/`resolveCliConfigSubtree` below are + * the SAME runtime functions `./effect` exports, just re-typed here to widen + * their options parameter to the internal-only, Go-parity `goViperCompat` + * knob (`InternalLoadCliConfigOptions`/`InternalResolveCliConfigOptions`) — + * this module otherwise only re-exports types and registry data, not + * independent implementations. */ export { ENV_CAPTURE_REGEX } from "./lib/env.ts"; export { AUTH_HOOK_NAMES, unmappedSecretApiPaths } from "./project-config/registry-auth.ts"; @@ -10,9 +19,5 @@ export { projectConfigMappingRows } from "./project-config/registry.ts"; export { type ProjectConfigMappingRow } from "./project-config/registry-row.ts"; export { type ProjectConfigApiAttributes } from "./project-config/api-attributes.ts"; export { type InternalLoadCliConfigOptions } from "./config-document.ts"; -export { - type InternalResolveCliConfigOptions, - resolveCliConfigValue, - resolveCliConfigSubtree, -} from "./project.ts"; -export { loadCliConfig, loadCliConfigFile } from "./io.ts"; +export { resolveCliConfigValue, resolveCliConfigSubtree } from "./project.ts"; +export { loadCliConfig } from "./io.ts"; diff --git a/packages/config/src/lib/resolve.ts b/packages/config/src/lib/resolve.ts index e7ba3eb5de..5c4a2cba70 100644 --- a/packages/config/src/lib/resolve.ts +++ b/packages/config/src/lib/resolve.ts @@ -19,16 +19,6 @@ export type ResolvedCliConfigValue = T extends string ? { readonly [K in keyof T]: ResolvedCliConfigValue } : T; -/** - * Currently empty: this package's one `resolveCliConfigValue`/ - * `resolveCliConfigSubtree` option (`goViperCompat`) is internal-only — see - * {@link InternalResolveCliConfigOptions} in `../project.ts`, exported from - * `@supabase/config/internal`. Kept as a named type (rather than removed - * entirely) so the public sync resolvers below have a stable options - * parameter to extend if a public knob is ever added. - */ -export interface ResolveCliConfigOptions {} - export function toPathSegments(path: string): ReadonlyArray { if (path === "") { return []; @@ -119,7 +109,21 @@ function redactValue(value: unknown, path: ReadonlyArray, goViperCompat: * Effect-typed `resolveCliConfigValue`/`resolveCliConfigSubtree` (which wrap * this in `Effect.sync` and additionally accept the internal-only * `goViperCompat` option). + * + * Declared as an overload pair rather than a single generic signature: the + * body's `unknown`-typed implementation signature is what lets + * `interpolateValue`/`redactValue` (both genuinely `unknown -> unknown`, + * since the recursion branches on runtime shape, not on `T`) flow straight + * through to the return without an `as` cast — callers only ever see the + * generic overload above, which resolves `T` from the argument and returns + * `ResolvedCliConfigValue` directly. */ +export function resolveCliConfigValueAtPath( + value: T, + cliProjectEnv: Pick, + path: ReadonlyArray, + goViperCompat: boolean, +): ResolvedCliConfigValue; export function resolveCliConfigValueAtPath( value: unknown, cliProjectEnv: Pick, @@ -140,19 +144,18 @@ export function resolveCliConfigValueAtPath( * a caller that already has a project's env values but not the full * `CliProjectEnvironment` shape (e.g. `paths`/`loadedPaths`/`sources`) can pass * `{ values }` directly instead of threading through the whole loaded object. + * + * Has no options parameter: this package's one resolver knob (`goViperCompat`) + * is internal-only — see `InternalResolveCliConfigOptions` in `../project.ts`, + * exported from `@supabase/config/internal`. Adding a public knob later is a + * non-breaking, additive change. */ export function resolveCliConfigValue( value: T, cliProjectEnv: Pick, configPath: string, - _options?: ResolveCliConfigOptions, ): ResolvedCliConfigValue { - return resolveCliConfigValueAtPath( - value, - cliProjectEnv, - toPathSegments(configPath), - false, - ) as ResolvedCliConfigValue; + return resolveCliConfigValueAtPath(value, cliProjectEnv, toPathSegments(configPath), false); } /** See {@link resolveCliConfigValue}'s doc comment for why `cliProjectEnv` only needs `.values`. */ @@ -160,12 +163,6 @@ export function resolveCliConfigSubtree( value: T, cliProjectEnv: Pick, pathPrefix: string, - _options?: ResolveCliConfigOptions, ): ResolvedCliConfigValue { - return resolveCliConfigValueAtPath( - value, - cliProjectEnv, - toPathSegments(pathPrefix), - false, - ) as ResolvedCliConfigValue; + return resolveCliConfigValueAtPath(value, cliProjectEnv, toPathSegments(pathPrefix), false); } diff --git a/packages/config/src/lib/resolve.unit.test.ts b/packages/config/src/lib/resolve.unit.test.ts new file mode 100644 index 0000000000..b2765ca3aa --- /dev/null +++ b/packages/config/src/lib/resolve.unit.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from "vitest"; +import { Redacted } from "effect"; +import { resolveCliConfigValue, resolveCliConfigSubtree } from "../index.ts"; + +// Behavioral coverage of the two public sync resolvers, imported from the +// public `.` entrypoint (not `./resolve.ts` directly) — this is the exact +// surface an external consumer sees, options param removed (CLI-2234). + +describe("resolveCliConfigValue", () => { + test("a plain leaf passes through unchanged", () => { + expect(resolveCliConfigValue("hello", { values: {} }, "some.path")).toBe("hello"); + }); + + test("an env(NAME) reference resolves from the supplied values", () => { + expect(resolveCliConfigValue("env(FOO)", { values: { FOO: "bar" } }, "some.path")).toBe("bar"); + }); + + test("an unresolved env(NAME) reference is preserved verbatim", () => { + expect(resolveCliConfigValue("env(FOO)", { values: {} }, "some.path")).toBe("env(FOO)"); + }); + + test("a value at a schema-known secret path resolves and is wrapped in Redacted", () => { + const resolved = resolveCliConfigValue( + "env(OPENAI_API_KEY)", + { values: { OPENAI_API_KEY: "sk-test" } }, + "studio.openai_api_key", + ); + + expect(Redacted.isRedacted(resolved)).toBe(true); + if (Redacted.isRedacted(resolved)) { + expect(Redacted.value(resolved)).toBe("sk-test"); + } + }); +}); + +describe("resolveCliConfigSubtree", () => { + test("resolves and redacts nested leaves under a path prefix", () => { + const resolved = resolveCliConfigSubtree( + { openai_api_key: "env(OPENAI_API_KEY)", api_url: "http://127.0.0.1" }, + { values: { OPENAI_API_KEY: "sk-test" } }, + "studio", + ); + + expect(resolved.api_url).toBe("http://127.0.0.1"); + expect(Redacted.isRedacted(resolved.openai_api_key)).toBe(true); + if (Redacted.isRedacted(resolved.openai_api_key)) { + expect(Redacted.value(resolved.openai_api_key)).toBe("sk-test"); + } + }); +}); diff --git a/packages/config/src/monorepo-import-contract.unit.test.ts b/packages/config/src/monorepo-import-contract.unit.test.ts index 1168068c0b..5e027cd00e 100644 --- a/packages/config/src/monorepo-import-contract.unit.test.ts +++ b/packages/config/src/monorepo-import-contract.unit.test.ts @@ -1,23 +1,23 @@ import { describe, expect, test } from "vitest"; import { readdirSync, readFileSync } from "node:fs"; -import { dirname, join } from "node:path"; +import { dirname, join, sep } from "node:path"; import { fileURLToPath } from "node:url"; -// Enforces the two monorepo-wide import rules from `packages/config/AGENTS.md` +// Enforces the three monorepo-wide import rules from `packages/config/AGENTS.md` // ("Monorepo import rule"): `@supabase/config/io` has zero internal // consumers by design (it exists only for external, non-Effect-native -// Node/Bun code), and this package's internals must never be deep-imported -// (only the `.`/`./io`/`./effect`/`./internal` entrypoints are supported -// import paths — `@supabase/config/internal` is deliberately NOT checked by -// either rule below: unlike `./io`, `apps/cli` is an expected consumer). +// Node/Bun code), this package's internals must never be deep-imported (only +// the `.`/`./io`/`./effect`/`./internal` entrypoints are supported import +// paths), and `@supabase/config/internal` — unlike `./io` — IS an expected +// consumer, but only from `apps/cli`. // // A plain substring scan (no parsing) is enough for this — it's fast and the -// two forbidden specifiers can't appear by accident. The forbidden strings -// below are built by concatenation so this file's own source can never -// self-match (on top of the directory exclusion below, which already keeps -// this whole package — where those specifier strings legitimately appear in -// doc comments, generated `api-report/` declarations, and the build script's -// own Node-consumer smoke-test source string — out of the walk). +// forbidden specifiers can't appear by accident. The forbidden strings below +// are built by concatenation so this file's own source can never self-match +// (on top of the directory exclusion below, which already keeps this whole +// package — where those specifier strings legitimately appear in doc +// comments, generated `api-report/` declarations, and the build script's own +// smoke-test source string — out of the walk). // const srcDir = dirname(fileURLToPath(import.meta.url)); const repoRoot = join(srcDir, "..", "..", ".."); @@ -25,6 +25,8 @@ const repoRoot = join(srcDir, "..", "..", ".."); const configPackageName = ["@supabase", "config"].join("/"); const forbiddenIoSpecifier = `${configPackageName}/io`; const forbiddenDeepImportPrefix = `${configPackageName}/src/`; +const internalSpecifier = `${configPackageName}/internal`; +const allowedInternalConsumerPrefix = `${join(repoRoot, "apps", "cli")}${sep}`; const EXCLUDED_DIR_NAMES = new Set(["node_modules", "dist", ".repos"]); const thisPackageDir = join(srcDir, ".."); @@ -61,4 +63,11 @@ describe("monorepo import contract for @supabase/config", () => { test("no file outside this package deep-imports @supabase/config/src/*", () => { expect(findViolations(forbiddenDeepImportPrefix)).toEqual([]); }); + + test("every @supabase/config/internal import outside this package is under apps/cli/", () => { + const violations = findViolations(internalSpecifier).filter( + (file) => !file.startsWith(allowedInternalConsumerPrefix), + ); + expect(violations).toEqual([]); + }); }); diff --git a/packages/config/src/project-config/project-schema.ts b/packages/config/src/project-config/project-schema.ts index 7ade12fac7..b1aa0e6e8e 100644 --- a/packages/config/src/project-config/project-schema.ts +++ b/packages/config/src/project-config/project-schema.ts @@ -1,9 +1,3 @@ -import type { StandardSchemaV1 } from "@standard-schema/spec"; -import { Schema, SchemaAST } from "effect"; -import { CliConfigSchema } from "../base.ts"; -import { HOSTED_SECTION_KEYS } from "./hosted-sections.ts"; -import type { ProjectConfig } from "./project-config.ts"; - /** * Runtime companion to {@link ProjectConfig} (`./project-config.ts`) — a * schema that VALIDATES the same sparse hosted-section overlay @@ -29,14 +23,18 @@ import type { ProjectConfig } from "./project-config.ts"; * `x-secret` annotation (ADR 0019 rule 5 — `fromConfigDocument`/ * `fromApiProjectConfig` never populate a secret leaf either), the same * detection `../lib/secret-paths.ts`'s own walk uses. A container whose - * value type consists ENTIRELY of secret leaves (e.g. `db.vault`, a - * `Record`) ends up an empty `Objects` node (no - * surviving properties or index signatures) — `SchemaAST`'s own + * value type consists ENTIRELY of secret leaves (`db.vault`, a + * `Record`) ends up an empty `Objects` node this way + * (no surviving properties or index signatures) — `SchemaAST`'s own * documented behavior for that shape is "accepts any value except - * `null`/`undefined`", which is the closest a schema can get to "this - * container held nothing but secrets, so nothing concrete is left to - * validate here" without special-casing an empty-object type that JSON - * Schema has no way to express either. + * `null`/`undefined`", the closest a schema can get to "this container + * held nothing but secrets, so nothing concrete is left to validate + * here" without special-casing an empty-object type JSON Schema has no + * way to express either. Two OTHER hosted-section leaves land on that + * same empty-`Objects` shape for an unrelated reason: + * `storage.analytics.buckets.*` and `storage.vector.buckets.*` are + * already `Schema.Struct({})` at the SOURCE level (`../storage.ts`) — + * genuinely empty structs, untouched by this walk's secret-stripping. * - Wraps every SURVIVING property in `optionalKey` (via * {@link toOptionalAst}), recursing into its type — mirroring * `DeepPartial`'s `{ readonly [K in keyof T]?: DeepPartial }` @@ -59,22 +57,30 @@ import type { ProjectConfig } from "./project-config.ts"; * cannot generally satisfy — e.g. `{ auth: { email: { smtp: { enabled: * true } } } }` with no `host` yet is a legal, if incomplete, * `ProjectConfig` fragment, but `requiredWhenEnabled("host", ...)` would - * reject it. Every LEAF-level check (`Schema.Number.check(isInt(), - * isGreaterThanOrEqualTo(0))`, `Schema.isPattern(...)`, port-range - * bounds, …) lives on a non-`Objects` node and is left untouched. + * reject it. Every LEAF-level check survives untouched, since it lives + * on a non-`Objects` node — today that's only `workers.*.instances`'s + * `Schema.Number.check(isInt(), isGreaterThanOrEqualTo(0))` and the + * `[workers]` record's own key pattern (`Schema.isPattern(...)` on + * `workerName`, `../workers.ts`). There is no port-range (or other + * numeric-bound) leaf check anywhere in this schema today. * - Recurses into `Union` members (e.g. `storage.file_size_limit`'s * `Schema.Union([String, Number])`, and every `Schema.Literals`-backed - * enum, which V4 also compiles to a `Union`) and `Suspend` thunks, so a - * secret-bearing or object-shaped member nested inside either would - * still be reached. Every other node kind (every leaf: `String`, - * `Number`, `Boolean`, `Literal`, …) is returned unchanged — there is - * nothing further to drop or partialize on a leaf. This module's own AST - * node kinds are enumerated explicitly, via each class's PUBLIC - * constructor, rather than through a generic `.recur()`-style - * mechanism: unlike `.repos/effect`'s vendored source, the installed - * `effect` release's own `AST#recur` is `@internal` (absent from its - * published `.d.ts`), so a truly generic fallback isn't available - * through the public API surface this package is allowed to depend on. + * enum, which V4 also compiles to a `Union`), so a secret-bearing or + * object-shaped member nested inside one would still be reached. Every + * other node kind (every leaf: `String`, `Number`, `Boolean`, + * `Literal`, …) is returned unchanged — there is nothing further to + * drop or partialize on a leaf. This module's own AST node kinds are + * enumerated explicitly, via each class's PUBLIC constructor, rather + * than through a generic `.recur()`-style mechanism: unlike + * `.repos/effect`'s vendored source, the installed `effect` release's + * own `AST#recur` is `@internal` (absent from its published `.d.ts`), + * so a truly generic fallback isn't available through the public API + * surface this package is allowed to depend on. + * `./project-schema.unit.test.ts`'s AST-walk exhaustiveness guard walks + * the derived AST and fails loudly if a node kind outside this + * enumerated set (or a reintroduced `Suspend`, deliberately unhandled + * here — see {@link toDeepOptionalHostedAst}) ever appears, rather than + * silently mishandling it. * * `_apiResponse` (ADR 0019) is deliberately NOT part of this schema: it's * attached as a non-enumerable property that ordinary decode/validation can @@ -88,6 +94,11 @@ import type { ProjectConfig } from "./project-config.ts"; * future release adds), and JSON Schema's own default is permissive — this * derivation matches that norm rather than rejecting anything unrecognized. */ +import type { StandardSchemaV1 } from "@standard-schema/spec"; +import { Schema, SchemaAST } from "effect"; +import { CliConfigSchema } from "../base.ts"; +import type { ProjectConfig } from "./project-config.ts"; + function isSecretAst(ast: SchemaAST.AST): boolean { return ast.annotations?.["x-secret"] === true; } @@ -97,14 +108,26 @@ function isSecretAst(ast: SchemaAST.AST): boolean { * (`Schema.optionalKey(Schema.make(ast)).ast`) rather than the internal * `SchemaAST.optionalKey` this repo's vendored `.repos/effect` snapshot * exposes publicly but the installed `effect` release does not — see this - * module's own doc comment. `Schema.make` performs no structural check - * against the throwaway `unknown` `Codec` parameter here; only `ast` itself - * (read straight back off the wrapped schema) is used. + * module's own doc comment. Conscious exception to this repo's `as`-cast + * policy's spirit (a typed-constructor call standing in for one): `Schema.make` + * performs no structural check against the throwaway `unknown` `Codec` + * parameter here; only `ast` itself (read straight back off the wrapped + * schema) is used. */ function toOptionalAst(ast: SchemaAST.AST): SchemaAST.AST { return Schema.optionalKey(Schema.make>(ast)).ast; } +/** + * `Suspend` is deliberately UNHANDLED here (falls through to the final + * `return ast` below, verbatim, untouched) rather than recursed into: no + * `Schema.suspend`-backed recursive type is reachable from the seven hosted + * sections today, so this is unreachable in practice, and + * `./project-schema.unit.test.ts`'s AST-walk exhaustiveness guard fails + * loudly the moment one is introduced — a reviewable prompt to design real + * `Suspend` handling (thunk identity/`$defs` implications included) instead + * of silently mishandling recursion. + */ function toDeepOptionalHostedAst(ast: SchemaAST.AST): SchemaAST.AST { if (SchemaAST.isObjects(ast)) { const propertySignatures = ast.propertySignatures @@ -149,15 +172,6 @@ function toDeepOptionalHostedAst(ast: SchemaAST.AST): SchemaAST.AST { ast.encodingChecks, ); } - if (SchemaAST.isSuspend(ast)) { - return new SchemaAST.Suspend( - () => toDeepOptionalHostedAst(ast.thunk()), - ast.annotations, - ast.checks, - ast.encoding, - ast.context, - ); - } return ast; } @@ -179,16 +193,13 @@ const hostedSectionsStruct = Schema.Struct({ // The literal pick above still names the same seven keys as // `HOSTED_SECTION_KEYS` by hand, since a type-safe `Schema.Struct` field -// object can't be built from an array without an `as` cast — this guard -// catches the two lists drifting apart (an edit to one without the other) at -// import time instead of silently validating the wrong section set. -const pickedHostedSectionKeys = Object.keys(hostedSectionsStruct.fields).toSorted(); -const declaredHostedSectionKeys = HOSTED_SECTION_KEYS.toSorted(); -if (JSON.stringify(pickedHostedSectionKeys) !== JSON.stringify(declaredHostedSectionKeys)) { - throw new Error( - "project-schema.ts's picked hosted-section fields drifted from HOSTED_SECTION_KEYS", - ); -} +// object can't be built from an array without an `as` cast. The two lists +// drifting apart (an edit to one without the other) is caught by +// `./project-schema.unit.test.ts`'s own assertion against +// `ProjectConfigSchema.ast`'s top-level property names vs. +// `HOSTED_SECTION_KEYS`, not by an import-time throw here (CLI-2234) — a +// schema-module import should never be able to crash a consumer's process +// for a condition a test already covers. const projectConfigAst = toDeepOptionalHostedAst(SchemaAST.toType(hostedSectionsStruct.ast)); @@ -215,7 +226,11 @@ type ProjectConfigSchemaType = Omit; * only transitively through `effect` under pnpm's strict `node_modules` * isolation — which tsc's declaration emit refuses to synthesize into * `project-schema.d.ts` as non-portable. Explicitly importing the type here - * pins `@standard-schema/spec` as a direct dependency instead. + * pins `@standard-schema/spec` as a direct dependency instead. Conscious + * exception to this repo's `as`-cast policy's spirit: `Schema.make`'s type + * parameter here is asserted, not verified, against `projectConfigAst` — see + * {@link ProjectConfigSchemaType}'s doc comment for the independent + * compile-time cross-check that catches drift instead. */ export const ProjectConfigSchema: StandardSchemaV1< ProjectConfigSchemaType, diff --git a/packages/config/src/project-config/project-schema.unit.test.ts b/packages/config/src/project-config/project-schema.unit.test.ts index ee4e73829f..9bebcd60bb 100644 --- a/packages/config/src/project-config/project-schema.unit.test.ts +++ b/packages/config/src/project-config/project-schema.unit.test.ts @@ -141,6 +141,94 @@ describe("ProjectConfigSchema secret-strip exhaustiveness", () => { expect(findAtPattern(ProjectConfigSchema.ast, pattern)).toBeUndefined(); } }); + + // Guards against a vacuous pass: if an ANCESTOR of `pattern` vanished + // (e.g. a whole section got dropped by an unrelated bug), `findAtPattern` + // for the full secret path also returns `undefined` — indistinguishable, + // from that assertion alone, from the secret leaf being correctly + // stripped. Asserting the parent path is still reachable rules that out. + test("the parent of every stripped x-secret path is still reachable", () => { + for (const pattern of reachablePatterns) { + const parentPattern = pattern.slice(0, -1); + const parent = + parentPattern.length === 0 + ? ProjectConfigSchema.ast + : findAtPattern(ProjectConfigSchema.ast, parentPattern); + expect(parent, `parent of ${JSON.stringify(pattern)} vanished`).toBeDefined(); + } + }); +}); + +describe("ProjectConfigSchema hosted-section keys", () => { + // Moved from an import-time throw in `project-schema.ts` (CLI-2234): a + // schema-module import should never be able to crash a consumer's + // process for a condition a test already covers. Asserts against the + // PUBLIC, observable `ProjectConfigSchema.ast` rather than reaching into + // the module's private `hostedSectionsStruct`. + test("the schema's own top-level property names are exactly HOSTED_SECTION_KEYS", () => { + if (!SchemaAST.isObjects(ProjectConfigSchema.ast)) { + throw new Error("expected ProjectConfigSchema.ast to be an Objects node"); + } + const actualKeys = ProjectConfigSchema.ast.propertySignatures.map((property) => + String(property.name), + ); + expect(actualKeys.toSorted()).toEqual([...HOSTED_SECTION_KEYS].toSorted()); + }); +}); + +describe("ProjectConfigSchema derivation AST-walk exhaustiveness", () => { + // CLI-2234 group 7c/7d: `toDeepOptionalHostedAst` (`project-schema.ts`) + // enumerates AST node kinds explicitly rather than through a generic + // recursion helper (see that module's doc comment for why) and + // deliberately leaves `Suspend` unhandled. This walks the ACTUAL derived + // `ProjectConfigSchema.ast` and fails loudly the moment a node kind + // outside the set that derivation is written to understand appears, + // rather than letting a future schema addition silently fall through + // `toDeepOptionalHostedAst`'s final `return ast` (correct for a true + // leaf, silently wrong for an unhandled container/recursive kind). + const HANDLED_CONTAINER_TAGS = new Set(["Objects", "Arrays", "Union"]); + const HANDLED_LEAF_TAGS = new Set(["String", "Number", "Boolean", "Literal"]); + + function walk(ast: SchemaAST.AST, seen: Set): void { + if (seen.has(ast)) { + return; + } + seen.add(ast); + + if (HANDLED_CONTAINER_TAGS.has(ast._tag) || HANDLED_LEAF_TAGS.has(ast._tag)) { + if (SchemaAST.isObjects(ast)) { + for (const property of ast.propertySignatures) { + walk(property.type, seen); + } + for (const indexSignature of ast.indexSignatures) { + walk(indexSignature.type, seen); + } + } else if (SchemaAST.isArrays(ast)) { + for (const element of ast.elements) { + walk(element, seen); + } + for (const rest of ast.rest) { + walk(rest, seen); + } + } else if (SchemaAST.isUnion(ast)) { + for (const member of ast.types) { + walk(member, seen); + } + } + return; + } + + throw new Error( + `ProjectConfigSchema's derived AST contains a node kind ("${ast._tag}") that ` + + "toDeepOptionalHostedAst (project-schema.ts) isn't written to understand yet — " + + "the derivation must learn this new node kind (secret-stripping, optionality, and " + + "checks-stripping all need a deliberate decision for it) before this guard can pass.", + ); + } + + test("every node kind reachable from ProjectConfigSchema.ast is in the handled set", () => { + walk(ProjectConfigSchema.ast, new Set()); + }); }); describe("ProjectConfigSchema local-only sections", () => { diff --git a/packages/config/src/project.ts b/packages/config/src/project.ts index 229ebee950..e06cd2302f 100644 --- a/packages/config/src/project.ts +++ b/packages/config/src/project.ts @@ -4,7 +4,6 @@ import { resolveCliConfigValueAtPath, toPathSegments, type ResolvedCliConfigValue, - type ResolveCliConfigOptions, } from "./lib/resolve.ts"; import { findCliProjectPaths, type CliProjectPaths } from "./paths.ts"; @@ -197,7 +196,7 @@ export interface LoadCliProjectEnvironmentOptions { * Not covered by semver — exported from `@supabase/config/internal` only. See * that module's header for why. */ -export interface InternalResolveCliConfigOptions extends ResolveCliConfigOptions { +export interface InternalResolveCliConfigOptions { /** * Opt into Go/viper-parity `env()` matching (case-agnostic * `^env\((.*)\)$`). Defaults to `false`, which uses the pre-PR-#5765 strict @@ -265,14 +264,13 @@ export function resolveCliConfigValue( configPath: string, options?: InternalResolveCliConfigOptions, ): Effect.Effect> { - return Effect.sync( - () => - resolveCliConfigValueAtPath( - value, - cliProjectEnv, - toPathSegments(configPath), - options?.goViperCompat ?? false, - ) as ResolvedCliConfigValue, + return Effect.sync(() => + resolveCliConfigValueAtPath( + value, + cliProjectEnv, + toPathSegments(configPath), + options?.goViperCompat ?? false, + ), ); } @@ -283,13 +281,12 @@ export function resolveCliConfigSubtree( pathPrefix: string, options?: InternalResolveCliConfigOptions, ): Effect.Effect> { - return Effect.sync( - () => - resolveCliConfigValueAtPath( - value, - cliProjectEnv, - toPathSegments(pathPrefix), - options?.goViperCompat ?? false, - ) as ResolvedCliConfigValue, + return Effect.sync(() => + resolveCliConfigValueAtPath( + value, + cliProjectEnv, + toPathSegments(pathPrefix), + options?.goViperCompat ?? false, + ), ); } diff --git a/packages/config/src/promise-facade.ts b/packages/config/src/promise-facade.ts index a42a0fced9..5c7bf888dc 100644 --- a/packages/config/src/promise-facade.ts +++ b/packages/config/src/promise-facade.ts @@ -20,10 +20,17 @@ import { CliConfigStore } from "./cli-config.service.ts"; * member names. * * A rejection from `loadCliConfig`, `loadCliConfigFile`, or `saveCliConfig` - * can carry any of `CliConfigStoreError`'s members (`cli-config.service.ts`): - * this package's own `CliConfigParseError` / `DuplicateRemoteProjectIdError` / - * `InvalidRemoteProjectIdError` / `CliProjectEnvParseError`, or `PlatformError` - * for a host/OS failure — distinguish via `instanceof`. + * can carry any of five typed failures — this package's own + * `CliConfigParseError`, `DuplicateRemoteProjectIdError`, + * `InvalidRemoteProjectIdError`, `CliProjectEnvParseError`, or `PlatformError` + * (from `effect/PlatformError`) for a host/OS failure — distinguish via + * `instanceof`. One exception: `saveCliConfig`'s atomic-write step maps a + * rename failure to a defect rather than one of these typed failures (see + * `io.ts`'s `writeFileAtomic`) — the returned promise still rejects, but with + * the raw, un-mapped failure, not an instance of any class listed above. This + * is a deliberate design choice (a rename failure after a successful write + * indicates something is wrong with the filesystem itself, not a recoverable + * config condition), not an oversight. */ export interface CliConfigIo { readonly loadCliConfig: ( diff --git a/packages/config/src/schema-metadata.ts b/packages/config/src/schema-metadata.ts index 19be5bab62..01dca5072c 100644 --- a/packages/config/src/schema-metadata.ts +++ b/packages/config/src/schema-metadata.ts @@ -1 +1,3 @@ export const CLI_CONFIG_SCHEMA_URL = "https://supabase.com/docs/cli/config.schema.json"; +/** Sibling of {@link CLI_CONFIG_SCHEMA_URL} for `ProjectConfigSchema`'s generated JSON Schema document (`toProjectConfigJsonSchema`) — same `/docs/cli/` path, same `apps/cli/scripts/generate-docs.ts` pathname-derivation convention. */ +export const PROJECT_CONFIG_SCHEMA_URL = "https://supabase.com/docs/cli/project-config.schema.json"; From 57945aef23c9ad5e79f8cb00d142d75c33d29e10 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Thu, 27 Aug 2026 18:48:59 +0100 Subject: [PATCH 6/9] fix(cli): pin pg-topo types to its shipped declarations under customConditions (CLI-2234) --- apps/cli/tsconfig.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index 3fefb21ac8..fe40faa8e6 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -9,7 +9,17 @@ // affects any OTHER dependency whose own exports map declares a `bun` // condition (e.g. `@supabase/pg-topo`) — see this package's AGENTS.md/PR // notes for a known collision that surfaces there. - "customConditions": ["bun"] + "customConditions": ["bun"], + // `@supabase/pg-topo` (external, from supabase/pg-toolbelt) also declares + // a `bun` exports condition, pointing at its UNBUILT `src/*.ts`, which + // carries type errors at 1.0.0-alpha.5 that its shipped `dist/*.d.ts` + // does not surface. `paths` wins over exports-condition resolution, so + // pin its types to the published declarations. Drop this once a fixed + // pg-topo release (>= 1.0.0-alpha.6) clears the pnpm minimumReleaseAge + // window and is bumped in this package. + "paths": { + "@supabase/pg-topo": ["./node_modules/@supabase/pg-topo/dist/index.d.ts"] + } }, "exclude": ["supabase", "src/shared/workers/stacks"] } From a2b0e35f60a57847a8d5c8d0688846028c7045fd Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Fri, 28 Aug 2026 12:23:54 +0100 Subject: [PATCH 7/9] refactor(config): replace checked-in api-report with a CI base-vs-head declaration compare (CLI-2234) --- .gitattributes | 7 - .github/workflows/test.yml | 8 + .oxfmtrc.json | 3 +- .oxlintrc.json | 1 - knip.json | 5 +- package.json | 1 + packages/config/.gitignore | 4 + packages/config/AGENTS.md | 18 +- packages/config/README.md | 5 +- packages/config/api-report/analytics.d.ts | 10 - packages/config/api-report/api.d.ts | 15 - packages/config/api-report/auth/captcha.d.ts | 6 - packages/config/api-report/auth/email.d.ts | 28 - packages/config/api-report/auth/hooks.d.ts | 33 - packages/config/api-report/auth/index.d.ts | 361 -- packages/config/api-report/auth/mfa.d.ts | 19 - .../config/api-report/auth/providers.d.ts | 183 - .../config/api-report/auth/rate_limit.d.ts | 10 - packages/config/api-report/auth/sessions.d.ts | 5 - packages/config/api-report/auth/sms.d.ts | 36 - .../config/api-report/auth/third_party.d.ts | 25 - packages/config/api-report/auth/web3.d.ts | 9 - packages/config/api-report/base.d.ts | 1621 -------- packages/config/api-report/bun.d.ts | 9 - .../config/api-report/cli-config.layer.d.ts | 3 - .../config/api-report/cli-config.service.d.ts | 23 - .../config/api-report/config-document.d.ts | 128 - packages/config/api-report/config-format.d.ts | 11 - packages/config/api-report/db.d.ts | 56 - packages/config/api-report/edge_runtime.d.ts | 8 - packages/config/api-report/effect.d.ts | 38 - packages/config/api-report/errors.d.ts | 155 - packages/config/api-report/experimental.d.ts | 24 - .../api-report/functions-manifest-model.d.ts | 12 - .../config/api-report/functions-manifest.d.ts | 11 - packages/config/api-report/functions.d.ts | 9 - packages/config/api-report/inbucket.d.ts | 9 - packages/config/api-report/index.d.ts | 20 - packages/config/api-report/internal.d.ts | 23 - packages/config/api-report/io-browser.d.ts | 9 - packages/config/api-report/io.d.ts | 3374 ----------------- packages/config/api-report/lib/env.d.ts | 36 - packages/config/api-report/lib/resolve.d.ts | 46 - packages/config/api-report/lib/schema.d.ts | 16 - .../config/api-report/lib/secret-paths.d.ts | 13 - packages/config/api-report/node.d.ts | 9 - packages/config/api-report/paths.d.ts | 38 - .../project-config/api-attributes.d.ts | 117 - .../project-config/hosted-sections.d.ts | 12 - .../project-config/project-config.d.ts | 374 -- .../project-config/project-schema.d.ts | 134 - .../project-config/registry-auth.d.ts | 43 - .../project-config/registry-row.d.ts | 146 - .../api-report/project-config/registry.d.ts | 7 - packages/config/api-report/project.d.ts | 71 - .../config/api-report/promise-facade.d.ts | 40 - packages/config/api-report/realtime.d.ts | 6 - .../config/api-report/schema-metadata.d.ts | 3 - packages/config/api-report/sparse.d.ts | 137 - packages/config/api-report/storage.d.ts | 30 - packages/config/api-report/studio.d.ts | 7 - packages/config/api-report/workers.d.ts | 15 - packages/config/package.json | 1 - packages/config/scripts/build.ts | 144 +- packages/config/src/api-report.unit.test.ts | 105 - .../config/src/entrypoint-purity.unit.test.ts | 5 +- .../src/monorepo-import-contract.unit.test.ts | 4 +- ...report.json => tsconfig.declarations.json} | 6 +- tools/config-api-compare.ts | 503 +++ turbo.json | 9 +- 70 files changed, 577 insertions(+), 7835 deletions(-) delete mode 100644 .gitattributes create mode 100644 packages/config/.gitignore delete mode 100644 packages/config/api-report/analytics.d.ts delete mode 100644 packages/config/api-report/api.d.ts delete mode 100644 packages/config/api-report/auth/captcha.d.ts delete mode 100644 packages/config/api-report/auth/email.d.ts delete mode 100644 packages/config/api-report/auth/hooks.d.ts delete mode 100644 packages/config/api-report/auth/index.d.ts delete mode 100644 packages/config/api-report/auth/mfa.d.ts delete mode 100644 packages/config/api-report/auth/providers.d.ts delete mode 100644 packages/config/api-report/auth/rate_limit.d.ts delete mode 100644 packages/config/api-report/auth/sessions.d.ts delete mode 100644 packages/config/api-report/auth/sms.d.ts delete mode 100644 packages/config/api-report/auth/third_party.d.ts delete mode 100644 packages/config/api-report/auth/web3.d.ts delete mode 100644 packages/config/api-report/base.d.ts delete mode 100644 packages/config/api-report/bun.d.ts delete mode 100644 packages/config/api-report/cli-config.layer.d.ts delete mode 100644 packages/config/api-report/cli-config.service.d.ts delete mode 100644 packages/config/api-report/config-document.d.ts delete mode 100644 packages/config/api-report/config-format.d.ts delete mode 100644 packages/config/api-report/db.d.ts delete mode 100644 packages/config/api-report/edge_runtime.d.ts delete mode 100644 packages/config/api-report/effect.d.ts delete mode 100644 packages/config/api-report/errors.d.ts delete mode 100644 packages/config/api-report/experimental.d.ts delete mode 100644 packages/config/api-report/functions-manifest-model.d.ts delete mode 100644 packages/config/api-report/functions-manifest.d.ts delete mode 100644 packages/config/api-report/functions.d.ts delete mode 100644 packages/config/api-report/inbucket.d.ts delete mode 100644 packages/config/api-report/index.d.ts delete mode 100644 packages/config/api-report/internal.d.ts delete mode 100644 packages/config/api-report/io-browser.d.ts delete mode 100644 packages/config/api-report/io.d.ts delete mode 100644 packages/config/api-report/lib/env.d.ts delete mode 100644 packages/config/api-report/lib/resolve.d.ts delete mode 100644 packages/config/api-report/lib/schema.d.ts delete mode 100644 packages/config/api-report/lib/secret-paths.d.ts delete mode 100644 packages/config/api-report/node.d.ts delete mode 100644 packages/config/api-report/paths.d.ts delete mode 100644 packages/config/api-report/project-config/api-attributes.d.ts delete mode 100644 packages/config/api-report/project-config/hosted-sections.d.ts delete mode 100644 packages/config/api-report/project-config/project-config.d.ts delete mode 100644 packages/config/api-report/project-config/project-schema.d.ts delete mode 100644 packages/config/api-report/project-config/registry-auth.d.ts delete mode 100644 packages/config/api-report/project-config/registry-row.d.ts delete mode 100644 packages/config/api-report/project-config/registry.d.ts delete mode 100644 packages/config/api-report/project.d.ts delete mode 100644 packages/config/api-report/promise-facade.d.ts delete mode 100644 packages/config/api-report/realtime.d.ts delete mode 100644 packages/config/api-report/schema-metadata.d.ts delete mode 100644 packages/config/api-report/sparse.d.ts delete mode 100644 packages/config/api-report/storage.d.ts delete mode 100644 packages/config/api-report/studio.d.ts delete mode 100644 packages/config/api-report/workers.d.ts delete mode 100644 packages/config/src/api-report.unit.test.ts rename packages/config/{tsconfig.api-report.json => tsconfig.declarations.json} (63%) create mode 100644 tools/config-api-compare.ts diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 01cc9ae0c1..0000000000 --- a/.gitattributes +++ /dev/null @@ -1,7 +0,0 @@ -# `packages/config/api-report/` is a checked-in mirror of compiled `.d.ts` -# output, byte-diffed against a fresh regenerate by -# `packages/config/src/api-report.unit.test.ts` (CLI-2234). Windows autocrlf -# line-ending rewriting would break that comparison — treat these files as -# binary (no line-ending normalization) so they stay byte-identical across -# platforms. -packages/config/api-report/** -text diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 82ad7fb067..af9cc07490 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -67,6 +67,14 @@ jobs: - name: Check code quality run: pnpm run check:all + # Advisory only (base-vs-head diff, no acceptance artifact to gate a + # required check on) — the hard release-time gate is tracked under + # CLI-2233. `continue-on-error` flags the diff without failing the job; + # the tool's own fetch fallback covers this checkout's shallow clone. + - name: config type-surface diff (advisory) + continue-on-error: true + run: pnpm run check:config-api + test-unit: if: | !startsWith(github.head_ref, 'release-notes/') && diff --git a/.oxfmtrc.json b/.oxfmtrc.json index ea399a230a..0e2a3a42c0 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -5,7 +5,6 @@ "apps/cli-e2e/fixtures/", "apps/docs/content/docs/commands/", "apps/docs/public/", - "**/testdata/", - "**/api-report/" + "**/testdata/" ] } diff --git a/.oxlintrc.json b/.oxlintrc.json index 01efbbc24a..89de4dd693 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -10,7 +10,6 @@ "apps/cli-e2e/fixtures", "**/testdata", "**/dist", - "**/api-report", "**/coverage", "**/.next", "**/.source" diff --git a/knip.json b/knip.json index 130ff84456..43305fb407 100644 --- a/knip.json +++ b/knip.json @@ -3,7 +3,7 @@ "exclude": ["catalogReferences"], "workspaces": { ".": { - "entry": [".github/scripts/**/*.ts", "tools/release/*.ts"], + "entry": [".github/scripts/**/*.ts", "tools/*.ts", "tools/release/*.ts"], "ignore": [".repos/**", "apps/cli-go/**"], "ignoreBinaries": ["go"], "ignoreDependencies": ["verdaccio"] @@ -35,8 +35,7 @@ "ignoreDependencies": ["undici"] }, "packages/config": { - "entry": ["src/**/*.test.ts"], - "ignore": ["api-report/**"] + "entry": ["src/**/*.test.ts"] }, "packages/process-compose": { "entry": ["src/**/*.test.ts", "tests/**/*.ts"] diff --git a/package.json b/package.json index 8fdbb1c711..5e98cf50dc 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "fmt:fix": "oxfmt --config .oxfmtrc.json", "knip:check": "knip-bun", "knip:fix": "knip-bun --fix", + "check:config-api": "bun tools/config-api-compare.ts", "repos:install": "git submodule update --init --recursive", "repos:pull": "git submodule update --remote", "local-registry": "bun tools/release/local-registry.ts", diff --git a/packages/config/.gitignore b/packages/config/.gitignore new file mode 100644 index 0000000000..6f4e986fa0 --- /dev/null +++ b/packages/config/.gitignore @@ -0,0 +1,4 @@ +# Scratch tree `tools/config-api-compare.ts` (repo root) extracts the PR base +# revision's package source into, so tsc resolves dependencies by walking up +# to this package's own node_modules. Cleaned up on exit; never committed. +.api-compare/ diff --git a/packages/config/AGENTS.md b/packages/config/AGENTS.md index 31fc753a3d..0c17e8147a 100644 --- a/packages/config/AGENTS.md +++ b/packages/config/AGENTS.md @@ -90,22 +90,17 @@ surface must update that test deliberately — it is not meant to be a silent pa merely asserting it — plus a positive-control probe (bundling `projectConfigMappingRows` from `dist/internal.js`) proving the registry-only marker is actually detectable by this bundling method before trusting its absence elsewhere as meaningful. -5. Syncs `api-report/` — a declarations-only build (`tsconfig.api-report.json`) built into a - scratch directory and atomically swapped into the checked-in `api-report/` directory (never a - partial write). `src/api-report.unit.test.ts` regenerates the same build and diffs it against - that mirror, so any type-surface change anywhere in `src/` shows up as a reviewable - `api-report/` diff instead of passing silently — commit that diff whenever it appears. Run - `pnpm run api-report:update` for just this step (declaration emit + sync only) without the rest - of the build. -6. Runs a pack-and-install smoke test: `npm pack`s the real publish tarball (governed by `files`/ +5. Runs a pack-and-install smoke test: `npm pack`s the real publish tarball (governed by `files`/ `.npmignore` — the exact thing `npm publish` would ship), extracts it into a fresh, isolated consumer project, symlinks in the real, already pnpm-resolved runtime deps (network-free), and imports every entrypoint and JSON artifact through a real `node` process — catching `files`/ `exports` drift a workspace-link smoke test or a `tsc`-only build would miss entirely. -`dist/` is gitignored and rebuilt on demand; `api-report/` is the one build output that is checked -in. Re-run the build and commit the resulting `api-report/` diff whenever a change touches this -package's public type surface. +`dist/` is gitignored and rebuilt on demand — no build output is checked in. The public type +surface is instead enforced per-PR by export snapshots and purity walkers (see "Testing" below) +plus the repo-root `pnpm check:config-api` (`tools/config-api-compare.ts`), which diffs this +package's declaration output between the PR base and head commits and is advisory at PR time. A +release-time tarball diff is planned under CLI-2233 as the hard gate. ### Publishing the tarball (CLI-2234) @@ -128,7 +123,6 @@ must stay green after any entrypoint or type-surface change: - `src/entrypoint-purity.unit.test.ts` — the pure-graph invariant above (also walked separately for `src/io-browser.ts`, the `browser` condition target for `./io`), plus pinned export-name snapshots for `.`/`./effect`/`./internal` and the package.json `exports` map shape. -- `src/api-report.unit.test.ts` — the checked-in `api-report/` mirror described above. - `src/monorepo-import-contract.unit.test.ts` — the "Monorepo import rule" above: no internal `./io` consumer, no deep `@supabase/config/src/*` import, and no `@supabase/config/internal` import outside `apps/cli/` — scanning `apps/` and `packages/` while excluding this package's own diff --git a/packages/config/README.md b/packages/config/README.md index 262b4f741a..dba81fdf56 100644 --- a/packages/config/README.md +++ b/packages/config/README.md @@ -318,7 +318,8 @@ try { The runtime export surface of `.`, `./io`, and `./effect`, plus the two generated JSON Schema artifacts (`./schema.json`, `./project-schema.json`), is this package's published contract. `./internal` carries no such guarantee. See [AGENTS.md](https://github.com/supabase/cli/blob/develop/packages/config/AGENTS.md) for how that contract is -enforced (export-surface snapshots and a checked-in API report). +enforced (export-surface snapshots, purity walkers, and a base-vs-head type-surface diff advisory +at PR time — a release-time tarball diff hard gate is planned under CLI-2233). ## Usage @@ -376,7 +377,7 @@ Package-local checks and development commands run from `packages/config`: ```sh pnpm types:check pnpm run test # Run tests -pnpm run build # Compile dist/, generate schema.json/project-schema.json, sync api-report/ +pnpm run build # Compile dist/, generate schema.json/project-schema.json ``` See [AGENTS.md](https://github.com/supabase/cli/blob/develop/packages/config/AGENTS.md) for the build pipeline and contract-enforcement details. diff --git a/packages/config/api-report/analytics.d.ts b/packages/config/api-report/analytics.d.ts deleted file mode 100644 index 34806114d3..0000000000 --- a/packages/config/api-report/analytics.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Schema } from "effect"; -export declare const analytics: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly backend: Schema.withDecodingDefaultKey, never>; - readonly vector_port: Schema.optionalKey; - readonly gcp_project_id: Schema.optionalKey; - readonly gcp_project_number: Schema.optionalKey; - readonly gcp_jwt_path: Schema.optionalKey; -}>, never>; diff --git a/packages/config/api-report/api.d.ts b/packages/config/api-report/api.d.ts deleted file mode 100644 index e1273b53c7..0000000000 --- a/packages/config/api-report/api.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Schema } from "effect"; -export declare const api: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly schemas: Schema.withDecodingDefaultKey, never>; - readonly extra_search_path: Schema.withDecodingDefaultKey, never>; - readonly max_rows: Schema.withDecodingDefaultKey; - readonly auto_expose_new_tables: Schema.optionalKey; - readonly tls: Schema.withDecodingDefaultKey; - readonly cert_path: Schema.optionalKey; - readonly key_path: Schema.optionalKey; - }>, never>; - readonly external_url: Schema.optionalKey; -}>, never>; diff --git a/packages/config/api-report/auth/captcha.d.ts b/packages/config/api-report/auth/captcha.d.ts deleted file mode 100644 index a3d71214e2..0000000000 --- a/packages/config/api-report/auth/captcha.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Schema } from "effect"; -export declare const captcha: Schema.withDecodingDefaultKey; - readonly provider: Schema.optionalKey>; - readonly secret: Schema.optionalKey; -}>, never>; diff --git a/packages/config/api-report/auth/email.d.ts b/packages/config/api-report/auth/email.d.ts deleted file mode 100644 index 11473a107c..0000000000 --- a/packages/config/api-report/auth/email.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Schema } from "effect"; -export declare const email: Schema.withDecodingDefaultKey; - readonly double_confirm_changes: Schema.withDecodingDefaultKey; - readonly enable_confirmations: Schema.withDecodingDefaultKey; - readonly secure_password_change: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - readonly otp_length: Schema.withDecodingDefaultKey; - readonly otp_expiry: Schema.withDecodingDefaultKey; - readonly smtp: Schema.optionalKey; - readonly host: Schema.optionalKey; - readonly port: Schema.optionalKey; - readonly user: Schema.optionalKey; - readonly pass: Schema.optionalKey; - readonly admin_email: Schema.optionalKey; - readonly sender_name: Schema.optionalKey; - }>, never>>; - readonly template: Schema.withDecodingDefault; - readonly content_path: Schema.withDecodingDefaultKey; - }>, never>>, never>; - readonly notification: Schema.withDecodingDefault; - readonly subject: Schema.withDecodingDefaultKey; - readonly content_path: Schema.withDecodingDefaultKey; - }>, never>>, never>; -}>, never>; diff --git a/packages/config/api-report/auth/hooks.d.ts b/packages/config/api-report/auth/hooks.d.ts deleted file mode 100644 index 3498f24cdf..0000000000 --- a/packages/config/api-report/auth/hooks.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Schema } from "effect"; -export declare const hook: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly password_verification_attempt: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly custom_access_token: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly send_sms: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly send_email: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly before_user_created: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; -}>, never>; diff --git a/packages/config/api-report/auth/index.d.ts b/packages/config/api-report/auth/index.d.ts deleted file mode 100644 index 028b544f82..0000000000 --- a/packages/config/api-report/auth/index.d.ts +++ /dev/null @@ -1,361 +0,0 @@ -import { Schema } from "effect"; -export declare const auth: Schema.withDecodingDefaultKey; - readonly site_url: Schema.withDecodingDefaultKey; - readonly additional_redirect_urls: Schema.withDecodingDefaultKey, never>; - readonly jwt_expiry: Schema.withDecodingDefaultKey; - readonly jwt_issuer: Schema.optionalKey; - readonly signing_keys_path: Schema.optionalKey; - readonly enable_refresh_token_rotation: Schema.withDecodingDefaultKey; - readonly refresh_token_reuse_interval: Schema.withDecodingDefaultKey; - readonly enable_manual_linking: Schema.withDecodingDefaultKey; - readonly enable_signup: Schema.withDecodingDefaultKey; - readonly enable_anonymous_sign_ins: Schema.withDecodingDefaultKey; - readonly minimum_password_length: Schema.withDecodingDefaultKey; - readonly password_requirements: Schema.withDecodingDefaultKey, never>; - readonly publishable_key: Schema.optionalKey; - readonly secret_key: Schema.optionalKey; - readonly jwt_secret: Schema.optionalKey; - readonly anon_key: Schema.optionalKey; - readonly service_role_key: Schema.optionalKey; - readonly rate_limit: Schema.withDecodingDefaultKey; - readonly sms_sent: Schema.withDecodingDefaultKey; - readonly anonymous_users: Schema.withDecodingDefaultKey; - readonly token_refresh: Schema.withDecodingDefaultKey; - readonly sign_in_sign_ups: Schema.withDecodingDefaultKey; - readonly token_verifications: Schema.withDecodingDefaultKey; - readonly web3: Schema.withDecodingDefaultKey; - }>, never>; - readonly captcha: Schema.optionalKey; - readonly provider: Schema.optionalKey>; - readonly secret: Schema.optionalKey; - }>, never>>; - readonly hook: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly password_verification_attempt: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly custom_access_token: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly send_sms: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly send_email: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly before_user_created: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - }>, never>; - readonly mfa: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - }>, never>; - readonly phone: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - readonly otp_length: Schema.withDecodingDefaultKey; - readonly template: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - }>, never>; - readonly web_authn: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - }>, never>; - readonly max_enrolled_factors: Schema.withDecodingDefaultKey; - }>, never>; - readonly sessions: Schema.optionalKey; - readonly inactivity_timeout: Schema.optionalKey; - }>, never>>; - readonly email: Schema.withDecodingDefaultKey; - readonly double_confirm_changes: Schema.withDecodingDefaultKey; - readonly enable_confirmations: Schema.withDecodingDefaultKey; - readonly secure_password_change: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - readonly otp_length: Schema.withDecodingDefaultKey; - readonly otp_expiry: Schema.withDecodingDefaultKey; - readonly smtp: Schema.optionalKey; - readonly host: Schema.optionalKey; - readonly port: Schema.optionalKey; - readonly user: Schema.optionalKey; - readonly pass: Schema.optionalKey; - readonly admin_email: Schema.optionalKey; - readonly sender_name: Schema.optionalKey; - }>, never>>; - readonly template: Schema.withDecodingDefault; - readonly content_path: Schema.withDecodingDefaultKey; - }>, never>>, never>; - readonly notification: Schema.withDecodingDefault; - readonly subject: Schema.withDecodingDefaultKey; - readonly content_path: Schema.withDecodingDefaultKey; - }>, never>>, never>; - }>, never>; - readonly sms: Schema.withDecodingDefaultKey; - readonly enable_confirmations: Schema.withDecodingDefaultKey; - readonly template: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - readonly twilio: Schema.withDecodingDefaultKey; - readonly account_sid: Schema.withDecodingDefaultKey; - readonly message_service_sid: Schema.withDecodingDefaultKey; - readonly auth_token: Schema.optionalKey; - }>, never>; - readonly twilio_verify: Schema.withDecodingDefaultKey; - readonly account_sid: Schema.optionalKey; - readonly message_service_sid: Schema.optionalKey; - readonly auth_token: Schema.optionalKey; - }>, never>; - readonly messagebird: Schema.withDecodingDefaultKey; - readonly originator: Schema.optionalKey; - readonly access_key: Schema.optionalKey; - }>, never>; - readonly textlocal: Schema.withDecodingDefaultKey; - readonly sender: Schema.optionalKey; - readonly api_key: Schema.optionalKey; - }>, never>; - readonly vonage: Schema.withDecodingDefaultKey; - readonly from: Schema.optionalKey; - readonly api_key: Schema.optionalKey; - readonly api_secret: Schema.optionalKey; - }>, never>; - readonly test_otp: Schema.optionalKey>; - }>, never>; - readonly external: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly azure: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly bitbucket: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly discord: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly facebook: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly github: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly gitlab: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly google: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly kakao: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly keycloak: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly linkedin_oidc: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly notion: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly twitch: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly twitter: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly x: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly slack_oidc: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly spotify: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly workos: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly zoom: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - }>, never>; - readonly web3: Schema.withDecodingDefaultKey; - }>, never>; - readonly ethereum: Schema.withDecodingDefaultKey; - }>, never>; - }>, never>; - readonly oauth_server: Schema.withDecodingDefaultKey; - readonly authorization_url_path: Schema.withDecodingDefaultKey; - readonly allow_dynamic_registration: Schema.withDecodingDefaultKey; - }>, never>; - readonly third_party: Schema.withDecodingDefaultKey; - readonly project_id: Schema.optionalKey; - }>, never>; - readonly auth0: Schema.withDecodingDefaultKey; - readonly tenant: Schema.optionalKey; - readonly tenant_region: Schema.optionalKey; - }>, never>; - readonly aws_cognito: Schema.withDecodingDefaultKey; - readonly user_pool_id: Schema.optionalKey; - readonly user_pool_region: Schema.optionalKey; - }>, never>; - readonly clerk: Schema.withDecodingDefaultKey; - readonly domain: Schema.optionalKey; - }>, never>; - readonly workos: Schema.withDecodingDefaultKey; - readonly issuer_url: Schema.optionalKey; - }>, never>; - }>, never>; -}>, never>; diff --git a/packages/config/api-report/auth/mfa.d.ts b/packages/config/api-report/auth/mfa.d.ts deleted file mode 100644 index 4ca7caeeff..0000000000 --- a/packages/config/api-report/auth/mfa.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Schema } from "effect"; -export declare const mfa: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - }>, never>; - readonly phone: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - readonly otp_length: Schema.withDecodingDefaultKey; - readonly template: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - }>, never>; - readonly web_authn: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - }>, never>; - readonly max_enrolled_factors: Schema.withDecodingDefaultKey; -}>, never>; diff --git a/packages/config/api-report/auth/providers.d.ts b/packages/config/api-report/auth/providers.d.ts deleted file mode 100644 index dd8ff38964..0000000000 --- a/packages/config/api-report/auth/providers.d.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { Schema } from "effect"; -/** - * Go's deprecated `linkedin`/`slack` provider ids (`pkg/config/config.go:1418- - * 1423`) are intentionally NOT modeled here — only their `_oidc` replacements - * (`linkedin_oidc`, `slack_oidc`) are, matching Go's `(e external) validate()`, - * which unconditionally deletes the deprecated keys before anything decodes - * them. `io.ts`'s `normalizeDeprecatedExternalProviders` strips a config's - * `linkedin`/`slack` table (warning on stderr when it was `enabled`, same as - * Go) before this schema ever sees it. - */ -export declare const external: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly azure: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly bitbucket: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly discord: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly facebook: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly github: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly gitlab: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly google: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly kakao: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly keycloak: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly linkedin_oidc: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly notion: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly twitch: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly twitter: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly x: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly slack_oidc: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly spotify: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly workos: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly zoom: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; -}>, never>; diff --git a/packages/config/api-report/auth/rate_limit.d.ts b/packages/config/api-report/auth/rate_limit.d.ts deleted file mode 100644 index 564eca1092..0000000000 --- a/packages/config/api-report/auth/rate_limit.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Schema } from "effect"; -export declare const rate_limit: Schema.withDecodingDefaultKey; - readonly sms_sent: Schema.withDecodingDefaultKey; - readonly anonymous_users: Schema.withDecodingDefaultKey; - readonly token_refresh: Schema.withDecodingDefaultKey; - readonly sign_in_sign_ups: Schema.withDecodingDefaultKey; - readonly token_verifications: Schema.withDecodingDefaultKey; - readonly web3: Schema.withDecodingDefaultKey; -}>, never>; diff --git a/packages/config/api-report/auth/sessions.d.ts b/packages/config/api-report/auth/sessions.d.ts deleted file mode 100644 index fc3794247c..0000000000 --- a/packages/config/api-report/auth/sessions.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Schema } from "effect"; -export declare const sessions: Schema.withDecodingDefaultKey; - readonly inactivity_timeout: Schema.optionalKey; -}>, never>; diff --git a/packages/config/api-report/auth/sms.d.ts b/packages/config/api-report/auth/sms.d.ts deleted file mode 100644 index f6b2dbaf21..0000000000 --- a/packages/config/api-report/auth/sms.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Schema } from "effect"; -export declare const sms: Schema.withDecodingDefaultKey; - readonly enable_confirmations: Schema.withDecodingDefaultKey; - readonly template: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - readonly twilio: Schema.withDecodingDefaultKey; - readonly account_sid: Schema.withDecodingDefaultKey; - readonly message_service_sid: Schema.withDecodingDefaultKey; - readonly auth_token: Schema.optionalKey; - }>, never>; - readonly twilio_verify: Schema.withDecodingDefaultKey; - readonly account_sid: Schema.optionalKey; - readonly message_service_sid: Schema.optionalKey; - readonly auth_token: Schema.optionalKey; - }>, never>; - readonly messagebird: Schema.withDecodingDefaultKey; - readonly originator: Schema.optionalKey; - readonly access_key: Schema.optionalKey; - }>, never>; - readonly textlocal: Schema.withDecodingDefaultKey; - readonly sender: Schema.optionalKey; - readonly api_key: Schema.optionalKey; - }>, never>; - readonly vonage: Schema.withDecodingDefaultKey; - readonly from: Schema.optionalKey; - readonly api_key: Schema.optionalKey; - readonly api_secret: Schema.optionalKey; - }>, never>; - readonly test_otp: Schema.optionalKey>; -}>, never>; diff --git a/packages/config/api-report/auth/third_party.d.ts b/packages/config/api-report/auth/third_party.d.ts deleted file mode 100644 index 03805089a4..0000000000 --- a/packages/config/api-report/auth/third_party.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Schema } from "effect"; -export declare const third_party: Schema.withDecodingDefaultKey; - readonly project_id: Schema.optionalKey; - }>, never>; - readonly auth0: Schema.withDecodingDefaultKey; - readonly tenant: Schema.optionalKey; - readonly tenant_region: Schema.optionalKey; - }>, never>; - readonly aws_cognito: Schema.withDecodingDefaultKey; - readonly user_pool_id: Schema.optionalKey; - readonly user_pool_region: Schema.optionalKey; - }>, never>; - readonly clerk: Schema.withDecodingDefaultKey; - readonly domain: Schema.optionalKey; - }>, never>; - readonly workos: Schema.withDecodingDefaultKey; - readonly issuer_url: Schema.optionalKey; - }>, never>; -}>, never>; diff --git a/packages/config/api-report/auth/web3.d.ts b/packages/config/api-report/auth/web3.d.ts deleted file mode 100644 index 97dca31015..0000000000 --- a/packages/config/api-report/auth/web3.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Schema } from "effect"; -export declare const web3: Schema.withDecodingDefaultKey; - }>, never>; - readonly ethereum: Schema.withDecodingDefaultKey; - }>, never>; -}>, never>; diff --git a/packages/config/api-report/base.d.ts b/packages/config/api-report/base.d.ts deleted file mode 100644 index 00ccac4bd0..0000000000 --- a/packages/config/api-report/base.d.ts +++ /dev/null @@ -1,1621 +0,0 @@ -import { Schema } from "effect"; -/** - * Exported separately (not inlined into {@link CliConfigSchema}) so - * `packages/config/src/io.ts` can decode it on its own with - * `disableChecks: true`. Go's `Config.Validate` only ever checks - * `remotes.*.project_id` format for every remote block - * (`apps/cli-go/pkg/config/config.go:996-1001`, "Since remote config is merged - * to base, we only need to validate the project_id field") — every other - * business-rule check (`Auth.External.validate()`, `Auth.Sms.validate()`, - * etc.) runs exactly once, against the merged effective config - * (`config.go:1136-1152`), never iterated over `c.Remotes[*]`. Decoding this - * schema normally (checks enabled) would apply those same business-rule - * `.check()`s — embedded in `auth`/`db`/etc. — to every remote regardless of - * selection, rejecting configs Go accepts (e.g. an unselected - * `[remotes.prod.auth.external.github] enabled = true` stub with no secret). - */ -export declare const RemotesSchema: Schema.$Record; - readonly analytics: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly backend: Schema.withDecodingDefaultKey, never>; - readonly vector_port: Schema.optionalKey; - readonly gcp_project_id: Schema.optionalKey; - readonly gcp_project_number: Schema.optionalKey; - readonly gcp_jwt_path: Schema.optionalKey; - }>, never>; - readonly api: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly schemas: Schema.withDecodingDefaultKey, never>; - readonly extra_search_path: Schema.withDecodingDefaultKey, never>; - readonly max_rows: Schema.withDecodingDefaultKey; - readonly auto_expose_new_tables: Schema.optionalKey; - readonly tls: Schema.withDecodingDefaultKey; - readonly cert_path: Schema.optionalKey; - readonly key_path: Schema.optionalKey; - }>, never>; - readonly external_url: Schema.optionalKey; - }>, never>; - readonly auth: Schema.withDecodingDefaultKey; - readonly site_url: Schema.withDecodingDefaultKey; - readonly additional_redirect_urls: Schema.withDecodingDefaultKey, never>; - readonly jwt_expiry: Schema.withDecodingDefaultKey; - readonly jwt_issuer: Schema.optionalKey; - readonly signing_keys_path: Schema.optionalKey; - readonly enable_refresh_token_rotation: Schema.withDecodingDefaultKey; - readonly refresh_token_reuse_interval: Schema.withDecodingDefaultKey; - readonly enable_manual_linking: Schema.withDecodingDefaultKey; - readonly enable_signup: Schema.withDecodingDefaultKey; - readonly enable_anonymous_sign_ins: Schema.withDecodingDefaultKey; - readonly minimum_password_length: Schema.withDecodingDefaultKey; - readonly password_requirements: Schema.withDecodingDefaultKey, never>; - readonly publishable_key: Schema.optionalKey; - readonly secret_key: Schema.optionalKey; - readonly jwt_secret: Schema.optionalKey; - readonly anon_key: Schema.optionalKey; - readonly service_role_key: Schema.optionalKey; - readonly rate_limit: Schema.withDecodingDefaultKey; - readonly sms_sent: Schema.withDecodingDefaultKey; - readonly anonymous_users: Schema.withDecodingDefaultKey; - readonly token_refresh: Schema.withDecodingDefaultKey; - readonly sign_in_sign_ups: Schema.withDecodingDefaultKey; - readonly token_verifications: Schema.withDecodingDefaultKey; - readonly web3: Schema.withDecodingDefaultKey; - }>, never>; - readonly captcha: Schema.optionalKey; - readonly provider: Schema.optionalKey>; - readonly secret: Schema.optionalKey; - }>, never>>; - readonly hook: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly password_verification_attempt: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly custom_access_token: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly send_sms: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly send_email: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly before_user_created: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - }>, never>; - readonly mfa: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - }>, never>; - readonly phone: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - readonly otp_length: Schema.withDecodingDefaultKey; - readonly template: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - }>, never>; - readonly web_authn: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - }>, never>; - readonly max_enrolled_factors: Schema.withDecodingDefaultKey; - }>, never>; - readonly sessions: Schema.optionalKey; - readonly inactivity_timeout: Schema.optionalKey; - }>, never>>; - readonly email: Schema.withDecodingDefaultKey; - readonly double_confirm_changes: Schema.withDecodingDefaultKey; - readonly enable_confirmations: Schema.withDecodingDefaultKey; - readonly secure_password_change: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - readonly otp_length: Schema.withDecodingDefaultKey; - readonly otp_expiry: Schema.withDecodingDefaultKey; - readonly smtp: Schema.optionalKey; - readonly host: Schema.optionalKey; - readonly port: Schema.optionalKey; - readonly user: Schema.optionalKey; - readonly pass: Schema.optionalKey; - readonly admin_email: Schema.optionalKey; - readonly sender_name: Schema.optionalKey; - }>, never>>; - readonly template: Schema.withDecodingDefault; - readonly content_path: Schema.withDecodingDefaultKey; - }>, never>>, never>; - readonly notification: Schema.withDecodingDefault; - readonly subject: Schema.withDecodingDefaultKey; - readonly content_path: Schema.withDecodingDefaultKey; - }>, never>>, never>; - }>, never>; - readonly sms: Schema.withDecodingDefaultKey; - readonly enable_confirmations: Schema.withDecodingDefaultKey; - readonly template: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - readonly twilio: Schema.withDecodingDefaultKey; - readonly account_sid: Schema.withDecodingDefaultKey; - readonly message_service_sid: Schema.withDecodingDefaultKey; - readonly auth_token: Schema.optionalKey; - }>, never>; - readonly twilio_verify: Schema.withDecodingDefaultKey; - readonly account_sid: Schema.optionalKey; - readonly message_service_sid: Schema.optionalKey; - readonly auth_token: Schema.optionalKey; - }>, never>; - readonly messagebird: Schema.withDecodingDefaultKey; - readonly originator: Schema.optionalKey; - readonly access_key: Schema.optionalKey; - }>, never>; - readonly textlocal: Schema.withDecodingDefaultKey; - readonly sender: Schema.optionalKey; - readonly api_key: Schema.optionalKey; - }>, never>; - readonly vonage: Schema.withDecodingDefaultKey; - readonly from: Schema.optionalKey; - readonly api_key: Schema.optionalKey; - readonly api_secret: Schema.optionalKey; - }>, never>; - readonly test_otp: Schema.optionalKey>; - }>, never>; - readonly external: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly azure: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly bitbucket: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly discord: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly facebook: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly github: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly gitlab: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly google: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly kakao: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly keycloak: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly linkedin_oidc: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly notion: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly twitch: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly twitter: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly x: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly slack_oidc: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly spotify: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly workos: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly zoom: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - }>, never>; - readonly web3: Schema.withDecodingDefaultKey; - }>, never>; - readonly ethereum: Schema.withDecodingDefaultKey; - }>, never>; - }>, never>; - readonly oauth_server: Schema.withDecodingDefaultKey; - readonly authorization_url_path: Schema.withDecodingDefaultKey; - readonly allow_dynamic_registration: Schema.withDecodingDefaultKey; - }>, never>; - readonly third_party: Schema.withDecodingDefaultKey; - readonly project_id: Schema.optionalKey; - }>, never>; - readonly auth0: Schema.withDecodingDefaultKey; - readonly tenant: Schema.optionalKey; - readonly tenant_region: Schema.optionalKey; - }>, never>; - readonly aws_cognito: Schema.withDecodingDefaultKey; - readonly user_pool_id: Schema.optionalKey; - readonly user_pool_region: Schema.optionalKey; - }>, never>; - readonly clerk: Schema.withDecodingDefaultKey; - readonly domain: Schema.optionalKey; - }>, never>; - readonly workos: Schema.withDecodingDefaultKey; - readonly issuer_url: Schema.optionalKey; - }>, never>; - }>, never>; - }>, never>; - readonly db: Schema.withDecodingDefaultKey; - readonly shadow_port: Schema.withDecodingDefaultKey; - readonly health_timeout: Schema.withDecodingDefaultKey; - readonly major_version: Schema.withDecodingDefaultKey; - readonly pooler: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly pool_mode: Schema.withDecodingDefaultKey, never>; - readonly default_pool_size: Schema.withDecodingDefaultKey; - readonly max_client_conn: Schema.withDecodingDefaultKey; - }>, never>; - readonly migrations: Schema.withDecodingDefaultKey; - readonly schema_paths: Schema.withDecodingDefaultKey, never>; - }>, never>; - readonly seed: Schema.withDecodingDefaultKey; - readonly sql_paths: Schema.withDecodingDefaultKey, never>; - }>, never>; - readonly settings: Schema.optionalKey; - readonly logical_decoding_work_mem: Schema.optionalKey; - readonly maintenance_work_mem: Schema.optionalKey; - readonly max_connections: Schema.optionalKey; - readonly max_locks_per_transaction: Schema.optionalKey; - readonly max_parallel_maintenance_workers: Schema.optionalKey; - readonly max_parallel_workers: Schema.optionalKey; - readonly max_parallel_workers_per_gather: Schema.optionalKey; - readonly max_replication_slots: Schema.optionalKey; - readonly max_slot_wal_keep_size: Schema.optionalKey; - readonly max_standby_archive_delay: Schema.optionalKey; - readonly max_standby_streaming_delay: Schema.optionalKey; - readonly max_wal_size: Schema.optionalKey; - readonly max_wal_senders: Schema.optionalKey; - readonly max_worker_processes: Schema.optionalKey; - readonly session_replication_role: Schema.optionalKey>; - readonly shared_buffers: Schema.optionalKey; - readonly statement_timeout: Schema.optionalKey; - readonly track_activity_query_size: Schema.optionalKey; - readonly track_commit_timestamp: Schema.optionalKey; - readonly wal_keep_size: Schema.optionalKey; - readonly wal_sender_timeout: Schema.optionalKey; - readonly work_mem: Schema.optionalKey; - }>, never>>; - readonly network_restrictions: Schema.withDecodingDefaultKey; - readonly allowed_cidrs: Schema.withDecodingDefaultKey, never>; - readonly allowed_cidrs_v6: Schema.withDecodingDefaultKey, never>; - }>, never>; - readonly ssl_enforcement: Schema.optionalKey; - }>, never>>; - readonly vault: Schema.optionalKey>; - }>, never>; - readonly edge_runtime: Schema.withDecodingDefaultKey; - readonly policy: Schema.withDecodingDefaultKey, never>; - readonly inspector_port: Schema.withDecodingDefaultKey; - readonly deno_version: Schema.withDecodingDefaultKey; - readonly secrets: Schema.optionalKey>; - }>, never>; - readonly functions: Schema.withDecodingDefault; - readonly verify_jwt: Schema.withDecodingDefaultKey; - readonly import_map: Schema.withDecodingDefaultKey; - readonly entrypoint: Schema.withDecodingDefaultKey; - readonly static_files: Schema.withDecodingDefaultKey, never>; - readonly env: Schema.withDecodingDefaultKey, never>; - }>, never>>, never>; - readonly local_smtp: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly smtp_port: Schema.optionalKey; - readonly pop3_port: Schema.optionalKey; - readonly admin_email: Schema.optionalKey; - readonly sender_name: Schema.optionalKey; - }>, never>; - readonly realtime: Schema.withDecodingDefaultKey; - readonly ip_version: Schema.withDecodingDefaultKey, never>; - readonly max_header_length: Schema.withDecodingDefaultKey; - }>, never>; - readonly storage: Schema.withDecodingDefaultKey; - readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; - readonly image_transformation: Schema.optionalKey; - }>, never>>; - readonly buckets: Schema.optionalKey; - readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; - readonly allowed_mime_types: Schema.withDecodingDefaultKey, never>; - readonly objects_path: Schema.withDecodingDefaultKey; - }>, never>>>; - readonly s3_protocol: Schema.withDecodingDefaultKey; - }>, never>; - readonly analytics: Schema.withDecodingDefaultKey; - readonly max_namespaces: Schema.withDecodingDefaultKey; - readonly max_tables: Schema.withDecodingDefaultKey; - readonly max_catalogs: Schema.withDecodingDefaultKey; - readonly buckets: Schema.withDecodingDefault, never>>, never>; - }>, never>; - readonly vector: Schema.withDecodingDefaultKey; - readonly max_buckets: Schema.withDecodingDefaultKey; - readonly max_indexes: Schema.withDecodingDefaultKey; - readonly buckets: Schema.withDecodingDefault, never>>, never>; - }>, never>; - }>, never>; - readonly studio: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly api_url: Schema.withDecodingDefaultKey; - readonly openai_api_key: Schema.optionalKey; - }>, never>; - readonly workers: Schema.withDecodingDefault; - readonly size: Schema.optionalKey; - readonly instances: Schema.optionalKey; - readonly source: Schema.optionalKey; - }>>, never>; - readonly experimental: Schema.withDecodingDefaultKey; - readonly s3_host: Schema.optionalKey; - readonly s3_region: Schema.optionalKey; - readonly s3_access_key: Schema.optionalKey; - readonly s3_secret_key: Schema.optionalKey; - readonly webhooks: Schema.optionalKey; - }>, never>>; - readonly pgdelta: Schema.optionalKey; - readonly declarative_schema_path: Schema.optionalKey; - readonly format_options: Schema.optionalKey; - }>, never>>; - readonly inspect: Schema.optionalKey; - readonly name: Schema.optionalKey; - readonly pass: Schema.optionalKey; - readonly fail: Schema.optionalKey; - }>, never>>, never>; - }>, never>>; - }>, never>; -}>, never>>; -export declare const CliConfigSchema: Schema.Struct<{ - readonly project_id: Schema.optionalKey; - readonly analytics: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly backend: Schema.withDecodingDefaultKey, never>; - readonly vector_port: Schema.optionalKey; - readonly gcp_project_id: Schema.optionalKey; - readonly gcp_project_number: Schema.optionalKey; - readonly gcp_jwt_path: Schema.optionalKey; - }>, never>; - readonly api: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly schemas: Schema.withDecodingDefaultKey, never>; - readonly extra_search_path: Schema.withDecodingDefaultKey, never>; - readonly max_rows: Schema.withDecodingDefaultKey; - readonly auto_expose_new_tables: Schema.optionalKey; - readonly tls: Schema.withDecodingDefaultKey; - readonly cert_path: Schema.optionalKey; - readonly key_path: Schema.optionalKey; - }>, never>; - readonly external_url: Schema.optionalKey; - }>, never>; - readonly auth: Schema.withDecodingDefaultKey; - readonly site_url: Schema.withDecodingDefaultKey; - readonly additional_redirect_urls: Schema.withDecodingDefaultKey, never>; - readonly jwt_expiry: Schema.withDecodingDefaultKey; - readonly jwt_issuer: Schema.optionalKey; - readonly signing_keys_path: Schema.optionalKey; - readonly enable_refresh_token_rotation: Schema.withDecodingDefaultKey; - readonly refresh_token_reuse_interval: Schema.withDecodingDefaultKey; - readonly enable_manual_linking: Schema.withDecodingDefaultKey; - readonly enable_signup: Schema.withDecodingDefaultKey; - readonly enable_anonymous_sign_ins: Schema.withDecodingDefaultKey; - readonly minimum_password_length: Schema.withDecodingDefaultKey; - readonly password_requirements: Schema.withDecodingDefaultKey, never>; - readonly publishable_key: Schema.optionalKey; - readonly secret_key: Schema.optionalKey; - readonly jwt_secret: Schema.optionalKey; - readonly anon_key: Schema.optionalKey; - readonly service_role_key: Schema.optionalKey; - readonly rate_limit: Schema.withDecodingDefaultKey; - readonly sms_sent: Schema.withDecodingDefaultKey; - readonly anonymous_users: Schema.withDecodingDefaultKey; - readonly token_refresh: Schema.withDecodingDefaultKey; - readonly sign_in_sign_ups: Schema.withDecodingDefaultKey; - readonly token_verifications: Schema.withDecodingDefaultKey; - readonly web3: Schema.withDecodingDefaultKey; - }>, never>; - readonly captcha: Schema.optionalKey; - readonly provider: Schema.optionalKey>; - readonly secret: Schema.optionalKey; - }>, never>>; - readonly hook: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly password_verification_attempt: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly custom_access_token: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly send_sms: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly send_email: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly before_user_created: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - }>, never>; - readonly mfa: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - }>, never>; - readonly phone: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - readonly otp_length: Schema.withDecodingDefaultKey; - readonly template: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - }>, never>; - readonly web_authn: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - }>, never>; - readonly max_enrolled_factors: Schema.withDecodingDefaultKey; - }>, never>; - readonly sessions: Schema.optionalKey; - readonly inactivity_timeout: Schema.optionalKey; - }>, never>>; - readonly email: Schema.withDecodingDefaultKey; - readonly double_confirm_changes: Schema.withDecodingDefaultKey; - readonly enable_confirmations: Schema.withDecodingDefaultKey; - readonly secure_password_change: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - readonly otp_length: Schema.withDecodingDefaultKey; - readonly otp_expiry: Schema.withDecodingDefaultKey; - readonly smtp: Schema.optionalKey; - readonly host: Schema.optionalKey; - readonly port: Schema.optionalKey; - readonly user: Schema.optionalKey; - readonly pass: Schema.optionalKey; - readonly admin_email: Schema.optionalKey; - readonly sender_name: Schema.optionalKey; - }>, never>>; - readonly template: Schema.withDecodingDefault; - readonly content_path: Schema.withDecodingDefaultKey; - }>, never>>, never>; - readonly notification: Schema.withDecodingDefault; - readonly subject: Schema.withDecodingDefaultKey; - readonly content_path: Schema.withDecodingDefaultKey; - }>, never>>, never>; - }>, never>; - readonly sms: Schema.withDecodingDefaultKey; - readonly enable_confirmations: Schema.withDecodingDefaultKey; - readonly template: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - readonly twilio: Schema.withDecodingDefaultKey; - readonly account_sid: Schema.withDecodingDefaultKey; - readonly message_service_sid: Schema.withDecodingDefaultKey; - readonly auth_token: Schema.optionalKey; - }>, never>; - readonly twilio_verify: Schema.withDecodingDefaultKey; - readonly account_sid: Schema.optionalKey; - readonly message_service_sid: Schema.optionalKey; - readonly auth_token: Schema.optionalKey; - }>, never>; - readonly messagebird: Schema.withDecodingDefaultKey; - readonly originator: Schema.optionalKey; - readonly access_key: Schema.optionalKey; - }>, never>; - readonly textlocal: Schema.withDecodingDefaultKey; - readonly sender: Schema.optionalKey; - readonly api_key: Schema.optionalKey; - }>, never>; - readonly vonage: Schema.withDecodingDefaultKey; - readonly from: Schema.optionalKey; - readonly api_key: Schema.optionalKey; - readonly api_secret: Schema.optionalKey; - }>, never>; - readonly test_otp: Schema.optionalKey>; - }>, never>; - readonly external: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly azure: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly bitbucket: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly discord: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly facebook: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly github: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly gitlab: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly google: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly kakao: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly keycloak: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly linkedin_oidc: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly notion: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly twitch: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly twitter: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly x: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly slack_oidc: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly spotify: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly workos: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly zoom: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - }>, never>; - readonly web3: Schema.withDecodingDefaultKey; - }>, never>; - readonly ethereum: Schema.withDecodingDefaultKey; - }>, never>; - }>, never>; - readonly oauth_server: Schema.withDecodingDefaultKey; - readonly authorization_url_path: Schema.withDecodingDefaultKey; - readonly allow_dynamic_registration: Schema.withDecodingDefaultKey; - }>, never>; - readonly third_party: Schema.withDecodingDefaultKey; - readonly project_id: Schema.optionalKey; - }>, never>; - readonly auth0: Schema.withDecodingDefaultKey; - readonly tenant: Schema.optionalKey; - readonly tenant_region: Schema.optionalKey; - }>, never>; - readonly aws_cognito: Schema.withDecodingDefaultKey; - readonly user_pool_id: Schema.optionalKey; - readonly user_pool_region: Schema.optionalKey; - }>, never>; - readonly clerk: Schema.withDecodingDefaultKey; - readonly domain: Schema.optionalKey; - }>, never>; - readonly workos: Schema.withDecodingDefaultKey; - readonly issuer_url: Schema.optionalKey; - }>, never>; - }>, never>; - }>, never>; - readonly db: Schema.withDecodingDefaultKey; - readonly shadow_port: Schema.withDecodingDefaultKey; - readonly health_timeout: Schema.withDecodingDefaultKey; - readonly major_version: Schema.withDecodingDefaultKey; - readonly pooler: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly pool_mode: Schema.withDecodingDefaultKey, never>; - readonly default_pool_size: Schema.withDecodingDefaultKey; - readonly max_client_conn: Schema.withDecodingDefaultKey; - }>, never>; - readonly migrations: Schema.withDecodingDefaultKey; - readonly schema_paths: Schema.withDecodingDefaultKey, never>; - }>, never>; - readonly seed: Schema.withDecodingDefaultKey; - readonly sql_paths: Schema.withDecodingDefaultKey, never>; - }>, never>; - readonly settings: Schema.optionalKey; - readonly logical_decoding_work_mem: Schema.optionalKey; - readonly maintenance_work_mem: Schema.optionalKey; - readonly max_connections: Schema.optionalKey; - readonly max_locks_per_transaction: Schema.optionalKey; - readonly max_parallel_maintenance_workers: Schema.optionalKey; - readonly max_parallel_workers: Schema.optionalKey; - readonly max_parallel_workers_per_gather: Schema.optionalKey; - readonly max_replication_slots: Schema.optionalKey; - readonly max_slot_wal_keep_size: Schema.optionalKey; - readonly max_standby_archive_delay: Schema.optionalKey; - readonly max_standby_streaming_delay: Schema.optionalKey; - readonly max_wal_size: Schema.optionalKey; - readonly max_wal_senders: Schema.optionalKey; - readonly max_worker_processes: Schema.optionalKey; - readonly session_replication_role: Schema.optionalKey>; - readonly shared_buffers: Schema.optionalKey; - readonly statement_timeout: Schema.optionalKey; - readonly track_activity_query_size: Schema.optionalKey; - readonly track_commit_timestamp: Schema.optionalKey; - readonly wal_keep_size: Schema.optionalKey; - readonly wal_sender_timeout: Schema.optionalKey; - readonly work_mem: Schema.optionalKey; - }>, never>>; - readonly network_restrictions: Schema.withDecodingDefaultKey; - readonly allowed_cidrs: Schema.withDecodingDefaultKey, never>; - readonly allowed_cidrs_v6: Schema.withDecodingDefaultKey, never>; - }>, never>; - readonly ssl_enforcement: Schema.optionalKey; - }>, never>>; - readonly vault: Schema.optionalKey>; - }>, never>; - readonly edge_runtime: Schema.withDecodingDefaultKey; - readonly policy: Schema.withDecodingDefaultKey, never>; - readonly inspector_port: Schema.withDecodingDefaultKey; - readonly deno_version: Schema.withDecodingDefaultKey; - readonly secrets: Schema.optionalKey>; - }>, never>; - readonly functions: Schema.withDecodingDefault; - readonly verify_jwt: Schema.withDecodingDefaultKey; - readonly import_map: Schema.withDecodingDefaultKey; - readonly entrypoint: Schema.withDecodingDefaultKey; - readonly static_files: Schema.withDecodingDefaultKey, never>; - readonly env: Schema.withDecodingDefaultKey, never>; - }>, never>>, never>; - readonly local_smtp: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly smtp_port: Schema.optionalKey; - readonly pop3_port: Schema.optionalKey; - readonly admin_email: Schema.optionalKey; - readonly sender_name: Schema.optionalKey; - }>, never>; - readonly realtime: Schema.withDecodingDefaultKey; - readonly ip_version: Schema.withDecodingDefaultKey, never>; - readonly max_header_length: Schema.withDecodingDefaultKey; - }>, never>; - readonly storage: Schema.withDecodingDefaultKey; - readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; - readonly image_transformation: Schema.optionalKey; - }>, never>>; - readonly buckets: Schema.optionalKey; - readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; - readonly allowed_mime_types: Schema.withDecodingDefaultKey, never>; - readonly objects_path: Schema.withDecodingDefaultKey; - }>, never>>>; - readonly s3_protocol: Schema.withDecodingDefaultKey; - }>, never>; - readonly analytics: Schema.withDecodingDefaultKey; - readonly max_namespaces: Schema.withDecodingDefaultKey; - readonly max_tables: Schema.withDecodingDefaultKey; - readonly max_catalogs: Schema.withDecodingDefaultKey; - readonly buckets: Schema.withDecodingDefault, never>>, never>; - }>, never>; - readonly vector: Schema.withDecodingDefaultKey; - readonly max_buckets: Schema.withDecodingDefaultKey; - readonly max_indexes: Schema.withDecodingDefaultKey; - readonly buckets: Schema.withDecodingDefault, never>>, never>; - }>, never>; - }>, never>; - readonly studio: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly api_url: Schema.withDecodingDefaultKey; - readonly openai_api_key: Schema.optionalKey; - }>, never>; - readonly workers: Schema.withDecodingDefault; - readonly size: Schema.optionalKey; - readonly instances: Schema.optionalKey; - readonly source: Schema.optionalKey; - }>>, never>; - readonly experimental: Schema.withDecodingDefaultKey; - readonly s3_host: Schema.optionalKey; - readonly s3_region: Schema.optionalKey; - readonly s3_access_key: Schema.optionalKey; - readonly s3_secret_key: Schema.optionalKey; - readonly webhooks: Schema.optionalKey; - }>, never>>; - readonly pgdelta: Schema.optionalKey; - readonly declarative_schema_path: Schema.optionalKey; - readonly format_options: Schema.optionalKey; - }>, never>>; - readonly inspect: Schema.optionalKey; - readonly name: Schema.optionalKey; - readonly pass: Schema.optionalKey; - readonly fail: Schema.optionalKey; - }>, never>>, never>; - }>, never>>; - }>, never>; - readonly remotes: Schema.withDecodingDefault; - readonly analytics: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly backend: Schema.withDecodingDefaultKey, never>; - readonly vector_port: Schema.optionalKey; - readonly gcp_project_id: Schema.optionalKey; - readonly gcp_project_number: Schema.optionalKey; - readonly gcp_jwt_path: Schema.optionalKey; - }>, never>; - readonly api: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly schemas: Schema.withDecodingDefaultKey, never>; - readonly extra_search_path: Schema.withDecodingDefaultKey, never>; - readonly max_rows: Schema.withDecodingDefaultKey; - readonly auto_expose_new_tables: Schema.optionalKey; - readonly tls: Schema.withDecodingDefaultKey; - readonly cert_path: Schema.optionalKey; - readonly key_path: Schema.optionalKey; - }>, never>; - readonly external_url: Schema.optionalKey; - }>, never>; - readonly auth: Schema.withDecodingDefaultKey; - readonly site_url: Schema.withDecodingDefaultKey; - readonly additional_redirect_urls: Schema.withDecodingDefaultKey, never>; - readonly jwt_expiry: Schema.withDecodingDefaultKey; - readonly jwt_issuer: Schema.optionalKey; - readonly signing_keys_path: Schema.optionalKey; - readonly enable_refresh_token_rotation: Schema.withDecodingDefaultKey; - readonly refresh_token_reuse_interval: Schema.withDecodingDefaultKey; - readonly enable_manual_linking: Schema.withDecodingDefaultKey; - readonly enable_signup: Schema.withDecodingDefaultKey; - readonly enable_anonymous_sign_ins: Schema.withDecodingDefaultKey; - readonly minimum_password_length: Schema.withDecodingDefaultKey; - readonly password_requirements: Schema.withDecodingDefaultKey, never>; - readonly publishable_key: Schema.optionalKey; - readonly secret_key: Schema.optionalKey; - readonly jwt_secret: Schema.optionalKey; - readonly anon_key: Schema.optionalKey; - readonly service_role_key: Schema.optionalKey; - readonly rate_limit: Schema.withDecodingDefaultKey; - readonly sms_sent: Schema.withDecodingDefaultKey; - readonly anonymous_users: Schema.withDecodingDefaultKey; - readonly token_refresh: Schema.withDecodingDefaultKey; - readonly sign_in_sign_ups: Schema.withDecodingDefaultKey; - readonly token_verifications: Schema.withDecodingDefaultKey; - readonly web3: Schema.withDecodingDefaultKey; - }>, never>; - readonly captcha: Schema.optionalKey; - readonly provider: Schema.optionalKey>; - readonly secret: Schema.optionalKey; - }>, never>>; - readonly hook: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly password_verification_attempt: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly custom_access_token: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly send_sms: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly send_email: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - readonly before_user_created: Schema.withDecodingDefaultKey; - readonly uri: Schema.optionalKey; - readonly secrets: Schema.optionalKey; - }>, never>; - }>, never>; - readonly mfa: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - }>, never>; - readonly phone: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - readonly otp_length: Schema.withDecodingDefaultKey; - readonly template: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - }>, never>; - readonly web_authn: Schema.withDecodingDefaultKey; - readonly verify_enabled: Schema.withDecodingDefaultKey; - }>, never>; - readonly max_enrolled_factors: Schema.withDecodingDefaultKey; - }>, never>; - readonly sessions: Schema.optionalKey; - readonly inactivity_timeout: Schema.optionalKey; - }>, never>>; - readonly email: Schema.withDecodingDefaultKey; - readonly double_confirm_changes: Schema.withDecodingDefaultKey; - readonly enable_confirmations: Schema.withDecodingDefaultKey; - readonly secure_password_change: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - readonly otp_length: Schema.withDecodingDefaultKey; - readonly otp_expiry: Schema.withDecodingDefaultKey; - readonly smtp: Schema.optionalKey; - readonly host: Schema.optionalKey; - readonly port: Schema.optionalKey; - readonly user: Schema.optionalKey; - readonly pass: Schema.optionalKey; - readonly admin_email: Schema.optionalKey; - readonly sender_name: Schema.optionalKey; - }>, never>>; - readonly template: Schema.withDecodingDefault; - readonly content_path: Schema.withDecodingDefaultKey; - }>, never>>, never>; - readonly notification: Schema.withDecodingDefault; - readonly subject: Schema.withDecodingDefaultKey; - readonly content_path: Schema.withDecodingDefaultKey; - }>, never>>, never>; - }>, never>; - readonly sms: Schema.withDecodingDefaultKey; - readonly enable_confirmations: Schema.withDecodingDefaultKey; - readonly template: Schema.withDecodingDefaultKey; - readonly max_frequency: Schema.withDecodingDefaultKey; - readonly twilio: Schema.withDecodingDefaultKey; - readonly account_sid: Schema.withDecodingDefaultKey; - readonly message_service_sid: Schema.withDecodingDefaultKey; - readonly auth_token: Schema.optionalKey; - }>, never>; - readonly twilio_verify: Schema.withDecodingDefaultKey; - readonly account_sid: Schema.optionalKey; - readonly message_service_sid: Schema.optionalKey; - readonly auth_token: Schema.optionalKey; - }>, never>; - readonly messagebird: Schema.withDecodingDefaultKey; - readonly originator: Schema.optionalKey; - readonly access_key: Schema.optionalKey; - }>, never>; - readonly textlocal: Schema.withDecodingDefaultKey; - readonly sender: Schema.optionalKey; - readonly api_key: Schema.optionalKey; - }>, never>; - readonly vonage: Schema.withDecodingDefaultKey; - readonly from: Schema.optionalKey; - readonly api_key: Schema.optionalKey; - readonly api_secret: Schema.optionalKey; - }>, never>; - readonly test_otp: Schema.optionalKey>; - }>, never>; - readonly external: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly azure: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly bitbucket: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly discord: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly facebook: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly github: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly gitlab: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly google: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly kakao: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly keycloak: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly linkedin_oidc: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly notion: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly twitch: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly twitter: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly x: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly slack_oidc: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly spotify: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly workos: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - readonly zoom: Schema.withDecodingDefaultKey; - readonly client_id: Schema.withDecodingDefaultKey; - readonly secret: Schema.optionalKey; - readonly url: Schema.withDecodingDefaultKey; - readonly redirect_uri: Schema.withDecodingDefaultKey; - readonly skip_nonce_check: Schema.withDecodingDefaultKey; - readonly email_optional: Schema.withDecodingDefaultKey; - }>, never>; - }>, never>; - readonly web3: Schema.withDecodingDefaultKey; - }>, never>; - readonly ethereum: Schema.withDecodingDefaultKey; - }>, never>; - }>, never>; - readonly oauth_server: Schema.withDecodingDefaultKey; - readonly authorization_url_path: Schema.withDecodingDefaultKey; - readonly allow_dynamic_registration: Schema.withDecodingDefaultKey; - }>, never>; - readonly third_party: Schema.withDecodingDefaultKey; - readonly project_id: Schema.optionalKey; - }>, never>; - readonly auth0: Schema.withDecodingDefaultKey; - readonly tenant: Schema.optionalKey; - readonly tenant_region: Schema.optionalKey; - }>, never>; - readonly aws_cognito: Schema.withDecodingDefaultKey; - readonly user_pool_id: Schema.optionalKey; - readonly user_pool_region: Schema.optionalKey; - }>, never>; - readonly clerk: Schema.withDecodingDefaultKey; - readonly domain: Schema.optionalKey; - }>, never>; - readonly workos: Schema.withDecodingDefaultKey; - readonly issuer_url: Schema.optionalKey; - }>, never>; - }>, never>; - }>, never>; - readonly db: Schema.withDecodingDefaultKey; - readonly shadow_port: Schema.withDecodingDefaultKey; - readonly health_timeout: Schema.withDecodingDefaultKey; - readonly major_version: Schema.withDecodingDefaultKey; - readonly pooler: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly pool_mode: Schema.withDecodingDefaultKey, never>; - readonly default_pool_size: Schema.withDecodingDefaultKey; - readonly max_client_conn: Schema.withDecodingDefaultKey; - }>, never>; - readonly migrations: Schema.withDecodingDefaultKey; - readonly schema_paths: Schema.withDecodingDefaultKey, never>; - }>, never>; - readonly seed: Schema.withDecodingDefaultKey; - readonly sql_paths: Schema.withDecodingDefaultKey, never>; - }>, never>; - readonly settings: Schema.optionalKey; - readonly logical_decoding_work_mem: Schema.optionalKey; - readonly maintenance_work_mem: Schema.optionalKey; - readonly max_connections: Schema.optionalKey; - readonly max_locks_per_transaction: Schema.optionalKey; - readonly max_parallel_maintenance_workers: Schema.optionalKey; - readonly max_parallel_workers: Schema.optionalKey; - readonly max_parallel_workers_per_gather: Schema.optionalKey; - readonly max_replication_slots: Schema.optionalKey; - readonly max_slot_wal_keep_size: Schema.optionalKey; - readonly max_standby_archive_delay: Schema.optionalKey; - readonly max_standby_streaming_delay: Schema.optionalKey; - readonly max_wal_size: Schema.optionalKey; - readonly max_wal_senders: Schema.optionalKey; - readonly max_worker_processes: Schema.optionalKey; - readonly session_replication_role: Schema.optionalKey>; - readonly shared_buffers: Schema.optionalKey; - readonly statement_timeout: Schema.optionalKey; - readonly track_activity_query_size: Schema.optionalKey; - readonly track_commit_timestamp: Schema.optionalKey; - readonly wal_keep_size: Schema.optionalKey; - readonly wal_sender_timeout: Schema.optionalKey; - readonly work_mem: Schema.optionalKey; - }>, never>>; - readonly network_restrictions: Schema.withDecodingDefaultKey; - readonly allowed_cidrs: Schema.withDecodingDefaultKey, never>; - readonly allowed_cidrs_v6: Schema.withDecodingDefaultKey, never>; - }>, never>; - readonly ssl_enforcement: Schema.optionalKey; - }>, never>>; - readonly vault: Schema.optionalKey>; - }>, never>; - readonly edge_runtime: Schema.withDecodingDefaultKey; - readonly policy: Schema.withDecodingDefaultKey, never>; - readonly inspector_port: Schema.withDecodingDefaultKey; - readonly deno_version: Schema.withDecodingDefaultKey; - readonly secrets: Schema.optionalKey>; - }>, never>; - readonly functions: Schema.withDecodingDefault; - readonly verify_jwt: Schema.withDecodingDefaultKey; - readonly import_map: Schema.withDecodingDefaultKey; - readonly entrypoint: Schema.withDecodingDefaultKey; - readonly static_files: Schema.withDecodingDefaultKey, never>; - readonly env: Schema.withDecodingDefaultKey, never>; - }>, never>>, never>; - readonly local_smtp: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly smtp_port: Schema.optionalKey; - readonly pop3_port: Schema.optionalKey; - readonly admin_email: Schema.optionalKey; - readonly sender_name: Schema.optionalKey; - }>, never>; - readonly realtime: Schema.withDecodingDefaultKey; - readonly ip_version: Schema.withDecodingDefaultKey, never>; - readonly max_header_length: Schema.withDecodingDefaultKey; - }>, never>; - readonly storage: Schema.withDecodingDefaultKey; - readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; - readonly image_transformation: Schema.optionalKey; - }>, never>>; - readonly buckets: Schema.optionalKey; - readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; - readonly allowed_mime_types: Schema.withDecodingDefaultKey, never>; - readonly objects_path: Schema.withDecodingDefaultKey; - }>, never>>>; - readonly s3_protocol: Schema.withDecodingDefaultKey; - }>, never>; - readonly analytics: Schema.withDecodingDefaultKey; - readonly max_namespaces: Schema.withDecodingDefaultKey; - readonly max_tables: Schema.withDecodingDefaultKey; - readonly max_catalogs: Schema.withDecodingDefaultKey; - readonly buckets: Schema.withDecodingDefault, never>>, never>; - }>, never>; - readonly vector: Schema.withDecodingDefaultKey; - readonly max_buckets: Schema.withDecodingDefaultKey; - readonly max_indexes: Schema.withDecodingDefaultKey; - readonly buckets: Schema.withDecodingDefault, never>>, never>; - }>, never>; - }>, never>; - readonly studio: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly api_url: Schema.withDecodingDefaultKey; - readonly openai_api_key: Schema.optionalKey; - }>, never>; - readonly workers: Schema.withDecodingDefault; - readonly size: Schema.optionalKey; - readonly instances: Schema.optionalKey; - readonly source: Schema.optionalKey; - }>>, never>; - readonly experimental: Schema.withDecodingDefaultKey; - readonly s3_host: Schema.optionalKey; - readonly s3_region: Schema.optionalKey; - readonly s3_access_key: Schema.optionalKey; - readonly s3_secret_key: Schema.optionalKey; - readonly webhooks: Schema.optionalKey; - }>, never>>; - readonly pgdelta: Schema.optionalKey; - readonly declarative_schema_path: Schema.optionalKey; - readonly format_options: Schema.optionalKey; - }>, never>>; - readonly inspect: Schema.optionalKey; - readonly name: Schema.optionalKey; - readonly pass: Schema.optionalKey; - readonly fail: Schema.optionalKey; - }>, never>>, never>; - }>, never>>; - }>, never>; - }>, never>>, never>; -}>; -export declare function toCliConfigJsonSchema(): { - $schema: string; - $defs?: import("effect/JsonSchema").Definitions | undefined; -}; -export type CliConfig = typeof CliConfigSchema.Type; -export type CliConfigJson = typeof CliConfigSchema.Encoded; diff --git a/packages/config/api-report/bun.d.ts b/packages/config/api-report/bun.d.ts deleted file mode 100644 index ef8acf8a4c..0000000000 --- a/packages/config/api-report/bun.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -export declare const loadCliConfig: (cwd: string, options?: import("./config-document.ts").LoadCliConfigOptions) => Promise; -export declare const findCliProjectRoot: (cwd: string) => Promise; -export declare const findCliProjectPaths: (cwd: string) => Promise; -export declare const loadCliConfigFile: (path: string) => Promise; -export declare const loadCliProjectEnvironment: (options: import("./project.ts").LoadCliProjectEnvironmentOptions) => Promise; -export declare const saveCliConfig: (options: import("./config-document.ts").SaveCliConfigOptions) => Promise; -export declare const inferFunctionsManifest: (cwd: string) => Promise; -export type { CliConfigIo } from "./promise-facade.ts"; -export * from "./index.ts"; diff --git a/packages/config/api-report/cli-config.layer.d.ts b/packages/config/api-report/cli-config.layer.d.ts deleted file mode 100644 index d6196598a0..0000000000 --- a/packages/config/api-report/cli-config.layer.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { FileSystem, Layer, Path } from "effect"; -import { CliConfigStore } from "./cli-config.service.ts"; -export declare const cliConfigStoreLayer: Layer.Layer; diff --git a/packages/config/api-report/cli-config.service.d.ts b/packages/config/api-report/cli-config.service.d.ts deleted file mode 100644 index e6481b24f8..0000000000 --- a/packages/config/api-report/cli-config.service.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Effect } from "effect"; -import { Context } from "effect"; -import type { PlatformError } from "effect/PlatformError"; -import type { LoadedCliConfig, LoadCliConfigOptions, SaveCliConfigOptions } from "./config-document.ts"; -import type { CliConfigParseError, CliProjectEnvParseError, DuplicateRemoteProjectIdError, InvalidRemoteProjectIdError } from "./errors.ts"; -/** - * Every error a `load`/`loadFile`/`save` rejection can carry: this package's - * own tagged failures (a malformed config document, a duplicate or - * malformed `[remotes.*]` block, a malformed `.env`/`.env.local` file) plus - * `PlatformError`, the single tagged wrapper Effect's `FileSystem` service - * uses for every host/OS failure (`effect/PlatformError`). A Promise-based - * consumer (`@supabase/config/io`) can distinguish these via `instanceof`. - */ -type CliConfigStoreError = CliConfigParseError | DuplicateRemoteProjectIdError | InvalidRemoteProjectIdError | CliProjectEnvParseError | PlatformError; -interface CliConfigStoreShape { - readonly load: (cwd: string, options?: LoadCliConfigOptions) => Effect.Effect; - readonly loadFile: (path: string) => Effect.Effect; - readonly save: (options: SaveCliConfigOptions) => Effect.Effect; -} -declare const CliConfigStore_base: Context.ServiceClass; -export declare class CliConfigStore extends CliConfigStore_base { -} -export {}; diff --git a/packages/config/api-report/config-document.d.ts b/packages/config/api-report/config-document.d.ts deleted file mode 100644 index 3dfb05807e..0000000000 --- a/packages/config/api-report/config-document.d.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { type CliConfig } from "./base.ts"; -import type { ConfigFormat } from "./config-format.ts"; -import type { CliProjectEnvironment } from "./project.ts"; -/** Shared with `io.ts`'s `getSchemaRef`, which reads this key back off a raw document. */ -export declare const cliConfigSchemaKey = "$schema"; -export type CliConfigValueSource = "environment" | "local" | "remote"; -export interface CliConfigValueOrigin { - readonly path: ReadonlyArray; - readonly source: CliConfigValueSource; -} -export interface LoadedCliConfig { - readonly path: string; - readonly format: ConfigFormat; - readonly config: CliConfig; - readonly schemaRef?: string; - readonly ignoredPaths: ReadonlyArray; - /** - * The raw, post-`env()`-interpolation document the `config` was decoded from, - * with any matching `[remotes.*]` override already merged in (see - * {@link LoadCliConfigOptions.projectRef}). Lets callers inspect key - * presence — which the decoded `config` loses because the schema defaults - * optional sections — without re-reading the file. Present whenever the file - * parsed to an object. - */ - readonly document?: Record; - /** - * Name of the `[remotes.]` block whose subtree was merged over the base - * config because its `project_id` matched the requested `projectRef`. - * `undefined` when no `projectRef` was requested or none matched. - */ - readonly appliedRemote?: string; - /** - * The top-level `auth.external.{linkedin,slack}` sub-objects that were stripped from - * {@link document} before it was returned (provider id → the removed object), keyed by - * provider id. Empty when neither deprecated block was present. See - * `normalizeDeprecatedExternalProviders`'s doc comment for why a caller doing its own - * Go-parity scan over `document` (e.g. a decrypt-or-abort secret check) may need to fold - * this back in — Go's decode-time decrypt hook sees these blocks before its later - * validate-time deletion, so `document` alone under-reports what Go would have decrypted. - * Present (possibly `{}`) whenever {@link document} is; absent from `saveCliConfig`'s - * result, which has no document to strip from. - */ - readonly removedDeprecatedExternalProviders?: Readonly>; - /** The source that supplied each explicitly configured effective leaf value. */ - readonly valueOrigins?: ReadonlyArray; -} -export declare const cliConfigValueSourceAt: (loaded: Pick, path: ReadonlyArray) => CliConfigValueSource | undefined; -/** - * When `projectRef` is set, the matching `[remotes.]` block (the one - * whose `project_id` equals it) is merged over the base config before decode, - * mirroring Go's `config.Load` with `Config.ProjectId` set - * (`apps/cli-go/pkg/config/config.go:503-562`). Omitting it loads the base - * config verbatim (no merge), so existing callers are unaffected. Go's - * duplicate-`project_id`/project-ref-format checks across every - * `[remotes.*]` block (`config.go:594-602,996-1001`) run unconditionally on - * every config load in Go, not only when a caller ends up selecting a - * remote — but here they only run when {@link InternalLoadCliConfigOptions.goViperCompat} - * is `true`, regardless of whether `projectRef` is set, so non-Go-parity - * callers that never select a remote (and never opt into Go parity) aren't - * broken by an unrelated duplicate/malformed `[remotes.*]` block. - */ -export interface LoadCliConfigOptions { - readonly projectRef?: string; - /** - * Pre-resolved project environment used to interpolate `env()` references. - * When omitted, the environment is resolved internally from `.env`/`.env.local` - * layered over `process.env` (the default for most callers). Callers that need - * Go-accurate, environment-specific resolution (e.g. `functions serve`, which - * also reads `.env.` files) resolve it themselves and pass it in - * so loading does not re-read those files or depend on `process.env` mutation. - */ - readonly cliProjectEnv?: CliProjectEnvironment; - /** See {@link FindCliProjectPathsOptions.search}. */ - readonly search?: boolean; - /** - * Skip the `config.json`-over-`config.toml` preference below and only ever - * load `config.toml`. Go's `Config.Load`/`NewPathBuilder` - * (`apps/cli-go/pkg/config/utils.go:43-48`) has no concept of a JSON project - * config file — it always resolves `supabase/config.toml` and treats a - * missing file as defaults — so Go-parity callers (the legacy `status`/`stop` - * ports) must set this to avoid picking up a stray `config.json` that Go - * would never see. - */ - readonly tomlOnly?: boolean; -} -/** - * Not covered by semver — exported from `@supabase/config/internal` only. See - * that module's header for why. - */ -export interface InternalLoadCliConfigOptions extends LoadCliConfigOptions { - /** - * Opt into the Go/viper-parity decode+validation semantics this loader - * otherwise omits, so only the Go-parity legacy shell (and shared modules - * invoked exclusively by it) pays for them. Defaults to `false` = pre-PR-#5765 - * behavior, which `next/`, `packages/stack`, and the functions manifest rely - * on. When `true`, mirrors Go's `config.Load` exactly: - * - runs the unconditional duplicate-`project_id` and project-ref-format - * checks across every `[remotes.*]` block (`config.go:594-602,996-1001`), - * even when no `projectRef` is requested; - * - warns on stderr for deprecated `auth.external.{linkedin,slack}` blocks - * (`config.go:1418-1423`) — the block is stripped from the decoded config - * either way, since the schema ignores excess properties; - * - matches `env(...)` references case-agnostically (`^env\((.*)\)$`) - * rather than the strict SCREAMING_SNAKE_CASE form; - * - splits a comma-separated string into a `[]string`-typed field (Go's - * `mapstructure.StringToSliceHookFunc(",")`, `config.go:775-784`), not - * just an `env()`-substituted one. - */ - readonly goViperCompat?: boolean; -} -export interface SaveCliConfigOptions { - readonly cwd: string; - readonly config: CliConfig; - readonly format?: ConfigFormat; - readonly schemaRef?: string; -} -/** - * Shared with `io.ts`, which uses it to inspect raw (pre-decode) config - * documents while resolving `[remotes.*]` overrides and stripping deprecated - * sections. - */ -export declare function isObject(value: unknown): value is Record; -export declare function encodeCliConfigToJson(config: CliConfig): string; -export declare function encodeCliConfigToToml(config: CliConfig): string; -/** Shared with `io.ts`'s `saveCliConfig`, which needs the `schemaRef`-carrying variant. */ -export declare function encodeCliConfigToJsonDocument(config: CliConfig, schemaRef: string | undefined): string; -/** Shared with `io.ts`'s `saveCliConfig`, which needs the `schemaRef`-carrying variant. */ -export declare function encodeCliConfigToTomlDocument(config: CliConfig, schemaRef: string | undefined): string; diff --git a/packages/config/api-report/config-format.d.ts b/packages/config/api-report/config-format.d.ts deleted file mode 100644 index 4fe89b4fa0..0000000000 --- a/packages/config/api-report/config-format.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Leaf module with zero imports of its own. `errors.ts` — this package's - * most primitive module — needs `ConfigFormat` for {@link CliConfigParseError}, - * so this type lives here rather than in `config-document.ts`, which itself - * imports from `project.ts`, which imports from `errors.ts`. Defining - * `ConfigFormat` in `config-document.ts` would create an - * `errors.ts` → `config-document.ts` → `project.ts` → `errors.ts` import - * cycle (benign at runtime today, but a live constraint for declaration - * emit). - */ -export type ConfigFormat = "json" | "toml"; diff --git a/packages/config/api-report/db.d.ts b/packages/config/api-report/db.d.ts deleted file mode 100644 index 31639d7251..0000000000 --- a/packages/config/api-report/db.d.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Schema } from "effect"; -export declare const db: Schema.withDecodingDefaultKey; - readonly shadow_port: Schema.withDecodingDefaultKey; - readonly health_timeout: Schema.withDecodingDefaultKey; - readonly major_version: Schema.withDecodingDefaultKey; - readonly pooler: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly pool_mode: Schema.withDecodingDefaultKey, never>; - readonly default_pool_size: Schema.withDecodingDefaultKey; - readonly max_client_conn: Schema.withDecodingDefaultKey; - }>, never>; - readonly migrations: Schema.withDecodingDefaultKey; - readonly schema_paths: Schema.withDecodingDefaultKey, never>; - }>, never>; - readonly seed: Schema.withDecodingDefaultKey; - readonly sql_paths: Schema.withDecodingDefaultKey, never>; - }>, never>; - readonly settings: Schema.optionalKey; - readonly logical_decoding_work_mem: Schema.optionalKey; - readonly maintenance_work_mem: Schema.optionalKey; - readonly max_connections: Schema.optionalKey; - readonly max_locks_per_transaction: Schema.optionalKey; - readonly max_parallel_maintenance_workers: Schema.optionalKey; - readonly max_parallel_workers: Schema.optionalKey; - readonly max_parallel_workers_per_gather: Schema.optionalKey; - readonly max_replication_slots: Schema.optionalKey; - readonly max_slot_wal_keep_size: Schema.optionalKey; - readonly max_standby_archive_delay: Schema.optionalKey; - readonly max_standby_streaming_delay: Schema.optionalKey; - readonly max_wal_size: Schema.optionalKey; - readonly max_wal_senders: Schema.optionalKey; - readonly max_worker_processes: Schema.optionalKey; - readonly session_replication_role: Schema.optionalKey>; - readonly shared_buffers: Schema.optionalKey; - readonly statement_timeout: Schema.optionalKey; - readonly track_activity_query_size: Schema.optionalKey; - readonly track_commit_timestamp: Schema.optionalKey; - readonly wal_keep_size: Schema.optionalKey; - readonly wal_sender_timeout: Schema.optionalKey; - readonly work_mem: Schema.optionalKey; - }>, never>>; - readonly network_restrictions: Schema.withDecodingDefaultKey; - readonly allowed_cidrs: Schema.withDecodingDefaultKey, never>; - readonly allowed_cidrs_v6: Schema.withDecodingDefaultKey, never>; - }>, never>; - readonly ssl_enforcement: Schema.optionalKey; - }>, never>>; - readonly vault: Schema.optionalKey>; -}>, never>; diff --git a/packages/config/api-report/edge_runtime.d.ts b/packages/config/api-report/edge_runtime.d.ts deleted file mode 100644 index df980f8f36..0000000000 --- a/packages/config/api-report/edge_runtime.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Schema } from "effect"; -export declare const edge_runtime: Schema.withDecodingDefaultKey; - readonly policy: Schema.withDecodingDefaultKey, never>; - readonly inspector_port: Schema.withDecodingDefaultKey; - readonly deno_version: Schema.withDecodingDefaultKey; - readonly secrets: Schema.optionalKey>; -}>, never>; diff --git a/packages/config/api-report/effect.d.ts b/packages/config/api-report/effect.d.ts deleted file mode 100644 index 8d86d05ba5..0000000000 --- a/packages/config/api-report/effect.d.ts +++ /dev/null @@ -1,38 +0,0 @@ -export * from "./index.ts"; -import type { Effect } from "effect"; -import type { LoadCliConfigOptions } from "./config-document.ts"; -import type { ResolvedCliConfigValue } from "./lib/resolve.ts"; -import * as io from "./io.ts"; -import type { CliProjectEnvironment } from "./project.ts"; -export { configJsonPath, configTomlPath, saveCliConfig } from "./io.ts"; -/** - * Narrowed to the public `LoadCliConfigOptions` (no `goViperCompat`). The - * underlying implementation in `./io.ts` is typed against the wider - * `InternalLoadCliConfigOptions` (a strict superset — one additional optional - * field), so assigning it here is a safe, cast-free narrowing: a function - * accepting the wider options type is assignable to a variable typed to - * accept only the narrower one. `@supabase/config/internal` re-exports this - * same runtime function typed to additionally show `goViperCompat`. - */ -export declare const loadCliConfig: (cwd: string, options?: LoadCliConfigOptions) => ReturnType; -/** See {@link loadCliConfig}'s doc comment for the narrowing rationale. */ -export declare const loadCliConfigFile: (filePath: string, options?: LoadCliConfigOptions) => ReturnType; -export { inferFunctionsManifest } from "./functions-manifest.ts"; -export { loadDotEnvFile, loadCliProjectEnvironment } from "./project.ts"; -/** - * Explicit named exports take precedence over `export * from "./index.ts"` - * above for a shared name (ESM re-export resolution), so these Effect-typed - * variants deliberately shadow `./index.ts`'s plain sync - * `resolveCliConfigValue`/`resolveCliConfigSubtree` on this subpath — the - * Effect-typed variant wins on `./effect`; the sync variant lives on `.`. - * - * Narrowed to no options parameter (no `goViperCompat`) for the same reason - * as {@link loadCliConfig} above; `@supabase/config/internal` re-exports - * these same runtime functions typed to additionally show `goViperCompat`. - */ -export declare const resolveCliConfigValue: (value: T, cliProjectEnv: Pick, configPath: string) => Effect.Effect>; -/** See {@link resolveCliConfigValue}'s doc comment for the shadowing and narrowing rationale. */ -export declare const resolveCliConfigSubtree: (value: T, cliProjectEnv: Pick, pathPrefix: string) => Effect.Effect>; -export { findCliProjectPaths, findCliProjectRoot } from "./paths.ts"; -export { cliConfigStoreLayer } from "./cli-config.layer.ts"; -export { CliConfigStore } from "./cli-config.service.ts"; diff --git a/packages/config/api-report/errors.d.ts b/packages/config/api-report/errors.d.ts deleted file mode 100644 index 016a60e317..0000000000 --- a/packages/config/api-report/errors.d.ts +++ /dev/null @@ -1,155 +0,0 @@ -import type { ConfigFormat } from "./config-format.ts"; -declare const CliConfigParseError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { - readonly _tag: "CliConfigParseError"; -} & Readonly; -export declare class CliConfigParseError extends CliConfigParseError_base<{ - readonly path: string; - readonly format: ConfigFormat; - readonly cause: unknown; - /** - * The pre-schema-decode `edge_runtime` subtree (post env-interpolation and - * `[remotes.*]` merge) — present only when the failure happened during - * *schema* decode (`Schema.decodeUnknownSync`), not during raw TOML/JSON - * parsing. `Schema.decodeUnknownSync` is all-or-nothing: a single invalid - * field anywhere in the document discards the entire decode, unlike Go's - * `viper`+`mapstructure` decode (`apps/cli-go/pkg/config/config.go:749`), - * which mutates the target struct field-by-field and keeps whatever - * independently decoded before hitting an unrelated error. Callers that - * need Go's tolerance for a single subtree (e.g. `secrets set` recovering - * `edge_runtime.secrets` when an unrelated field like `analytics.port` is - * malformed) can re-decode this subtree against the full schema themselves. - * Only `edge_runtime` is retained, not the whole document — several callers - * of `loadCliConfig` don't catch `CliConfigParseError` at all, so - * this error can propagate with whatever is attached here, and no caller - * needs anything outside `edge_runtime` today. Every `edge_runtime.secrets` - * value is wrapped in `Redacted` (mirroring `secret()`'s `x-secret` - * treatment elsewhere in this package) so an uncaught error can't - * accidentally leak a resolved secret into a log or trace; callers must - * unwrap via `Redacted.value` before re-decoding. `undefined` when the - * document never parsed at all — that class has no recoverable structure in - * either implementation. - */ - readonly document?: { - readonly edge_runtime?: unknown; - }; - /** - * Name of the `[remotes.]` block whose subtree was merged over the - * base document before the decode that produced this error, when a - * `projectRef` was supplied and one matched. Mirrors `appliedRemote` on - * {@link LoadedCliConfig} for the success path. Go's `loadFromFile` - * prints `Loading config override: [remotes.]` to stderr - * unconditionally, *before* `mapstructure` decode ever runs - * (`apps/cli-go/pkg/config/config.go:604-609`) — so the notice is still due - * even when the subsequent decode fails. Callers that tolerate a - * schema-decode failure and keep going (e.g. `secrets set`) must surface - * this themselves; callers that let the error propagate get no such - * notice from Go either, since `c.load(v)` fails before `Run` prints - * anything else. `undefined` when no `projectRef` was requested or none - * matched — same as the raw-parse-failure case, where remote merging never - * runs at all. - */ - readonly appliedRemote?: string; -}> { -} -/** - * Renders `detail` under the shared {@link ProjectConfigParseError} message - * convention: `": "`, or `": at data.attributes.: - * "` when `apiPath` is given and non-empty. Every construction site - * (`./project-config/project-config.ts`, `./project-config/registry-row.ts`, - * `./project-config/registry.ts`) builds its message through this helper so - * the "at data.attributes...." rendering stays identical everywhere an - * `apiPath` is known. - */ -export declare function formatProjectConfigParseErrorMessage(detail: string, apiPath?: ReadonlyArray): string; -/** - * {@link ProjectConfigParseError} is, by construction, always the same - * underlying situation: this package's mirrored schema/registry - * (`./project-config/api-attributes.ts`, `./project-config/registry*.ts`) is - * behind what the Management API actually sent. There is therefore exactly - * one remediation, attached as `suggestion` at every construction site: - * upgrade first (a newer package version may already map or leniently accept - * the offending shape), then report if it persists. - */ -export declare const PROJECT_CONFIG_PARSE_ERROR_SUGGESTION = "Try upgrading the Supabase CLI to the latest version. If the error persists on the latest version, report it at https://github.com/supabase/cli/issues."; -declare const ProjectConfigParseError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { - readonly _tag: "ProjectConfigParseError"; -} & Readonly; -/** - * A Management API v2 project-config response failed to map into a - * `ProjectConfig`: the envelope/attributes shape didn't decode, or a - * registry-mapped field carried a value of the wrong type. `message` is a - * human-readable summary built via {@link formatProjectConfigParseErrorMessage} - * at every construction site; `detail` optionally carries a fuller, - * multi-issue rendering (currently only populated for a schema decode - * failure, via `SchemaIssue.makeFormatterDefault()`); `suggestion` is always - * {@link PROJECT_CONFIG_PARSE_ERROR_SUGGESTION}. Unknown keys never cause - * this on their own — the mapping decode is lenient toward - * API-ahead-of-package skew by design (ADR 0019, rule 2) — with one - * documented trade: an own `data` or `attributes` key found on what was - * actually meant to be a bare-attributes payload is indistinguishable from a - * real envelope and is treated as one (`unwrapApiResponse`'s docstring in - * `./project-config/project-config.ts`), so a section genuinely named either - * of those two words would trigger envelope validation instead of being - * tolerated as an unmapped key. - */ -export declare class ProjectConfigParseError extends ProjectConfigParseError_base<{ - readonly message: string; - /** - * What actually went wrong, as a closed union telemetry can branch on: - * `"api_response"` (the default when absent) — the Management API payload - * itself failed to decode or map; `"caller_misuse"` — the CALLER handed - * this package's own API an invalid argument (a `toProjectConfig` source - * carrying neither/both keys or not an object at all, a non-object - * `attachApiResponse` operand). Misuse is a programming error in the - * consumer: the upgrade `suggestion` does not apply to it, and it must not - * be reported as an external platform failure. - */ - readonly reason?: "api_response" | "caller_misuse"; - /** - * Path under v2 `data.attributes` of the offending value; `undefined` when - * the response envelope/attributes shape itself failed to decode. - */ - readonly apiPath?: ReadonlyArray; - readonly cause: unknown; - /** Fuller, multi-issue detail beyond `message`'s single-issue summary. */ - readonly detail?: string; - readonly suggestion?: string; -}> { -} -declare const CliProjectEnvParseError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { - readonly _tag: "CliProjectEnvParseError"; -} & Readonly; -export declare class CliProjectEnvParseError extends CliProjectEnvParseError_base<{ - readonly path: string; - readonly line: number; -}> { -} -declare const DuplicateRemoteProjectIdError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { - readonly _tag: "DuplicateRemoteProjectIdError"; -} & Readonly; -/** - * Two `[remotes.*]` blocks declare the same `project_id` as the requested - * `projectRef`. Mirrors Go's `loadFromFile` guard - * (`apps/cli-go/pkg/config/config.go:508-509`); `message` matches the Go string - * verbatim so callers can surface it without rewrapping. - */ -export declare class DuplicateRemoteProjectIdError extends DuplicateRemoteProjectIdError_base<{ - readonly message: string; -}> { -} -declare const InvalidRemoteProjectIdError_base: new = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & { - readonly _tag: "InvalidRemoteProjectIdError"; -} & Readonly; -/** - * A `[remotes.]` block's `project_id` is not a valid 20-lowercase-letter - * project ref. Mirrors Go's `Config.Validate` (`apps/cli-go/pkg/config/config.go: - * 558,996-1001`), which checks every remote's `project_id` against `refPattern` - * on every config load — regardless of whether that remote ends up selected — - * so this fails before Docker/API access, same as Go. `message` matches the Go - * string verbatim so callers can surface it without rewrapping. - */ -export declare class InvalidRemoteProjectIdError extends InvalidRemoteProjectIdError_base<{ - readonly message: string; -}> { -} -export {}; diff --git a/packages/config/api-report/experimental.d.ts b/packages/config/api-report/experimental.d.ts deleted file mode 100644 index e9fcf4a742..0000000000 --- a/packages/config/api-report/experimental.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Schema } from "effect"; -export declare const experimental: Schema.withDecodingDefaultKey; - readonly s3_host: Schema.optionalKey; - readonly s3_region: Schema.optionalKey; - readonly s3_access_key: Schema.optionalKey; - readonly s3_secret_key: Schema.optionalKey; - readonly webhooks: Schema.optionalKey; - }>, never>>; - readonly pgdelta: Schema.optionalKey; - readonly declarative_schema_path: Schema.optionalKey; - readonly format_options: Schema.optionalKey; - }>, never>>; - readonly inspect: Schema.optionalKey; - readonly name: Schema.optionalKey; - readonly pass: Schema.optionalKey; - readonly fail: Schema.optionalKey; - }>, never>>, never>; - }>, never>>; -}>, never>; diff --git a/packages/config/api-report/functions-manifest-model.d.ts b/packages/config/api-report/functions-manifest-model.d.ts deleted file mode 100644 index d57a361f5a..0000000000 --- a/packages/config/api-report/functions-manifest-model.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -export declare const edgeFunctionsDirectoryName = "functions"; -export declare const edgeFunctionEntrypointFileName = "index.ts"; -export declare const edgeFunctionDenoConfigFileName = "deno.json"; -export interface ResolvedFunctionConfig { - readonly enabled: boolean; - readonly verify_jwt: boolean; - readonly import_map: string; - readonly entrypoint: string; - readonly static_files: ReadonlyArray; - readonly env: Readonly>; -} -export type FunctionsManifest = Readonly>; diff --git a/packages/config/api-report/functions-manifest.d.ts b/packages/config/api-report/functions-manifest.d.ts deleted file mode 100644 index 07f5bde98f..0000000000 --- a/packages/config/api-report/functions-manifest.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Effect, FileSystem, Path } from "effect"; -import { type CliConfig } from "./base.ts"; -import { type ResolvedFunctionConfig } from "./functions-manifest-model.ts"; -interface InferFunctionsManifestOptions { - readonly cwd: string; - readonly config?: CliConfig; - /** Forwarded to {@link findCliProjectPaths}'s own `search` option — see its doc comment. */ - readonly search?: boolean; -} -export declare const inferFunctionsManifest: (options: InferFunctionsManifestOptions) => Effect.Effect, import("./errors.ts").CliConfigParseError | import("./errors.ts").CliProjectEnvParseError | import("./errors.ts").DuplicateRemoteProjectIdError | import("./errors.ts").InvalidRemoteProjectIdError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>; -export {}; diff --git a/packages/config/api-report/functions.d.ts b/packages/config/api-report/functions.d.ts deleted file mode 100644 index c2d5357303..0000000000 --- a/packages/config/api-report/functions.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Schema } from "effect"; -export declare const functions: Schema.withDecodingDefault; - readonly verify_jwt: Schema.withDecodingDefaultKey; - readonly import_map: Schema.withDecodingDefaultKey; - readonly entrypoint: Schema.withDecodingDefaultKey; - readonly static_files: Schema.withDecodingDefaultKey, never>; - readonly env: Schema.withDecodingDefaultKey, never>; -}>, never>>, never>; diff --git a/packages/config/api-report/inbucket.d.ts b/packages/config/api-report/inbucket.d.ts deleted file mode 100644 index 66f6d27bdb..0000000000 --- a/packages/config/api-report/inbucket.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Schema } from "effect"; -export declare const inbucket: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly smtp_port: Schema.optionalKey; - readonly pop3_port: Schema.optionalKey; - readonly admin_email: Schema.optionalKey; - readonly sender_name: Schema.optionalKey; -}>, never>; diff --git a/packages/config/api-report/index.d.ts b/packages/config/api-report/index.d.ts deleted file mode 100644 index 21701d9e24..0000000000 --- a/packages/config/api-report/index.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Pure, browser/edge-safe entrypoint. Must never export an Effect-returning - * function, nor pull `@effect/platform-*` or `node:`/`bun:` modules into its - * transitive graph. Effect-core `FileSystem`/`Path` TAG references reachable - * from this graph are fine — they're inert without a platform layer provided. - * File IO and Effect-native services live at `@supabase/config/io` and - * `@supabase/config/effect`. - */ -export { CliConfigSchema, toCliConfigJsonSchema, type CliConfig, type CliConfigJson, } from "./base.ts"; -export { CliConfigParseError, CliProjectEnvParseError, DuplicateRemoteProjectIdError, InvalidRemoteProjectIdError, ProjectConfigParseError, } from "./errors.ts"; -export type { ConfigFormat } from "./config-format.ts"; -export { type LoadedCliConfig, type LoadCliConfigOptions, type CliConfigValueOrigin, type CliConfigValueSource, type SaveCliConfigOptions, encodeCliConfigToJson, encodeCliConfigToToml, cliConfigValueSourceAt, } from "./config-document.ts"; -export { edgeFunctionDenoConfigFileName, edgeFunctionEntrypointFileName, edgeFunctionsDirectoryName, type FunctionsManifest, type ResolvedFunctionConfig, } from "./functions-manifest-model.ts"; -export type { LoadCliProjectEnvironmentOptions, CliProjectEnvironment } from "./project.ts"; -export { type ResolvedCliConfigValue, resolveCliConfigValue, resolveCliConfigSubtree, } from "./lib/resolve.ts"; -export type { CliProjectPaths } from "./paths.ts"; -export { CLI_CONFIG_SCHEMA_URL, PROJECT_CONFIG_SCHEMA_URL } from "./schema-metadata.ts"; -export { type EffectiveConfig, type SparseCliConfig, getDefaultCliConfig, omitDefaultValues, subtractCliConfig, } from "./sparse.ts"; -export { type CliConfigWithRawPresence, type ProjectConfig, type ReadonlyJsonValue, type ToProjectConfigSource, attachApiResponse, comparableProjectConfigPaths, fromApiProjectConfig, fromConfigDocument, isComparableProjectConfigPath, toProjectConfig, unmappedApiFields, } from "./project-config/project-config.ts"; -export { ProjectConfigSchema, toProjectConfigJsonSchema } from "./project-config/project-schema.ts"; diff --git a/packages/config/api-report/internal.d.ts b/packages/config/api-report/internal.d.ts deleted file mode 100644 index 22bdbf08ee..0000000000 --- a/packages/config/api-report/internal.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * NOT covered by semver. This subpath exists solely for `apps/cli`'s own use - * and its contract-guard tests — every export here (its existence, its shape, - * its behavior) can change or vanish in any release without notice. External - * consumers must use `.`, `./effect`, or `./io` instead; only `apps/cli` may - * import `@supabase/config/internal` (enforced by - * `src/monorepo-import-contract.unit.test.ts`). - * - * `loadCliConfig`/`resolveCliConfigValue`/`resolveCliConfigSubtree` below are - * the SAME runtime functions `./effect` exports, just re-typed here to widen - * their options parameter to the internal-only, Go-parity `goViperCompat` - * knob (`InternalLoadCliConfigOptions`/`InternalResolveCliConfigOptions`) — - * this module otherwise only re-exports types and registry data, not - * independent implementations. - */ -export { ENV_CAPTURE_REGEX } from "./lib/env.ts"; -export { AUTH_HOOK_NAMES, unmappedSecretApiPaths } from "./project-config/registry-auth.ts"; -export { projectConfigMappingRows } from "./project-config/registry.ts"; -export { type ProjectConfigMappingRow } from "./project-config/registry-row.ts"; -export { type ProjectConfigApiAttributes } from "./project-config/api-attributes.ts"; -export { type InternalLoadCliConfigOptions } from "./config-document.ts"; -export { resolveCliConfigValue, resolveCliConfigSubtree } from "./project.ts"; -export { loadCliConfig } from "./io.ts"; diff --git a/packages/config/api-report/io-browser.d.ts b/packages/config/api-report/io-browser.d.ts deleted file mode 100644 index ef8acf8a4c..0000000000 --- a/packages/config/api-report/io-browser.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -export declare const loadCliConfig: (cwd: string, options?: import("./config-document.ts").LoadCliConfigOptions) => Promise; -export declare const findCliProjectRoot: (cwd: string) => Promise; -export declare const findCliProjectPaths: (cwd: string) => Promise; -export declare const loadCliConfigFile: (path: string) => Promise; -export declare const loadCliProjectEnvironment: (options: import("./project.ts").LoadCliProjectEnvironmentOptions) => Promise; -export declare const saveCliConfig: (options: import("./config-document.ts").SaveCliConfigOptions) => Promise; -export declare const inferFunctionsManifest: (cwd: string) => Promise; -export type { CliConfigIo } from "./promise-facade.ts"; -export * from "./index.ts"; diff --git a/packages/config/api-report/io.d.ts b/packages/config/api-report/io.d.ts deleted file mode 100644 index 6840a686ba..0000000000 --- a/packages/config/api-report/io.d.ts +++ /dev/null @@ -1,3374 +0,0 @@ -import { Effect, FileSystem, Path } from "effect"; -import { type InternalLoadCliConfigOptions, type CliConfigValueSource, type SaveCliConfigOptions } from "./config-document.ts"; -import type { ConfigFormat } from "./config-format.ts"; -import { DuplicateRemoteProjectIdError, InvalidRemoteProjectIdError, CliConfigParseError } from "./errors.ts"; -export declare const configJsonPath: (cwd: string) => Effect.Effect; -export declare const configTomlPath: (cwd: string) => Effect.Effect; -export declare const loadCliConfigFile: (filePath: string, options?: InternalLoadCliConfigOptions | undefined) => Effect.Effect<{ - path: string; - format: "json" | "toml"; - config: { - readonly project_id?: string | undefined; - readonly analytics: { - readonly enabled: boolean; - readonly port: number; - readonly backend: string; - readonly vector_port?: number | undefined; - readonly gcp_project_id?: string | undefined; - readonly gcp_project_number?: string | undefined; - readonly gcp_jwt_path?: string | undefined; - }; - readonly api: { - readonly enabled: boolean; - readonly port: number; - readonly schemas: readonly string[]; - readonly extra_search_path: readonly string[]; - readonly max_rows: number; - readonly auto_expose_new_tables?: boolean | undefined; - readonly tls: { - readonly enabled: boolean; - readonly cert_path?: string | undefined; - readonly key_path?: string | undefined; - }; - readonly external_url?: string | undefined; - }; - readonly auth: { - readonly enabled: boolean; - readonly site_url: string; - readonly additional_redirect_urls: readonly string[]; - readonly jwt_expiry: number; - readonly jwt_issuer?: string | undefined; - readonly signing_keys_path?: string | undefined; - readonly enable_refresh_token_rotation: boolean; - readonly refresh_token_reuse_interval: number; - readonly enable_manual_linking: boolean; - readonly enable_signup: boolean; - readonly enable_anonymous_sign_ins: boolean; - readonly minimum_password_length: number; - readonly password_requirements: string; - readonly publishable_key?: string | undefined; - readonly secret_key?: string | undefined; - readonly jwt_secret?: string | undefined; - readonly anon_key?: string | undefined; - readonly service_role_key?: string | undefined; - readonly rate_limit: { - readonly email_sent: number; - readonly sms_sent: number; - readonly anonymous_users: number; - readonly token_refresh: number; - readonly sign_in_sign_ups: number; - readonly token_verifications: number; - readonly web3: number; - }; - readonly captcha?: { - readonly enabled: boolean; - readonly provider?: string | undefined; - readonly secret?: string | undefined; - } | undefined; - readonly hook: { - readonly mfa_verification_attempt: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly password_verification_attempt: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly custom_access_token: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly send_sms: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly send_email: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly before_user_created: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - }; - readonly mfa: { - readonly totp: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - }; - readonly phone: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - readonly otp_length: number; - readonly template: string; - readonly max_frequency: string; - }; - readonly web_authn: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - }; - readonly max_enrolled_factors: number; - }; - readonly sessions?: { - readonly timebox?: string | undefined; - readonly inactivity_timeout?: string | undefined; - } | undefined; - readonly email: { - readonly enable_signup: boolean; - readonly double_confirm_changes: boolean; - readonly enable_confirmations: boolean; - readonly secure_password_change: boolean; - readonly max_frequency: string; - readonly otp_length: number; - readonly otp_expiry: number; - readonly smtp?: { - readonly enabled: boolean; - readonly host?: string | undefined; - readonly port?: number | undefined; - readonly user?: string | undefined; - readonly pass?: string | undefined; - readonly admin_email?: string | undefined; - readonly sender_name?: string | undefined; - } | undefined; - readonly template: { - readonly [x: string]: { - readonly subject: string; - readonly content_path: string; - }; - }; - readonly notification: { - readonly [x: string]: { - readonly enabled: boolean; - readonly subject: string; - readonly content_path: string; - }; - }; - }; - readonly sms: { - readonly enable_signup: boolean; - readonly enable_confirmations: boolean; - readonly template: string; - readonly max_frequency: string; - readonly twilio: { - readonly enabled: boolean; - readonly account_sid: string; - readonly message_service_sid: string; - readonly auth_token?: string | undefined; - }; - readonly twilio_verify: { - readonly enabled: boolean; - readonly account_sid?: string | undefined; - readonly message_service_sid?: string | undefined; - readonly auth_token?: string | undefined; - }; - readonly messagebird: { - readonly enabled: boolean; - readonly originator?: string | undefined; - readonly access_key?: string | undefined; - }; - readonly textlocal: { - readonly enabled: boolean; - readonly sender?: string | undefined; - readonly api_key?: string | undefined; - }; - readonly vonage: { - readonly enabled: boolean; - readonly from?: string | undefined; - readonly api_key?: string | undefined; - readonly api_secret?: string | undefined; - }; - readonly test_otp?: { - readonly [x: string]: string; - } | undefined; - }; - readonly external: { - readonly apple: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly azure: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly bitbucket: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly discord: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly facebook: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly github: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly gitlab: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly google: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly kakao: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly keycloak: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly linkedin_oidc: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly notion: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly twitch: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly twitter: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly x: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly slack_oidc: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly spotify: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly workos: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly zoom: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - }; - readonly web3: { - readonly solana: { - readonly enabled: boolean; - }; - readonly ethereum: { - readonly enabled: boolean; - }; - }; - readonly oauth_server: { - readonly enabled: boolean; - readonly authorization_url_path: string; - readonly allow_dynamic_registration: boolean; - }; - readonly third_party: { - readonly firebase: { - readonly enabled: boolean; - readonly project_id?: string | undefined; - }; - readonly auth0: { - readonly enabled: boolean; - readonly tenant?: string | undefined; - readonly tenant_region?: string | undefined; - }; - readonly aws_cognito: { - readonly enabled: boolean; - readonly user_pool_id?: string | undefined; - readonly user_pool_region?: string | undefined; - }; - readonly clerk: { - readonly enabled: boolean; - readonly domain?: string | undefined; - }; - readonly workos: { - readonly enabled: boolean; - readonly issuer_url?: string | undefined; - }; - }; - }; - readonly db: { - readonly port: number; - readonly shadow_port: number; - readonly health_timeout: string; - readonly major_version: number; - readonly pooler: { - readonly enabled: boolean; - readonly port: number; - readonly pool_mode: string; - readonly default_pool_size: number; - readonly max_client_conn: number; - }; - readonly migrations: { - readonly enabled: boolean; - readonly schema_paths: readonly string[]; - }; - readonly seed: { - readonly enabled: boolean; - readonly sql_paths: readonly string[]; - }; - readonly settings?: { - readonly effective_cache_size?: string | undefined; - readonly logical_decoding_work_mem?: string | undefined; - readonly maintenance_work_mem?: string | undefined; - readonly max_connections?: number | undefined; - readonly max_locks_per_transaction?: number | undefined; - readonly max_parallel_maintenance_workers?: number | undefined; - readonly max_parallel_workers?: number | undefined; - readonly max_parallel_workers_per_gather?: number | undefined; - readonly max_replication_slots?: number | undefined; - readonly max_slot_wal_keep_size?: string | undefined; - readonly max_standby_archive_delay?: string | undefined; - readonly max_standby_streaming_delay?: string | undefined; - readonly max_wal_size?: string | undefined; - readonly max_wal_senders?: number | undefined; - readonly max_worker_processes?: number | undefined; - readonly session_replication_role?: string | undefined; - readonly shared_buffers?: string | undefined; - readonly statement_timeout?: string | undefined; - readonly track_activity_query_size?: string | undefined; - readonly track_commit_timestamp?: boolean | undefined; - readonly wal_keep_size?: string | undefined; - readonly wal_sender_timeout?: string | undefined; - readonly work_mem?: string | undefined; - } | undefined; - readonly network_restrictions: { - readonly enabled: boolean; - readonly allowed_cidrs: readonly string[]; - readonly allowed_cidrs_v6: readonly string[]; - }; - readonly ssl_enforcement?: { - readonly enabled: boolean; - } | undefined; - readonly vault?: { - readonly [x: string]: string; - } | undefined; - }; - readonly edge_runtime: { - readonly enabled: boolean; - readonly policy: string; - readonly inspector_port: number; - readonly deno_version: number; - readonly secrets?: { - readonly [x: string]: string; - } | undefined; - }; - readonly functions: { - readonly [x: string]: { - readonly enabled: boolean; - readonly verify_jwt: boolean; - readonly import_map: string; - readonly entrypoint: string; - readonly static_files: readonly string[]; - readonly env: { - readonly [x: string]: string; - }; - }; - }; - readonly local_smtp: { - readonly enabled: boolean; - readonly port: number; - readonly smtp_port?: number | undefined; - readonly pop3_port?: number | undefined; - readonly admin_email?: string | undefined; - readonly sender_name?: string | undefined; - }; - readonly realtime: { - readonly enabled: boolean; - readonly ip_version: string; - readonly max_header_length: number; - }; - readonly storage: { - readonly enabled: boolean; - readonly file_size_limit: string; - readonly image_transformation?: { - readonly enabled: boolean; - } | undefined; - readonly buckets?: { - readonly [x: string]: { - readonly public: boolean; - readonly file_size_limit: string; - readonly allowed_mime_types: readonly string[]; - readonly objects_path: string; - }; - } | undefined; - readonly s3_protocol: { - readonly enabled: boolean; - }; - readonly analytics: { - readonly enabled: boolean; - readonly max_namespaces: number; - readonly max_tables: number; - readonly max_catalogs: number; - readonly buckets: { - readonly [x: string]: {}; - }; - }; - readonly vector: { - readonly enabled: boolean; - readonly max_buckets: number; - readonly max_indexes: number; - readonly buckets: { - readonly [x: string]: {}; - }; - }; - }; - readonly studio: { - readonly enabled: boolean; - readonly port: number; - readonly api_url: string; - readonly openai_api_key?: string | undefined; - }; - readonly workers: { - readonly [x: string]: { - readonly runtime?: string | undefined; - readonly size?: string | undefined; - readonly instances?: number | undefined; - readonly source?: string | undefined; - }; - }; - readonly experimental: { - readonly orioledb_version?: string | undefined; - readonly s3_host?: string | undefined; - readonly s3_region?: string | undefined; - readonly s3_access_key?: string | undefined; - readonly s3_secret_key?: string | undefined; - readonly webhooks?: { - readonly enabled: boolean; - } | undefined; - readonly pgdelta?: { - readonly enabled: boolean; - readonly declarative_schema_path?: string | undefined; - readonly format_options?: string | undefined; - } | undefined; - readonly inspect?: { - readonly rules: readonly { - readonly query?: string | undefined; - readonly name?: string | undefined; - readonly pass?: string | undefined; - readonly fail?: string | undefined; - }[]; - } | undefined; - }; - readonly remotes: { - readonly [x: string]: { - readonly project_id: string; - readonly analytics: { - readonly enabled: boolean; - readonly port: number; - readonly backend: string; - readonly vector_port?: number | undefined; - readonly gcp_project_id?: string | undefined; - readonly gcp_project_number?: string | undefined; - readonly gcp_jwt_path?: string | undefined; - }; - readonly api: { - readonly enabled: boolean; - readonly port: number; - readonly schemas: readonly string[]; - readonly extra_search_path: readonly string[]; - readonly max_rows: number; - readonly auto_expose_new_tables?: boolean | undefined; - readonly tls: { - readonly enabled: boolean; - readonly cert_path?: string | undefined; - readonly key_path?: string | undefined; - }; - readonly external_url?: string | undefined; - }; - readonly auth: { - readonly enabled: boolean; - readonly site_url: string; - readonly additional_redirect_urls: readonly string[]; - readonly jwt_expiry: number; - readonly jwt_issuer?: string | undefined; - readonly signing_keys_path?: string | undefined; - readonly enable_refresh_token_rotation: boolean; - readonly refresh_token_reuse_interval: number; - readonly enable_manual_linking: boolean; - readonly enable_signup: boolean; - readonly enable_anonymous_sign_ins: boolean; - readonly minimum_password_length: number; - readonly password_requirements: string; - readonly publishable_key?: string | undefined; - readonly secret_key?: string | undefined; - readonly jwt_secret?: string | undefined; - readonly anon_key?: string | undefined; - readonly service_role_key?: string | undefined; - readonly rate_limit: { - readonly email_sent: number; - readonly sms_sent: number; - readonly anonymous_users: number; - readonly token_refresh: number; - readonly sign_in_sign_ups: number; - readonly token_verifications: number; - readonly web3: number; - }; - readonly captcha?: { - readonly enabled: boolean; - readonly provider?: string | undefined; - readonly secret?: string | undefined; - } | undefined; - readonly hook: { - readonly mfa_verification_attempt: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly password_verification_attempt: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly custom_access_token: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly send_sms: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly send_email: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly before_user_created: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - }; - readonly mfa: { - readonly totp: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - }; - readonly phone: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - readonly otp_length: number; - readonly template: string; - readonly max_frequency: string; - }; - readonly web_authn: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - }; - readonly max_enrolled_factors: number; - }; - readonly sessions?: { - readonly timebox?: string | undefined; - readonly inactivity_timeout?: string | undefined; - } | undefined; - readonly email: { - readonly enable_signup: boolean; - readonly double_confirm_changes: boolean; - readonly enable_confirmations: boolean; - readonly secure_password_change: boolean; - readonly max_frequency: string; - readonly otp_length: number; - readonly otp_expiry: number; - readonly smtp?: { - readonly enabled: boolean; - readonly host?: string | undefined; - readonly port?: number | undefined; - readonly user?: string | undefined; - readonly pass?: string | undefined; - readonly admin_email?: string | undefined; - readonly sender_name?: string | undefined; - } | undefined; - readonly template: { - readonly [x: string]: { - readonly subject: string; - readonly content_path: string; - }; - }; - readonly notification: { - readonly [x: string]: { - readonly enabled: boolean; - readonly subject: string; - readonly content_path: string; - }; - }; - }; - readonly sms: { - readonly enable_signup: boolean; - readonly enable_confirmations: boolean; - readonly template: string; - readonly max_frequency: string; - readonly twilio: { - readonly enabled: boolean; - readonly account_sid: string; - readonly message_service_sid: string; - readonly auth_token?: string | undefined; - }; - readonly twilio_verify: { - readonly enabled: boolean; - readonly account_sid?: string | undefined; - readonly message_service_sid?: string | undefined; - readonly auth_token?: string | undefined; - }; - readonly messagebird: { - readonly enabled: boolean; - readonly originator?: string | undefined; - readonly access_key?: string | undefined; - }; - readonly textlocal: { - readonly enabled: boolean; - readonly sender?: string | undefined; - readonly api_key?: string | undefined; - }; - readonly vonage: { - readonly enabled: boolean; - readonly from?: string | undefined; - readonly api_key?: string | undefined; - readonly api_secret?: string | undefined; - }; - readonly test_otp?: { - readonly [x: string]: string; - } | undefined; - }; - readonly external: { - readonly apple: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly azure: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly bitbucket: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly discord: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly facebook: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly github: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly gitlab: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly google: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly kakao: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly keycloak: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly linkedin_oidc: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly notion: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly twitch: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly twitter: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly x: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly slack_oidc: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly spotify: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly workos: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly zoom: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - }; - readonly web3: { - readonly solana: { - readonly enabled: boolean; - }; - readonly ethereum: { - readonly enabled: boolean; - }; - }; - readonly oauth_server: { - readonly enabled: boolean; - readonly authorization_url_path: string; - readonly allow_dynamic_registration: boolean; - }; - readonly third_party: { - readonly firebase: { - readonly enabled: boolean; - readonly project_id?: string | undefined; - }; - readonly auth0: { - readonly enabled: boolean; - readonly tenant?: string | undefined; - readonly tenant_region?: string | undefined; - }; - readonly aws_cognito: { - readonly enabled: boolean; - readonly user_pool_id?: string | undefined; - readonly user_pool_region?: string | undefined; - }; - readonly clerk: { - readonly enabled: boolean; - readonly domain?: string | undefined; - }; - readonly workos: { - readonly enabled: boolean; - readonly issuer_url?: string | undefined; - }; - }; - }; - readonly db: { - readonly port: number; - readonly shadow_port: number; - readonly health_timeout: string; - readonly major_version: number; - readonly pooler: { - readonly enabled: boolean; - readonly port: number; - readonly pool_mode: string; - readonly default_pool_size: number; - readonly max_client_conn: number; - }; - readonly migrations: { - readonly enabled: boolean; - readonly schema_paths: readonly string[]; - }; - readonly seed: { - readonly enabled: boolean; - readonly sql_paths: readonly string[]; - }; - readonly settings?: { - readonly effective_cache_size?: string | undefined; - readonly logical_decoding_work_mem?: string | undefined; - readonly maintenance_work_mem?: string | undefined; - readonly max_connections?: number | undefined; - readonly max_locks_per_transaction?: number | undefined; - readonly max_parallel_maintenance_workers?: number | undefined; - readonly max_parallel_workers?: number | undefined; - readonly max_parallel_workers_per_gather?: number | undefined; - readonly max_replication_slots?: number | undefined; - readonly max_slot_wal_keep_size?: string | undefined; - readonly max_standby_archive_delay?: string | undefined; - readonly max_standby_streaming_delay?: string | undefined; - readonly max_wal_size?: string | undefined; - readonly max_wal_senders?: number | undefined; - readonly max_worker_processes?: number | undefined; - readonly session_replication_role?: string | undefined; - readonly shared_buffers?: string | undefined; - readonly statement_timeout?: string | undefined; - readonly track_activity_query_size?: string | undefined; - readonly track_commit_timestamp?: boolean | undefined; - readonly wal_keep_size?: string | undefined; - readonly wal_sender_timeout?: string | undefined; - readonly work_mem?: string | undefined; - } | undefined; - readonly network_restrictions: { - readonly enabled: boolean; - readonly allowed_cidrs: readonly string[]; - readonly allowed_cidrs_v6: readonly string[]; - }; - readonly ssl_enforcement?: { - readonly enabled: boolean; - } | undefined; - readonly vault?: { - readonly [x: string]: string; - } | undefined; - }; - readonly edge_runtime: { - readonly enabled: boolean; - readonly policy: string; - readonly inspector_port: number; - readonly deno_version: number; - readonly secrets?: { - readonly [x: string]: string; - } | undefined; - }; - readonly functions: { - readonly [x: string]: { - readonly enabled: boolean; - readonly verify_jwt: boolean; - readonly import_map: string; - readonly entrypoint: string; - readonly static_files: readonly string[]; - readonly env: { - readonly [x: string]: string; - }; - }; - }; - readonly local_smtp: { - readonly enabled: boolean; - readonly port: number; - readonly smtp_port?: number | undefined; - readonly pop3_port?: number | undefined; - readonly admin_email?: string | undefined; - readonly sender_name?: string | undefined; - }; - readonly realtime: { - readonly enabled: boolean; - readonly ip_version: string; - readonly max_header_length: number; - }; - readonly storage: { - readonly enabled: boolean; - readonly file_size_limit: string; - readonly image_transformation?: { - readonly enabled: boolean; - } | undefined; - readonly buckets?: { - readonly [x: string]: { - readonly public: boolean; - readonly file_size_limit: string; - readonly allowed_mime_types: readonly string[]; - readonly objects_path: string; - }; - } | undefined; - readonly s3_protocol: { - readonly enabled: boolean; - }; - readonly analytics: { - readonly enabled: boolean; - readonly max_namespaces: number; - readonly max_tables: number; - readonly max_catalogs: number; - readonly buckets: { - readonly [x: string]: {}; - }; - }; - readonly vector: { - readonly enabled: boolean; - readonly max_buckets: number; - readonly max_indexes: number; - readonly buckets: { - readonly [x: string]: {}; - }; - }; - }; - readonly studio: { - readonly enabled: boolean; - readonly port: number; - readonly api_url: string; - readonly openai_api_key?: string | undefined; - }; - readonly workers: { - readonly [x: string]: { - readonly runtime?: string | undefined; - readonly size?: string | undefined; - readonly instances?: number | undefined; - readonly source?: string | undefined; - }; - }; - readonly experimental: { - readonly orioledb_version?: string | undefined; - readonly s3_host?: string | undefined; - readonly s3_region?: string | undefined; - readonly s3_access_key?: string | undefined; - readonly s3_secret_key?: string | undefined; - readonly webhooks?: { - readonly enabled: boolean; - } | undefined; - readonly pgdelta?: { - readonly enabled: boolean; - readonly declarative_schema_path?: string | undefined; - readonly format_options?: string | undefined; - } | undefined; - readonly inspect?: { - readonly rules: readonly { - readonly query?: string | undefined; - readonly name?: string | undefined; - readonly pass?: string | undefined; - readonly fail?: string | undefined; - }[]; - } | undefined; - }; - }; - }; - }; - schemaRef: string | undefined; - ignoredPaths: never[]; - document: Record | undefined; - appliedRemote: string | undefined; - removedDeprecatedExternalProviders: Readonly>; - valueOrigins: { - path: string[]; - source: CliConfigValueSource; - }[]; -}, CliConfigParseError | import("./errors.ts").CliProjectEnvParseError | DuplicateRemoteProjectIdError | InvalidRemoteProjectIdError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>; -export declare const loadCliConfig: (cwd: string, options?: InternalLoadCliConfigOptions | undefined) => Effect.Effect<{ - path: string; - format: "json" | "toml"; - config: { - readonly project_id?: string | undefined; - readonly analytics: { - readonly enabled: boolean; - readonly port: number; - readonly backend: string; - readonly vector_port?: number | undefined; - readonly gcp_project_id?: string | undefined; - readonly gcp_project_number?: string | undefined; - readonly gcp_jwt_path?: string | undefined; - }; - readonly api: { - readonly enabled: boolean; - readonly port: number; - readonly schemas: readonly string[]; - readonly extra_search_path: readonly string[]; - readonly max_rows: number; - readonly auto_expose_new_tables?: boolean | undefined; - readonly tls: { - readonly enabled: boolean; - readonly cert_path?: string | undefined; - readonly key_path?: string | undefined; - }; - readonly external_url?: string | undefined; - }; - readonly auth: { - readonly enabled: boolean; - readonly site_url: string; - readonly additional_redirect_urls: readonly string[]; - readonly jwt_expiry: number; - readonly jwt_issuer?: string | undefined; - readonly signing_keys_path?: string | undefined; - readonly enable_refresh_token_rotation: boolean; - readonly refresh_token_reuse_interval: number; - readonly enable_manual_linking: boolean; - readonly enable_signup: boolean; - readonly enable_anonymous_sign_ins: boolean; - readonly minimum_password_length: number; - readonly password_requirements: string; - readonly publishable_key?: string | undefined; - readonly secret_key?: string | undefined; - readonly jwt_secret?: string | undefined; - readonly anon_key?: string | undefined; - readonly service_role_key?: string | undefined; - readonly rate_limit: { - readonly email_sent: number; - readonly sms_sent: number; - readonly anonymous_users: number; - readonly token_refresh: number; - readonly sign_in_sign_ups: number; - readonly token_verifications: number; - readonly web3: number; - }; - readonly captcha?: { - readonly enabled: boolean; - readonly provider?: string | undefined; - readonly secret?: string | undefined; - } | undefined; - readonly hook: { - readonly mfa_verification_attempt: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly password_verification_attempt: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly custom_access_token: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly send_sms: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly send_email: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly before_user_created: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - }; - readonly mfa: { - readonly totp: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - }; - readonly phone: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - readonly otp_length: number; - readonly template: string; - readonly max_frequency: string; - }; - readonly web_authn: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - }; - readonly max_enrolled_factors: number; - }; - readonly sessions?: { - readonly timebox?: string | undefined; - readonly inactivity_timeout?: string | undefined; - } | undefined; - readonly email: { - readonly enable_signup: boolean; - readonly double_confirm_changes: boolean; - readonly enable_confirmations: boolean; - readonly secure_password_change: boolean; - readonly max_frequency: string; - readonly otp_length: number; - readonly otp_expiry: number; - readonly smtp?: { - readonly enabled: boolean; - readonly host?: string | undefined; - readonly port?: number | undefined; - readonly user?: string | undefined; - readonly pass?: string | undefined; - readonly admin_email?: string | undefined; - readonly sender_name?: string | undefined; - } | undefined; - readonly template: { - readonly [x: string]: { - readonly subject: string; - readonly content_path: string; - }; - }; - readonly notification: { - readonly [x: string]: { - readonly enabled: boolean; - readonly subject: string; - readonly content_path: string; - }; - }; - }; - readonly sms: { - readonly enable_signup: boolean; - readonly enable_confirmations: boolean; - readonly template: string; - readonly max_frequency: string; - readonly twilio: { - readonly enabled: boolean; - readonly account_sid: string; - readonly message_service_sid: string; - readonly auth_token?: string | undefined; - }; - readonly twilio_verify: { - readonly enabled: boolean; - readonly account_sid?: string | undefined; - readonly message_service_sid?: string | undefined; - readonly auth_token?: string | undefined; - }; - readonly messagebird: { - readonly enabled: boolean; - readonly originator?: string | undefined; - readonly access_key?: string | undefined; - }; - readonly textlocal: { - readonly enabled: boolean; - readonly sender?: string | undefined; - readonly api_key?: string | undefined; - }; - readonly vonage: { - readonly enabled: boolean; - readonly from?: string | undefined; - readonly api_key?: string | undefined; - readonly api_secret?: string | undefined; - }; - readonly test_otp?: { - readonly [x: string]: string; - } | undefined; - }; - readonly external: { - readonly apple: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly azure: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly bitbucket: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly discord: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly facebook: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly github: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly gitlab: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly google: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly kakao: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly keycloak: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly linkedin_oidc: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly notion: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly twitch: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly twitter: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly x: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly slack_oidc: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly spotify: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly workos: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly zoom: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - }; - readonly web3: { - readonly solana: { - readonly enabled: boolean; - }; - readonly ethereum: { - readonly enabled: boolean; - }; - }; - readonly oauth_server: { - readonly enabled: boolean; - readonly authorization_url_path: string; - readonly allow_dynamic_registration: boolean; - }; - readonly third_party: { - readonly firebase: { - readonly enabled: boolean; - readonly project_id?: string | undefined; - }; - readonly auth0: { - readonly enabled: boolean; - readonly tenant?: string | undefined; - readonly tenant_region?: string | undefined; - }; - readonly aws_cognito: { - readonly enabled: boolean; - readonly user_pool_id?: string | undefined; - readonly user_pool_region?: string | undefined; - }; - readonly clerk: { - readonly enabled: boolean; - readonly domain?: string | undefined; - }; - readonly workos: { - readonly enabled: boolean; - readonly issuer_url?: string | undefined; - }; - }; - }; - readonly db: { - readonly port: number; - readonly shadow_port: number; - readonly health_timeout: string; - readonly major_version: number; - readonly pooler: { - readonly enabled: boolean; - readonly port: number; - readonly pool_mode: string; - readonly default_pool_size: number; - readonly max_client_conn: number; - }; - readonly migrations: { - readonly enabled: boolean; - readonly schema_paths: readonly string[]; - }; - readonly seed: { - readonly enabled: boolean; - readonly sql_paths: readonly string[]; - }; - readonly settings?: { - readonly effective_cache_size?: string | undefined; - readonly logical_decoding_work_mem?: string | undefined; - readonly maintenance_work_mem?: string | undefined; - readonly max_connections?: number | undefined; - readonly max_locks_per_transaction?: number | undefined; - readonly max_parallel_maintenance_workers?: number | undefined; - readonly max_parallel_workers?: number | undefined; - readonly max_parallel_workers_per_gather?: number | undefined; - readonly max_replication_slots?: number | undefined; - readonly max_slot_wal_keep_size?: string | undefined; - readonly max_standby_archive_delay?: string | undefined; - readonly max_standby_streaming_delay?: string | undefined; - readonly max_wal_size?: string | undefined; - readonly max_wal_senders?: number | undefined; - readonly max_worker_processes?: number | undefined; - readonly session_replication_role?: string | undefined; - readonly shared_buffers?: string | undefined; - readonly statement_timeout?: string | undefined; - readonly track_activity_query_size?: string | undefined; - readonly track_commit_timestamp?: boolean | undefined; - readonly wal_keep_size?: string | undefined; - readonly wal_sender_timeout?: string | undefined; - readonly work_mem?: string | undefined; - } | undefined; - readonly network_restrictions: { - readonly enabled: boolean; - readonly allowed_cidrs: readonly string[]; - readonly allowed_cidrs_v6: readonly string[]; - }; - readonly ssl_enforcement?: { - readonly enabled: boolean; - } | undefined; - readonly vault?: { - readonly [x: string]: string; - } | undefined; - }; - readonly edge_runtime: { - readonly enabled: boolean; - readonly policy: string; - readonly inspector_port: number; - readonly deno_version: number; - readonly secrets?: { - readonly [x: string]: string; - } | undefined; - }; - readonly functions: { - readonly [x: string]: { - readonly enabled: boolean; - readonly verify_jwt: boolean; - readonly import_map: string; - readonly entrypoint: string; - readonly static_files: readonly string[]; - readonly env: { - readonly [x: string]: string; - }; - }; - }; - readonly local_smtp: { - readonly enabled: boolean; - readonly port: number; - readonly smtp_port?: number | undefined; - readonly pop3_port?: number | undefined; - readonly admin_email?: string | undefined; - readonly sender_name?: string | undefined; - }; - readonly realtime: { - readonly enabled: boolean; - readonly ip_version: string; - readonly max_header_length: number; - }; - readonly storage: { - readonly enabled: boolean; - readonly file_size_limit: string; - readonly image_transformation?: { - readonly enabled: boolean; - } | undefined; - readonly buckets?: { - readonly [x: string]: { - readonly public: boolean; - readonly file_size_limit: string; - readonly allowed_mime_types: readonly string[]; - readonly objects_path: string; - }; - } | undefined; - readonly s3_protocol: { - readonly enabled: boolean; - }; - readonly analytics: { - readonly enabled: boolean; - readonly max_namespaces: number; - readonly max_tables: number; - readonly max_catalogs: number; - readonly buckets: { - readonly [x: string]: {}; - }; - }; - readonly vector: { - readonly enabled: boolean; - readonly max_buckets: number; - readonly max_indexes: number; - readonly buckets: { - readonly [x: string]: {}; - }; - }; - }; - readonly studio: { - readonly enabled: boolean; - readonly port: number; - readonly api_url: string; - readonly openai_api_key?: string | undefined; - }; - readonly workers: { - readonly [x: string]: { - readonly runtime?: string | undefined; - readonly size?: string | undefined; - readonly instances?: number | undefined; - readonly source?: string | undefined; - }; - }; - readonly experimental: { - readonly orioledb_version?: string | undefined; - readonly s3_host?: string | undefined; - readonly s3_region?: string | undefined; - readonly s3_access_key?: string | undefined; - readonly s3_secret_key?: string | undefined; - readonly webhooks?: { - readonly enabled: boolean; - } | undefined; - readonly pgdelta?: { - readonly enabled: boolean; - readonly declarative_schema_path?: string | undefined; - readonly format_options?: string | undefined; - } | undefined; - readonly inspect?: { - readonly rules: readonly { - readonly query?: string | undefined; - readonly name?: string | undefined; - readonly pass?: string | undefined; - readonly fail?: string | undefined; - }[]; - } | undefined; - }; - readonly remotes: { - readonly [x: string]: { - readonly project_id: string; - readonly analytics: { - readonly enabled: boolean; - readonly port: number; - readonly backend: string; - readonly vector_port?: number | undefined; - readonly gcp_project_id?: string | undefined; - readonly gcp_project_number?: string | undefined; - readonly gcp_jwt_path?: string | undefined; - }; - readonly api: { - readonly enabled: boolean; - readonly port: number; - readonly schemas: readonly string[]; - readonly extra_search_path: readonly string[]; - readonly max_rows: number; - readonly auto_expose_new_tables?: boolean | undefined; - readonly tls: { - readonly enabled: boolean; - readonly cert_path?: string | undefined; - readonly key_path?: string | undefined; - }; - readonly external_url?: string | undefined; - }; - readonly auth: { - readonly enabled: boolean; - readonly site_url: string; - readonly additional_redirect_urls: readonly string[]; - readonly jwt_expiry: number; - readonly jwt_issuer?: string | undefined; - readonly signing_keys_path?: string | undefined; - readonly enable_refresh_token_rotation: boolean; - readonly refresh_token_reuse_interval: number; - readonly enable_manual_linking: boolean; - readonly enable_signup: boolean; - readonly enable_anonymous_sign_ins: boolean; - readonly minimum_password_length: number; - readonly password_requirements: string; - readonly publishable_key?: string | undefined; - readonly secret_key?: string | undefined; - readonly jwt_secret?: string | undefined; - readonly anon_key?: string | undefined; - readonly service_role_key?: string | undefined; - readonly rate_limit: { - readonly email_sent: number; - readonly sms_sent: number; - readonly anonymous_users: number; - readonly token_refresh: number; - readonly sign_in_sign_ups: number; - readonly token_verifications: number; - readonly web3: number; - }; - readonly captcha?: { - readonly enabled: boolean; - readonly provider?: string | undefined; - readonly secret?: string | undefined; - } | undefined; - readonly hook: { - readonly mfa_verification_attempt: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly password_verification_attempt: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly custom_access_token: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly send_sms: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly send_email: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly before_user_created: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - }; - readonly mfa: { - readonly totp: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - }; - readonly phone: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - readonly otp_length: number; - readonly template: string; - readonly max_frequency: string; - }; - readonly web_authn: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - }; - readonly max_enrolled_factors: number; - }; - readonly sessions?: { - readonly timebox?: string | undefined; - readonly inactivity_timeout?: string | undefined; - } | undefined; - readonly email: { - readonly enable_signup: boolean; - readonly double_confirm_changes: boolean; - readonly enable_confirmations: boolean; - readonly secure_password_change: boolean; - readonly max_frequency: string; - readonly otp_length: number; - readonly otp_expiry: number; - readonly smtp?: { - readonly enabled: boolean; - readonly host?: string | undefined; - readonly port?: number | undefined; - readonly user?: string | undefined; - readonly pass?: string | undefined; - readonly admin_email?: string | undefined; - readonly sender_name?: string | undefined; - } | undefined; - readonly template: { - readonly [x: string]: { - readonly subject: string; - readonly content_path: string; - }; - }; - readonly notification: { - readonly [x: string]: { - readonly enabled: boolean; - readonly subject: string; - readonly content_path: string; - }; - }; - }; - readonly sms: { - readonly enable_signup: boolean; - readonly enable_confirmations: boolean; - readonly template: string; - readonly max_frequency: string; - readonly twilio: { - readonly enabled: boolean; - readonly account_sid: string; - readonly message_service_sid: string; - readonly auth_token?: string | undefined; - }; - readonly twilio_verify: { - readonly enabled: boolean; - readonly account_sid?: string | undefined; - readonly message_service_sid?: string | undefined; - readonly auth_token?: string | undefined; - }; - readonly messagebird: { - readonly enabled: boolean; - readonly originator?: string | undefined; - readonly access_key?: string | undefined; - }; - readonly textlocal: { - readonly enabled: boolean; - readonly sender?: string | undefined; - readonly api_key?: string | undefined; - }; - readonly vonage: { - readonly enabled: boolean; - readonly from?: string | undefined; - readonly api_key?: string | undefined; - readonly api_secret?: string | undefined; - }; - readonly test_otp?: { - readonly [x: string]: string; - } | undefined; - }; - readonly external: { - readonly apple: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly azure: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly bitbucket: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly discord: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly facebook: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly github: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly gitlab: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly google: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly kakao: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly keycloak: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly linkedin_oidc: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly notion: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly twitch: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly twitter: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly x: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly slack_oidc: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly spotify: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly workos: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly zoom: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - }; - readonly web3: { - readonly solana: { - readonly enabled: boolean; - }; - readonly ethereum: { - readonly enabled: boolean; - }; - }; - readonly oauth_server: { - readonly enabled: boolean; - readonly authorization_url_path: string; - readonly allow_dynamic_registration: boolean; - }; - readonly third_party: { - readonly firebase: { - readonly enabled: boolean; - readonly project_id?: string | undefined; - }; - readonly auth0: { - readonly enabled: boolean; - readonly tenant?: string | undefined; - readonly tenant_region?: string | undefined; - }; - readonly aws_cognito: { - readonly enabled: boolean; - readonly user_pool_id?: string | undefined; - readonly user_pool_region?: string | undefined; - }; - readonly clerk: { - readonly enabled: boolean; - readonly domain?: string | undefined; - }; - readonly workos: { - readonly enabled: boolean; - readonly issuer_url?: string | undefined; - }; - }; - }; - readonly db: { - readonly port: number; - readonly shadow_port: number; - readonly health_timeout: string; - readonly major_version: number; - readonly pooler: { - readonly enabled: boolean; - readonly port: number; - readonly pool_mode: string; - readonly default_pool_size: number; - readonly max_client_conn: number; - }; - readonly migrations: { - readonly enabled: boolean; - readonly schema_paths: readonly string[]; - }; - readonly seed: { - readonly enabled: boolean; - readonly sql_paths: readonly string[]; - }; - readonly settings?: { - readonly effective_cache_size?: string | undefined; - readonly logical_decoding_work_mem?: string | undefined; - readonly maintenance_work_mem?: string | undefined; - readonly max_connections?: number | undefined; - readonly max_locks_per_transaction?: number | undefined; - readonly max_parallel_maintenance_workers?: number | undefined; - readonly max_parallel_workers?: number | undefined; - readonly max_parallel_workers_per_gather?: number | undefined; - readonly max_replication_slots?: number | undefined; - readonly max_slot_wal_keep_size?: string | undefined; - readonly max_standby_archive_delay?: string | undefined; - readonly max_standby_streaming_delay?: string | undefined; - readonly max_wal_size?: string | undefined; - readonly max_wal_senders?: number | undefined; - readonly max_worker_processes?: number | undefined; - readonly session_replication_role?: string | undefined; - readonly shared_buffers?: string | undefined; - readonly statement_timeout?: string | undefined; - readonly track_activity_query_size?: string | undefined; - readonly track_commit_timestamp?: boolean | undefined; - readonly wal_keep_size?: string | undefined; - readonly wal_sender_timeout?: string | undefined; - readonly work_mem?: string | undefined; - } | undefined; - readonly network_restrictions: { - readonly enabled: boolean; - readonly allowed_cidrs: readonly string[]; - readonly allowed_cidrs_v6: readonly string[]; - }; - readonly ssl_enforcement?: { - readonly enabled: boolean; - } | undefined; - readonly vault?: { - readonly [x: string]: string; - } | undefined; - }; - readonly edge_runtime: { - readonly enabled: boolean; - readonly policy: string; - readonly inspector_port: number; - readonly deno_version: number; - readonly secrets?: { - readonly [x: string]: string; - } | undefined; - }; - readonly functions: { - readonly [x: string]: { - readonly enabled: boolean; - readonly verify_jwt: boolean; - readonly import_map: string; - readonly entrypoint: string; - readonly static_files: readonly string[]; - readonly env: { - readonly [x: string]: string; - }; - }; - }; - readonly local_smtp: { - readonly enabled: boolean; - readonly port: number; - readonly smtp_port?: number | undefined; - readonly pop3_port?: number | undefined; - readonly admin_email?: string | undefined; - readonly sender_name?: string | undefined; - }; - readonly realtime: { - readonly enabled: boolean; - readonly ip_version: string; - readonly max_header_length: number; - }; - readonly storage: { - readonly enabled: boolean; - readonly file_size_limit: string; - readonly image_transformation?: { - readonly enabled: boolean; - } | undefined; - readonly buckets?: { - readonly [x: string]: { - readonly public: boolean; - readonly file_size_limit: string; - readonly allowed_mime_types: readonly string[]; - readonly objects_path: string; - }; - } | undefined; - readonly s3_protocol: { - readonly enabled: boolean; - }; - readonly analytics: { - readonly enabled: boolean; - readonly max_namespaces: number; - readonly max_tables: number; - readonly max_catalogs: number; - readonly buckets: { - readonly [x: string]: {}; - }; - }; - readonly vector: { - readonly enabled: boolean; - readonly max_buckets: number; - readonly max_indexes: number; - readonly buckets: { - readonly [x: string]: {}; - }; - }; - }; - readonly studio: { - readonly enabled: boolean; - readonly port: number; - readonly api_url: string; - readonly openai_api_key?: string | undefined; - }; - readonly workers: { - readonly [x: string]: { - readonly runtime?: string | undefined; - readonly size?: string | undefined; - readonly instances?: number | undefined; - readonly source?: string | undefined; - }; - }; - readonly experimental: { - readonly orioledb_version?: string | undefined; - readonly s3_host?: string | undefined; - readonly s3_region?: string | undefined; - readonly s3_access_key?: string | undefined; - readonly s3_secret_key?: string | undefined; - readonly webhooks?: { - readonly enabled: boolean; - } | undefined; - readonly pgdelta?: { - readonly enabled: boolean; - readonly declarative_schema_path?: string | undefined; - readonly format_options?: string | undefined; - } | undefined; - readonly inspect?: { - readonly rules: readonly { - readonly query?: string | undefined; - readonly name?: string | undefined; - readonly pass?: string | undefined; - readonly fail?: string | undefined; - }[]; - } | undefined; - }; - }; - }; - }; - schemaRef: string | undefined; - document: Record | undefined; - appliedRemote: string | undefined; - removedDeprecatedExternalProviders: Readonly>; - valueOrigins: { - path: string[]; - source: CliConfigValueSource; - }[]; - ignoredPaths: string[]; -} | null, CliConfigParseError | import("./errors.ts").CliProjectEnvParseError | DuplicateRemoteProjectIdError | InvalidRemoteProjectIdError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>; -export declare const saveCliConfig: (options: SaveCliConfigOptions) => Effect.Effect<{ - path: string; - format: ConfigFormat; - config: { - readonly project_id?: string | undefined; - readonly analytics: { - readonly enabled: boolean; - readonly port: number; - readonly backend: string; - readonly vector_port?: number | undefined; - readonly gcp_project_id?: string | undefined; - readonly gcp_project_number?: string | undefined; - readonly gcp_jwt_path?: string | undefined; - }; - readonly api: { - readonly enabled: boolean; - readonly port: number; - readonly schemas: readonly string[]; - readonly extra_search_path: readonly string[]; - readonly max_rows: number; - readonly auto_expose_new_tables?: boolean | undefined; - readonly tls: { - readonly enabled: boolean; - readonly cert_path?: string | undefined; - readonly key_path?: string | undefined; - }; - readonly external_url?: string | undefined; - }; - readonly auth: { - readonly enabled: boolean; - readonly site_url: string; - readonly additional_redirect_urls: readonly string[]; - readonly jwt_expiry: number; - readonly jwt_issuer?: string | undefined; - readonly signing_keys_path?: string | undefined; - readonly enable_refresh_token_rotation: boolean; - readonly refresh_token_reuse_interval: number; - readonly enable_manual_linking: boolean; - readonly enable_signup: boolean; - readonly enable_anonymous_sign_ins: boolean; - readonly minimum_password_length: number; - readonly password_requirements: string; - readonly publishable_key?: string | undefined; - readonly secret_key?: string | undefined; - readonly jwt_secret?: string | undefined; - readonly anon_key?: string | undefined; - readonly service_role_key?: string | undefined; - readonly rate_limit: { - readonly email_sent: number; - readonly sms_sent: number; - readonly anonymous_users: number; - readonly token_refresh: number; - readonly sign_in_sign_ups: number; - readonly token_verifications: number; - readonly web3: number; - }; - readonly captcha?: { - readonly enabled: boolean; - readonly provider?: string | undefined; - readonly secret?: string | undefined; - } | undefined; - readonly hook: { - readonly mfa_verification_attempt: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly password_verification_attempt: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly custom_access_token: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly send_sms: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly send_email: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly before_user_created: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - }; - readonly mfa: { - readonly totp: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - }; - readonly phone: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - readonly otp_length: number; - readonly template: string; - readonly max_frequency: string; - }; - readonly web_authn: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - }; - readonly max_enrolled_factors: number; - }; - readonly sessions?: { - readonly timebox?: string | undefined; - readonly inactivity_timeout?: string | undefined; - } | undefined; - readonly email: { - readonly enable_signup: boolean; - readonly double_confirm_changes: boolean; - readonly enable_confirmations: boolean; - readonly secure_password_change: boolean; - readonly max_frequency: string; - readonly otp_length: number; - readonly otp_expiry: number; - readonly smtp?: { - readonly enabled: boolean; - readonly host?: string | undefined; - readonly port?: number | undefined; - readonly user?: string | undefined; - readonly pass?: string | undefined; - readonly admin_email?: string | undefined; - readonly sender_name?: string | undefined; - } | undefined; - readonly template: { - readonly [x: string]: { - readonly subject: string; - readonly content_path: string; - }; - }; - readonly notification: { - readonly [x: string]: { - readonly enabled: boolean; - readonly subject: string; - readonly content_path: string; - }; - }; - }; - readonly sms: { - readonly enable_signup: boolean; - readonly enable_confirmations: boolean; - readonly template: string; - readonly max_frequency: string; - readonly twilio: { - readonly enabled: boolean; - readonly account_sid: string; - readonly message_service_sid: string; - readonly auth_token?: string | undefined; - }; - readonly twilio_verify: { - readonly enabled: boolean; - readonly account_sid?: string | undefined; - readonly message_service_sid?: string | undefined; - readonly auth_token?: string | undefined; - }; - readonly messagebird: { - readonly enabled: boolean; - readonly originator?: string | undefined; - readonly access_key?: string | undefined; - }; - readonly textlocal: { - readonly enabled: boolean; - readonly sender?: string | undefined; - readonly api_key?: string | undefined; - }; - readonly vonage: { - readonly enabled: boolean; - readonly from?: string | undefined; - readonly api_key?: string | undefined; - readonly api_secret?: string | undefined; - }; - readonly test_otp?: { - readonly [x: string]: string; - } | undefined; - }; - readonly external: { - readonly apple: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly azure: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly bitbucket: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly discord: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly facebook: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly github: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly gitlab: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly google: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly kakao: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly keycloak: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly linkedin_oidc: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly notion: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly twitch: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly twitter: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly x: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly slack_oidc: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly spotify: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly workos: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly zoom: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - }; - readonly web3: { - readonly solana: { - readonly enabled: boolean; - }; - readonly ethereum: { - readonly enabled: boolean; - }; - }; - readonly oauth_server: { - readonly enabled: boolean; - readonly authorization_url_path: string; - readonly allow_dynamic_registration: boolean; - }; - readonly third_party: { - readonly firebase: { - readonly enabled: boolean; - readonly project_id?: string | undefined; - }; - readonly auth0: { - readonly enabled: boolean; - readonly tenant?: string | undefined; - readonly tenant_region?: string | undefined; - }; - readonly aws_cognito: { - readonly enabled: boolean; - readonly user_pool_id?: string | undefined; - readonly user_pool_region?: string | undefined; - }; - readonly clerk: { - readonly enabled: boolean; - readonly domain?: string | undefined; - }; - readonly workos: { - readonly enabled: boolean; - readonly issuer_url?: string | undefined; - }; - }; - }; - readonly db: { - readonly port: number; - readonly shadow_port: number; - readonly health_timeout: string; - readonly major_version: number; - readonly pooler: { - readonly enabled: boolean; - readonly port: number; - readonly pool_mode: string; - readonly default_pool_size: number; - readonly max_client_conn: number; - }; - readonly migrations: { - readonly enabled: boolean; - readonly schema_paths: readonly string[]; - }; - readonly seed: { - readonly enabled: boolean; - readonly sql_paths: readonly string[]; - }; - readonly settings?: { - readonly effective_cache_size?: string | undefined; - readonly logical_decoding_work_mem?: string | undefined; - readonly maintenance_work_mem?: string | undefined; - readonly max_connections?: number | undefined; - readonly max_locks_per_transaction?: number | undefined; - readonly max_parallel_maintenance_workers?: number | undefined; - readonly max_parallel_workers?: number | undefined; - readonly max_parallel_workers_per_gather?: number | undefined; - readonly max_replication_slots?: number | undefined; - readonly max_slot_wal_keep_size?: string | undefined; - readonly max_standby_archive_delay?: string | undefined; - readonly max_standby_streaming_delay?: string | undefined; - readonly max_wal_size?: string | undefined; - readonly max_wal_senders?: number | undefined; - readonly max_worker_processes?: number | undefined; - readonly session_replication_role?: string | undefined; - readonly shared_buffers?: string | undefined; - readonly statement_timeout?: string | undefined; - readonly track_activity_query_size?: string | undefined; - readonly track_commit_timestamp?: boolean | undefined; - readonly wal_keep_size?: string | undefined; - readonly wal_sender_timeout?: string | undefined; - readonly work_mem?: string | undefined; - } | undefined; - readonly network_restrictions: { - readonly enabled: boolean; - readonly allowed_cidrs: readonly string[]; - readonly allowed_cidrs_v6: readonly string[]; - }; - readonly ssl_enforcement?: { - readonly enabled: boolean; - } | undefined; - readonly vault?: { - readonly [x: string]: string; - } | undefined; - }; - readonly edge_runtime: { - readonly enabled: boolean; - readonly policy: string; - readonly inspector_port: number; - readonly deno_version: number; - readonly secrets?: { - readonly [x: string]: string; - } | undefined; - }; - readonly functions: { - readonly [x: string]: { - readonly enabled: boolean; - readonly verify_jwt: boolean; - readonly import_map: string; - readonly entrypoint: string; - readonly static_files: readonly string[]; - readonly env: { - readonly [x: string]: string; - }; - }; - }; - readonly local_smtp: { - readonly enabled: boolean; - readonly port: number; - readonly smtp_port?: number | undefined; - readonly pop3_port?: number | undefined; - readonly admin_email?: string | undefined; - readonly sender_name?: string | undefined; - }; - readonly realtime: { - readonly enabled: boolean; - readonly ip_version: string; - readonly max_header_length: number; - }; - readonly storage: { - readonly enabled: boolean; - readonly file_size_limit: string; - readonly image_transformation?: { - readonly enabled: boolean; - } | undefined; - readonly buckets?: { - readonly [x: string]: { - readonly public: boolean; - readonly file_size_limit: string; - readonly allowed_mime_types: readonly string[]; - readonly objects_path: string; - }; - } | undefined; - readonly s3_protocol: { - readonly enabled: boolean; - }; - readonly analytics: { - readonly enabled: boolean; - readonly max_namespaces: number; - readonly max_tables: number; - readonly max_catalogs: number; - readonly buckets: { - readonly [x: string]: {}; - }; - }; - readonly vector: { - readonly enabled: boolean; - readonly max_buckets: number; - readonly max_indexes: number; - readonly buckets: { - readonly [x: string]: {}; - }; - }; - }; - readonly studio: { - readonly enabled: boolean; - readonly port: number; - readonly api_url: string; - readonly openai_api_key?: string | undefined; - }; - readonly workers: { - readonly [x: string]: { - readonly runtime?: string | undefined; - readonly size?: string | undefined; - readonly instances?: number | undefined; - readonly source?: string | undefined; - }; - }; - readonly experimental: { - readonly orioledb_version?: string | undefined; - readonly s3_host?: string | undefined; - readonly s3_region?: string | undefined; - readonly s3_access_key?: string | undefined; - readonly s3_secret_key?: string | undefined; - readonly webhooks?: { - readonly enabled: boolean; - } | undefined; - readonly pgdelta?: { - readonly enabled: boolean; - readonly declarative_schema_path?: string | undefined; - readonly format_options?: string | undefined; - } | undefined; - readonly inspect?: { - readonly rules: readonly { - readonly query?: string | undefined; - readonly name?: string | undefined; - readonly pass?: string | undefined; - readonly fail?: string | undefined; - }[]; - } | undefined; - }; - readonly remotes: { - readonly [x: string]: { - readonly project_id: string; - readonly analytics: { - readonly enabled: boolean; - readonly port: number; - readonly backend: string; - readonly vector_port?: number | undefined; - readonly gcp_project_id?: string | undefined; - readonly gcp_project_number?: string | undefined; - readonly gcp_jwt_path?: string | undefined; - }; - readonly api: { - readonly enabled: boolean; - readonly port: number; - readonly schemas: readonly string[]; - readonly extra_search_path: readonly string[]; - readonly max_rows: number; - readonly auto_expose_new_tables?: boolean | undefined; - readonly tls: { - readonly enabled: boolean; - readonly cert_path?: string | undefined; - readonly key_path?: string | undefined; - }; - readonly external_url?: string | undefined; - }; - readonly auth: { - readonly enabled: boolean; - readonly site_url: string; - readonly additional_redirect_urls: readonly string[]; - readonly jwt_expiry: number; - readonly jwt_issuer?: string | undefined; - readonly signing_keys_path?: string | undefined; - readonly enable_refresh_token_rotation: boolean; - readonly refresh_token_reuse_interval: number; - readonly enable_manual_linking: boolean; - readonly enable_signup: boolean; - readonly enable_anonymous_sign_ins: boolean; - readonly minimum_password_length: number; - readonly password_requirements: string; - readonly publishable_key?: string | undefined; - readonly secret_key?: string | undefined; - readonly jwt_secret?: string | undefined; - readonly anon_key?: string | undefined; - readonly service_role_key?: string | undefined; - readonly rate_limit: { - readonly email_sent: number; - readonly sms_sent: number; - readonly anonymous_users: number; - readonly token_refresh: number; - readonly sign_in_sign_ups: number; - readonly token_verifications: number; - readonly web3: number; - }; - readonly captcha?: { - readonly enabled: boolean; - readonly provider?: string | undefined; - readonly secret?: string | undefined; - } | undefined; - readonly hook: { - readonly mfa_verification_attempt: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly password_verification_attempt: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly custom_access_token: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly send_sms: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly send_email: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - readonly before_user_created: { - readonly enabled: boolean; - readonly uri?: string | undefined; - readonly secrets?: string | undefined; - }; - }; - readonly mfa: { - readonly totp: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - }; - readonly phone: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - readonly otp_length: number; - readonly template: string; - readonly max_frequency: string; - }; - readonly web_authn: { - readonly enroll_enabled: boolean; - readonly verify_enabled: boolean; - }; - readonly max_enrolled_factors: number; - }; - readonly sessions?: { - readonly timebox?: string | undefined; - readonly inactivity_timeout?: string | undefined; - } | undefined; - readonly email: { - readonly enable_signup: boolean; - readonly double_confirm_changes: boolean; - readonly enable_confirmations: boolean; - readonly secure_password_change: boolean; - readonly max_frequency: string; - readonly otp_length: number; - readonly otp_expiry: number; - readonly smtp?: { - readonly enabled: boolean; - readonly host?: string | undefined; - readonly port?: number | undefined; - readonly user?: string | undefined; - readonly pass?: string | undefined; - readonly admin_email?: string | undefined; - readonly sender_name?: string | undefined; - } | undefined; - readonly template: { - readonly [x: string]: { - readonly subject: string; - readonly content_path: string; - }; - }; - readonly notification: { - readonly [x: string]: { - readonly enabled: boolean; - readonly subject: string; - readonly content_path: string; - }; - }; - }; - readonly sms: { - readonly enable_signup: boolean; - readonly enable_confirmations: boolean; - readonly template: string; - readonly max_frequency: string; - readonly twilio: { - readonly enabled: boolean; - readonly account_sid: string; - readonly message_service_sid: string; - readonly auth_token?: string | undefined; - }; - readonly twilio_verify: { - readonly enabled: boolean; - readonly account_sid?: string | undefined; - readonly message_service_sid?: string | undefined; - readonly auth_token?: string | undefined; - }; - readonly messagebird: { - readonly enabled: boolean; - readonly originator?: string | undefined; - readonly access_key?: string | undefined; - }; - readonly textlocal: { - readonly enabled: boolean; - readonly sender?: string | undefined; - readonly api_key?: string | undefined; - }; - readonly vonage: { - readonly enabled: boolean; - readonly from?: string | undefined; - readonly api_key?: string | undefined; - readonly api_secret?: string | undefined; - }; - readonly test_otp?: { - readonly [x: string]: string; - } | undefined; - }; - readonly external: { - readonly apple: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly azure: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly bitbucket: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly discord: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly facebook: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly github: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly gitlab: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly google: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly kakao: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly keycloak: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly linkedin_oidc: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly notion: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly twitch: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly twitter: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly x: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly slack_oidc: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly spotify: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly workos: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - readonly zoom: { - readonly enabled: boolean; - readonly client_id: string; - readonly secret?: string | undefined; - readonly url: string; - readonly redirect_uri: string; - readonly skip_nonce_check: boolean; - readonly email_optional: boolean; - }; - }; - readonly web3: { - readonly solana: { - readonly enabled: boolean; - }; - readonly ethereum: { - readonly enabled: boolean; - }; - }; - readonly oauth_server: { - readonly enabled: boolean; - readonly authorization_url_path: string; - readonly allow_dynamic_registration: boolean; - }; - readonly third_party: { - readonly firebase: { - readonly enabled: boolean; - readonly project_id?: string | undefined; - }; - readonly auth0: { - readonly enabled: boolean; - readonly tenant?: string | undefined; - readonly tenant_region?: string | undefined; - }; - readonly aws_cognito: { - readonly enabled: boolean; - readonly user_pool_id?: string | undefined; - readonly user_pool_region?: string | undefined; - }; - readonly clerk: { - readonly enabled: boolean; - readonly domain?: string | undefined; - }; - readonly workos: { - readonly enabled: boolean; - readonly issuer_url?: string | undefined; - }; - }; - }; - readonly db: { - readonly port: number; - readonly shadow_port: number; - readonly health_timeout: string; - readonly major_version: number; - readonly pooler: { - readonly enabled: boolean; - readonly port: number; - readonly pool_mode: string; - readonly default_pool_size: number; - readonly max_client_conn: number; - }; - readonly migrations: { - readonly enabled: boolean; - readonly schema_paths: readonly string[]; - }; - readonly seed: { - readonly enabled: boolean; - readonly sql_paths: readonly string[]; - }; - readonly settings?: { - readonly effective_cache_size?: string | undefined; - readonly logical_decoding_work_mem?: string | undefined; - readonly maintenance_work_mem?: string | undefined; - readonly max_connections?: number | undefined; - readonly max_locks_per_transaction?: number | undefined; - readonly max_parallel_maintenance_workers?: number | undefined; - readonly max_parallel_workers?: number | undefined; - readonly max_parallel_workers_per_gather?: number | undefined; - readonly max_replication_slots?: number | undefined; - readonly max_slot_wal_keep_size?: string | undefined; - readonly max_standby_archive_delay?: string | undefined; - readonly max_standby_streaming_delay?: string | undefined; - readonly max_wal_size?: string | undefined; - readonly max_wal_senders?: number | undefined; - readonly max_worker_processes?: number | undefined; - readonly session_replication_role?: string | undefined; - readonly shared_buffers?: string | undefined; - readonly statement_timeout?: string | undefined; - readonly track_activity_query_size?: string | undefined; - readonly track_commit_timestamp?: boolean | undefined; - readonly wal_keep_size?: string | undefined; - readonly wal_sender_timeout?: string | undefined; - readonly work_mem?: string | undefined; - } | undefined; - readonly network_restrictions: { - readonly enabled: boolean; - readonly allowed_cidrs: readonly string[]; - readonly allowed_cidrs_v6: readonly string[]; - }; - readonly ssl_enforcement?: { - readonly enabled: boolean; - } | undefined; - readonly vault?: { - readonly [x: string]: string; - } | undefined; - }; - readonly edge_runtime: { - readonly enabled: boolean; - readonly policy: string; - readonly inspector_port: number; - readonly deno_version: number; - readonly secrets?: { - readonly [x: string]: string; - } | undefined; - }; - readonly functions: { - readonly [x: string]: { - readonly enabled: boolean; - readonly verify_jwt: boolean; - readonly import_map: string; - readonly entrypoint: string; - readonly static_files: readonly string[]; - readonly env: { - readonly [x: string]: string; - }; - }; - }; - readonly local_smtp: { - readonly enabled: boolean; - readonly port: number; - readonly smtp_port?: number | undefined; - readonly pop3_port?: number | undefined; - readonly admin_email?: string | undefined; - readonly sender_name?: string | undefined; - }; - readonly realtime: { - readonly enabled: boolean; - readonly ip_version: string; - readonly max_header_length: number; - }; - readonly storage: { - readonly enabled: boolean; - readonly file_size_limit: string; - readonly image_transformation?: { - readonly enabled: boolean; - } | undefined; - readonly buckets?: { - readonly [x: string]: { - readonly public: boolean; - readonly file_size_limit: string; - readonly allowed_mime_types: readonly string[]; - readonly objects_path: string; - }; - } | undefined; - readonly s3_protocol: { - readonly enabled: boolean; - }; - readonly analytics: { - readonly enabled: boolean; - readonly max_namespaces: number; - readonly max_tables: number; - readonly max_catalogs: number; - readonly buckets: { - readonly [x: string]: {}; - }; - }; - readonly vector: { - readonly enabled: boolean; - readonly max_buckets: number; - readonly max_indexes: number; - readonly buckets: { - readonly [x: string]: {}; - }; - }; - }; - readonly studio: { - readonly enabled: boolean; - readonly port: number; - readonly api_url: string; - readonly openai_api_key?: string | undefined; - }; - readonly workers: { - readonly [x: string]: { - readonly runtime?: string | undefined; - readonly size?: string | undefined; - readonly instances?: number | undefined; - readonly source?: string | undefined; - }; - }; - readonly experimental: { - readonly orioledb_version?: string | undefined; - readonly s3_host?: string | undefined; - readonly s3_region?: string | undefined; - readonly s3_access_key?: string | undefined; - readonly s3_secret_key?: string | undefined; - readonly webhooks?: { - readonly enabled: boolean; - } | undefined; - readonly pgdelta?: { - readonly enabled: boolean; - readonly declarative_schema_path?: string | undefined; - readonly format_options?: string | undefined; - } | undefined; - readonly inspect?: { - readonly rules: readonly { - readonly query?: string | undefined; - readonly name?: string | undefined; - readonly pass?: string | undefined; - readonly fail?: string | undefined; - }[]; - } | undefined; - }; - }; - }; - }; - schemaRef: string | undefined; - ignoredPaths: never[]; -}, CliConfigParseError | import("./errors.ts").CliProjectEnvParseError | DuplicateRemoteProjectIdError | InvalidRemoteProjectIdError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>; diff --git a/packages/config/api-report/lib/env.d.ts b/packages/config/api-report/lib/env.d.ts deleted file mode 100644 index 542770900e..0000000000 --- a/packages/config/api-report/lib/env.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Schema, SchemaAST } from "effect"; -export declare const ENV_PATTERN = "^env\\((.*)\\)$"; -export declare const ENV_CAPTURE_REGEX: RegExp; -export declare const ENV_CAPTURE_REGEX_STRICT: RegExp; -export declare function isEnvReference(value: string, goViperCompat: boolean): boolean; -interface EnvAnnotations extends Schema.Annotations.Documentation { - readonly secret?: true; -} -export declare const env: (annotations?: EnvAnnotations) => Schema.String; -interface SecretAnnotations extends Schema.Annotations.Documentation { -} -export declare const secret: (annotations?: SecretAnnotations) => Schema.String; -/** - * Pre-decode env() substitution + schema-aware coercion. - * - * Walks the raw parsed document and the schema AST in parallel. For every - * string leaf matching `env(VAR)`: - * 1. Substitutes `env[VAR]` if set AND non-empty, else preserves the - * literal verbatim (Go-parity with - * `apps/cli-go/pkg/config/decode_hooks.go:14-21`, which gates on - * `len(env) > 0` — a set-but-empty var, e.g. a dotenv `KEY=` line, - * leaves the `env(KEY)` literal untouched just like an unset one). - * 2. If the schema at that path expects Number or Boolean, coerces the - * substituted string to the expected primitive — mirroring Go's - * mapstructure chain where `LoadEnvHook` returns a string that the next - * hook converts to the target type. - * - * Returns a new structure; does not mutate the input. - */ -export declare function interpolateEnvReferencesAgainstSchema(document: unknown, env: Readonly>, schema: { - readonly ast: SchemaAST.AST; -}, options?: { - readonly goViperCompat?: boolean; - readonly onResolvedEnv?: (path: ReadonlyArray) => void; -}): unknown; -export {}; diff --git a/packages/config/api-report/lib/resolve.d.ts b/packages/config/api-report/lib/resolve.d.ts deleted file mode 100644 index 0c6a805760..0000000000 --- a/packages/config/api-report/lib/resolve.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Redacted } from "effect"; -import type { CliProjectEnvironment } from "../project.ts"; -type ResolvedString = string | Redacted.Redacted; -export type ResolvedCliConfigValue = T extends string ? ResolvedString : T extends ReadonlyArray ? ReadonlyArray> : T extends Array ? Array> : T extends Record ? { - readonly [K in keyof T]: ResolvedCliConfigValue; -} & { - readonly [key: string]: ResolvedCliConfigValue; -} : T extends object ? { - readonly [K in keyof T]: ResolvedCliConfigValue; -} : T; -export declare function toPathSegments(path: string): ReadonlyArray; -/** - * Shared by the plain sync resolvers below and `../project.ts`'s - * Effect-typed `resolveCliConfigValue`/`resolveCliConfigSubtree` (which wrap - * this in `Effect.sync` and additionally accept the internal-only - * `goViperCompat` option). - * - * Declared as an overload pair rather than a single generic signature: the - * body's `unknown`-typed implementation signature is what lets - * `interpolateValue`/`redactValue` (both genuinely `unknown -> unknown`, - * since the recursion branches on runtime shape, not on `T`) flow straight - * through to the return without an `as` cast — callers only ever see the - * generic overload above, which resolves `T` from the argument and returns - * `ResolvedCliConfigValue` directly. - */ -export declare function resolveCliConfigValueAtPath(value: T, cliProjectEnv: Pick, path: ReadonlyArray, goViperCompat: boolean): ResolvedCliConfigValue; -/** - * Plain synchronous counterpart of `../project.ts`'s Effect-typed - * `resolveCliConfigValue`, exported from `.` under the same name — `./effect` - * re-exports the Effect-typed variant explicitly, which wins over this one's - * star re-export through `./index.ts` (see `../effect.ts`'s doc comment). - * - * `cliProjectEnv` only needs `.values` (`Pick`) — - * a caller that already has a project's env values but not the full - * `CliProjectEnvironment` shape (e.g. `paths`/`loadedPaths`/`sources`) can pass - * `{ values }` directly instead of threading through the whole loaded object. - * - * Has no options parameter: this package's one resolver knob (`goViperCompat`) - * is internal-only — see `InternalResolveCliConfigOptions` in `../project.ts`, - * exported from `@supabase/config/internal`. Adding a public knob later is a - * non-breaking, additive change. - */ -export declare function resolveCliConfigValue(value: T, cliProjectEnv: Pick, configPath: string): ResolvedCliConfigValue; -/** See {@link resolveCliConfigValue}'s doc comment for why `cliProjectEnv` only needs `.values`. */ -export declare function resolveCliConfigSubtree(value: T, cliProjectEnv: Pick, pathPrefix: string): ResolvedCliConfigValue; -export {}; diff --git a/packages/config/api-report/lib/schema.d.ts b/packages/config/api-report/lib/schema.d.ts deleted file mode 100644 index 6df4879aec..0000000000 --- a/packages/config/api-report/lib/schema.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Schema } from "effect"; -interface LinkMetadata { - readonly name: string; - readonly link: string; -} -declare module "effect/Schema" { - namespace Annotations { - interface Augment { - readonly tags?: ReadonlyArray | undefined; - readonly links?: ReadonlyArray | undefined; - readonly ["x-secret"]?: boolean | undefined; - } - } -} -export declare const stringEnum: >(values: Values, annotations?: Schema.Annotations.Documentation) => Schema.Literals; -export {}; diff --git a/packages/config/api-report/lib/secret-paths.d.ts b/packages/config/api-report/lib/secret-paths.d.ts deleted file mode 100644 index 756cda43fc..0000000000 --- a/packages/config/api-report/lib/secret-paths.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Derived from `CliConfigSchema` once, at module load — the schema's - * annotations are the single source of truth for which paths are secret; no - * hand-maintained list exists alongside it. A pattern segment is either a - * literal key or `"*"` (a dynamic `Schema.Record` key, e.g. `db.vault.*`, - * `edge_runtime.secrets.*`, `remotes.*.auth.jwt_secret`). Exported (beyond - * {@link isSecretPath}) so `../project-config/project-config.unit.test.ts` - * can build an exhaustive secret-strip probe from the same source of truth, - * rather than a second hand-picked field list. - */ -export declare const secretPathPatterns: (readonly string[])[]; -/** Whether `path` (root-relative segments into {@link CliConfigSchema}) names an `x-secret` leaf. */ -export declare function isSecretPath(path: ReadonlyArray): boolean; diff --git a/packages/config/api-report/node.d.ts b/packages/config/api-report/node.d.ts deleted file mode 100644 index ef8acf8a4c..0000000000 --- a/packages/config/api-report/node.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -export declare const loadCliConfig: (cwd: string, options?: import("./config-document.ts").LoadCliConfigOptions) => Promise; -export declare const findCliProjectRoot: (cwd: string) => Promise; -export declare const findCliProjectPaths: (cwd: string) => Promise; -export declare const loadCliConfigFile: (path: string) => Promise; -export declare const loadCliProjectEnvironment: (options: import("./project.ts").LoadCliProjectEnvironmentOptions) => Promise; -export declare const saveCliConfig: (options: import("./config-document.ts").SaveCliConfigOptions) => Promise; -export declare const inferFunctionsManifest: (cwd: string) => Promise; -export type { CliConfigIo } from "./promise-facade.ts"; -export * from "./index.ts"; diff --git a/packages/config/api-report/paths.d.ts b/packages/config/api-report/paths.d.ts deleted file mode 100644 index 23b424b346..0000000000 --- a/packages/config/api-report/paths.d.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Effect, FileSystem, Path } from "effect"; -export interface CliProjectPaths { - readonly projectRoot: string; - readonly supabaseDir: string; - readonly configPath: string; - readonly envPath: string; - readonly envLocalPath: string; -} -export interface FindCliProjectPathsOptions { - /** - * When `false`, only `cwd` itself is checked for `supabase/config.{json,toml}` — - * no ancestor climb. Go's own resolution never searches twice: an explicit - * `--workdir`/`SUPABASE_WORKDIR` is used exactly as given (`ChangeWorkDir`, - * `apps/cli-go/internal/utils/misc.go:238-257`), and once `os.Chdir`'d there, - * `config.toml` is read as a plain relative path with no further ancestor - * search (`NewPathBuilder`, `pkg/config/utils.go:43-48`). Ancestor climbing in - * Go only ever happens once, as the *default* when workdir is unset - * (`getProjectRoot`, `internal/utils/misc.go:216-231`). - * - * Callers that already hold an authoritative, Go-equivalent project root - * (e.g. the legacy `stop`/`status` ports' `cliSettings.workdir`, which mirrors - * `ChangeWorkDir`'s own explicit-vs-default resolution) should pass `false` - * here to avoid a second, un-Go-like ancestor search that could otherwise - * pick up an unrelated ancestor project's config. - * - * Defaults to `true` (the original ancestor-search behavior), so existing - * callers are unaffected. - */ - readonly search?: boolean; -} -export declare const findCliProjectPaths: (cwd: string, options?: FindCliProjectPathsOptions | undefined) => Effect.Effect<{ - projectRoot: string; - supabaseDir: string; - configPath: string; - envPath: string; - envLocalPath: string; -} | null, never, FileSystem.FileSystem | Path.Path>; -export declare const findCliProjectRoot: (cwd: string) => Effect.Effect; diff --git a/packages/config/api-report/project-config/api-attributes.d.ts b/packages/config/api-report/project-config/api-attributes.d.ts deleted file mode 100644 index 0f42aabe44..0000000000 --- a/packages/config/api-report/project-config/api-attributes.d.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { Schema } from "effect"; -export declare const ProjectConfigApiAttributesSchema: Schema.Struct<{ - readonly database: Schema.optionalKey; - readonly ssl_enforced: Schema.optionalKey; - readonly network_restrictions: Schema.optionalKey; - readonly status: Schema.optionalKey; - readonly allowed_cidrs: Schema.optionalKey; - readonly type: Schema.optionalKey; - }>>>; - readonly updated_at: Schema.optionalKey; - readonly applied_at: Schema.optionalKey; - }>>; - readonly postgres_settings: Schema.optionalKey; - readonly logical_decoding_work_mem: Schema.optionalKey; - readonly log_autovacuum_min_duration: Schema.optionalKey; - readonly log_checkpoints: Schema.optionalKey; - readonly log_connections: Schema.optionalKey; - readonly log_disconnections: Schema.optionalKey; - readonly log_duration: Schema.optionalKey; - readonly log_lock_waits: Schema.optionalKey; - readonly log_recovery_conflict_waits: Schema.optionalKey; - readonly log_replication_commands: Schema.optionalKey; - readonly log_startup_progress_interval: Schema.optionalKey; - readonly log_temp_files: Schema.optionalKey; - readonly maintenance_work_mem: Schema.optionalKey; - readonly track_activity_query_size: Schema.optionalKey; - readonly max_connections: Schema.optionalKey; - readonly max_locks_per_transaction: Schema.optionalKey; - readonly max_logical_replication_workers: Schema.optionalKey; - readonly max_parallel_maintenance_workers: Schema.optionalKey; - readonly max_parallel_workers: Schema.optionalKey; - readonly max_parallel_workers_per_gather: Schema.optionalKey; - readonly max_replication_slots: Schema.optionalKey; - readonly max_slot_wal_keep_size: Schema.optionalKey; - readonly max_standby_archive_delay: Schema.optionalKey; - readonly max_standby_streaming_delay: Schema.optionalKey; - readonly max_sync_workers_per_subscription: Schema.optionalKey; - readonly max_wal_size: Schema.optionalKey; - readonly max_wal_senders: Schema.optionalKey; - readonly max_worker_processes: Schema.optionalKey; - readonly session_replication_role: Schema.optionalKey; - readonly shared_buffers: Schema.optionalKey; - readonly statement_timeout: Schema.optionalKey; - readonly track_commit_timestamp: Schema.optionalKey; - readonly wal_keep_size: Schema.optionalKey; - readonly wal_sender_timeout: Schema.optionalKey; - readonly work_mem: Schema.optionalKey; - readonly checkpoint_timeout: Schema.optionalKey; - readonly hot_standby_feedback: Schema.optionalKey; - readonly cron_log_statement: Schema.optionalKey; - }>>; - }>>; - readonly pooler: Schema.optionalKey; - readonly ignore_startup_parameters: Schema.optionalKey; - readonly server_idle_timeout: Schema.optionalKey; - readonly server_lifetime: Schema.optionalKey; - readonly query_wait_timeout: Schema.optionalKey; - readonly reserve_pool_size: Schema.optionalKey; - readonly default_pool_size: Schema.optionalKey; - readonly max_client_conn: Schema.optionalKey; - }>>; - readonly auth: Schema.optionalKey>>; - readonly api: Schema.optionalKey; - readonly db_extra_search_path: Schema.optionalKey; - readonly max_rows: Schema.optionalKey; - readonly db_pool_acquisition_timeout: Schema.optionalKey; - readonly db_pool: Schema.optionalKey; - }>>; - readonly realtime: Schema.optionalKey; - readonly max_concurrent_users: Schema.optionalKey; - readonly max_events_per_second: Schema.optionalKey; - readonly max_bytes_per_second: Schema.optionalKey; - readonly max_channels_per_client: Schema.optionalKey; - readonly max_joins_per_second: Schema.optionalKey; - readonly max_presence_events_per_second: Schema.optionalKey; - readonly max_payload_size_in_kb: Schema.optionalKey; - readonly presence_enabled: Schema.optionalKey; - readonly suspend: Schema.optionalKey; - readonly connection_pool: Schema.optionalKey; - readonly postgres_changes_pool: Schema.optionalKey; - }>>; - readonly storage: Schema.optionalKey; - readonly features: Schema.optionalKey; - }>>; - readonly s3_protocol: Schema.optionalKey; - }>>; - readonly purge_cache: Schema.optionalKey; - readonly iceberg_catalog: Schema.optionalKey; - readonly max_namespaces: Schema.optionalKey; - readonly max_tables: Schema.optionalKey; - readonly max_catalogs: Schema.optionalKey; - }>>; - readonly vector_buckets: Schema.optionalKey; - readonly max_buckets: Schema.optionalKey; - readonly max_indexes: Schema.optionalKey; - }>>; - }>>; - readonly capabilities: Schema.optionalKey; - readonly upstream_target: Schema.optionalKey; - readonly migration_version: Schema.optionalKey; - readonly database_pool_mode: Schema.optionalKey; - }>>; -}>; -export type ProjectConfigApiAttributes = typeof ProjectConfigApiAttributesSchema.Type; diff --git a/packages/config/api-report/project-config/hosted-sections.d.ts b/packages/config/api-report/project-config/hosted-sections.d.ts deleted file mode 100644 index d169f94ae8..0000000000 --- a/packages/config/api-report/project-config/hosted-sections.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * The seven {@link CliConfig} (`../base.ts`) section keys a hosted - * project-config API response can speak for — the vocabulary ceiling for - * {@link ProjectConfig} (`./project-config.ts`)'s compile-time type and - * {@link ProjectConfigSchema} (`./project-schema.ts`)'s runtime derivation. - * Owned here rather than duplicated in either consumer, per this repo's - * policy of moving a shared constant to its correct owner instead of - * hand-keeping two copies in sync. - */ -export declare const HOSTED_SECTION_KEYS: readonly ["api", "auth", "db", "realtime", "storage", "workers", "experimental"]; -/** The seven keys {@link ProjectConfig}/{@link ProjectConfigSchema} can carry. */ -export type HostedSectionKey = (typeof HOSTED_SECTION_KEYS)[number]; diff --git a/packages/config/api-report/project-config/project-config.d.ts b/packages/config/api-report/project-config/project-config.d.ts deleted file mode 100644 index 1a58b4fe57..0000000000 --- a/packages/config/api-report/project-config/project-config.d.ts +++ /dev/null @@ -1,374 +0,0 @@ -import type { CliConfig } from "../base.ts"; -import { type DeepPartial, type EffectiveConfig } from "../sparse.ts"; -import { type HostedSectionKey } from "./hosted-sections.ts"; -/** - * A deeply-readonly JSON value — the shape of everything under - * `_apiResponse`, which holds (a clone of) a parsed Management API JSON - * payload and is recursively frozen at attach time. Typed recursively - * readonly so no narrowing path reaches a mutable view: with plain `unknown` - * values, `Array.isArray(...)` would narrow to a mutable array whose - * `.push` compiles and then throws against the frozen runtime value. (A - * programmatic `attachApiResponse` caller can technically hand over - * non-JSON structured-cloneable values — Dates, Maps; those step outside - * this type by their own choice, exactly like any other consumer-side - * assertion.) One narrowing caveat no user-space type can close: the lib's - * own `Array.isArray` guard is typed `arg is any[]`, so narrowing through it - * yields a MUTABLE array view (microsoft/TypeScript#17002) whose `.push` - * compiles and then throws against the frozen value — narrow with a - * readonly-preserving guard (`(v): v is ReadonlyArray => - * Array.isArray(v)`) instead. - */ -export type ReadonlyJsonValue = string | number | boolean | null | ReadonlyArray | { - readonly [key: string]: ReadonlyJsonValue; -}; -/** - * The hosted-project subset of {@link CliConfig}: the sections a Management - * API project-config response can speak for (`api`, `auth`, `db`, - * `realtime`, `storage`, `workers`, `experimental`) — never the local-only - * sections (`studio`, service ports, `edge_runtime`, `analytics`, - * `[remotes.*]`, …) that only make sense for a checkout on disk - * (`docs/cli-config-loading.md`'s vocabulary). - * - * Deliberately sparse (`DeepPartial`), not a fully-materialized `CliConfig` - * with schema defaults filled in: an API response never mentions a section - * or field it doesn't manage, and a `ProjectConfig` that flooded in schema - * defaults for everything it didn't report would fabricate drift against a - * local document that genuinely differs only where the API actually speaks - * (CLI-2230's design rule). Sparseness is also what makes a `ProjectConfig` - * usable as an operand of `subtractCliConfig`/`omitDefaultValues` - * (`../sparse.ts`): those helpers take an {@link EffectiveConfig}, and a - * `ProjectConfig` (minus `_apiResponse`, which those walks never see — see - * below) is structurally assignable to it, since `EffectiveConfig` is - * `DeepPartial>` and every key `ProjectConfig` - * can carry is one of `CliConfig`'s non-`remotes` keys. - * - * `_apiResponse` follows ADR 0019: present only on a value built by - * {@link fromApiProjectConfig} (never on one built by - * {@link fromConfigDocument}), holding a deep-cloned, deep-frozen copy of the - * raw, pre-mapping `data.attributes` object (frozen/cloned rather than - * aliasing the caller's object: neither this package nor a caller can - * accidentally mutate it after the fact). It is attached as a non-enumerable - * property at runtime (rule 1), so it is invisible to every *serializer* — - * `JSON.stringify`, object spread, `Object.assign`, `structuredClone` — and - * to the structural walks in `../sparse.ts`, and is therefore never - * persisted to a config file. Invisible to serializers is not invisible to - * every possible inspection, though: a debug inspector that deliberately - * shows non-enumerable own properties (e.g. Bun's `console.log`) still - * prints it. Never log an API-sourced `ProjectConfig` directly — the raw - * attributes can include an HMAC digest of a secret value. A caller that - * loses `_apiResponse` across a spread/`structuredClone`/state-store - * round-trip can re-attach it via {@link attachApiResponse}. - * - * The seven hosted-section keys above are a vocabulary-level ceiling, not a - * per-field guarantee: they name every section a project-config response - * *could* speak for, not how much of each section a given operand actually - * does. `fromConfigDocument`'s operand (a `CliConfig`/`EffectiveConfig`) can - * genuinely carry any field in any of the seven. `fromApiProjectConfig`'s - * operand speaks for far fewer — `realtime` maps zero rows today (every field - * is local dev-server tuning with no hosted counterpart, `./registry.ts`'s - * comment on `realtime`), and `workers`/`experimental` have no v2 - * project-config API counterpart at all, so an API-sourced `ProjectConfig` - * never carries those two keys regardless of what the remote project has - * configured. A comparison consumer (CLI-2156) must restrict its comparison - * to the fields both operands actually speak for, never treat one operand's - * whole-section presence/absence as drift against the other's — that - * granularity gap is not only whole-section: several record-entry and - * optional-substruct fields the registry maps *unconditionally* (every - * mailer template/notification row, `email.smtp.enabled`, every - * `db.settings.*` row, `sessions.timebox`/`inactivity_timeout`, - * `captcha.enabled`, …) appear on an API-sourced `ProjectConfig` even when a - * local document never declared that sub-section at all, since the mapping - * has no "the local document is silent here" signal to withhold on. Use - * {@link comparableProjectConfigPaths}/{@link isComparableProjectConfigPath} - * to restrict a comparison to exactly the fields `fromApiProjectConfig` can - * actually speak for, rather than hand-maintaining an equivalent field list. - * The gap runs the other direction too: `auth.oauth_server`, and - * `storage.analytics`/`storage.vector` when disabled, ARE comparable paths - * (`fromApiProjectConfig` maps them) that `fromConfigDocument` can be - * silent on entirely, since push cannot communicate that state at all — see - * ADR 0021's "unmanaged-by-push containers" family — so the same - * both-operands-speak-for restriction applies symmetrically, not only for - * the API arm's unconditional fields above. - * - * Per ADR 0021, a `ProjectConfig` value is NOT a verbatim projection of - * whichever operand produced it — both {@link fromConfigDocument} and - * {@link fromApiProjectConfig} canonicalize toward the state a `config push` - * would actually converge on (SMS-provider push precedence, disabled-sentinel - * pruning of gated siblings, duration/byte-size re-quantization, and more — - * see that ADR for the full enumeration). A `ProjectConfig` built from a - * document is therefore not a faithful rendering of what the user wrote in - * their config file; see {@link fromConfigDocument}'s own docstring. - */ -export type ProjectConfig = DeepPartial> & { - readonly _apiResponse?: { - readonly [key: string]: ReadonlyJsonValue; - }; -}; -/** - * A `{ config, document }` pair {@link fromConfigDocument} accepts as an - * alternative to a bare {@link EffectiveConfig} (human review round on PR - * #6339, thread 1): `document` is the raw, pre-decode document object - * (`LoadedCliConfig.document`, `../config-document.ts` — post-`env()`, - * remotes-merged, retained precisely so a caller can inspect key presence a - * decoded value loses to schema defaults) and unlocks raw-presence masking - * ({@link applyRawPresenceMask}) a bare `EffectiveConfig` operand cannot, - * since decode has already erased the distinction between "the file - * declared this with a default value" and "the file never mentioned this at - * all". `LoadedCliConfig` is structurally assignable to this interface - * WITHOUT a cast — its `config: CliConfig` fits `EffectiveConfig` (a - * `CliConfig` is one), its `document?: Record` matches - * exactly. Declared independently rather than importing `LoadedCliConfig` - * by name: not for pure-runtime-graph reasons (`config-document.ts` is - * already reachable from this package's pure entrypoint, and this very file - * already imports `isObject` from it), but so `fromConfigDocument`'s public - * contract doesn't couple its parameter shape to the loader's own type name - * — this type is local-checkout-side on its own terms (ADR 0020's `Cli*` - * convention), independent of which loader happens to produce a matching - * shape. - */ -export interface CliConfigWithRawPresence { - readonly config: EffectiveConfig; - readonly document?: Record; -} -/** - * Projects a {@link CliConfig} document (or any {@link EffectiveConfig} - * operand — a full `CliConfig` is one) down to its hosted-section subset. - * Copies each hosted section deeply and only when own-present on `config`, - * omitting every `x-secret` leaf ({@link copyHostedValueWithoutSecrets}) and - * canonicalizing every field a registry row's `normalizeDocument` covers - * ({@link applyDocumentNormalizations}) — parity with - * {@link fromApiProjectConfig}'s own secret omission and canonical - * duration/byte-size spellings, so the same logical hosted config compares - * equal regardless of which side produced it, and so this function never - * leaks a document's plaintext secrets onto a value that will sit next to - * an API-sourced `ProjectConfig` in a diff. The returned value is always a - * fresh copy — safe to call even when `config` is frozen (e.g. - * {@link getDefaultCliConfig}'s memo). Never attaches `_apiResponse`; that - * only happens in {@link fromApiProjectConfig}. Throws - * {@link ProjectConfigParseError} if a value at a normalized path is - * malformed in a way `normalizeDocument` cannot tolerate — in practice this - * should not happen, since every `normalizeDocument` implementation returns - * its input verbatim rather than throwing. - * - * NOT a verbatim projection of `config` (ADR 0021): beyond secret omission - * and per-field canonicalization, this function also applies - * {@link applySmsProviderPrecedence} (a document enabling several SMS - * providers converges on only the push-selected one staying `enabled`) and - * {@link applyDisabledSentinels} (a disabled section/entry drops the sibling - * fields the legacy push does not manage while it is off). The result - * predicts what the hosted config will look like AFTER pushing `config`, not - * `config`'s own declared hosted-section values — do not render it to a user - * as "your local config". - * - * The convergence prediction is exact for a genuinely sparse `config` — one - * that only carries the keys the caller means to speak for. It holds only - * "exact modulo schema defaults" for a fully-materialized decoded document - * passed BARE (the common case, since a full `CliConfig` is a valid - * operand): decode cannot recover whether the raw file actually wrote a key - * or merely inherited its schema default, a distinction the legacy push - * pipeline DOES read (e.g. it emits only the external providers the raw - * file declared, never every provider a decoded document defaults to). - * - * **This limit has a first-class remedy**: pass a {@link - * CliConfigWithRawPresence} pair instead of a bare `config` — this is the - * RECOMMENDED form whenever a `document` is available (i.e. whenever the - * config came from `loadCliConfig` rather than being constructed in-memory, - * e.g. `getDefaultCliConfig()`'s memo). With `document` present, this - * function additionally applies {@link applyRawPresenceMask}, mirroring the - * legacy push pipeline's own raw-presence gates - * (`apps/cli/src/legacy/commands/config/push/push.raw-presence.ts`) exactly, - * closing the gap for the fields those gates cover. Without `document`, this - * function's behavior is unchanged, and a caller diffing its output against - * a remote `ProjectConfig` should still first strip schema defaults with - * `omitDefaultValues` and intersect to the fields both operands actually - * speak for — see ADR 0021's "Limits" section for the verified boundary, - * which fields the presence mask covers, and the residual drift categories - * that remain deferred to CLI-2266 even with a `document` supplied. - * `@supabase/config/io`'s `loadCliConfig` supplies a `document`; - * `saveCliConfig`'s returned `LoadedCliConfig` does NOT (there is no raw - * file being re-read on a save) — passing that result here silently falls - * back to the un-remedied, bare-`config` behavior. - */ -export declare function fromConfigDocument(config: EffectiveConfig): ProjectConfig; -export declare function fromConfigDocument(loaded: CliConfigWithRawPresence): ProjectConfig; -export declare function fromConfigDocument(source: EffectiveConfig | CliConfigWithRawPresence): ProjectConfig; -/** - * DOCUMENT-arm only: at most one SMS provider can be live on the platform — - * the push switch selects the FIRST enabled provider in its fixed order and - * sends only that one (`switch (true)`, auth.sync.ts:2498-2539), so a - * document enabling several providers converges, after any push, on a hosted - * state where only the first is enabled. Later `enabled: true` flags flip to - * `false` here, and the entry sweep in {@link applyDisabledSentinels} (which - * runs next) prunes their siblings — matching what `fromApiProjectConfig` - * reports for that hosted state. The API arm never needs this: its five - * flags all derive from the single `sms_provider` discriminator. - */ -export declare const SMS_PROVIDER_PUSH_PRECEDENCE: readonly ["twilio", "twilio_verify", "messagebird", "textlocal", "vonage"]; -/** - * Fields the legacy push does not manage while their section's toggle is off - * — it writes only the disable sentinel for each of these (Data API: only - * `db_schema: ""`, api.sync.ts:130-145; network restrictions: whole flow - * skipped, db.sync.ts:148-150; SMTP: only `smtp_host: ""`, - * auth.sync.ts:2384-2397; storage Iceberg/Vector: whole feature omitted, - * storage.sync.ts:287-299; captcha provider/secret only when enabled, - * :2315-2324; hook URI/secrets only when enabled, :2551-2565; SMS provider - * credentials only for the selected provider, :2498-2539; whole Auth/Storage - * sections gated on their own `enabled`, :1224-1226 / storage.sync.ts's - * subset gating) — so projecting the (usually schema-filled or - * platform-retained) siblings would fabricate drift between representations - * of the same disabled state. Applied to BOTH normalizers' outputs: the - * mapped shape is identical on the document and API arms, so one pass keeps - * the two symmetric by construction. - */ -export declare const DISABLED_SENTINEL_PRUNES: ReadonlyArray<{ - readonly containerPath: ReadonlyArray; - /** Keys to drop when `enabled === false`; absent = drop every key but `enabled`. */ - readonly dropKeys?: ReadonlyArray; -}>; -/** Record-shaped containers whose per-entry `enabled: false` keeps only the flag. */ -export declare const DISABLED_SENTINEL_ENTRY_SWEEPS: ReadonlyArray<{ - readonly containerPath: ReadonlyArray; - /** Restrict the sweep to these entry keys (a container mixing records and scalars). */ - readonly entryKeys?: ReadonlyArray; -}>; -/** - * Maps a Management API v2 project-config response into a {@link - * ProjectConfig}, per ADR 0019: (1) unwraps whichever of the three envelope - * shapes `input` is, (2) decodes the unwrapped attributes leniently — an - * API-ahead-of-package field never fails this decode, only a genuinely - * malformed mapped field does — (3) walks the mapping registry - * (`./registry.ts`) to populate the typed sections, and (4) attaches a - * deep-cloned, deep-frozen copy of the raw, unwrapped attributes as a - * non-enumerable `_apiResponse` ({@link attachFrozenApiResponse}) so - * `unmappedApiFields` and forward-compatible consumers can still reach - * whatever the registry didn't map. Throws {@link ProjectConfigParseError} - * when `input` isn't an object, when the envelope is malformed, or when - * decoding/mapping a value fails. - * - * Also NOT a verbatim projection of the response (ADR 0021): a `null` on a - * gating boolean canonicalizes to `enabled: false` rather than being skipped - * (`gatedBoolRow`/the SMTP host anchor, `./registry-auth.ts`), the - * same {@link applyDisabledSentinels} pruning `fromConfigDocument` applies - * runs here too, and an out-of-domain value on a mapped field (e.g. a - * negative `storage.file_size_limit`) throws rather than canonicalizing to a - * wrong value. This makes an API-sourced and a document-sourced - * `ProjectConfig` comparable for the same hosted state, at the cost of this - * function's output also not being a byte-for-byte echo of what the API - * reported. - */ -export declare function fromApiProjectConfig(input: unknown): ProjectConfig; -/** - * Re-attaches `_apiResponse` to `config` after a caller's own spread, - * `structuredClone`, or state-store round-trip already dropped it — ADR - * 0019 rule 1 promises the attach step exists precisely because those - * operations are non-enumerable-property-blind by design, and a consumer - * that legitimately needs to carry the raw attributes across such a - * boundary (a state store, a serialized cache entry it then rehydrates) must - * be able to restore them explicitly rather than losing `unmappedApiFields` - * access permanently. Returns a NEW object: a shallow copy of `config`'s own - * enumerable properties, plus `rawAttributes` attached via the same - * clone-and-freeze path {@link fromApiProjectConfig} uses internally - * ({@link attachFrozenApiResponse}) — never mutates `config` in place. Throws - * {@link ProjectConfigParseError} when `config` is not an object, matching - * {@link toProjectConfig}'s own strictness — a non-object `config` used to - * silently substitute `{}`, discarding whatever the caller actually passed - * instead of surfacing the misuse. - */ -export declare function attachApiResponse(config: ProjectConfig, rawAttributes: Record): ProjectConfig; -/** - * Either operand `toProjectConfig` accepts: a local {@link EffectiveConfig} - * — or a {@link CliConfigWithRawPresence} pair, the RECOMMENDED form - * whenever a `document` is available (see {@link fromConfigDocument}'s own - * docstring) — to project down to the hosted subset, or a raw, - * not-yet-decoded Management API v2 project-config response (in any of the - * three envelope shapes {@link fromApiProjectConfig} accepts) to map. - */ -export type ToProjectConfigSource = { - readonly cliConfig: EffectiveConfig | CliConfigWithRawPresence; -} | { - readonly apiResponse: unknown; -}; -/** - * Thin dispatcher over the two normalizers above: routes to - * {@link fromApiProjectConfig} when `source` carries an own `apiResponse` - * property, otherwise to {@link fromConfigDocument} when it carries an own - * `cliConfig` property. A full `CliConfig` fits the `cliConfig` arm - * directly, since `CliConfig` is assignable to {@link EffectiveConfig}. - * Throws {@link ProjectConfigParseError} when `source` carries neither own - * key or both — `{}` and `{ cliConfig: x, apiResponse: y }` are equally - * meaningless dispatch requests, and failing loudly here beats a raw - * `TypeError` from reaching into a property that isn't there. - */ -export declare function toProjectConfig(source: ToProjectConfigSource): ProjectConfig; -/** - * The subtree of `config._apiResponse` that {@link projectConfigMappingRows} - * does not map — `{}` when `config` carries no `_apiResponse` at all - * (file-sourced config, or a `ProjectConfig` that was never built from an API - * response), which per ADR 0019 rule 1 does NOT mean "fully mapped". - * Registry-derived, not a second hand-maintained field list (ADR 0019 rule - * 5): a path is "mapped" when some row's `apiPath` or `alsoConsumes` names it - * exactly, including every `isSecret` row (deliberately omitted, but known) - * and every `unmappedSecretApiPaths` entry (deliberately omitted despite - * having no row at all). Empty objects are pruned from the result, so a - * subtree that is entirely mapped never shows up as `{}` noise. - * - * Reports at REGISTRY `apiPath` granularity, not full recursive fidelity: a - * key nested INSIDE a consumed subtree — including inside an element of a - * consumed array, e.g. an unexpected `comment` field on a - * `database.network_restrictions.allowed_cidrs` entry — is not itemized here - * either, since the whole subtree at that `apiPath` is already "known" to - * this registry version (`consumedApiPathKeys`'s own docstring). This is - * never lossy for the CALLER, only for this report: `_apiResponse` still - * carries every such key verbatim, so a consumer that needs full recursive - * fidelity reads it directly instead of relying on this helper. - * - * The result can include the HMAC digest the API reports for a secret-typed - * key neither a row nor `unmappedSecretApiPaths` knows about yet — a future - * GoTrue secret, say, added on the platform side before this package's - * `isSecret` rows catch up. Callers must not render this result blindly — an - * HMAC digest is not a value a user should see echoed back at them. Throws - * {@link ProjectConfigParseError} if `_apiResponse` is nested more than 64 - * levels deep, or if `config` is not a plain object (`reason: - * "caller_misuse"`). - */ -export declare function unmappedApiFields(config: ProjectConfig): { - readonly [key: string]: ReadonlyJsonValue; -}; -/** - * The deduped `configPath`s of every non-`isSecret` row in - * {@link projectConfigMappingRows}, in registry order — the fields - * `fromApiProjectConfig` can actually speak for. Exists so a diff consumer - * (CLI-2156/Studio) never hand-maintains an equivalent field list: as rows - * are added, removed, or renamed, this set moves with them automatically. - * Excludes secret rows (an API-sourced value for one is never populated, so - * it can never meaningfully participate in a comparison) and every field - * with no row at all (`realtime` in full, `workers`/`experimental`, and - * every "Deliberately unmapped" field the sibling registries document). - * - * This ONLY remedies the whole-SECTION-granularity gap (e.g. `realtime` in - * full never showing up as phantom drift just because it has zero rows). It - * does NOT remedy the finer, per-path granularity gap this file's own - * {@link ProjectConfig} docstring describes: `["auth", "email", "smtp", - * "enabled"]` IS a member of this list (`isComparableProjectConfigPath` - * returns `true` for it) and yet still fabricates drift against a document - * operand that never declared `[auth.email.smtp]` at all, because - * `subtractCliConfig`'s baseline has no `smtp` key to compare against and - * therefore keeps the API side's value verbatim (pinned by - * `project-config.unit.test.ts`'s "does NOT rescue a diff against a document - * operand that never declared the sub-section at all" test). A caller doing - * that comparison must additionally intersect with what the document-side - * operand actually declared — or accept that every field a row maps - * unconditionally will read as a remote-only statement whenever the document - * side is silent on it, never as neutral "no opinion". - */ -export declare const comparableProjectConfigPaths: ReadonlyArray>; -/** - * Whether `path` is a member of {@link comparableProjectConfigPaths} — or a - * DESCENDANT of one: a row that maps a container (e.g. `sms.test_otp`'s - * record) yields diff leaves like `["auth","sms","test_otp",""]` from - * a leaf-path traversal, and those entries are exactly as comparable as the - * mapped container itself. A bare PREFIX of a mapped path (e.g. - * `["auth","sms"]`) is still not comparable — it names a section, not a - * mapped value. - */ -export declare function isComparableProjectConfigPath(path: ReadonlyArray): boolean; diff --git a/packages/config/api-report/project-config/project-schema.d.ts b/packages/config/api-report/project-config/project-schema.d.ts deleted file mode 100644 index 2a51a62fca..0000000000 --- a/packages/config/api-report/project-config/project-schema.d.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Runtime companion to {@link ProjectConfig} (`./project-config.ts`) — a - * schema that VALIDATES the same sparse hosted-section overlay - * `ProjectConfig` only describes at compile time. Derived from - * {@link CliConfigSchema} (`../base.ts`), never hand-declared, so the two can - * never independently drift: every leaf type, annotation, and leaf-level - * check traces back to the exact schema `base.ts` decodes a config document - * with. - * - * Derivation, in order: - * - * 1. {@link hostedSectionsStruct} picks the seven {@link HOSTED_SECTION_KEYS} - * fields off `CliConfigSchema.fields` and rebuilds a fresh `Schema.Struct` - * from them — the same field schemas `CliConfigSchema` itself embeds, not - * copies. - * 2. `SchemaAST.toType` strips every encoding/transformation (decoding - * defaults, `env()` deferred substitution, …), leaving the DECODED shape — - * exactly what `ProjectConfig` describes; a `ProjectConfig` value is never - * re-encoded. - * 3. {@link toDeepOptionalHostedAst} then recursively rebuilds the result: - * - In every `Objects` node (struct OR record), drops any - * `PropertySignature`/`IndexSignature` whose value AST carries the - * `x-secret` annotation (ADR 0019 rule 5 — `fromConfigDocument`/ - * `fromApiProjectConfig` never populate a secret leaf either), the same - * detection `../lib/secret-paths.ts`'s own walk uses. A container whose - * value type consists ENTIRELY of secret leaves (`db.vault`, a - * `Record`) ends up an empty `Objects` node this way - * (no surviving properties or index signatures) — `SchemaAST`'s own - * documented behavior for that shape is "accepts any value except - * `null`/`undefined`", the closest a schema can get to "this container - * held nothing but secrets, so nothing concrete is left to validate - * here" without special-casing an empty-object type JSON Schema has no - * way to express either. Two OTHER hosted-section leaves land on that - * same empty-`Objects` shape for an unrelated reason: - * `storage.analytics.buckets.*` and `storage.vector.buckets.*` are - * already `Schema.Struct({})` at the SOURCE level (`../storage.ts`) — - * genuinely empty structs, untouched by this walk's secret-stripping. - * - Wraps every SURVIVING property in `optionalKey` (via - * {@link toOptionalAst}), recursing into its type — mirroring - * `DeepPartial`'s `{ readonly [K in keyof T]?: DeepPartial }` - * mapped type (`../sparse.ts`) at every object level reached, and - * recursing the same way into index-signature VALUE types (matching - * `DeepPartial`'s recursion into a `Record`'s value type — - * `Record` deep-partializes to `Record>`, not `X` verbatim). - * - Leaves an `Arrays` node completely untouched, INCLUDING its element - * types: `DeepPartial` special-cases arrays to pass `T` through - * verbatim rather than partializing element types (`../sparse.ts`), and - * no `x-secret` leaf sits inside an array anywhere in this schema - * (`../lib/secret-paths.ts`'s own docstring), so there is nothing this - * walk would otherwise need to change there anyway. - * - Strips every `checks` array attached DIRECTLY to an `Objects` node — - * the cross-field business-rule refinements this repo attaches with - * `.check()` on a whole struct (`requiredWhenEnabled` in - * `../auth/email.ts`/`../auth/providers.ts`, `validateSmsProviderSwitch` - * in `../auth/sms.ts`) encode invariants a deliberately sparse overlay - * cannot generally satisfy — e.g. `{ auth: { email: { smtp: { enabled: - * true } } } }` with no `host` yet is a legal, if incomplete, - * `ProjectConfig` fragment, but `requiredWhenEnabled("host", ...)` would - * reject it. Every LEAF-level check survives untouched, since it lives - * on a non-`Objects` node — today that's only `workers.*.instances`'s - * `Schema.Number.check(isInt(), isGreaterThanOrEqualTo(0))` and the - * `[workers]` record's own key pattern (`Schema.isPattern(...)` on - * `workerName`, `../workers.ts`). There is no port-range (or other - * numeric-bound) leaf check anywhere in this schema today. - * - Recurses into `Union` members (e.g. `storage.file_size_limit`'s - * `Schema.Union([String, Number])`, and every `Schema.Literals`-backed - * enum, which V4 also compiles to a `Union`), so a secret-bearing or - * object-shaped member nested inside one would still be reached. Every - * other node kind (every leaf: `String`, `Number`, `Boolean`, - * `Literal`, …) is returned unchanged — there is nothing further to - * drop or partialize on a leaf. This module's own AST node kinds are - * enumerated explicitly, via each class's PUBLIC constructor, rather - * than through a generic `.recur()`-style mechanism: unlike - * `.repos/effect`'s vendored source, the installed `effect` release's - * own `AST#recur` is `@internal` (absent from its published `.d.ts`), - * so a truly generic fallback isn't available through the public API - * surface this package is allowed to depend on. - * `./project-schema.unit.test.ts`'s AST-walk exhaustiveness guard walks - * the derived AST and fails loudly if a node kind outside this - * enumerated set (or a reintroduced `Suspend`, deliberately unhandled - * here — see {@link toDeepOptionalHostedAst}) ever appears, rather than - * silently mishandling it. - * - * `_apiResponse` (ADR 0019) is deliberately NOT part of this schema: it's - * attached as a non-enumerable property that ordinary decode/validation can - * never see, so there is nothing here for a schema to describe. - * - * Never `additionalProperties: false` ({@link toProjectConfigJsonSchema} - * passes `{ additionalProperties: true }` to `Schema.toJsonSchemaDocument`, - * and `ProjectConfigSchema` itself is never decoded with - * `onExcessProperty: "error"`): a `ProjectConfig` value can carry extra own - * keys a given schema VERSION doesn't yet model (a registry-mapped field a - * future release adds), and JSON Schema's own default is permissive — this - * derivation matches that norm rather than rejecting anything unrecognized. - */ -import type { StandardSchemaV1 } from "@standard-schema/spec"; -import { Schema } from "effect"; -import type { ProjectConfig } from "./project-config.ts"; -/** - * The runtime shape {@link projectConfigAst} validates: {@link ProjectConfig} - * minus `_apiResponse`, which — being non-enumerable and never serialized — - * has no runtime representation for a schema to check. `Schema.make` performs - * no structural verification against this annotation (the same trust-the- - * caller contract as effect's own `Json: Codec = make(SchemaAST.Json)` - * precedent); the type-level pin in `./project-schema.unit.test.ts` cross- - * checks this exact type expression against `ProjectConfig` independently, so - * a future edit to either side that silently drifts fails to compile there. - */ -type ProjectConfigSchemaType = Omit; -/** - * Runtime validation for {@link ProjectConfig} — both an Effect-native schema - * (decode/encode, `.ast`, …) and a spec-compliant Standard Schema - * (`~standard`), since {@link Schema.toStandardSchemaV1} augments and returns - * the SAME object rather than wrapping it in a second value. - * - * Annotated explicitly (rather than left inferred) because the inferred type - * names `StandardSchemaV1` from `@standard-schema/spec` — a package reachable - * only transitively through `effect` under pnpm's strict `node_modules` - * isolation — which tsc's declaration emit refuses to synthesize into - * `project-schema.d.ts` as non-portable. Explicitly importing the type here - * pins `@standard-schema/spec` as a direct dependency instead. Conscious - * exception to this repo's `as`-cast policy's spirit: `Schema.make`'s type - * parameter here is asserted, not verified, against `projectConfigAst` — see - * {@link ProjectConfigSchemaType}'s doc comment for the independent - * compile-time cross-check that catches drift instead. - */ -export declare const ProjectConfigSchema: StandardSchemaV1 & Schema.Codec; -/** JSON Schema (draft 2020-12) rendering of {@link ProjectConfigSchema}, mirroring `../base.ts`'s `toCliConfigJsonSchema`. */ -export declare function toProjectConfigJsonSchema(): { - $schema: string; - $defs?: import("effect/JsonSchema").Definitions | undefined; -}; -export {}; diff --git a/packages/config/api-report/project-config/registry-auth.d.ts b/packages/config/api-report/project-config/registry-auth.d.ts deleted file mode 100644 index 6b46edc970..0000000000 --- a/packages/config/api-report/project-config/registry-auth.d.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { type ProjectConfigMappingRow } from "./registry-row.ts"; -export declare const AUTH_HOOK_NAMES: readonly ["mfa_verification_attempt", "password_verification_attempt", "custom_access_token", "send_sms", "send_email", "before_user_created"]; -export declare const authMappingRows: ReadonlyArray; -/** - * API-side GoTrue keys shaped like a secret (suffix `_secret`, `_secrets`, - * `_auth_token`, `_api_secret`, `_access_key`, or `_api_key`) that have no - * registry row at all, verified exhaustively against the generated - * Management API v1 auth-config contract - * (`packages/api/src/generated/contracts.ts`'s `V1GetAuthServiceConfigOutput` - * — the authority for this registry's key set, not the legacy hand-mined - * `auth.sync.ts` interface, which is missing `external_slack` and - * `nimbus_oauth` entirely) (CLI-2230's `unmappedApiFields` secret-leak - * finding). Every OTHER secret-shaped GoTrue key already has an `isSecret` - * row above and is therefore already excluded from `unmappedApiFields` on - * its own merit; this list exists only for the ones that don't, so an HMAC - * digest can't leak into that report just because this registry hasn't grown - * a row for the field yet. `walkUnmapped` (`./project-config.ts`) treats - * every path here as consumed, same as a row's `apiPath`/`alsoConsumes`. - * - * `sms_vonage_api_key` is deliberately excluded despite the `_api_key` - * suffix: it is NOT `x-secret` on the config side (`../auth/sms.ts:286-292` - * has no `secret()` wrapper on it — `smsCredentialRows`'s comment) and - * already has an ordinary `stringRow`. - * - * Three orphans found, none with a config-schema counterpart at all: - * - `external_figma_secret`: `figma` is a GoTrue provider with no - * config-schema counterpart at all (`externalProviderRows`'s comment - * above), so it never gets a row of its own, secret or otherwise. - * - `external_slack_secret`: distinct from the mapped `slack_oidc` provider - * (`EXTERNAL_PROVIDERS`) — plain `slack` has no config-schema counterpart - * either. - * - `hook_after_user_created_secrets`: distinct from the mapped - * `before_user_created` hook (`AUTH_HOOK_NAMES`) — there is no - * `hook.after_user_created` config-schema section to target. - * - `nimbus_oauth_client_secret`: there is no `nimbus`-named external - * provider in the config schema at all. - * - * Guarded against regrowing a fourth orphan by - * `apps/cli/src/shared/config/project-config-auth-contract.unit.test.ts`, - * which walks the same generated contract's full key set, not just this - * hand-maintained list. - */ -export declare const unmappedSecretApiPaths: ReadonlyArray>; diff --git a/packages/config/api-report/project-config/registry-row.d.ts b/packages/config/api-report/project-config/registry-row.d.ts deleted file mode 100644 index d31e498da3..0000000000 --- a/packages/config/api-report/project-config/registry-row.d.ts +++ /dev/null @@ -1,146 +0,0 @@ -/** - * Registry-driven mapping between the Management API v2 project-config - * resource (`data.attributes`) and the hosted subset of `CliConfig` — one - * table of rows so the pull-direction normalizer (`fromApiProjectConfig`) and - * the future push-direction `*ToUpdateBody` mappers derive from a single - * source of truth (CLI-2230). Rows are data, not behavior: the assembly - * engine lives in `project-config.ts`, and `unmappedApiFields` derives its - * mapped-path set from these same rows. - * - * Null convention: the legacy push-direction apply (`config-sync/*.sync.ts`) - * merges remote values into a local document, so it maps API `null` to a - * zero value (`valOrDefault`). This registry produces a *standalone sparse* - * config instead, where "no value" must stay absent: the engine skips a row - * whose API value is `undefined` (key not reported) — unless the row declares - * `alsoConsumes` and a consumed sibling IS present, in which case the - * transform runs with `undefined` so it can still validate the sibling — and - * skips `null` unless the row declares a `transform`; a transform receives - * `null` and decides (e.g. `smtp_host: null` still means "SMTP disabled"). - */ -export interface ProjectConfigMappingRow { - /** Path segments into the hosted subset of `CliConfig`, e.g. `["api", "max_rows"]`. */ - readonly configPath: ReadonlyArray; - /** - * Path segments under v2 `data.attributes`, e.g. `["api", "db_schema"]` or - * `["auth", "site_url"]`. Several rows may share one `apiPath` when a - * single API field feeds multiple config fields (e.g. `api.db_schema` - * drives both `api.schemas` and the derived `api.enabled`). - */ - readonly apiPath: ReadonlyArray; - /** - * Maps the API-reported value to the config-side value; identity when - * absent. Receives the full decoded attributes object as a second argument - * for the rare row that combines sibling fields (declare those siblings in - * {@link alsoConsumes}). Returning `undefined` omits the field from the - * mapped output (e.g. an API enum member the config schema cannot - * represent). Narrowing failures throw `ProjectConfigParseError` via the - * `expect*` helpers. - */ - readonly transform?: (value: unknown, attributes: Record) => unknown; - /** - * Additional `data.attributes` paths this row's `transform` reads beyond - * `apiPath` (e.g. Apple/Google `external_*_additional_client_ids`, folded - * into `client_id`). Listed so `unmappedApiFields` counts them as mapped. - */ - readonly alsoConsumes?: ReadonlyArray>; - /** - * Canonicalizes a DOCUMENT-sourced value at `configPath` so a value pulled - * from the API and the same logical value spelled locally converge on one - * representation (CLI-2230's duration/byte-size finding) — e.g. a document - * duration of `"24h"` and an API-derived `"24h0m0s"` denote the same - * duration but compare unequal textually unless one side is normalized. - * Applied by `fromConfigDocument` only, at `configPath`, after the - * secret-omitting copy; never applied by `fromApiProjectConfig` (its output - * is already canonical). Must return the canonical value, the input - * verbatim when it cannot be parsed (a document value has already passed - * schema validation, so this must never throw), or `undefined` to REMOVE - * the field — unmanaged absence, for a value the push wrapper would omit - * entirely (e.g. an empty `test_otp` map); the engine prunes containers - * the removal empties. - */ - readonly normalizeDocument?: (value: unknown) => unknown; - /** - * Push-direction inverse (config value → API body value). Unused by - * `toProjectConfig` — carried so a future push mapper can derive from this - * registry instead of a second hand-maintained table. Absence does NOT mean - * identity: several rows have no faithful config→API inverse yet (every - * duration row, since `"1m0s"` must push as `60`; `BytesSize` strings; - * `email.smtp.port`'s number→string; `sms.test_otp`'s record→env string; - * the SMS provider selection rows). Absence means "not derived for this row - * yet" — zero rows currently define one; a push mapper must treat a missing - * `inverse` as unsupported for that row, never fall back to identity. Push - * derivation lands with the push-mapper work (CLI-2230 follow-up). - */ - readonly inverse?: (value: unknown) => unknown; - /** - * `x-secret` field: the API reports an HMAC digest of the value, never the - * plaintext, so the mapping omits the value entirely and pull flows must - * source it from the local document (ADR 0019, rule 5). The path still - * counts as mapped for `unmappedApiFields`. - */ - readonly isSecret?: boolean; - /** - * Unit/semantics note, e.g. `"csv → string[]"` or `"seconds → duration - * string"`. Documentation-only — never read at runtime. - */ - readonly unit?: string; -} -/** - * Narrowing helpers for `transform` implementations. The non-auth attribute - * sections are schema-typed before rows run, so these mostly guard the `auth` - * record (typed `Record` by the API) and document each row's - * expectation at its use site. - */ -export declare function expectString(value: unknown, apiPath: ReadonlyArray): string; -/** - * Narrows to a finite integer. The generated API contract types these fields - * `isInt`; the lenient mirror deliberately drops that check so API-ahead skew - * never fails the decode (ADR 0019 rule 2), so the rows for integer-typed - * config fields re-assert it here — a fractional value on an integer field is - * a malformed platform response, not tolerable skew. Only the session-hour - * durations stay on {@link expectNumber}: the contract types them as plain - * numbers and fractional hours are meaningful (the renderer rounds); every - * other numeric field — including the `*_max_frequency` seconds — is - * `isInt()` in the contract and narrows here. - */ -export declare function expectInteger(value: unknown, apiPath: ReadonlyArray): number; -/** - * Narrows to a finite number within `[min, max]` — for fields whose - * downstream formatter is only defined on a bounded range. The session-hour - * durations are the motivating case: the generated contract only requires - * them finite, but a huge-but-finite hours value overflows the nanosecond - * conversion into `"InfinityhNaNmNaNs"`, and a merely-large one stringifies - * in exponent notation (`"1e+22h0m0s"`) that no duration parser reads. - */ -export declare function expectNumberBetween(value: unknown, apiPath: ReadonlyArray, min: number, max: number): number; -export declare function expectBoolean(value: unknown, apiPath: ReadonlyArray): boolean; -/** - * Clamps a signed API integer to the unsigned domain the config schema - * expects. Replicates the legacy shell's `intToUint` - * (`apps/cli/src/legacy/shared/legacy-size-units.ts`), applied by the sync - * mappers to every uint-typed field pulled from the API. - */ -export declare function clampToUint(value: number): number; -/** - * Splits an API comma-separated list field into the string array the config - * schema holds. Replicates the legacy shell's `legacyStrToArr` + per-element - * trim as applied in `config-sync/api.sync.ts:92-93` (`db_schema`, - * `db_extra_search_path`). The `auth.sync.ts:1265` `uri_allow_list` site uses - * `legacyStrToArr` without the trim; trimming there too is a deliberate, - * benign normalization — push-direction bodies are built with `join(",")`, so - * round-tripped data never carries the spaces the trim would remove. - */ -export declare function splitCommaSeparated(value: string): ReadonlyArray; -/** - * DOCUMENT-side canonicalization for the three CSV-backed array rows - * (`api.schemas`, `api.extra_search_path`, `auth.additional_redirect_urls`): - * the push mapper joins the array with `","` (auth.sync.ts:2294, - * api.sync.ts:138,140) and the pull direction re-splits with - * {@link splitCommaSeparated}, so an element containing a literal comma (or - * padded with whitespace) round-trips into a DIFFERENT array — replaying - * join-then-split makes the document projection converge on the value that - * actually exists hosted after a push, same as the whole-second duration - * flooring. Non-array/non-string-element values stay verbatim (a document - * value has already passed schema validation; never throw here). - */ -export declare function canonicalizeCommaJoinedArray(value: unknown): unknown; diff --git a/packages/config/api-report/project-config/registry.d.ts b/packages/config/api-report/project-config/registry.d.ts deleted file mode 100644 index 0421bf47be..0000000000 --- a/packages/config/api-report/project-config/registry.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { type ProjectConfigMappingRow } from "./registry-row.ts"; -/** - * The full API↔`CliConfig` mapping table: this file's non-auth rows plus - * `./registry-auth.ts`'s auth rows. `fromApiProjectConfig`/ - * `unmappedApiFields` (`./project-config.ts`) are the only consumers. - */ -export declare const projectConfigMappingRows: ReadonlyArray; diff --git a/packages/config/api-report/project.d.ts b/packages/config/api-report/project.d.ts deleted file mode 100644 index c025d1d72f..0000000000 --- a/packages/config/api-report/project.d.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Effect, FileSystem } from "effect"; -import { CliProjectEnvParseError } from "./errors.ts"; -import { type ResolvedCliConfigValue } from "./lib/resolve.ts"; -import { type CliProjectPaths } from "./paths.ts"; -export interface CliProjectEnvironment { - readonly paths: CliProjectPaths; - readonly values: Readonly>; - readonly loadedPaths: ReadonlyArray; - readonly sources: Readonly>; -} -/** Parse one explicit dotenv file without applying ambient or project-local precedence. */ -export declare const loadDotEnvFile: (path: string) => Effect.Effect, CliProjectEnvParseError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem>; -export interface LoadCliProjectEnvironmentOptions { - readonly cwd: string; - readonly baseEnv?: Readonly>; - /** See {@link FindCliProjectPathsOptions.search}. */ - readonly search?: boolean; - /** - * Skip reading/parsing `paths.envLocalPath` (`supabase/.env.local`) - * entirely. Mirrors Go's `loadDefaultEnv` (`apps/cli-go/pkg/config/ - * config.go:1243-1250`), which omits `.env.local` from its candidate - * filename list whenever `SUPABASE_ENV=test` — so a malformed or - * intentionally non-test `.env.local` is invisible to Go in that mode and - * must not fail config loading here either. Defaults to `false` so - * existing callers that don't have a `SUPABASE_ENV` gate of their own - * (`next/`, `secrets set`) are unaffected. - */ - readonly skipEnvLocal?: boolean; -} -/** - * Not covered by semver — exported from `@supabase/config/internal` only. See - * that module's header for why. - */ -export interface InternalResolveCliConfigOptions { - /** - * Opt into Go/viper-parity `env()` matching (case-agnostic - * `^env\((.*)\)$`). Defaults to `false`, which uses the pre-PR-#5765 strict - * SCREAMING_SNAKE_CASE matcher (`ENV_CAPTURE_REGEX_STRICT`). Only the - * Go-parity legacy shell sets this to `true`. - */ - readonly goViperCompat?: boolean; -} -export declare const loadCliProjectEnvironment: (options: LoadCliProjectEnvironmentOptions) => Effect.Effect<{ - paths: { - projectRoot: string; - supabaseDir: string; - configPath: string; - envPath: string; - envLocalPath: string; - }; - values: Record; - loadedPaths: string[]; - sources: Record; -} | null, CliProjectEnvParseError | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | import("effect/Path").Path>; -/** - * Effect-typed counterpart of `./lib/resolve.ts`'s plain sync - * `resolveCliConfigValue`, additionally accepting the internal-only - * `goViperCompat` option (see {@link InternalResolveCliConfigOptions}). - * `../effect.ts` re-exports this explicitly, which wins over the sync - * version's star re-export through `./index.ts` (see that module's doc - * comment on the deliberate shadowing) — `@supabase/config/internal` - * re-exports this same function typed to show `goViperCompat`. - * - * `cliProjectEnv` only needs `.values` (`Pick`) — - * a caller that already has a project's env values but not the full - * `CliProjectEnvironment` shape (e.g. `paths`/`loadedPaths`/`sources`) can pass - * `{ values }` directly instead of threading through the whole loaded object. - */ -export declare function resolveCliConfigValue(value: T, cliProjectEnv: Pick, configPath: string, options?: InternalResolveCliConfigOptions): Effect.Effect>; -/** See {@link resolveCliConfigValue}'s doc comment for why `cliProjectEnv` only needs `.values`. */ -export declare function resolveCliConfigSubtree(value: T, cliProjectEnv: Pick, pathPrefix: string, options?: InternalResolveCliConfigOptions): Effect.Effect>; diff --git a/packages/config/api-report/promise-facade.d.ts b/packages/config/api-report/promise-facade.d.ts deleted file mode 100644 index 64611cc7fb..0000000000 --- a/packages/config/api-report/promise-facade.d.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { FileSystem, Path } from "effect"; -import { Layer } from "effect"; -import type { LoadedCliConfig, LoadCliConfigOptions, SaveCliConfigOptions } from "./config-document.ts"; -import type { FunctionsManifest } from "./functions-manifest-model.ts"; -import type { CliProjectPaths } from "./paths.ts"; -import type { LoadCliProjectEnvironmentOptions, CliProjectEnvironment } from "./project.ts"; -/** - * Names deliberately mirror `@supabase/config/effect` one-to-one — the - * subpath itself (`/io` vs `/effect`) conveys Promise-vs-Effect, not the - * member names. - * - * A rejection from `loadCliConfig`, `loadCliConfigFile`, or `saveCliConfig` - * can carry any of five typed failures — this package's own - * `CliConfigParseError`, `DuplicateRemoteProjectIdError`, - * `InvalidRemoteProjectIdError`, `CliProjectEnvParseError`, or `PlatformError` - * (from `effect/PlatformError`) for a host/OS failure — distinguish via - * `instanceof`. One exception: `saveCliConfig`'s atomic-write step maps a - * rename failure to a defect rather than one of these typed failures (see - * `io.ts`'s `writeFileAtomic`) — the returned promise still rejects, but with - * the raw, un-mapped failure, not an instance of any class listed above. This - * is a deliberate design choice (a rename failure after a successful write - * indicates something is wrong with the filesystem itself, not a recoverable - * config condition), not an oversight. - */ -export interface CliConfigIo { - readonly loadCliConfig: (cwd: string, options?: LoadCliConfigOptions) => Promise; - readonly findCliProjectRoot: (cwd: string) => Promise; - readonly findCliProjectPaths: (cwd: string) => Promise; - readonly loadCliConfigFile: (path: string) => Promise; - readonly loadCliProjectEnvironment: (options: LoadCliProjectEnvironmentOptions) => Promise; - readonly saveCliConfig: (options: SaveCliConfigOptions) => Promise; - readonly inferFunctionsManifest: (cwd: string) => Promise; -} -/** - * Builds the Promise-based `@supabase/config/io` facade over a given platform - * layer. `Layer`'s `ROut` is declared contravariant (`in ROut`), so a - * platform layer providing a superset of `FileSystem | Path` (e.g. - * `BunServices.layer` / `NodeServices.layer`) is assignable here. - */ -export declare function makeCliConfigIo(platformLayer: Layer.Layer): CliConfigIo; diff --git a/packages/config/api-report/realtime.d.ts b/packages/config/api-report/realtime.d.ts deleted file mode 100644 index acdcd82b30..0000000000 --- a/packages/config/api-report/realtime.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Schema } from "effect"; -export declare const realtime: Schema.withDecodingDefaultKey; - readonly ip_version: Schema.withDecodingDefaultKey, never>; - readonly max_header_length: Schema.withDecodingDefaultKey; -}>, never>; diff --git a/packages/config/api-report/schema-metadata.d.ts b/packages/config/api-report/schema-metadata.d.ts deleted file mode 100644 index ef19b8fb0e..0000000000 --- a/packages/config/api-report/schema-metadata.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export declare const CLI_CONFIG_SCHEMA_URL = "https://supabase.com/docs/cli/config.schema.json"; -/** Sibling of {@link CLI_CONFIG_SCHEMA_URL} for `ProjectConfigSchema`'s generated JSON Schema document (`toProjectConfigJsonSchema`) — same `/docs/cli/` path, same `apps/cli/scripts/generate-docs.ts` pathname-derivation convention. */ -export declare const PROJECT_CONFIG_SCHEMA_URL = "https://supabase.com/docs/cli/project-config.schema.json"; diff --git a/packages/config/api-report/sparse.d.ts b/packages/config/api-report/sparse.d.ts deleted file mode 100644 index 22cddd26db..0000000000 --- a/packages/config/api-report/sparse.d.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { type CliConfig } from "./base.ts"; -/** - * Sparse config subtraction — see `docs/adr/0018-sparse-config-subtraction.md`. - * - * A sparse config is a partial overlay containing only the values that differ - * from some baseline. In the primary case — subtracting the default config - * ({@link omitDefaultValues}) — the result is itself a valid config document: - * re-decoding refills exactly what was removed, so it denotes the same - * effective config under the current schema's defaults. Subtracting any other - * baseline (e.g. a remote block against the merged base config) yields an - * overlay meaningful only relative to that baseline. Arrays are compared - * wholesale (order-sensitive) and never subtracted element-wise, so a sparse - * value is always either an entire array or an object subtree of kept leaves — - * hence arrays survive `DeepPartial` unchanged below. - */ -export type DeepPartial = T extends ReadonlyArray ? T : T extends object ? { - readonly [K in keyof T]?: DeepPartial; -} : T; -export type SparseCliConfig = DeepPartial; -/** - * The family-neutral operand shape of the comparison core: a deeply partial - * root scope of {@link CliConfig}, without the nested `remotes` record. Every - * key an operand carries must hold its fully-resolved *effective* value; an - * absent key means the operand doesn't speak for that field — never that the - * field is at its default. Both config families fit: on the local side a full - * {@link CliConfig} document or a branch's merged effective config, and on - * the hosted side the sparse `ProjectConfig` subset produced by - * `toProjectConfig` — a Management API response never mentions local-only - * sections, so its operands are inherently partial. Keeping `remotes` out of - * the contract means neither operand has to fabricate one to type-check. - * (Replaces the former fully-materialized `BaseCliConfig` operand; see ADR - * 0018's addendum for the CLI-2230 ruling.) - */ -export type EffectiveConfig = DeepPartial>; -/** - * The default config: a {@link CliConfig} in which every value carries its - * schema-declared default. Derived by decoding `{}` through - * {@link CliConfigSchema} — the schema's `default` annotations and decoding - * defaults are the single source of truth, so there is no hand-maintained - * defaults table to drift. Fields declared `optionalKey` without a default - * (e.g. `project_id`, `api.external_url`) are absent. - * - * Memoized (and the memo shared with callers) rather than computed at module - * load, so importing the package doesn't pay for a full schema decode. The - * memo is deeply frozen before it is shared: it doubles as the module-wide - * subtraction baseline, so a caller mutation would silently corrupt every - * later {@link omitDefaultValues} result. - */ -export declare function getDefaultCliConfig(): CliConfig; -/** - * Recursively freezes `value` and returns it. Exported (not re-exported from - * `./index.ts` — this stays an internal cross-module helper, per CLI-2230's - * `_apiResponse` clone-and-freeze finding) so `./project-config/ - * project-config.ts` can freeze the cloned raw attributes it attaches, using - * the same freezing behavior {@link getDefaultCliConfig}'s memo relies on. - * - * Guarded against revisiting an already-frozen (or otherwise already-seen) - * object with a `WeakSet`, as defense in depth: {@link getDefaultCliConfig}'s - * memo is a decoded schema default, genuinely acyclic by construction, but - * `./project-config/project-config.ts`'s caller is a cloned Management API - * response — untrusted input — and that caller now bounds depth and cycles - * itself before ever calling this (`assertRawAttributesDepthWithinBound`). - * This guard exists so `deepFreeze` stays safe to call directly against - * arbitrary input even if that upstream bound is ever bypassed or forgotten, - * not because this function's own callers currently need it. - */ -export declare function deepFreeze(value: T): T; -/** - * Defines `key` as an own data property. Record keys come from user config - * files, and both smol-toml and `JSON.parse` produce an own `__proto__` key - * (a valid function name or remote label) that a plain `target[key] = value` - * assignment would feed to the legacy prototype setter, silently dropping the - * entry — or, for object values, swapping the target's prototype. - */ -export declare function setOwnProperty(target: Record, key: string, value: unknown): void; -/** - * The untyped subtraction walk: returns `value − baseline`, or `undefined` - * when nothing survives. Values strictly deep-equal (order-sensitive) to the - * baseline's are removed; objects recurse per key and are dropped once empty; - * arrays are removed wholesale on equality, never subtracted element-wise. A - * key with no counterpart in the baseline is kept verbatim — which is exactly - * how `remotes` and other record entries pass through untouched when the - * baseline is the default config. Symmetrically, a baseline-only key is - * ignored by design: subtraction reports what `value` declares, and in - * overlay semantics absence means *inherit*, so a missing key is not a - * removal. - * - * Shared with `io.ts`, which subtracts *encoded* documents before writing - * minimal config files; the typed entry points below operate on decoded - * {@link CliConfig} values, the only shape where "equals the default" is - * well-defined. - */ -export declare function subtractValue(value: unknown, baseline: unknown): unknown; -/** - * Returns the sparse config `config − baseline`. Directional: a value equal to - * the baseline's is removed even when it differs from the schema default, and - * a value differing from the baseline's is kept even when it equals the schema - * default. - * - * Operands must be *effective* wherever they speak: every key present must - * carry its fully-resolved value (a decode of a complete document, or of a - * raw-merged one — or the hosted values a Management API response reports), - * while an absent key is simply outside the comparison, per the absence rules - * above. A standalone-decoded `[remotes.*]` block is NOT a valid operand: - * decoding a sparse fragment materializes global defaults in every section it - * omitted, where the block meant to inherit from the base config, so the - * overlay would pin the branch to global defaults wherever the base overrides - * a field the block omits. To sparsify a branch's config (a `[remotes.*]` - * block declares overrides for a specific persistent Supabase branch, bound - * to it by `project_id`), subtract its merged effective config — the raw - * remote subtree merged over the raw base document *before* decoding, exactly - * as `io.ts`'s `mergeRemoteSubtree` does so remote schema defaults never leak - * in — against the base effective config, never the default config; see ADR - * 0018 for why the default-config baseline silently changes what the branch - * resolves to. - */ -export declare function subtractCliConfig(config: EffectiveConfig, baseline: EffectiveConfig): SparseCliConfig; -/** - * Returns the sparse config `config − default config`: only the values that - * differ from their schema defaults, per {@link subtractCliConfig}'s - * semantics. The result is itself a valid config document — re-decoding - * refills the removed defaults, yielding the same effective config. `remotes` - * blocks (per-persistent-branch overrides) pass through untouched (the - * default config has none), and undefaulted `optionalKey` fields always - * survive when present. - * - * The result is sparse at the root scope only: record-keyed entries - * (`functions.*`, `remotes.*`) survive whole, with every per-entry decoding - * default materialized — decoding fills them in, and the default config's - * empty records offer no per-entry baseline to subtract. This cancels out in - * a diff (both sides carry the same materialized defaults), but a consumer - * rendering the result directly must strip entry-level defaults itself. For a - * remote block that is necessarily the consumer's job — its correct baseline - * is the merged base config (ADR 0018); for function entries, `io.ts`'s - * `stripFunctionRecordDefaults` is the encoded-path precedent. - */ -export declare function omitDefaultValues(config: EffectiveConfig): SparseCliConfig; diff --git a/packages/config/api-report/storage.d.ts b/packages/config/api-report/storage.d.ts deleted file mode 100644 index a200d923be..0000000000 --- a/packages/config/api-report/storage.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Schema } from "effect"; -export declare const storage: Schema.withDecodingDefaultKey; - readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; - readonly image_transformation: Schema.optionalKey; - }>, never>>; - readonly buckets: Schema.optionalKey; - readonly file_size_limit: Schema.withDecodingDefaultKey, never, never>, never>; - readonly allowed_mime_types: Schema.withDecodingDefaultKey, never>; - readonly objects_path: Schema.withDecodingDefaultKey; - }>, never>>>; - readonly s3_protocol: Schema.withDecodingDefaultKey; - }>, never>; - readonly analytics: Schema.withDecodingDefaultKey; - readonly max_namespaces: Schema.withDecodingDefaultKey; - readonly max_tables: Schema.withDecodingDefaultKey; - readonly max_catalogs: Schema.withDecodingDefaultKey; - readonly buckets: Schema.withDecodingDefault, never>>, never>; - }>, never>; - readonly vector: Schema.withDecodingDefaultKey; - readonly max_buckets: Schema.withDecodingDefaultKey; - readonly max_indexes: Schema.withDecodingDefaultKey; - readonly buckets: Schema.withDecodingDefault, never>>, never>; - }>, never>; -}>, never>; diff --git a/packages/config/api-report/studio.d.ts b/packages/config/api-report/studio.d.ts deleted file mode 100644 index 50e04955ba..0000000000 --- a/packages/config/api-report/studio.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Schema } from "effect"; -export declare const studio: Schema.withDecodingDefaultKey; - readonly port: Schema.withDecodingDefaultKey; - readonly api_url: Schema.withDecodingDefaultKey; - readonly openai_api_key: Schema.optionalKey; -}>, never>; diff --git a/packages/config/api-report/workers.d.ts b/packages/config/api-report/workers.d.ts deleted file mode 100644 index 29e695eb95..0000000000 --- a/packages/config/api-report/workers.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Schema } from "effect"; -/** - * `[workers]` — one `[workers.]` table per worker, mirroring the - * `[functions.]` convention in the same file. - * - * Workers live at `supabase/workers//`; one whose code lives somewhere - * else entirely uses its own `source`, which is anchored to the project root and - * so can leave `supabase/`. - */ -export declare const workers: Schema.withDecodingDefault; - readonly size: Schema.optionalKey; - readonly instances: Schema.optionalKey; - readonly source: Schema.optionalKey; -}>>, never>; diff --git a/packages/config/package.json b/packages/config/package.json index f242a7319d..c4cb52673c 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -66,7 +66,6 @@ }, "scripts": { "build": "bun run ./scripts/build.ts", - "api-report:update": "bun run ./scripts/build.ts --api-report-only", "types:check": "tsc --noEmit", "test": "pnpm run test:unit", "test:unit": "pnpm exec turbo run @supabase/config#test:unit:run --", diff --git a/packages/config/scripts/build.ts b/packages/config/scripts/build.ts index 46e4bac5b9..019715e947 100644 --- a/packages/config/scripts/build.ts +++ b/packages/config/scripts/build.ts @@ -1,4 +1,4 @@ -import { mkdir, mkdtemp, realpath, rename, rm, symlink } from "node:fs/promises"; +import { mkdir, mkdtemp, realpath, rm, symlink } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { CliConfigSchema, toCliConfigJsonSchema } from "../src/base.ts"; @@ -10,7 +10,6 @@ import { CLI_CONFIG_SCHEMA_URL, PROJECT_CONFIG_SCHEMA_URL } from "../src/schema- import { collapseNonFiniteNumberUnions, withSchemaMetadata } from "./json-schema-postprocess.ts"; const packageRoot = path.resolve(import.meta.dir, ".."); -const apiReportOnly = process.argv.includes("--api-report-only"); async function runCommand(cmd: readonly string[], cwd: string = packageRoot): Promise { const child = Bun.spawn([...cmd], { cwd, stdout: "inherit", stderr: "inherit" }); @@ -199,59 +198,6 @@ async function verifyTreeShaking(): Promise { } } -/** - * Regenerates a declarations-only build (`tsconfig.api-report.json`, no - * `.d.ts.map`/`.js`) into a scratch dir INSIDE this package (so the final - * swap below is a same-filesystem, atomic `rename`) and swaps it into the - * checked-in `api-report/` (CLI-2234 enforcement layer 4). - * `src/api-report.unit.test.ts` regenerates the same way and diffs against - * this mirror, so any type-surface change becomes a reviewable `api-report/` - * diff instead of passing silently. - */ -async function syncApiReport(): Promise { - const apiReportDir = path.join(packageRoot, "api-report"); - const scratchDir = await mkdtemp(path.join(packageRoot, ".api-report-scratch-")); - - try { - await runCommand([ - "pnpm", - "exec", - "tsc", - "-p", - "tsconfig.api-report.json", - "--outDir", - scratchDir, - ]); - - const glob = new Bun.Glob("**/*.d.ts"); - let count = 0; - for await (const _relativePath of glob.scan({ cwd: scratchDir })) { - count++; - } - if (count === 0) { - throw new Error( - "the declarations-only compile produced zero .d.ts files — refusing to swap an empty tree " + - "into api-report/", - ); - } - - const staleDir = `${apiReportDir}.stale-${Date.now()}`; - const hadExistingApiReport = await Bun.file(path.join(apiReportDir, "index.d.ts")).exists(); - if (hadExistingApiReport) { - await rm(staleDir, { recursive: true, force: true }); - await rename(apiReportDir, staleDir); - } - await rename(scratchDir, apiReportDir); - if (hadExistingApiReport) { - await rm(staleDir, { recursive: true, force: true }); - } - - console.log(`[build] synced ${count} .d.ts files into api-report/ (atomic swap)`); - } finally { - await rm(scratchDir, { recursive: true, force: true }); - } -} - const SMOKE_TEST_RUNTIME_DEPS = [ "effect", "@effect/platform-node", @@ -435,52 +381,42 @@ async function verifyExportsMapTargetsExist(): Promise { console.log(`[build] verified ${targets.size} exports-map dist targets exist on disk.`); } -if (apiReportOnly) { - console.log("[build] --api-report-only: syncing api-report/ from a declarations-only compile..."); - await syncApiReport(); - console.log("[build] done."); -} else { - console.log("[build] removing stale dist/ (stale modules from renames must not ship)..."); - await rm(path.join(packageRoot, "dist"), { recursive: true, force: true }); - - console.log("[build] compiling TypeScript project (tsconfig.build.json)..."); - await runCommand(["pnpm", "exec", "tsc", "-p", "tsconfig.build.json"]); - - console.log("[build] rendering JSON Schema artifacts..."); - await renderCollapsedJsonSchema( - "./dist/schema.json", - toCliConfigJsonSchema(), - CliConfigSchema.ast, - { - id: CLI_CONFIG_SCHEMA_URL, - title: "Supabase CLI config (CliConfig)", - description: - "The Supabase CLI's local project config document (supabase/config.toml or supabase/config.json).", - }, - ); - await renderCollapsedJsonSchema( - "./dist/project-schema.json", - toProjectConfigJsonSchema(), - ProjectConfigSchema.ast, - { - id: PROJECT_CONFIG_SCHEMA_URL, - title: "Supabase hosted project config (ProjectConfig)", - description: - "The sparse, hosted-project subset of CliConfig that a Supabase project manages.", - }, - ); - - console.log("[build] verifying every exports-map dist target exists..."); - await verifyExportsMapTargetsExist(); - - console.log("[build] verifying the sideEffects:false tree-shaking claim..."); - await verifyTreeShaking(); - - console.log("[build] syncing api-report/ from a declarations-only compile..."); - await syncApiReport(); - - console.log("[build] running the pack-and-install smoke test..."); - await runPackAndInstallSmokeTest(); - - console.log("[build] done."); -} +console.log("[build] removing stale dist/ (stale modules from renames must not ship)..."); +await rm(path.join(packageRoot, "dist"), { recursive: true, force: true }); + +console.log("[build] compiling TypeScript project (tsconfig.build.json)..."); +await runCommand(["pnpm", "exec", "tsc", "-p", "tsconfig.build.json"]); + +console.log("[build] rendering JSON Schema artifacts..."); +await renderCollapsedJsonSchema( + "./dist/schema.json", + toCliConfigJsonSchema(), + CliConfigSchema.ast, + { + id: CLI_CONFIG_SCHEMA_URL, + title: "Supabase CLI config (CliConfig)", + description: + "The Supabase CLI's local project config document (supabase/config.toml or supabase/config.json).", + }, +); +await renderCollapsedJsonSchema( + "./dist/project-schema.json", + toProjectConfigJsonSchema(), + ProjectConfigSchema.ast, + { + id: PROJECT_CONFIG_SCHEMA_URL, + title: "Supabase hosted project config (ProjectConfig)", + description: "The sparse, hosted-project subset of CliConfig that a Supabase project manages.", + }, +); + +console.log("[build] verifying every exports-map dist target exists..."); +await verifyExportsMapTargetsExist(); + +console.log("[build] verifying the sideEffects:false tree-shaking claim..."); +await verifyTreeShaking(); + +console.log("[build] running the pack-and-install smoke test..."); +await runPackAndInstallSmokeTest(); + +console.log("[build] done."); diff --git a/packages/config/src/api-report.unit.test.ts b/packages/config/src/api-report.unit.test.ts deleted file mode 100644 index ac8b16f294..0000000000 --- a/packages/config/src/api-report.unit.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { describe, expect, test } from "vitest"; -import { mkdtemp, readFile, rm } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; - -// CLI-2234 enforcement layer 4: `packages/config/api-report/` is a checked-in -// mirror of this package's compiled `.d.ts` surface (synced by -// `scripts/build.ts`'s `syncApiReport`, via `tsconfig.api-report.json`). This -// regenerates that same declarations-only build into a temp dir with the -// exact same config and diffs it against the checked-in mirror, so a -// type-signature change anywhere in `src/` shows up as a reviewable -// `api-report/` diff instead of passing silently. - -const srcDir = dirname(fileURLToPath(import.meta.url)); -const packageRoot = join(srcDir, ".."); -const apiReportDir = join(packageRoot, "api-report"); - -const FAILURE_MESSAGE = - "type surface changed — run `pnpm --filter @supabase/config build` and review+commit the api-report/ diff"; - -async function listDeclarationFiles(root: string): Promise { - const glob = new Bun.Glob("**/*.d.ts"); - const relativePaths: string[] = []; - for await (const relativePath of glob.scan({ cwd: root })) { - relativePaths.push(relativePath); - } - return relativePaths.sort(); -} - -/** The first line at which `fresh`/`checkedIn` diverge, `undefined` when identical, for a precise mismatch message. */ -function firstDifferingLine(fresh: string, checkedIn: string): string | undefined { - const freshLines = fresh.split("\n"); - const checkedInLines = checkedIn.split("\n"); - const length = Math.max(freshLines.length, checkedInLines.length); - for (let index = 0; index < length; index++) { - if (freshLines[index] !== checkedInLines[index]) { - return ( - `line ${index + 1}: fresh=${JSON.stringify(freshLines[index])} ` + - `checked-in=${JSON.stringify(checkedInLines[index])}` - ); - } - } - return undefined; -} - -// This unit test spawns a real subprocess (`tsc`), a deliberate, narrow -// exception to this repo's usual "no subprocess in unit tests" default: the -// declarations-only compile it runs takes well under half a second, and it's -// the only thing that can actually guard the published type surface against -// silent drift (see this file's header comment). -describe("api-report/ mirrors the compiled declaration surface", () => { - test("a fresh declarations-only build matches the checked-in api-report/ mirror", async () => { - const scratchDir = await mkdtemp(join(tmpdir(), "supabase-config-api-report-test-")); - - try { - // Spawns this package's own `node_modules/.bin/tsc` directly rather - // than `pnpm exec tsc`: `bun --bun vitest` (this package's mandated - // test runner, per `AGENTS.md`) prepends a synthetic `node` shim - // directory (`/tmp/bun-node-*`, `node` -> `bun`) to `PATH` for the - // whole process tree, so any nested `#!/usr/bin/env node` script - // resolves to Bun instead of real Node. `pnpm`'s own launcher is - // exactly such a script, and its corepack wrapper needs `node:sqlite`, - // which Bun's Node-compat layer doesn't implement — so spawning `pnpm` - // unmodified from inside this test used to fail before ever reaching - // `tsc`, requiring PATH surgery to work around it. Going straight to - // the installed `tsc` bin sidesteps `pnpm`'s launcher (and its - // `node:sqlite` dependency) entirely — `tsc` itself has no such - // dependency, so Bun's `node` shim resolving it is fine. - const tscBinPath = join(packageRoot, "node_modules", ".bin", "tsc"); - const tsc = Bun.spawn( - [tscBinPath, "-p", "tsconfig.api-report.json", "--outDir", scratchDir], - { cwd: packageRoot, stdout: "pipe", stderr: "pipe" }, - ); - const [exitCode, stdout, stderr] = await Promise.all([ - tsc.exited, - new Response(tsc.stdout).text(), - new Response(tsc.stderr).text(), - ]); - expect(exitCode, `tsc failed:\n${stdout}\n${stderr}`).toBe(0); - - const [freshFiles, checkedInFiles] = await Promise.all([ - listDeclarationFiles(scratchDir), - listDeclarationFiles(apiReportDir), - ]); - - expect(freshFiles, FAILURE_MESSAGE).toEqual(checkedInFiles); - - const mismatches: string[] = []; - for (const relativePath of freshFiles) { - const [fresh, checkedIn] = await Promise.all([ - readFile(join(scratchDir, relativePath), "utf8"), - readFile(join(apiReportDir, relativePath), "utf8"), - ]); - if (fresh !== checkedIn) { - mismatches.push(`${relativePath} (${firstDifferingLine(fresh, checkedIn)})`); - } - } - - expect(mismatches, FAILURE_MESSAGE).toEqual([]); - } finally { - await rm(scratchDir, { recursive: true, force: true }); - } - }, 20_000); -}); diff --git a/packages/config/src/entrypoint-purity.unit.test.ts b/packages/config/src/entrypoint-purity.unit.test.ts index 96473d9105..45614165a4 100644 --- a/packages/config/src/entrypoint-purity.unit.test.ts +++ b/packages/config/src/entrypoint-purity.unit.test.ts @@ -583,9 +583,8 @@ describe("package.json exports map", () => { // The `types`/`default` conditions of `.`/`./effect`/`./internal`/`./io` // (node, browser, default) all point at `dist/` build outputs, which only // exist after `pnpm run build` — intentionally NOT checked here so this - // test stays build-independent. `src/api-report.unit.test.ts` and - // `scripts/build.ts`'s tree-shake/Node-consumer smoke test own dist - // correctness instead (CLI-2232). + // test stays build-independent. `scripts/build.ts`'s tree-shake/Node-consumer + // smoke test owns dist correctness instead (CLI-2232). test("the ./io bun condition target exists on disk (its only src target)", () => { expect(() => readFileSync(join(packageRoot, packageJson.exports["./io"].bun))).not.toThrow(); }); diff --git a/packages/config/src/monorepo-import-contract.unit.test.ts b/packages/config/src/monorepo-import-contract.unit.test.ts index 5e027cd00e..204bc5ba7b 100644 --- a/packages/config/src/monorepo-import-contract.unit.test.ts +++ b/packages/config/src/monorepo-import-contract.unit.test.ts @@ -16,8 +16,8 @@ import { fileURLToPath } from "node:url"; // are built by concatenation so this file's own source can never self-match // (on top of the directory exclusion below, which already keeps this whole // package — where those specifier strings legitimately appear in doc -// comments, generated `api-report/` declarations, and the build script's own -// smoke-test source string — out of the walk). +// comments and the build script's own smoke-test source string — out of the +// walk). // const srcDir = dirname(fileURLToPath(import.meta.url)); const repoRoot = join(srcDir, "..", "..", ".."); diff --git a/packages/config/tsconfig.api-report.json b/packages/config/tsconfig.declarations.json similarity index 63% rename from packages/config/tsconfig.api-report.json rename to packages/config/tsconfig.declarations.json index f6145b9a6e..be7328b4d5 100644 --- a/packages/config/tsconfig.api-report.json +++ b/packages/config/tsconfig.declarations.json @@ -1,7 +1,7 @@ { - // Declaration-only companion to `tsconfig.build.json`, used exclusively to - // populate `api-report/` (see `scripts/build.ts` and - // `src/api-report.unit.test.ts`). A second minimal config — rather than + // Declaration-only companion to `tsconfig.build.json`, used by + // `tools/config-api-compare.ts` (repo root) to emit both the base and head + // `.d.ts` trees it diffs (CLI-2234). A second minimal config — rather than // overriding `declarationMap` on the CLI — because `declarationMap` is a // boolean compiler option with no dedicated CLI negation flag; `--outDir` // is a plain string override and stays safe to pass on the command line at diff --git a/tools/config-api-compare.ts b/tools/config-api-compare.ts new file mode 100644 index 0000000000..04253cac23 --- /dev/null +++ b/tools/config-api-compare.ts @@ -0,0 +1,503 @@ +/** + * Diffs `@supabase/config`'s compiled `.d.ts` surface between a PR's base and + * head commits — a per-PR type-surface signal with zero committed artifacts + * (CLI-2234; replaces the checked-in `packages/config/api-report/` mirror). + * + * Usage: + * bun tools/config-api-compare.ts [--base ] + * + * Base ref resolution, in order: `--base`, then `GITHUB_BASE_REF` (prefixed + * `origin/`), then `origin/develop`. Resolves `git merge-base HEAD `, + * fetching `origin/` at depth 1 first when the ref is missing locally + * (a shallow CI clone only has the PR's own commits). + * + * Emits declarations twice with the same compiler settings — head from + * `packages/config/src` directly, base from a `git archive` of the + * merge-base extracted into `packages/config/.api-compare/base/` (so + * dependency resolution walks up to `packages/config/node_modules` using the + * CURRENT install, no second `pnpm install` needed) — then diffs the two + * `.d.ts` trees. + * + * Advisory at PR time (a base-vs-head diff has no acceptance artifact to + * gate on); the hard release-time gate is tracked under CLI-2233. + * + * Exit codes: 0 identical (or compare skipped), 1 surface differs, 2 tool + * failure. + */ + +import { appendFile, cp, mkdir, mkdtemp, readFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import process from "node:process"; +import { parseArgs } from "node:util"; + +const repoRoot = path.resolve(import.meta.dir, ".."); +const packageRoot = path.join(repoRoot, "packages", "config"); +const tscBinPath = path.join(packageRoot, "node_modules", ".bin", "tsc"); + +interface GitResult { + readonly exitCode: number; + readonly stdout: string; + readonly stderr: string; +} + +async function runGit(args: readonly string[], cwd: string): Promise { + const proc = Bun.spawn(["git", ...args], { cwd, stdout: "pipe", stderr: "pipe" }); + const [exitCode, stdout, stderr] = await Promise.all([ + proc.exited, + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + ]); + return { exitCode, stdout, stderr }; +} + +function requireBinaries(names: readonly string[]): void { + const missing = names.filter((name) => Bun.which(name) === null); + if (missing.length > 0) { + throw new Error(`this tool requires ${missing.join(", ")} on PATH.`); + } +} + +/** `--base` wins; else `GITHUB_BASE_REF` (a PR's base branch name, no remote prefix) under `origin/`; else `origin/develop`. */ +function resolveBaseRef(cliBase: string | undefined): string { + if (cliBase) { + return cliBase; + } + const githubBaseRef = process.env.GITHUB_BASE_REF; + if (githubBaseRef) { + return `origin/${githubBaseRef}`; + } + return "origin/develop"; +} + +/** + * Resolves `git merge-base HEAD `. A shallow CI checkout only has + * the PR's own commits, so `` can be locally unresolvable — when + * it's an `origin/` ref, fetch that branch at depth 1 and retry + * before giving up. A non-`origin/` ref (e.g. an explicit `--base `) + * that doesn't resolve locally is a caller error, not something this tool + * can fetch its way out of. + */ +async function resolveMergeBase(baseRef: string): Promise { + const attempt = await runGit(["merge-base", "HEAD", baseRef], repoRoot); + if (attempt.exitCode === 0) { + return attempt.stdout.trim(); + } + + if (!baseRef.startsWith("origin/")) { + throw new Error( + `could not resolve base ref "${baseRef}" (git merge-base: ${attempt.stderr.trim()}). Pass a ` + + `ref that already exists locally, or one under "origin/" so it can be fetched.`, + ); + } + + const branchName = baseRef.slice("origin/".length); + console.warn( + `[config-api-compare] ${baseRef} did not resolve locally (${attempt.stderr.trim()}); fetching ` + + `origin/${branchName} at depth 1...`, + ); + const fetch = await runGit( + ["fetch", "--depth=1", "origin", `+${branchName}:refs/remotes/origin/${branchName}`], + repoRoot, + ); + if (fetch.exitCode !== 0) { + throw new Error(`git fetch --depth=1 origin ${branchName} failed: ${fetch.stderr.trim()}`); + } + + const retry = await runGit(["merge-base", "HEAD", baseRef], repoRoot); + if (retry.exitCode !== 0) { + throw new Error( + `could not resolve base ref "${baseRef}" even after fetching origin/${branchName}: ` + + retry.stderr.trim(), + ); + } + return retry.stdout.trim(); +} + +async function shortSha(rev: string): Promise { + const result = await runGit(["rev-parse", "--short", rev], repoRoot); + return result.exitCode === 0 ? result.stdout.trim() : rev; +} + +async function pathExistsAtRev(rev: string, relativePath: string): Promise { + const proc = Bun.spawn(["git", "cat-file", "-e", `${rev}:${relativePath}`], { + cwd: repoRoot, + stdout: "ignore", + stderr: "ignore", + }); + return (await proc.exited) === 0; +} + +/** + * `git archive ` piped straight into `tar -x`, stripping + * the shared `packages/config/` prefix (2 path components) so the extracted + * tree lands directly under `destDir`. + */ +async function archiveAndExtract( + rev: string, + relativePaths: readonly string[], + destDir: string, +): Promise { + const archiveProc = Bun.spawn(["git", "archive", rev, ...relativePaths], { + cwd: repoRoot, + stdout: "pipe", + stderr: "pipe", + }); + const tarProc = Bun.spawn(["tar", "-x", "-C", destDir, "--strip-components=2"], { + stdin: archiveProc.stdout, + stdout: "pipe", + stderr: "pipe", + }); + const [archiveExitCode, tarExitCode, archiveStderr, tarStderr] = await Promise.all([ + archiveProc.exited, + tarProc.exited, + new Response(archiveProc.stderr).text(), + new Response(tarProc.stderr).text(), + ]); + if (archiveExitCode !== 0) { + throw new Error( + `git archive ${rev} ${relativePaths.join(" ")} failed: ${archiveStderr.trim()}`, + ); + } + if (tarExitCode !== 0) { + throw new Error(`tar extraction into ${destDir} failed: ${tarStderr.trim()}`); + } +} + +/** + * Materializes the base revision's `packages/config/src` (plus its + * declaration-emit config) under `baseExtractDir`, INSIDE `packages/config`, + * so tsc's node_modules walk from there reaches `packages/config/node_modules` + * with the CURRENT install — no second `pnpm install` needed. + * + * `tsconfig.build.json` is always the HEAD copy (tooling, not part of the + * compared surface, and required so `tsconfig.declarations.json`'s own + * `"extends": "./tsconfig.build.json"` resolves inside the extracted tree). + * `tsconfig.declarations.json` is the base revision's own copy when it has + * one; a merge-base that predates this file (e.g. still on the checked-in + * `api-report/` mirror, or older) falls back to the HEAD copy for the emit + * settings. + */ +async function extractBaseTree(mergeBase: string, baseExtractDir: string): Promise { + await mkdir(baseExtractDir, { recursive: true }); + + const srcRelativePath = "packages/config/src"; + const declarationsRelativePath = "packages/config/tsconfig.declarations.json"; + + if (!(await pathExistsAtRev(mergeBase, srcRelativePath))) { + throw new Error(`base revision ${mergeBase} has no ${srcRelativePath} — cannot compare`); + } + + const hasDeclarationsConfig = await pathExistsAtRev(mergeBase, declarationsRelativePath); + const archivePaths = hasDeclarationsConfig + ? [srcRelativePath, declarationsRelativePath] + : [srcRelativePath]; + await archiveAndExtract(mergeBase, archivePaths, baseExtractDir); + + await cp( + path.join(packageRoot, "tsconfig.build.json"), + path.join(baseExtractDir, "tsconfig.build.json"), + ); + if (!hasDeclarationsConfig) { + console.warn( + `[config-api-compare] base revision ${await shortSha(mergeBase)} predates ` + + "tsconfig.declarations.json — using the HEAD copy for emit settings.", + ); + await cp( + path.join(packageRoot, "tsconfig.declarations.json"), + path.join(baseExtractDir, "tsconfig.declarations.json"), + ); + } +} + +interface EmitResult { + readonly exitCode: number; + readonly stdout: string; + readonly stderr: string; + readonly fileCount: number; +} + +async function countDeclarationFiles(dir: string): Promise { + const glob = new Bun.Glob("**/*.d.ts"); + let count = 0; + for await (const _relativePath of glob.scan({ cwd: dir })) { + count++; + } + return count; +} + +async function listDeclarationFiles(dir: string): Promise { + const glob = new Bun.Glob("**/*.d.ts"); + const relativePaths: string[] = []; + for await (const relativePath of glob.scan({ cwd: dir })) { + relativePaths.push(relativePath); + } + return relativePaths.sort(); +} + +/** + * Spawns this package's own `node_modules/.bin/tsc` directly rather than + * `pnpm exec tsc` (the same corepack-avoidance lesson as the old + * `api-report.unit.test.ts`: a bun-shimmed `PATH` can route `pnpm`'s launcher + * through Bun's `node:sqlite`-less Node-compat layer). `noEmitOnError` + * defaults to false, so declarations are emitted even when the base tree's + * old source doesn't type-check cleanly against the current install's + * (newer) dependencies — a genuinely empty output is the only signal treated + * as a hard failure by the caller. + */ +async function emitDeclarations( + projectPath: string, + outDir: string, + cwd: string, +): Promise { + const proc = Bun.spawn([tscBinPath, "-p", projectPath, "--outDir", outDir], { + cwd, + stdout: "pipe", + stderr: "pipe", + }); + const [exitCode, stdout, stderr] = await Promise.all([ + proc.exited, + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + ]); + const fileCount = await countDeclarationFiles(outDir); + return { exitCode, stdout, stderr, fileCount }; +} + +async function unifiedDiff( + oldPath: string, + newPath: string, + oldLabel: string, + newLabel: string, +): Promise { + const proc = Bun.spawn(["diff", "-u", "-L", oldLabel, "-L", newLabel, oldPath, newPath], { + stdout: "pipe", + stderr: "pipe", + }); + const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]); + return stdout; +} + +interface FileEntry { + readonly status: "added" | "removed" | "changed"; + readonly path: string; + readonly diff: string; +} + +interface CompareResult { + readonly identical: boolean; + readonly entries: readonly FileEntry[]; +} + +/** Diffs the two emitted `.d.ts` trees (`**\/*.d.ts` only — the glob itself never matches `.d.ts.map`). */ +async function diffDeclarationTrees(headDir: string, baseDir: string): Promise { + const [headFiles, baseFiles] = await Promise.all([ + listDeclarationFiles(headDir), + listDeclarationFiles(baseDir), + ]); + const headSet = new Set(headFiles); + const baseSet = new Set(baseFiles); + + const entries: FileEntry[] = []; + + for (const relativePath of headFiles) { + if (!baseSet.has(relativePath)) { + entries.push({ + status: "added", + path: relativePath, + diff: await unifiedDiff( + "/dev/null", + path.join(headDir, relativePath), + "/dev/null", + `head/${relativePath}`, + ), + }); + continue; + } + + const [headContent, baseContent] = await Promise.all([ + readFile(path.join(headDir, relativePath), "utf8"), + readFile(path.join(baseDir, relativePath), "utf8"), + ]); + if (headContent !== baseContent) { + entries.push({ + status: "changed", + path: relativePath, + diff: await unifiedDiff( + path.join(baseDir, relativePath), + path.join(headDir, relativePath), + `base/${relativePath}`, + `head/${relativePath}`, + ), + }); + } + } + + for (const relativePath of baseFiles) { + if (!headSet.has(relativePath)) { + entries.push({ + status: "removed", + path: relativePath, + diff: await unifiedDiff( + path.join(baseDir, relativePath), + "/dev/null", + `base/${relativePath}`, + "/dev/null", + ), + }); + } + } + + entries.sort((a, b) => a.path.localeCompare(b.path)); + return { identical: entries.length === 0, entries }; +} + +function countByStatus(entries: readonly FileEntry[], status: FileEntry["status"]): number { + return entries.filter((entry) => entry.status === status).length; +} + +function renderTextReport(baseLabel: string, headLabel: string, result: CompareResult): string { + const lines: string[] = [`Config type-surface diff: ${baseLabel} -> ${headLabel}`, ""]; + if (result.identical) { + lines.push("No type-surface differences."); + return lines.join("\n"); + } + + lines.push( + `Added: ${countByStatus(result.entries, "added")}, ` + + `Removed: ${countByStatus(result.entries, "removed")}, ` + + `Changed: ${countByStatus(result.entries, "changed")}`, + "", + ); + for (const entry of result.entries) { + lines.push(`--- ${entry.status} ${entry.path} ---`, entry.diff.trimEnd(), ""); + } + return lines.join("\n"); +} + +function renderMarkdownSummary( + baseLabel: string, + headLabel: string, + result: CompareResult, +): string { + const lines: string[] = [ + "## Config type-surface diff (advisory)", + "", + `Comparing \`${baseLabel}\` against \`${headLabel}\` for \`@supabase/config\`'s compiled ` + + "declaration surface. Advisory only — see CLI-2233 for the planned release-time hard gate.", + "", + ]; + if (result.identical) { + lines.push("No type-surface differences."); + return lines.join("\n"); + } + + lines.push( + `**${countByStatus(result.entries, "added")} added, ` + + `${countByStatus(result.entries, "removed")} removed, ` + + `${countByStatus(result.entries, "changed")} changed**`, + "", + ); + for (const entry of result.entries) { + lines.push( + `
${entry.status}: ${entry.path}`, + "", + "```diff", + entry.diff.trimEnd(), + "```", + "", + "
", + "", + ); + } + return lines.join("\n"); +} + +function renderSkippedSummary(baseLabel: string, headLabel: string, baseEmit: EmitResult): string { + return [ + "## Config type-surface diff (advisory)", + "", + `⚠️ Compare skipped: the base revision (\`${baseLabel}\`) declaration emit produced zero ` + + `\`.d.ts\` files against \`${headLabel}\` (tsc exit ${baseEmit.exitCode}). Old source failing ` + + 'to emit against the current install\'s dependencies is treated as "nothing to compare" ' + + "rather than a false positive.", + ].join("\n"); +} + +async function writeStepSummary(markdown: string): Promise { + const summaryPath = process.env.GITHUB_STEP_SUMMARY; + if (!summaryPath) { + return; + } + await appendFile(summaryPath, `${markdown}\n`); +} + +async function main(): Promise { + requireBinaries(["git", "tar", "diff"]); + + const { values } = parseArgs({ options: { base: { type: "string" } } }); + const baseRef = resolveBaseRef(values.base); + const mergeBase = await resolveMergeBase(baseRef); + const [baseLabel, headLabel] = await Promise.all([shortSha(mergeBase), shortSha("HEAD")]); + console.log( + `[config-api-compare] comparing merge-base ${baseLabel} (of ${baseRef}) against HEAD ${headLabel}...`, + ); + + const compareDir = path.join(packageRoot, ".api-compare"); + const baseExtractDir = path.join(compareDir, "base"); + const headOutDir = await mkdtemp(path.join(tmpdir(), "supabase-config-api-compare-head-")); + const baseOutDir = await mkdtemp(path.join(tmpdir(), "supabase-config-api-compare-base-")); + + try { + await rm(compareDir, { recursive: true, force: true }); + + console.log("[config-api-compare] emitting head declarations..."); + const headEmit = await emitDeclarations( + path.join(packageRoot, "tsconfig.declarations.json"), + headOutDir, + packageRoot, + ); + if (headEmit.fileCount === 0) { + throw new Error( + `head declaration emit produced zero .d.ts files (tsc exit ${headEmit.exitCode}):\n` + + `${headEmit.stdout}\n${headEmit.stderr}`, + ); + } + + console.log("[config-api-compare] extracting and emitting base declarations..."); + await extractBaseTree(mergeBase, baseExtractDir); + const baseEmit = await emitDeclarations( + path.join(baseExtractDir, "tsconfig.declarations.json"), + baseOutDir, + baseExtractDir, + ); + if (baseEmit.fileCount === 0) { + console.warn( + `[config-api-compare] WARNING: base declaration emit produced zero .d.ts files (tsc exit ` + + `${baseEmit.exitCode}) — skipping the compare rather than reporting a false surface diff.\n` + + baseEmit.stderr, + ); + await writeStepSummary(renderSkippedSummary(baseLabel, headLabel, baseEmit)); + return 0; + } + + const result = await diffDeclarationTrees(headOutDir, baseOutDir); + console.log(renderTextReport(baseLabel, headLabel, result)); + await writeStepSummary(renderMarkdownSummary(baseLabel, headLabel, result)); + + return result.identical ? 0 : 1; + } finally { + await Promise.all([ + rm(compareDir, { recursive: true, force: true }), + rm(headOutDir, { recursive: true, force: true }), + rm(baseOutDir, { recursive: true, force: true }), + ]); + } +} + +try { + process.exit(await main()); +} catch (error) { + console.error(`[config-api-compare] ${error instanceof Error ? error.message : String(error)}`); + process.exit(2); +} diff --git a/turbo.json b/turbo.json index 706473fdd5..a4f76a2b59 100644 --- a/turbo.json +++ b/turbo.json @@ -62,13 +62,8 @@ }, "@supabase/config#build": { "cache": true, - "inputs": [ - "$TURBO_DEFAULT$", - "!api-report/**", - "$TURBO_ROOT$/.bun-version", - "$TURBO_ROOT$/mise.lock" - ], - "outputs": ["dist/**", "api-report/**"] + "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/.bun-version", "$TURBO_ROOT$/mise.lock"], + "outputs": ["dist/**"] }, "@supabase/api#generate": { "cache": false, From 8b9803b50c171870bc8b49834a9cd7f17a4a80b3 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Fri, 28 Aug 2026 17:52:58 +0100 Subject: [PATCH 8/9] fix(config): address AI review findings on the export-surface/build PR (CLI-2234, CLI-2232) --- .github/workflows/test.yml | 4 +- AGENTS.md | 2 +- apps/cli/package.json | 1 + apps/cli/scripts/generate-docs.ts | 35 +- apps/cli/vitest.config.ts | 24 + apps/docs/public/cli/config.schema.json | 7497 ++++++++++------- .../public/cli/project-config.schema.json | 1972 +++++ docs/adr/0020-config-naming-vocabulary.md | 6 +- packages/config/AGENTS.md | 15 +- packages/config/docs/cli-config-loading.md | 6 +- packages/config/package.json | 1 + packages/config/scripts/build.ts | 6 +- .../config/scripts/json-schema-postprocess.ts | 7 +- .../json-schema-postprocess.unit.test.ts | 25 + packages/config/src/internal.ts | 8 +- packages/config/src/lib/resolve.ts | 8 +- .../src/project-config/project-schema.ts | 89 +- .../project-schema.unit.test.ts | 114 +- packages/config/vitest.config.ts | 17 + pnpm-lock.yaml | 6 + tools/config-api-compare.ts | 106 +- turbo.json | 10 +- 22 files changed, 6845 insertions(+), 3114 deletions(-) create mode 100644 apps/docs/public/cli/project-config.schema.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index af9cc07490..c13aa2c69e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,7 +70,9 @@ jobs: # Advisory only (base-vs-head diff, no acceptance artifact to gate a # required check on) — the hard release-time gate is tracked under # CLI-2233. `continue-on-error` flags the diff without failing the job; - # the tool's own fetch fallback covers this checkout's shallow clone. + # the tool's own fetch/unshallow fallback resolves a merge-base from + # this checkout's shallow clone, and skips the compare (exit 0) rather + # than failing when history still can't be resolved. - name: config type-surface diff (advisory) continue-on-error: true run: pnpm run check:config-api diff --git a/AGENTS.md b/AGENTS.md index cb487a6425..fe598ae530 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,7 +59,7 @@ Expected exceptions: Use the `Cli*` prefix for the local checkout side and a bare `Project*` name for the hosted Supabase project. Config-value helpers follow the config family regardless of their inputs (e.g. -`resolveCliConfigValue`, `MissingCliConfigValueError`). A symbol that deliberately spans both +`resolveCliConfigValue`, `CliConfigParseError`). A symbol that deliberately spans both families takes a family-neutral name instead of a misleading prefix (see the ADR 0020 addendum for the `EffectiveConfig` precedent). diff --git a/apps/cli/package.json b/apps/cli/package.json index faa2374194..39fa7ee7b1 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -94,6 +94,7 @@ "smol-toml": "^1.8.0", "tldts": "catalog:", "typescript": "catalog:", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "vitest": "catalog:", "yaml": "^2.9.0" }, diff --git a/apps/cli/scripts/generate-docs.ts b/apps/cli/scripts/generate-docs.ts index 5fa73b3b89..ff5d9f83d6 100644 --- a/apps/cli/scripts/generate-docs.ts +++ b/apps/cli/scripts/generate-docs.ts @@ -1,7 +1,7 @@ -import { mkdirSync, writeFileSync } from "node:fs"; +import { copyFileSync, mkdirSync, writeFileSync } from "node:fs"; import path from "node:path"; import process from "node:process"; -import { CLI_CONFIG_SCHEMA_URL, toCliConfigJsonSchema } from "@supabase/config"; +import { CLI_CONFIG_SCHEMA_URL, PROJECT_CONFIG_SCHEMA_URL } from "@supabase/config"; import { nextRoot } from "../src/next/cli/root.ts"; import { collectCommands, getHelpDoc } from "../src/next/docs/command-docs.ts"; import { formatHelpDocAsMarkdown } from "../src/next/docs/markdown-formatter.ts"; @@ -9,6 +9,7 @@ import { formatHelpDocAsMarkdown } from "../src/next/docs/markdown-formatter.ts" const BINARY_NAME = "supabase"; const defaultContentDir = path.resolve(import.meta.dir, "../../../apps/docs/content/docs/commands"); const defaultDocsPublicDir = path.resolve(import.meta.dir, "../../../apps/docs/public"); +const configPackageDistDir = path.resolve(import.meta.dir, "../../../packages/config/dist"); const contentDir = process.argv[2] ? path.resolve(process.cwd(), process.argv[2]) : defaultContentDir; @@ -72,16 +73,26 @@ function generateCommandDocs() { console.log(`\nGenerated ${pages.length} command page(s)`); } -function generateConfigSchemaAsset() { - const schema = toCliConfigJsonSchema(); - const schemaPathname = new URL(CLI_CONFIG_SCHEMA_URL).pathname.replace(/^\/docs/, ""); - const filePath = path.join(defaultDocsPublicDir, schemaPathname); - - mkdirSync(path.dirname(filePath), { recursive: true }); - writeFileSync(filePath, `${JSON.stringify(schema, null, 2)}\n`); - - console.log(`Generated: ${path.relative(path.resolve(import.meta.dir, "../../.."), filePath)}`); +/** + * Copies `@supabase/config`'s already post-processed (metadata + number- + * union-collapsed) `dist/*.json` schema artifact straight to its docs-site + * public path, rather than re-rendering `toCliConfigJsonSchema()`/ + * `toProjectConfigJsonSchema()` here (CLI-2234) — re-rendering would bypass + * `json-schema-postprocess.ts` and produce a document whose `$id` doesn't + * match what actually gets published. Requires `@supabase/config#build` to + * have already run (wired via `turbo.json`). + */ +function copyConfigSchemaAsset(schemaUrl: string, distFileName: string) { + const schemaPathname = new URL(schemaUrl).pathname.replace(/^\/docs/, ""); + const destPath = path.join(defaultDocsPublicDir, schemaPathname); + const sourcePath = path.join(configPackageDistDir, distFileName); + + mkdirSync(path.dirname(destPath), { recursive: true }); + copyFileSync(sourcePath, destPath); + + console.log(`Generated: ${path.relative(path.resolve(import.meta.dir, "../../.."), destPath)}`); } generateCommandDocs(); -generateConfigSchemaAsset(); +copyConfigSchemaAsset(CLI_CONFIG_SCHEMA_URL, "schema.json"); +copyConfigSchemaAsset(PROJECT_CONFIG_SCHEMA_URL, "project-schema.json"); diff --git a/apps/cli/vitest.config.ts b/apps/cli/vitest.config.ts index 9c1578d853..992e5a6580 100644 --- a/apps/cli/vitest.config.ts +++ b/apps/cli/vitest.config.ts @@ -1,4 +1,5 @@ import { readFileSync } from "node:fs"; +import { defaultClientConditions, defaultServerConditions } from "vite"; import { defineConfig } from "vitest/config"; function dockerfileTextPlugin() { @@ -15,7 +16,22 @@ function dockerfileTextPlugin() { }; } +// Workspace packages such as @supabase/config publish a `bun` export +// condition pointing at their TypeScript source (see +// packages/config/package.json's `exports` map); without it, Vite's resolver +// falls through to the `default` condition and loads the built `dist/*.js` +// output instead — which is stale, or missing entirely on a fresh clone +// before the package has been built. Extending (not replacing) Vite's +// default condition lists keeps every other package's exports resolution +// unchanged. Required on every inline `test.projects` entry below too: +// Vitest builds a separate Vite config per project and does not inherit +// these from the root config (see PR #6366 finding 0). +const workspacePackageResolve = { conditions: [...defaultClientConditions, "bun"] }; +const workspacePackageSsrResolve = { conditions: [...defaultServerConditions, "bun"] }; + export default defineConfig({ + resolve: workspacePackageResolve, + ssr: { resolve: workspacePackageSsrResolve }, plugins: [dockerfileTextPlugin()], test: { passWithNoTests: true, @@ -41,6 +57,8 @@ export default defineConfig({ }, projects: [ { + resolve: workspacePackageResolve, + ssr: { resolve: workspacePackageSsrResolve }, plugins: [dockerfileTextPlugin()], test: { name: "unit", @@ -49,6 +67,8 @@ export default defineConfig({ }, }, { + resolve: workspacePackageResolve, + ssr: { resolve: workspacePackageSsrResolve }, plugins: [dockerfileTextPlugin()], test: { name: "integration", @@ -56,6 +76,8 @@ export default defineConfig({ }, }, { + resolve: workspacePackageResolve, + ssr: { resolve: workspacePackageSsrResolve }, plugins: [dockerfileTextPlugin()], test: { name: "e2e", @@ -69,6 +91,8 @@ export default defineConfig({ }, }, { + resolve: workspacePackageResolve, + ssr: { resolve: workspacePackageSsrResolve }, plugins: [dockerfileTextPlugin()], test: { // Live tests run against one provisioned project on the configured diff --git a/apps/docs/public/cli/config.schema.json b/apps/docs/public/cli/config.schema.json index 71eb17061d..32f2fe9e42 100644 --- a/apps/docs/public/cli/config.schema.json +++ b/apps/docs/public/cli/config.schema.json @@ -1,5 +1,8 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://supabase.com/docs/cli/config.schema.json", + "title": "Supabase CLI config (CliConfig)", + "description": "The Supabase CLI's local project config document (supabase/config.toml or supabase/config.json).", "type": "object", "properties": { "project_id": { @@ -15,33 +18,19 @@ "default": true }, "port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] + "type": "number", + "description": "Port to the local Logflare service.", + "default": 54327 }, "backend": { "type": "string", - "enum": [ - "postgres", - "bigquery" - ], + "enum": ["postgres", "bigquery"], "description": "Configure one of the supported backends:\n\n- `postgres`\n- `bigquery`", "default": "postgres" }, "vector_port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] + "type": "number", + "description": "Port to the local syslog ingest service." }, "gcp_project_id": { "type": "string", @@ -67,37 +56,53 @@ "default": true }, "port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] + "type": "number", + "description": "Port to use for the API URL.", + "default": 54321 }, "schemas": { - "$ref": "#/$defs/Arrays_" + "type": "array", + "items": { + "type": "string", + "description": "Schemas to expose in your API. Tables, views and stored procedures in this schema will get API endpoints." + }, + "default": ["public", "graphql_public"] }, "extra_search_path": { - "$ref": "#/$defs/Arrays_1" + "type": "array", + "items": { + "type": "string", + "description": "Extra schemas to add to the search_path of every request." + }, + "default": ["public", "extensions"] }, "max_rows": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] + "type": "number", + "description": "The maximum number of rows returned from a view, table, or stored procedure. Limits payload size for accidental or malicious requests.", + "default": 1000 }, "auto_expose_new_tables": { "type": "boolean", "description": "Controls whether newly-created tables, views, sequences and functions in the `public` schema by `postgres` are reachable through the Data API roles (`anon`, `authenticated`, `service_role`) without explicit GRANTs. When unset, new entities are auto-exposed, matching the cloud default. Set to `false` to revoke the default Data API privileges so new entities require explicit GRANTs, matching a cloud project with the \"Default privileges for new entities\" toggle turned off." }, "tls": { - "$ref": "#/$defs/Objects_" + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable HTTPS endpoints locally using a self-signed certificate.", + "default": false + }, + "cert_path": { + "type": "string", + "description": "Path to the self-signed certificate." + }, + "key_path": { + "type": "string", + "description": "Path to the self-signed certificate private key." + } + }, + "additionalProperties": false }, "external_url": { "type": "string", @@ -120,17 +125,18 @@ "default": "http://127.0.0.1:3000" }, "additional_redirect_urls": { - "$ref": "#/$defs/Arrays_2" + "type": "array", + "items": { + "type": "string", + "description": "A URL that auth providers are permitted to redirect to." + }, + "description": "A list of exact URLs that auth providers are permitted to redirect to post authentication.", + "default": ["https://127.0.0.1:3000"] }, "jwt_expiry": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] + "type": "number", + "description": "How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one week).", + "default": 3600 }, "jwt_issuer": { "type": "string", @@ -146,14 +152,9 @@ "default": true }, "refresh_token_reuse_interval": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] + "type": "number", + "description": "Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.", + "default": 10 }, "enable_manual_linking": { "type": "boolean", @@ -171,17 +172,20 @@ "default": false }, "minimum_password_length": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] + "type": "number", + "description": "Passwords shorter than this value will be rejected as weak.", + "default": 6 }, "password_requirements": { - "$ref": "#/$defs/Union_1" + "type": "string", + "enum": [ + "", + "letters_digits", + "lower_upper_letters_digits", + "lower_upper_letters_digits_symbols" + ], + "description": "Password character requirements.", + "default": "" }, "publishable_key": { "type": "string", @@ -204,2618 +208,2061 @@ "description": "Service role key override." }, "rate_limit": { - "$ref": "#/$defs/Objects_1" + "type": "object", + "properties": { + "email_sent": { + "type": "number", + "description": "Number of emails that can be sent per hour.", + "default": 2 + }, + "sms_sent": { + "type": "number", + "description": "Number of SMS messages that can be sent per hour.", + "default": 30 + }, + "anonymous_users": { + "type": "number", + "description": "Number of anonymous sign-ins that can be made per hour per IP address.", + "default": 30 + }, + "token_refresh": { + "type": "number", + "description": "Number of sessions that can be refreshed in a 5 minute interval per IP address.", + "default": 150 + }, + "sign_in_sign_ups": { + "type": "number", + "description": "Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address.", + "default": 30 + }, + "token_verifications": { + "type": "number", + "description": "Number of OTP or magic link verifications that can be made in a 5 minute interval per IP address.", + "default": 30 + }, + "web3": { + "type": "number", + "description": "Number of Web3 logins that can be made in a 5 minute interval per IP address.", + "default": 30 + } + }, + "additionalProperties": false }, "captcha": { - "$ref": "#/$defs/Objects_2" + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable CAPTCHA verification.", + "default": false + }, + "provider": { + "type": "string", + "enum": ["hcaptcha", "turnstile"], + "description": "CAPTCHA provider to use." + }, + "secret": { + "type": "string", + "description": "Secret key for the CAPTCHA provider." + } + }, + "additionalProperties": false }, "hook": { - "$ref": "#/$defs/Objects_3" - }, - "mfa": { - "$ref": "#/$defs/Objects_4" - }, - "sessions": { - "$ref": "#/$defs/Objects_5" - }, - "email": { - "$ref": "#/$defs/Objects_6" - }, - "sms": { - "$ref": "#/$defs/Objects_7" - }, - "external": { - "$ref": "#/$defs/Objects_8" - }, - "web3": { - "$ref": "#/$defs/Objects_9" - }, - "oauth_server": { - "$ref": "#/$defs/Objects_11" - }, - "third_party": { - "$ref": "#/$defs/Objects_12" - } - }, - "additionalProperties": false - }, - "db": { - "type": "object", - "properties": { - "port": { - "anyOf": [ - { - "type": "number" + "type": "object", + "properties": { + "mfa_verification_attempt": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the mfa verification hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + }, + "secrets": { + "type": "string", + "description": "Secret value to pass to the function or endpoint." + } + }, + "additionalProperties": false }, - { - "$ref": "#/$defs/Union_" + "password_verification_attempt": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the password verification hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + }, + "secrets": { + "type": "string", + "description": "Secret value to pass to the function or endpoint." + } + }, + "additionalProperties": false + }, + "custom_access_token": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the custom access token hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + }, + "secrets": { + "type": "string", + "description": "Secret value to pass to the function or endpoint." + } + }, + "additionalProperties": false + }, + "send_sms": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the send sms hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + }, + "secrets": { + "type": "string", + "description": "Secret value to pass to the function or endpoint." + } + }, + "additionalProperties": false + }, + "send_email": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the send email hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + }, + "secrets": { + "type": "string", + "description": "Secret value to pass to the function or endpoint." + } + }, + "additionalProperties": false + }, + "before_user_created": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the before user created hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + }, + "secrets": { + "type": "string", + "description": "Secret value to pass to the function or endpoint." + } + }, + "additionalProperties": false } - ] + }, + "additionalProperties": false }, - "shadow_port": { - "anyOf": [ - { - "type": "number" + "mfa": { + "type": "object", + "properties": { + "totp": { + "type": "object", + "properties": { + "enroll_enabled": { + "type": "boolean", + "description": "Allow/disallow TOTP enrollment for users.", + "default": false + }, + "verify_enabled": { + "type": "boolean", + "description": "Allow/disallow TOTP verification for users.", + "default": false + } + }, + "additionalProperties": false }, - { - "$ref": "#/$defs/Union_" + "phone": { + "type": "object", + "properties": { + "enroll_enabled": { + "type": "boolean", + "description": "Allow/disallow phone enrollment for users.", + "default": false + }, + "verify_enabled": { + "type": "boolean", + "description": "Allow/disallow phone verification for users.", + "default": false + }, + "otp_length": { + "type": "number", + "description": "The length of the OTP code.", + "default": 6 + }, + "template": { + "type": "string", + "description": "The template to use for the phone message.", + "default": "Your code is {{ .Code }}" + }, + "max_frequency": { + "type": "string", + "description": "The maximum frequency of the phone messages.", + "default": "5s" + } + }, + "additionalProperties": false + }, + "web_authn": { + "type": "object", + "properties": { + "enroll_enabled": { + "type": "boolean", + "description": "Allow/disallow WebAuthn enrollment for users.", + "default": false + }, + "verify_enabled": { + "type": "boolean", + "description": "Allow/disallow WebAuthn verification for users.", + "default": false + } + }, + "additionalProperties": false + }, + "max_enrolled_factors": { + "type": "number", + "description": "The maximum number of MFA factors a user can enroll in.", + "default": 10 } - ] - }, - "health_timeout": { - "type": "string", - "description": "Maximum amount of time to wait for health check when starting the local database.", - "default": "2m" + }, + "additionalProperties": false }, - "major_version": { - "anyOf": [ - { - "type": "number" + "sessions": { + "type": "object", + "properties": { + "timebox": { + "type": "string", + "description": "The timebox for the user session." }, - { - "$ref": "#/$defs/Union_" + "inactivity_timeout": { + "type": "string", + "description": "The inactivity timeout for the user session." } - ] - }, - "pooler": { - "$ref": "#/$defs/Objects_13" - }, - "migrations": { - "$ref": "#/$defs/Objects_14" - }, - "seed": { - "$ref": "#/$defs/Objects_15" - }, - "settings": { - "$ref": "#/$defs/Objects_16" - }, - "network_restrictions": { - "$ref": "#/$defs/Objects_17" - }, - "ssl_enforcement": { - "$ref": "#/$defs/Objects_18" - }, - "vault": { - "$ref": "#/$defs/Objects_19" - } - }, - "additionalProperties": false - }, - "edge_runtime": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the local Edge Runtime service.", - "default": true - }, - "policy": { - "type": "string", - "enum": [ - "oneshot", - "per_worker" - ], - "description": "Configure the supported request policy.", - "default": "per_worker" + }, + "additionalProperties": false, + "default": {} }, - "inspector_port": { - "anyOf": [ - { - "type": "number" + "email": { + "type": "object", + "properties": { + "enable_signup": { + "type": "boolean", + "description": "Allow/disallow new user signups via email to your project.", + "default": true }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "deno_version": { - "anyOf": [ - { - "type": "number" + "double_confirm_changes": { + "type": "boolean", + "description": "If enabled, a user will be required to confirm any email change on both the old and new email addresses.", + "default": true }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "secrets": { - "$ref": "#/$defs/Objects_20" - } - }, - "additionalProperties": false - }, - "functions": { - "anyOf": [ - { - "$ref": "#/$defs/Objects_21" - }, - { - "type": "null" - } - ] - }, - "local_smtp": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the local SMTP testing server.", - "default": true - }, - "port": { - "anyOf": [ - { - "type": "number" + "enable_confirmations": { + "type": "boolean", + "description": "If enabled, users need to confirm their email address before signing in.", + "default": false }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "smtp_port": { - "anyOf": [ - { - "type": "number" + "secure_password_change": { + "type": "boolean", + "description": "If enabled, users will need to reauthenticate or have logged in recently to change their password.", + "default": false }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "pop3_port": { - "anyOf": [ - { - "type": "number" + "max_frequency": { + "type": "string", + "description": "Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.", + "default": "1s" }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "admin_email": { - "type": "string", - "description": "Admin email address for test email sender metadata." - }, - "sender_name": { - "type": "string", - "description": "Sender name for test email sender metadata." - } - }, - "additionalProperties": false - }, - "realtime": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the local Realtime service.", - "default": true - }, - "ip_version": { - "type": "string", - "enum": [ - "IPv4", - "IPv6" - ], - "description": "Bind realtime via either IPv4 or IPv6.", - "default": "IPv4" - }, - "max_header_length": { - "anyOf": [ - { - "type": "number" + "otp_length": { + "type": "number", + "description": "Number of characters used in the email OTP.", + "default": 6 }, - { - "$ref": "#/$defs/Union_" - } - ] - } - }, - "additionalProperties": false - }, - "storage": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the local Storage service.", - "default": true - }, - "file_size_limit": { - "anyOf": [ - { - "type": "string" + "otp_expiry": { + "type": "number", + "description": "Number of seconds before the email OTP expires.", + "default": 3600 }, - { + "smtp": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable SMTP for email delivery.", + "default": false + }, + "host": { + "type": "string", + "description": "Hostname or IP address of the SMTP server." + }, + "port": { + "type": "number", + "description": "Port number of the SMTP server." + }, + "user": { + "type": "string", + "description": "Username for authenticating with the SMTP server." + }, + "pass": { + "type": "string", + "description": "Password for authenticating with the SMTP server." + }, + "admin_email": { + "type": "string", + "description": "Email used as the sender for emails sent from the application." + }, + "sender_name": { + "type": "string", + "description": "Display name used as the sender for emails sent from the application." + } + }, + "additionalProperties": false + }, + "template": { "anyOf": [ { - "type": "number" + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "subject": { + "type": "string", + "description": "Subject line for the email template.", + "default": "" + }, + "content_path": { + "type": "string", + "description": "Path to the HTML template.", + "default": "" + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "description": "Custom email template configuration.", + "default": {} }, { - "$ref": "#/$defs/Union_" + "type": "null" } ] - } - ] - }, - "image_transformation": { - "$ref": "#/$defs/Objects_22" - }, - "buckets": { - "$ref": "#/$defs/Objects_23" - }, - "s3_protocol": { - "$ref": "#/$defs/Objects_24" - }, - "analytics": { - "$ref": "#/$defs/Objects_25" - }, - "vector": { - "$ref": "#/$defs/Objects_26" - } - }, - "additionalProperties": false - }, - "studio": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the local Supabase Studio dashboard.", - "default": true - }, - "port": { - "anyOf": [ - { - "type": "number" }, - { - "$ref": "#/$defs/Union_" + "notification": { + "anyOf": [ + { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the notification email.", + "default": false + }, + "subject": { + "type": "string", + "description": "Subject line for the notification email.", + "default": "" + }, + "content_path": { + "type": "string", + "description": "Path to the HTML notification template.", + "default": "" + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "description": "Notification email configuration.", + "default": {} + }, + { + "type": "null" + } + ] } - ] - }, - "api_url": { - "type": "string", - "description": "External URL of the API server that frontend connects to.", - "default": "http://127.0.0.1" + }, + "additionalProperties": false }, - "openai_api_key": { - "type": "string", - "description": "OpenAI API key to use for Supabase AI in the Supabase Studio.", - "examples": [ - "env(OPENAI_API_KEY)" - ] - } - }, - "additionalProperties": false - }, - "workers": { - "anyOf": [ - { - "$ref": "#/$defs/Objects_27" - }, - { - "type": "null" - } - ] - }, - "experimental": { - "type": "object", - "properties": { - "orioledb_version": { - "type": "string", - "description": "Postgres storage engine version for OrioleDB." - }, - "s3_host": { - "type": "string", - "description": "S3 bucket URL.", - "examples": [ - ".s3-.amazonaws.com", - "env(S3_HOST)" - ] - }, - "s3_region": { - "type": "string", - "description": "S3 bucket region.", - "examples": [ - "us-east-1", - "env(S3_REGION)" - ] - }, - "s3_access_key": { - "type": "string", - "description": "S3 access key.", - "examples": [ - "env(S3_ACCESS_KEY)" - ] - }, - "s3_secret_key": { - "type": "string", - "description": "S3 secret key.", - "examples": [ - "env(S3_SECRET_KEY)" - ] - }, - "webhooks": { - "$ref": "#/$defs/Objects_28" - }, - "pgdelta": { - "$ref": "#/$defs/Objects_29" + "sms": { + "type": "object", + "properties": { + "enable_signup": { + "type": "boolean", + "description": "Allow/disallow new user signups via SMS to your project.", + "default": false + }, + "enable_confirmations": { + "type": "boolean", + "description": "If enabled, users need to confirm their phone number before signing in.", + "default": false + }, + "template": { + "type": "string", + "description": "The template to use for the SMS message.", + "default": "Your code is {{ .Code }}" + }, + "max_frequency": { + "type": "string", + "description": "Controls the minimum amount of time that must pass before sending another sms otp.", + "default": "5s" + }, + "twilio": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable Twilio provider for phone login.", + "default": false + }, + "account_sid": { + "type": "string", + "description": "The account SID for the Twilio API.", + "default": "" + }, + "message_service_sid": { + "type": "string", + "description": "The message service SID for the Twilio API.", + "default": "" + }, + "auth_token": { + "type": "string", + "description": "The auth token for the Twilio API.", + "examples": ["env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"] + } + }, + "additionalProperties": false + }, + "twilio_verify": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable Twilio Verify provider for phone verification.", + "default": false + }, + "account_sid": { + "type": "string", + "description": "The account SID for the Twilio API." + }, + "message_service_sid": { + "type": "string", + "description": "The message service SID for the Twilio API." + }, + "auth_token": { + "type": "string", + "description": "The auth token for the Twilio API." + } + }, + "additionalProperties": false + }, + "messagebird": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable MessageBird provider for phone login.", + "default": false + }, + "originator": { + "type": "string", + "description": "The originator of the SMS message." + }, + "access_key": { + "type": "string", + "description": "The access key for the MessageBird API." + } + }, + "additionalProperties": false + }, + "textlocal": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable Textlocal provider for phone login.", + "default": false + }, + "sender": { + "type": "string", + "description": "The sender of the SMS message." + }, + "api_key": { + "type": "string", + "description": "The API key for the Textlocal API." + } + }, + "additionalProperties": false + }, + "vonage": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable Vonage provider for phone login.", + "default": false + }, + "from": { + "type": "string", + "description": "The sender of the SMS message." + }, + "api_key": { + "type": "string", + "description": "The API key for the Vonage API." + }, + "api_secret": { + "type": "string", + "description": "The API secret for the Vonage API." + } + }, + "additionalProperties": false + }, + "test_otp": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Use pre-defined map of phone number to OTP for testing." + } + }, + "additionalProperties": false }, - "inspect": { - "$ref": "#/$defs/Objects_30" - } - }, - "additionalProperties": false - }, - "remotes": { - "anyOf": [ - { - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "properties": { - "project_id": { - "type": "string", - "description": "Remote project reference.", - "default": "" - }, - "analytics": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the local Logflare service.", - "default": true - }, - "port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "backend": { - "type": "string", - "enum": [ - "postgres", - "bigquery" - ], - "description": "Configure one of the supported backends:\n\n- `postgres`\n- `bigquery`", - "default": "postgres" - }, - "vector_port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "gcp_project_id": { - "type": "string", - "description": "GCP project ID." - }, - "gcp_project_number": { - "type": "string", - "description": "GCP project number." - }, - "gcp_jwt_path": { - "type": "string", - "description": "Path to the GCP JWT file." - } - }, - "additionalProperties": false - }, - "api": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the local PostgREST service.", - "default": true - }, - "port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "schemas": { - "$ref": "#/$defs/Arrays_" - }, - "extra_search_path": { - "$ref": "#/$defs/Arrays_1" - }, - "max_rows": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "auto_expose_new_tables": { - "type": "boolean", - "description": "Controls whether newly-created tables, views, sequences and functions in the `public` schema by `postgres` are reachable through the Data API roles (`anon`, `authenticated`, `service_role`) without explicit GRANTs. When unset, new entities are auto-exposed, matching the cloud default. Set to `false` to revoke the default Data API privileges so new entities require explicit GRANTs, matching a cloud project with the \"Default privileges for new entities\" toggle turned off." - }, - "tls": { - "$ref": "#/$defs/Objects_" - }, - "external_url": { - "type": "string", - "description": "External URL for accessing the API server." - } - }, - "additionalProperties": false - }, - "auth": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the local GoTrue service.", - "default": true - }, - "site_url": { - "type": "string", - "description": "The base URL of your website. Used as an allow-list for redirects and for constructing URLs used in emails.", - "default": "http://127.0.0.1:3000" - }, - "additional_redirect_urls": { - "$ref": "#/$defs/Arrays_2" - }, - "jwt_expiry": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "jwt_issuer": { - "type": "string", - "description": "JWT issuer URL." - }, - "signing_keys_path": { - "type": "string", - "description": "Path to the JWT signing keys file." - }, - "enable_refresh_token_rotation": { - "type": "boolean", - "description": "If disabled, the refresh token will never expire.", - "default": true - }, - "refresh_token_reuse_interval": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "enable_manual_linking": { - "type": "boolean", - "description": "Allow/disallow testing manual linking of accounts.", - "default": false - }, - "enable_signup": { - "type": "boolean", - "description": "Allow/disallow new user signups to your project.", - "default": true - }, - "enable_anonymous_sign_ins": { - "type": "boolean", - "description": "Allow/disallow anonymous sign-ins to your project.", - "default": false - }, - "minimum_password_length": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "password_requirements": { - "$ref": "#/$defs/Union_1" - }, - "publishable_key": { - "type": "string", - "description": "Publishable key override." - }, - "secret_key": { - "type": "string", - "description": "Secret key override." - }, - "jwt_secret": { - "type": "string", - "description": "JWT secret override." - }, - "anon_key": { - "type": "string", - "description": "Anon key override." - }, - "service_role_key": { - "type": "string", - "description": "Service role key override." - }, - "rate_limit": { - "$ref": "#/$defs/Objects_1" - }, - "captcha": { - "$ref": "#/$defs/Objects_2" - }, - "hook": { - "$ref": "#/$defs/Objects_3" - }, - "mfa": { - "$ref": "#/$defs/Objects_4" - }, - "sessions": { - "$ref": "#/$defs/Objects_5" - }, - "email": { - "$ref": "#/$defs/Objects_6" - }, - "sms": { - "$ref": "#/$defs/Objects_7" - }, - "external": { - "$ref": "#/$defs/Objects_8" - }, - "web3": { - "$ref": "#/$defs/Objects_9" - }, - "oauth_server": { - "$ref": "#/$defs/Objects_11" - }, - "third_party": { - "$ref": "#/$defs/Objects_12" - } - }, - "additionalProperties": false - }, - "db": { - "type": "object", - "properties": { - "port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "shadow_port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "health_timeout": { - "type": "string", - "description": "Maximum amount of time to wait for health check when starting the local database.", - "default": "2m" - }, - "major_version": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "pooler": { - "$ref": "#/$defs/Objects_13" - }, - "migrations": { - "$ref": "#/$defs/Objects_14" - }, - "seed": { - "$ref": "#/$defs/Objects_15" - }, - "settings": { - "$ref": "#/$defs/Objects_16" - }, - "network_restrictions": { - "$ref": "#/$defs/Objects_17" - }, - "ssl_enforcement": { - "$ref": "#/$defs/Objects_18" - }, - "vault": { - "$ref": "#/$defs/Objects_19" - } - }, - "additionalProperties": false - }, - "edge_runtime": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the local Edge Runtime service.", - "default": true - }, - "policy": { - "type": "string", - "enum": [ - "oneshot", - "per_worker" - ], - "description": "Configure the supported request policy.", - "default": "per_worker" - }, - "inspector_port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "deno_version": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "secrets": { - "$ref": "#/$defs/Objects_20" - } - }, - "additionalProperties": false - }, - "functions": { - "anyOf": [ - { - "$ref": "#/$defs/Objects_21" - }, - { - "type": "null" - } - ] - }, - "local_smtp": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the local SMTP testing server.", - "default": true - }, - "port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "smtp_port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "pop3_port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "admin_email": { - "type": "string", - "description": "Admin email address for test email sender metadata." - }, - "sender_name": { - "type": "string", - "description": "Sender name for test email sender metadata." - } - }, - "additionalProperties": false - }, - "realtime": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the local Realtime service.", - "default": true - }, - "ip_version": { - "type": "string", - "enum": [ - "IPv4", - "IPv6" - ], - "description": "Bind realtime via either IPv4 or IPv6.", - "default": "IPv4" - }, - "max_header_length": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - } - }, - "additionalProperties": false - }, - "storage": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the local Storage service.", - "default": true - }, - "file_size_limit": { - "anyOf": [ - { - "type": "string" - }, - { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - } - ] - }, - "image_transformation": { - "$ref": "#/$defs/Objects_22" - }, - "buckets": { - "$ref": "#/$defs/Objects_23" - }, - "s3_protocol": { - "$ref": "#/$defs/Objects_24" - }, - "analytics": { - "$ref": "#/$defs/Objects_25" - }, - "vector": { - "$ref": "#/$defs/Objects_26" - } - }, - "additionalProperties": false - }, - "studio": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the local Supabase Studio dashboard.", - "default": true - }, - "port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "api_url": { - "type": "string", - "description": "External URL of the API server that frontend connects to.", - "default": "http://127.0.0.1" - }, - "openai_api_key": { - "type": "string", - "description": "OpenAI API key to use for Supabase AI in the Supabase Studio.", - "examples": [ - "env(OPENAI_API_KEY)" - ] - } - }, - "additionalProperties": false - }, - "workers": { - "anyOf": [ - { - "$ref": "#/$defs/Objects_27" - }, - { - "type": "null" - } - ] - }, - "experimental": { - "type": "object", - "properties": { - "orioledb_version": { - "type": "string", - "description": "Postgres storage engine version for OrioleDB." - }, - "s3_host": { - "type": "string", - "description": "S3 bucket URL.", - "examples": [ - ".s3-.amazonaws.com", - "env(S3_HOST)" - ] - }, - "s3_region": { - "type": "string", - "description": "S3 bucket region.", - "examples": [ - "us-east-1", - "env(S3_REGION)" - ] - }, - "s3_access_key": { - "type": "string", - "description": "S3 access key.", - "examples": [ - "env(S3_ACCESS_KEY)" - ] - }, - "s3_secret_key": { - "type": "string", - "description": "S3 secret key.", - "examples": [ - "env(S3_SECRET_KEY)" - ] - }, - "webhooks": { - "$ref": "#/$defs/Objects_28" - }, - "pgdelta": { - "$ref": "#/$defs/Objects_29" - }, - "inspect": { - "$ref": "#/$defs/Objects_30" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "null" - } - ] - }, - "description": "Remote branch-specific project configuration.", - "default": {} - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false, - "$defs": { - "Union_": { - "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] - }, - "Arrays_": { - "type": "array", - "items": { - "type": "string", - "description": "Schemas to expose in your API. Tables, views and stored procedures in this schema will get API endpoints." - }, - "default": [ - "public", - "graphql_public" - ] - }, - "Arrays_1": { - "type": "array", - "items": { - "type": "string", - "description": "Extra schemas to add to the search_path of every request." - }, - "default": [ - "public", - "extensions" - ] - }, - "Objects_": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable HTTPS endpoints locally using a self-signed certificate.", - "default": false - }, - "cert_path": { - "type": "string", - "description": "Path to the self-signed certificate." - }, - "key_path": { - "type": "string", - "description": "Path to the self-signed certificate private key." - } - }, - "additionalProperties": false - }, - "Arrays_2": { - "type": "array", - "items": { - "type": "string", - "description": "A URL that auth providers are permitted to redirect to." - }, - "description": "A list of exact URLs that auth providers are permitted to redirect to post authentication.", - "default": [ - "https://127.0.0.1:3000" - ] - }, - "Union_1": { - "type": "string", - "enum": [ - "", - "letters_digits", - "lower_upper_letters_digits", - "lower_upper_letters_digits_symbols" - ], - "description": "Password character requirements.", - "default": "" - }, - "Objects_1": { - "type": "object", - "properties": { - "email_sent": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "sms_sent": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "anonymous_users": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "token_refresh": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "sign_in_sign_ups": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "token_verifications": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "web3": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - } - }, - "additionalProperties": false - }, - "Objects_2": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable CAPTCHA verification.", - "default": false - }, - "provider": { - "type": "string", - "enum": [ - "hcaptcha", - "turnstile" - ], - "description": "CAPTCHA provider to use." - }, - "secret": { - "type": "string", - "description": "Secret key for the CAPTCHA provider." - } - }, - "additionalProperties": false - }, - "Objects_3": { - "type": "object", - "properties": { - "mfa_verification_attempt": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable or disable the mfa verification hook.", - "default": false - }, - "uri": { - "type": "string", - "description": "The URI of the postgres function or HTTP endpoint to call." - }, - "secrets": { - "type": "string", - "description": "Secret value to pass to the function or endpoint." - } - }, - "additionalProperties": false - }, - "password_verification_attempt": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable or disable the password verification hook.", - "default": false - }, - "uri": { - "type": "string", - "description": "The URI of the postgres function or HTTP endpoint to call." - }, - "secrets": { - "type": "string", - "description": "Secret value to pass to the function or endpoint." - } - }, - "additionalProperties": false - }, - "custom_access_token": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable or disable the custom access token hook.", - "default": false - }, - "uri": { - "type": "string", - "description": "The URI of the postgres function or HTTP endpoint to call." - }, - "secrets": { - "type": "string", - "description": "Secret value to pass to the function or endpoint." - } - }, - "additionalProperties": false - }, - "send_sms": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable or disable the send sms hook.", - "default": false - }, - "uri": { - "type": "string", - "description": "The URI of the postgres function or HTTP endpoint to call." - }, - "secrets": { - "type": "string", - "description": "Secret value to pass to the function or endpoint." - } - }, - "additionalProperties": false - }, - "send_email": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable or disable the send email hook.", - "default": false - }, - "uri": { - "type": "string", - "description": "The URI of the postgres function or HTTP endpoint to call." - }, - "secrets": { - "type": "string", - "description": "Secret value to pass to the function or endpoint." - } - }, - "additionalProperties": false - }, - "before_user_created": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable or disable the before user created hook.", - "default": false - }, - "uri": { - "type": "string", - "description": "The URI of the postgres function or HTTP endpoint to call." - }, - "secrets": { - "type": "string", - "description": "Secret value to pass to the function or endpoint." - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "Objects_4": { - "type": "object", - "properties": { - "totp": { - "type": "object", - "properties": { - "enroll_enabled": { - "type": "boolean", - "description": "Allow/disallow TOTP enrollment for users.", - "default": false - }, - "verify_enabled": { - "type": "boolean", - "description": "Allow/disallow TOTP verification for users.", - "default": false - } - }, - "additionalProperties": false - }, - "phone": { - "type": "object", - "properties": { - "enroll_enabled": { - "type": "boolean", - "description": "Allow/disallow phone enrollment for users.", - "default": false - }, - "verify_enabled": { - "type": "boolean", - "description": "Allow/disallow phone verification for users.", - "default": false - }, - "otp_length": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "template": { - "type": "string", - "description": "The template to use for the phone message.", - "default": "Your code is {{ .Code }}" - }, - "max_frequency": { - "type": "string", - "description": "The maximum frequency of the phone messages.", - "default": "5s" - } - }, - "additionalProperties": false - }, - "web_authn": { - "type": "object", - "properties": { - "enroll_enabled": { - "type": "boolean", - "description": "Allow/disallow WebAuthn enrollment for users.", - "default": false - }, - "verify_enabled": { - "type": "boolean", - "description": "Allow/disallow WebAuthn verification for users.", - "default": false - } - }, - "additionalProperties": false - }, - "max_enrolled_factors": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - } - }, - "additionalProperties": false - }, - "Objects_5": { - "type": "object", - "properties": { - "timebox": { - "type": "string", - "description": "The timebox for the user session." - }, - "inactivity_timeout": { - "type": "string", - "description": "The inactivity timeout for the user session." - } - }, - "additionalProperties": false, - "default": {} - }, - "Objects_6": { - "type": "object", - "properties": { - "enable_signup": { - "type": "boolean", - "description": "Allow/disallow new user signups via email to your project.", - "default": true - }, - "double_confirm_changes": { - "type": "boolean", - "description": "If enabled, a user will be required to confirm any email change on both the old and new email addresses.", - "default": true - }, - "enable_confirmations": { - "type": "boolean", - "description": "If enabled, users need to confirm their email address before signing in.", - "default": false - }, - "secure_password_change": { - "type": "boolean", - "description": "If enabled, users will need to reauthenticate or have logged in recently to change their password.", - "default": false - }, - "max_frequency": { - "type": "string", - "description": "Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.", - "default": "1s" - }, - "otp_length": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "otp_expiry": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "smtp": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable SMTP for email delivery.", - "default": false - }, - "host": { - "type": "string", - "description": "Hostname or IP address of the SMTP server." - }, - "port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "user": { - "type": "string", - "description": "Username for authenticating with the SMTP server." - }, - "pass": { - "type": "string", - "description": "Password for authenticating with the SMTP server." - }, - "admin_email": { - "type": "string", - "description": "Email used as the sender for emails sent from the application." - }, - "sender_name": { - "type": "string", - "description": "Display name used as the sender for emails sent from the application." - } - }, - "additionalProperties": false - }, - "template": { - "anyOf": [ - { - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "properties": { - "subject": { - "type": "string", - "description": "Subject line for the email template.", - "default": "" - }, - "content_path": { - "type": "string", - "description": "Path to the HTML template.", - "default": "" - } - }, - "additionalProperties": false - }, - { - "type": "null" - } - ] - }, - "description": "Custom email template configuration.", - "default": {} - }, - { - "type": "null" - } - ] - }, - "notification": { - "anyOf": [ - { - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable the notification email.", - "default": false - }, - "subject": { - "type": "string", - "description": "Subject line for the notification email.", - "default": "" - }, - "content_path": { - "type": "string", - "description": "Path to the HTML notification template.", - "default": "" - } - }, - "additionalProperties": false - }, - { - "type": "null" - } - ] - }, - "description": "Notification email configuration.", - "default": {} - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - }, - "Objects_7": { - "type": "object", - "properties": { - "enable_signup": { - "type": "boolean", - "description": "Allow/disallow new user signups via SMS to your project.", - "default": false - }, - "enable_confirmations": { - "type": "boolean", - "description": "If enabled, users need to confirm their phone number before signing in.", - "default": false - }, - "template": { - "type": "string", - "description": "The template to use for the SMS message.", - "default": "Your code is {{ .Code }}" - }, - "max_frequency": { - "type": "string", - "description": "Controls the minimum amount of time that must pass before sending another sms otp.", - "default": "5s" - }, - "twilio": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable/disable Twilio provider for phone login.", - "default": false - }, - "account_sid": { - "type": "string", - "description": "The account SID for the Twilio API.", - "default": "" - }, - "message_service_sid": { - "type": "string", - "description": "The message service SID for the Twilio API.", - "default": "" - }, - "auth_token": { - "type": "string", - "description": "The auth token for the Twilio API.", - "examples": [ - "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" - ] - } - }, - "additionalProperties": false - }, - "twilio_verify": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable/disable Twilio Verify provider for phone verification.", - "default": false - }, - "account_sid": { - "type": "string", - "description": "The account SID for the Twilio API." - }, - "message_service_sid": { - "type": "string", - "description": "The message service SID for the Twilio API." - }, - "auth_token": { - "type": "string", - "description": "The auth token for the Twilio API." - } - }, - "additionalProperties": false - }, - "messagebird": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable/disable MessageBird provider for phone login.", - "default": false - }, - "originator": { - "type": "string", - "description": "The originator of the SMS message." - }, - "access_key": { - "type": "string", - "description": "The access key for the MessageBird API." - } - }, - "additionalProperties": false - }, - "textlocal": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable/disable Textlocal provider for phone login.", - "default": false - }, - "sender": { - "type": "string", - "description": "The sender of the SMS message." - }, - "api_key": { - "type": "string", - "description": "The API key for the Textlocal API." - } - }, - "additionalProperties": false - }, - "vonage": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable/disable Vonage provider for phone login.", - "default": false - }, - "from": { - "type": "string", - "description": "The sender of the SMS message." - }, - "api_key": { - "type": "string", - "description": "The API key for the Vonage API." - }, - "api_secret": { - "type": "string", - "description": "The API secret for the Vonage API." - } - }, - "additionalProperties": false - }, - "test_otp": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "description": "Use pre-defined map of phone number to OTP for testing." - } - }, - "additionalProperties": false - }, - "Objects_8": { - "type": "object", - "properties": { - "apple": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Use the Apple OAuth provider.", - "default": false - }, - "client_id": { - "type": "string", - "description": "Client ID for the Apple OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the Apple OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" - ] - }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" - }, - "redirect_uri": { - "type": "string", - "description": "The URI the Apple OAuth2 provider will redirect to with the code and state values.", - "default": "" - }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false - }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false - } - }, - "additionalProperties": false - }, - "azure": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Use the Azure OAuth provider.", - "default": false - }, - "client_id": { - "type": "string", - "description": "Client ID for the Azure OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the Azure OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_AZURE_SECRET)" - ] - }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" - }, - "redirect_uri": { - "type": "string", - "description": "The URI the Azure OAuth2 provider will redirect to with the code and state values.", - "default": "" - }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false - }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false - } - }, - "additionalProperties": false - }, - "bitbucket": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Use the Bitbucket OAuth provider.", - "default": false - }, - "client_id": { - "type": "string", - "description": "Client ID for the Bitbucket OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the Bitbucket OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_BITBUCKET_SECRET)" - ] - }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" - }, - "redirect_uri": { - "type": "string", - "description": "The URI the Bitbucket OAuth2 provider will redirect to with the code and state values.", - "default": "" - }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false - }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false - } - }, - "additionalProperties": false - }, - "discord": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Use the Discord OAuth provider.", - "default": false - }, - "client_id": { - "type": "string", - "description": "Client ID for the Discord OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the Discord OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_DISCORD_SECRET)" - ] - }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" - }, - "redirect_uri": { - "type": "string", - "description": "The URI the Discord OAuth2 provider will redirect to with the code and state values.", - "default": "" - }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false - }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false - } - }, - "additionalProperties": false - }, - "facebook": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Use the Facebook OAuth provider.", - "default": false - }, - "client_id": { - "type": "string", - "description": "Client ID for the Facebook OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the Facebook OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_FACEBOOK_SECRET)" - ] - }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" - }, - "redirect_uri": { - "type": "string", - "description": "The URI the Facebook OAuth2 provider will redirect to with the code and state values.", - "default": "" - }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false - }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false - } - }, - "additionalProperties": false - }, - "github": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Use the GitHub OAuth provider.", - "default": false - }, - "client_id": { - "type": "string", - "description": "Client ID for the GitHub OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the GitHub OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_GITHUB_SECRET)" - ] - }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" - }, - "redirect_uri": { - "type": "string", - "description": "The URI the GitHub OAuth2 provider will redirect to with the code and state values.", - "default": "" - }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false - }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false - } - }, - "additionalProperties": false - }, - "gitlab": { + "external": { "type": "object", "properties": { - "enabled": { - "type": "boolean", - "description": "Use the GitLab OAuth provider.", - "default": false + "apple": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Apple OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Apple OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Apple OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Apple OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "client_id": { - "type": "string", - "description": "Client ID for the GitLab OAuth provider.", - "default": "" + "azure": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Azure OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Azure OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Azure OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_AZURE_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Azure OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "secret": { - "type": "string", - "description": "Client secret for the GitLab OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_GITLAB_SECRET)" - ] + "bitbucket": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Bitbucket OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Bitbucket OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Bitbucket OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_BITBUCKET_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Bitbucket OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "https://gitlab.com" + "discord": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Discord OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Discord OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Discord OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_DISCORD_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Discord OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "redirect_uri": { - "type": "string", - "description": "The URI the GitLab OAuth2 provider will redirect to with the code and state values.", - "default": "" + "facebook": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Facebook OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Facebook OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Facebook OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_FACEBOOK_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Facebook OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false + "github": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the GitHub OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the GitHub OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the GitHub OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_GITHUB_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the GitHub OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false - } - }, - "additionalProperties": false - }, - "google": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Use the Google OAuth provider.", - "default": false + "gitlab": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the GitLab OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the GitLab OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the GitLab OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_GITLAB_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "https://gitlab.com" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the GitLab OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "client_id": { - "type": "string", - "description": "Client ID for the Google OAuth provider.", - "default": "" + "google": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Google OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Google OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Google OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_GOOGLE_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Google OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "secret": { - "type": "string", - "description": "Client secret for the Google OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_GOOGLE_SECRET)" - ] + "kakao": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Kakao OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Kakao OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Kakao OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_KAKAO_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Kakao OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" + "keycloak": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Keycloak OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Keycloak OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Keycloak OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_KEYCLOAK_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "", + "examples": ["https://keycloak.example.com/realms/myrealm"] + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Keycloak OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "redirect_uri": { - "type": "string", - "description": "The URI the Google OAuth2 provider will redirect to with the code and state values.", - "default": "" + "linkedin_oidc": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the LinkedIn OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the LinkedIn OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the LinkedIn OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_LINKEDIN_OIDC_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the LinkedIn OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false + "notion": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Notion OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Notion OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Notion OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_NOTION_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Notion OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false - } - }, - "additionalProperties": false - }, - "kakao": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Use the Kakao OAuth provider.", - "default": false + "twitch": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Twitch OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Twitch OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Twitch OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_TWITCH_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Twitch OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "client_id": { - "type": "string", - "description": "Client ID for the Kakao OAuth provider.", - "default": "" + "twitter": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Twitter OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Twitter OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Twitter OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_TWITTER_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Twitter OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "secret": { - "type": "string", - "description": "Client secret for the Kakao OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_KAKAO_SECRET)" - ] + "x": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the X OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the X OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the X OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_X_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the X OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" + "slack_oidc": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Slack OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Slack OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Slack OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_SLACK_OIDC_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Slack OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "redirect_uri": { - "type": "string", - "description": "The URI the Kakao OAuth2 provider will redirect to with the code and state values.", - "default": "" + "spotify": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Spotify OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Spotify OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Spotify OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_SPOTIFY_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Spotify OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false + "workos": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the WorkOS OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the WorkOS OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the WorkOS OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_WORKOS_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the WorkOS OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "zoom": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Zoom OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Zoom OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Zoom OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_ZOOM_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Zoom OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false } }, "additionalProperties": false }, - "keycloak": { + "web3": { "type": "object", "properties": { - "enabled": { - "type": "boolean", - "description": "Use the Keycloak OAuth provider.", - "default": false - }, - "client_id": { - "type": "string", - "description": "Client ID for the Keycloak OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the Keycloak OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_KEYCLOAK_SECRET)" - ] - }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "", - "examples": [ - "https://keycloak.example.com/realms/myrealm" - ] - }, - "redirect_uri": { - "type": "string", - "description": "The URI the Keycloak OAuth2 provider will redirect to with the code and state values.", - "default": "" - }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false + "solana": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this Web3 provider.", + "default": false + } + }, + "additionalProperties": false }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "ethereum": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this Web3 provider.", + "default": false + } + }, + "additionalProperties": false } }, "additionalProperties": false }, - "linkedin_oidc": { + "oauth_server": { "type": "object", "properties": { "enabled": { "type": "boolean", - "description": "Use the LinkedIn OAuth provider.", + "description": "Enable OAuth server functionality.", "default": false }, - "client_id": { - "type": "string", - "description": "Client ID for the LinkedIn OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the LinkedIn OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_LINKEDIN_OIDC_SECRET)" - ] - }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" - }, - "redirect_uri": { + "authorization_url_path": { "type": "string", - "description": "The URI the LinkedIn OAuth2 provider will redirect to with the code and state values.", - "default": "" - }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false + "description": "Path for OAuth consent flow UI.", + "default": "/oauth/consent" }, - "email_optional": { + "allow_dynamic_registration": { "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", + "description": "Allow dynamic client registration.", "default": false } }, "additionalProperties": false }, - "notion": { + "third_party": { "type": "object", "properties": { - "enabled": { - "type": "boolean", - "description": "Use the Notion OAuth provider.", - "default": false - }, - "client_id": { - "type": "string", - "description": "Client ID for the Notion OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the Notion OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_NOTION_SECRET)" - ] + "firebase": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "project_id": { + "type": "string", + "description": "Firebase project ID." + } + }, + "additionalProperties": false }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" + "auth0": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "tenant": { + "type": "string", + "description": "Auth0 tenant." + }, + "tenant_region": { + "type": "string", + "description": "Auth0 tenant region." + } + }, + "additionalProperties": false }, - "redirect_uri": { - "type": "string", - "description": "The URI the Notion OAuth2 provider will redirect to with the code and state values.", - "default": "" + "aws_cognito": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "user_pool_id": { + "type": "string", + "description": "AWS Cognito user pool ID." + }, + "user_pool_region": { + "type": "string", + "description": "AWS Cognito user pool region." + } + }, + "additionalProperties": false }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false + "clerk": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "domain": { + "type": "string", + "description": "Clerk domain." + } + }, + "additionalProperties": false }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "workos": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "issuer_url": { + "type": "string", + "description": "WorkOS issuer URL." + } + }, + "additionalProperties": false } }, "additionalProperties": false + } + }, + "additionalProperties": false + }, + "db": { + "type": "object", + "properties": { + "port": { + "type": "number", + "description": "Port to use for the local database URL.", + "default": 54322 + }, + "shadow_port": { + "type": "number", + "description": "Port used by db diff command to initialize the shadow database.", + "default": 54320 + }, + "health_timeout": { + "type": "string", + "description": "Maximum amount of time to wait for health check when starting the local database.", + "default": "2m" + }, + "major_version": { + "type": "number", + "description": "The database major version to use. This has to be the same as your remote database's.", + "default": 17 }, - "twitch": { + "pooler": { "type": "object", "properties": { "enabled": { "type": "boolean", - "description": "Use the Twitch OAuth provider.", + "description": "Enable the local PgBouncer service.", "default": false }, - "client_id": { - "type": "string", - "description": "Client ID for the Twitch OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the Twitch OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_TWITCH_SECRET)" - ] - }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" + "port": { + "type": "number", + "description": "Port to use for the local connection pooler.", + "default": 54329 }, - "redirect_uri": { + "pool_mode": { "type": "string", - "description": "The URI the Twitch OAuth2 provider will redirect to with the code and state values.", - "default": "" - }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false - }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "enum": ["transaction", "session"], + "description": "Specifies when a server connection can be reused by other clients.", + "default": "transaction" + }, + "default_pool_size": { + "type": "number", + "description": "How many server connections to allow per user/database pair.", + "default": 20 + }, + "max_client_conn": { + "type": "number", + "description": "Maximum number of client connections allowed.", + "default": 100 } }, "additionalProperties": false }, - "twitter": { + "migrations": { "type": "object", "properties": { "enabled": { "type": "boolean", - "description": "Use the Twitter OAuth provider.", - "default": false - }, - "client_id": { - "type": "string", - "description": "Client ID for the Twitter OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the Twitter OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_TWITTER_SECRET)" - ] - }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" - }, - "redirect_uri": { - "type": "string", - "description": "The URI the Twitter OAuth2 provider will redirect to with the code and state values.", - "default": "" - }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false + "description": "If disabled, migrations will be skipped during a db push or reset.", + "default": true }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "schema_paths": { + "type": "array", + "items": { + "type": "string", + "description": "Schema file path, directory, or glob relative to the supabase directory." + }, + "description": "Ordered list of schema files, directories, or glob patterns that describe your database.", + "default": [] } }, "additionalProperties": false }, - "x": { + "seed": { "type": "object", "properties": { "enabled": { "type": "boolean", - "description": "Use the X OAuth provider.", - "default": false - }, - "client_id": { - "type": "string", - "description": "Client ID for the X OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the X OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_X_SECRET)" - ] + "description": "Enable seeding the database with SQL files.", + "default": true }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" - }, - "redirect_uri": { - "type": "string", - "description": "The URI the X OAuth2 provider will redirect to with the code and state values.", - "default": "" - }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false - }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "sql_paths": { + "type": "array", + "items": { + "type": "string", + "description": "Path to a SQL file used to seed the database." + }, + "description": "Ordered list of seed files to load during db reset.", + "default": ["./seed.sql"] } }, "additionalProperties": false }, - "slack_oidc": { + "settings": { "type": "object", "properties": { - "enabled": { - "type": "boolean", - "description": "Use the Slack OAuth provider.", - "default": false + "effective_cache_size": { + "type": "string" }, - "client_id": { - "type": "string", - "description": "Client ID for the Slack OAuth provider.", - "default": "" + "logical_decoding_work_mem": { + "type": "string" }, - "secret": { - "type": "string", - "description": "Client secret for the Slack OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_SLACK_OIDC_SECRET)" - ] + "maintenance_work_mem": { + "type": "string" }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" + "max_connections": { + "type": "number" }, - "redirect_uri": { - "type": "string", - "description": "The URI the Slack OAuth2 provider will redirect to with the code and state values.", - "default": "" + "max_locks_per_transaction": { + "type": "number" }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false + "max_parallel_maintenance_workers": { + "type": "number" }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false - } - }, - "additionalProperties": false - }, - "spotify": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Use the Spotify OAuth provider.", - "default": false + "max_parallel_workers": { + "type": "number" }, - "client_id": { - "type": "string", - "description": "Client ID for the Spotify OAuth provider.", - "default": "" + "max_parallel_workers_per_gather": { + "type": "number" }, - "secret": { - "type": "string", - "description": "Client secret for the Spotify OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_SPOTIFY_SECRET)" - ] + "max_replication_slots": { + "type": "number" }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" + "max_slot_wal_keep_size": { + "type": "string" + }, + "max_standby_archive_delay": { + "type": "string" + }, + "max_standby_streaming_delay": { + "type": "string" + }, + "max_wal_size": { + "type": "string" + }, + "max_wal_senders": { + "type": "number" + }, + "max_worker_processes": { + "type": "number" }, - "redirect_uri": { + "session_replication_role": { "type": "string", - "description": "The URI the Spotify OAuth2 provider will redirect to with the code and state values.", - "default": "" + "enum": ["origin", "replica", "local"], + "description": "Session replication role." }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false + "shared_buffers": { + "type": "string" }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "statement_timeout": { + "type": "string" + }, + "track_activity_query_size": { + "type": "string" + }, + "track_commit_timestamp": { + "type": "boolean" + }, + "wal_keep_size": { + "type": "string" + }, + "wal_sender_timeout": { + "type": "string" + }, + "work_mem": { + "type": "string" } }, "additionalProperties": false }, - "workos": { + "network_restrictions": { "type": "object", "properties": { "enabled": { "type": "boolean", - "description": "Use the WorkOS OAuth provider.", + "description": "Enable management of network restrictions.", "default": false }, - "client_id": { - "type": "string", - "description": "Client ID for the WorkOS OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the WorkOS OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_WORKOS_SECRET)" - ] - }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" - }, - "redirect_uri": { - "type": "string", - "description": "The URI the WorkOS OAuth2 provider will redirect to with the code and state values.", - "default": "" - }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false + "allowed_cidrs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allowed IPv4 CIDR blocks.", + "default": ["0.0.0.0/0"] }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "allowed_cidrs_v6": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allowed IPv6 CIDR blocks.", + "default": ["::/0"] } }, "additionalProperties": false }, - "zoom": { + "ssl_enforcement": { "type": "object", "properties": { "enabled": { "type": "boolean", - "description": "Use the Zoom OAuth provider.", - "default": false - }, - "client_id": { - "type": "string", - "description": "Client ID for the Zoom OAuth provider.", - "default": "" - }, - "secret": { - "type": "string", - "description": "Client secret for the Zoom OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", - "examples": [ - "env(SUPABASE_AUTH_EXTERNAL_ZOOM_SECRET)" - ] - }, - "url": { - "type": "string", - "description": "The base URL used for constructing the URLs to request authorization and access tokens.", - "default": "" - }, - "redirect_uri": { - "type": "string", - "description": "The URI the Zoom OAuth2 provider will redirect to with the code and state values.", - "default": "" - }, - "skip_nonce_check": { - "type": "boolean", - "description": "If true, the nonce check will be skipped.", - "default": false - }, - "email_optional": { - "type": "boolean", - "description": "If true, authentication succeeds when the provider does not return an email address.", + "description": "Reject non-secure connections to the database.", "default": false } }, "additionalProperties": false + }, + "vault": { + "type": "object", + "additionalProperties": { + "type": "string", + "description": "Vault secret value." + }, + "description": "Vault secrets." } }, "additionalProperties": false }, - "Objects_10": { + "edge_runtime": { "type": "object", "properties": { "enabled": { "type": "boolean", - "description": "Enable this Web3 provider.", - "default": false + "description": "Enable the local Edge Runtime service.", + "default": true + }, + "policy": { + "type": "string", + "enum": ["oneshot", "per_worker"], + "description": "Configure the supported request policy.", + "default": "per_worker" + }, + "inspector_port": { + "type": "number", + "description": "Port to run the Edge Functions inspector on.", + "default": 8083 + }, + "deno_version": { + "type": "number", + "description": "The Deno major version to use.", + "default": 2 + }, + "secrets": { + "type": "object", + "additionalProperties": { + "type": "string", + "description": "Secret value exposed to the edge runtime." + }, + "description": "Secrets exposed to the edge runtime." } }, "additionalProperties": false }, - "Objects_9": { + "functions": { + "anyOf": [ + { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "anyOf": [ + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Controls whether a function is deployed or served.", + "default": true + }, + "verify_jwt": { + "type": "boolean", + "description": "By default, deployed or locally served functions reject requests without a valid JWT.", + "default": true + }, + "import_map": { + "type": "string", + "description": "Import map file to use for the Function.", + "default": "" + }, + "entrypoint": { + "type": "string", + "description": "Entrypoint path to the Function. Defaults to \"functions/slug/index.ts\".", + "default": "" + }, + "static_files": { + "type": "array", + "items": { + "type": "string", + "description": "Static file glob for the function." + }, + "description": "Static files to bundle with the function.", + "default": [] + }, + "env": { + "type": "object", + "patternProperties": { + "^[A-Z_][A-Z0-9_]*$": { + "type": "string", + "pattern": "^env\\((.*)\\)$", + "description": "Reference to a project environment variable available to the Function." + } + }, + "description": "Environment variables from the project environment that this Function can access.", + "default": {} + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + } + }, + "description": "Function-specific configuration keyed by function slug.", + "default": {} + }, + { + "type": "null" + } + ] + }, + "local_smtp": { "type": "object", "properties": { - "solana": { - "$ref": "#/$defs/Objects_10" + "enabled": { + "type": "boolean", + "description": "Enable the local SMTP testing server.", + "default": true + }, + "port": { + "type": "number", + "description": "Port to use for the email testing server web interface.\n\nEmails sent with the local dev setup are monitored and available from the web interface.", + "default": 54324 + }, + "smtp_port": { + "type": "number", + "description": "Optional SMTP port to expose for local testing." + }, + "pop3_port": { + "type": "number", + "description": "Optional POP3 port to expose for local testing." + }, + "admin_email": { + "type": "string", + "description": "Admin email address for test email sender metadata." }, - "ethereum": { - "$ref": "#/$defs/Objects_10" + "sender_name": { + "type": "string", + "description": "Sender name for test email sender metadata." } }, "additionalProperties": false }, - "Objects_11": { + "realtime": { "type": "object", "properties": { "enabled": { "type": "boolean", - "description": "Enable OAuth server functionality.", - "default": false + "description": "Enable the local Realtime service.", + "default": true }, - "authorization_url_path": { + "ip_version": { "type": "string", - "description": "Path for OAuth consent flow UI.", - "default": "/oauth/consent" - }, - "allow_dynamic_registration": { - "type": "boolean", - "description": "Allow dynamic client registration.", - "default": false + "enum": ["IPv4", "IPv6"], + "description": "Bind realtime via either IPv4 or IPv6.", + "default": "IPv4" + }, + "max_header_length": { + "type": "number", + "description": "Maximum length of the HTTP header.", + "default": 4096 } }, "additionalProperties": false }, - "Objects_12": { + "storage": { "type": "object", "properties": { - "firebase": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable this third-party auth provider.", - "default": false + "enabled": { + "type": "boolean", + "description": "Enable the local Storage service.", + "default": true + }, + "file_size_limit": { + "anyOf": [ + { + "type": "string" }, - "project_id": { - "type": "string", - "description": "Firebase project ID." + { + "type": "number" } - }, - "additionalProperties": false + ] }, - "auth0": { + "image_transformation": { "type": "object", "properties": { "enabled": { "type": "boolean", - "description": "Enable this third-party auth provider.", + "description": "Enable image transformation.", "default": false - }, - "tenant": { - "type": "string", - "description": "Auth0 tenant." - }, - "tenant_region": { - "type": "string", - "description": "Auth0 tenant region." } }, "additionalProperties": false }, - "aws_cognito": { + "buckets": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "public": { + "type": "boolean", + "description": "Enable public access to the bucket.", + "default": false + }, + "file_size_limit": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "allowed_mime_types": { + "type": "array", + "items": { + "type": "string", + "description": "A MIME type allowed for the bucket." + }, + "description": "The list of allowed MIME types for the bucket.", + "default": [] + }, + "objects_path": { + "type": "string", + "description": "The path to the objects in the bucket.", + "default": "" + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "description": "Storage buckets configuration." + }, + "s3_protocol": { "type": "object", "properties": { "enabled": { "type": "boolean", - "description": "Enable this third-party auth provider.", - "default": false - }, - "user_pool_id": { - "type": "string", - "description": "AWS Cognito user pool ID." - }, - "user_pool_region": { - "type": "string", - "description": "AWS Cognito user pool region." + "description": "Allow connections via S3 compatible clients.", + "default": true } }, "additionalProperties": false }, - "clerk": { + "analytics": { "type": "object", "properties": { "enabled": { "type": "boolean", - "description": "Enable this third-party auth provider.", + "description": "Enable analytics buckets.", "default": false }, - "domain": { - "type": "string", - "description": "Clerk domain." + "max_namespaces": { + "type": "number", + "description": "Maximum number of analytics namespaces.", + "default": 5 + }, + "max_tables": { + "type": "number", + "description": "Maximum number of analytics tables.", + "default": 10 + }, + "max_catalogs": { + "type": "number", + "description": "Maximum number of analytics catalogs.", + "default": 2 + }, + "buckets": { + "anyOf": [ + { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "object" + }, + { + "type": "array" + } + ] + }, + { + "type": "null" + } + ] + }, + "description": "Analytics bucket configuration.", + "default": {} + }, + { + "type": "null" + } + ] } }, "additionalProperties": false }, - "workos": { + "vector": { "type": "object", "properties": { "enabled": { "type": "boolean", - "description": "Enable this third-party auth provider.", - "default": false + "description": "Enable vector buckets.", + "default": true }, - "issuer_url": { - "type": "string", - "description": "WorkOS issuer URL." + "max_buckets": { + "type": "number", + "description": "Maximum number of vector buckets.", + "default": 10 + }, + "max_indexes": { + "type": "number", + "description": "Maximum number of vector indexes.", + "default": 5 + }, + "buckets": { + "anyOf": [ + { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "object" + }, + { + "type": "array" + } + ] + }, + { + "type": "null" + } + ] + }, + "description": "Vector bucket configuration.", + "default": {} + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -2823,631 +2270,2625 @@ }, "additionalProperties": false }, - "Objects_13": { + "studio": { "type": "object", "properties": { "enabled": { "type": "boolean", - "description": "Enable the local PgBouncer service.", - "default": false + "description": "Enable the local Supabase Studio dashboard.", + "default": true }, "port": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] + "type": "number", + "description": "Port to use for Supabase Studio.", + "default": 54323 }, - "pool_mode": { + "api_url": { "type": "string", - "enum": [ - "transaction", - "session" - ], - "description": "Specifies when a server connection can be reused by other clients.", - "default": "transaction" - }, - "default_pool_size": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "max_client_conn": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - } - }, - "additionalProperties": false - }, - "Objects_14": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "If disabled, migrations will be skipped during a db push or reset.", - "default": true - }, - "schema_paths": { - "type": "array", - "items": { - "type": "string", - "description": "Schema file path, directory, or glob relative to the supabase directory." - }, - "description": "Ordered list of schema files, directories, or glob patterns that describe your database.", - "default": [] - } - }, - "additionalProperties": false - }, - "Objects_15": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable seeding the database with SQL files.", - "default": true + "description": "External URL of the API server that frontend connects to.", + "default": "http://127.0.0.1" }, - "sql_paths": { - "type": "array", - "items": { - "type": "string", - "description": "Path to a SQL file used to seed the database." - }, - "description": "Ordered list of seed files to load during db reset.", - "default": [ - "./seed.sql" - ] + "openai_api_key": { + "type": "string", + "description": "OpenAI API key to use for Supabase AI in the Supabase Studio.", + "examples": ["env(OPENAI_API_KEY)"] } }, "additionalProperties": false }, - "Union_2": { + "workers": { "anyOf": [ { - "type": "number" + "type": "object", + "patternProperties": { + "^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$": { + "type": "object", + "properties": { + "runtime": { + "type": "string", + "description": "Runtime the worker is built on: `dockerfile` to build the directory's own\nDockerfile, or one of the catalog runtimes (`node`, `deno`). Guessed from\nmarker files when unset.", + "examples": ["node"] + }, + "size": { + "type": "string", + "description": "Instance size, denominated by memory. Each size implies its own vCPU count,\nso it is the one dial rather than two.", + "examples": ["2gb"] + }, + "instances": { + "type": "integer", + "minimum": 0, + "description": "Number of instances to run. Every deploy sends a complete spec, so a count\nrecorded here is what keeps a scaled worker scaled; `--instances` overrides\nit for one deploy. Defaults to 1.", + "examples": [3] + }, + "source": { + "type": "string", + "description": "Directory holding the worker's code, relative to the project root, when it\ndoes not live at `supabase/workers//`.", + "examples": ["packages/api"] + } + }, + "additionalProperties": false + } + }, + "description": "Worker-specific configuration keyed by worker name.", + "default": {} }, { - "$ref": "#/$defs/Union_" + "type": "null" } ] }, - "Objects_16": { + "experimental": { "type": "object", "properties": { - "effective_cache_size": { - "type": "string" - }, - "logical_decoding_work_mem": { - "type": "string" - }, - "maintenance_work_mem": { - "type": "string" - }, - "max_connections": { - "$ref": "#/$defs/Union_2" - }, - "max_locks_per_transaction": { - "$ref": "#/$defs/Union_2" - }, - "max_parallel_maintenance_workers": { - "$ref": "#/$defs/Union_2" - }, - "max_parallel_workers": { - "$ref": "#/$defs/Union_2" - }, - "max_parallel_workers_per_gather": { - "$ref": "#/$defs/Union_2" - }, - "max_replication_slots": { - "$ref": "#/$defs/Union_2" - }, - "max_slot_wal_keep_size": { - "type": "string" - }, - "max_standby_archive_delay": { - "type": "string" - }, - "max_standby_streaming_delay": { - "type": "string" - }, - "max_wal_size": { - "type": "string" - }, - "max_wal_senders": { - "$ref": "#/$defs/Union_2" - }, - "max_worker_processes": { - "$ref": "#/$defs/Union_2" - }, - "session_replication_role": { + "orioledb_version": { "type": "string", - "enum": [ - "origin", - "replica", - "local" - ], - "description": "Session replication role." - }, - "shared_buffers": { - "type": "string" - }, - "statement_timeout": { - "type": "string" + "description": "Postgres storage engine version for OrioleDB." }, - "track_activity_query_size": { - "type": "string" + "s3_host": { + "type": "string", + "description": "S3 bucket URL.", + "examples": [".s3-.amazonaws.com", "env(S3_HOST)"] }, - "track_commit_timestamp": { - "type": "boolean" + "s3_region": { + "type": "string", + "description": "S3 bucket region.", + "examples": ["us-east-1", "env(S3_REGION)"] }, - "wal_keep_size": { - "type": "string" + "s3_access_key": { + "type": "string", + "description": "S3 access key.", + "examples": ["env(S3_ACCESS_KEY)"] }, - "wal_sender_timeout": { - "type": "string" + "s3_secret_key": { + "type": "string", + "description": "S3 secret key.", + "examples": ["env(S3_SECRET_KEY)"] }, - "work_mem": { - "type": "string" - } - }, - "additionalProperties": false - }, - "Objects_17": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable management of network restrictions.", - "default": false + "webhooks": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable experimental webhooks.", + "default": false + } + }, + "additionalProperties": false }, - "allowed_cidrs": { - "type": "array", - "items": { - "type": "string" + "pgdelta": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use pg-delta as the schema diff engine for db diff / db pull / db remote commit. Set false to fall back to the legacy migra engine.", + "default": false + }, + "declarative_schema_path": { + "type": "string", + "description": "Directory under supabase/ where declarative schema files are written.", + "examples": ["./schemas"] + }, + "format_options": { + "type": "string", + "description": "JSON string passed through to pg-delta SQL formatting.", + "examples": [ + "{\"keywordCase\":\"upper\",\"indent\":2,\"maxWidth\":80,\"commaStyle\":\"trailing\"}" + ] + } }, - "description": "Allowed IPv4 CIDR blocks.", - "default": [ - "0.0.0.0/0" - ] + "additionalProperties": false }, - "allowed_cidrs_v6": { - "type": "array", - "items": { - "type": "string" + "inspect": { + "type": "object", + "properties": { + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Inspection query." + }, + "name": { + "type": "string", + "description": "Inspection rule name." + }, + "pass": { + "type": "string", + "description": "Success message." + }, + "fail": { + "type": "string", + "description": "Failure message." + } + }, + "additionalProperties": false + }, + "description": "Inspection rules.", + "default": [] + } }, - "description": "Allowed IPv6 CIDR blocks.", - "default": [ - "::/0" - ] - } - }, - "additionalProperties": false - }, - "Objects_18": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Reject non-secure connections to the database.", - "default": false + "additionalProperties": false } }, "additionalProperties": false }, - "Objects_19": { - "type": "object", - "additionalProperties": { - "type": "string", - "description": "Vault secret value." - }, - "description": "Vault secrets." - }, - "Objects_20": { - "type": "object", - "additionalProperties": { - "type": "string", - "description": "Secret value exposed to the edge runtime." - }, - "description": "Secrets exposed to the edge runtime." - }, - "Objects_21": { - "type": "object", - "patternProperties": { - "^[a-zA-Z0-9_-]+$": { - "anyOf": [ - { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Controls whether a function is deployed or served.", - "default": true - }, - "verify_jwt": { - "type": "boolean", - "description": "By default, deployed or locally served functions reject requests without a valid JWT.", - "default": true - }, - "import_map": { - "type": "string", - "description": "Import map file to use for the Function.", - "default": "" - }, - "entrypoint": { - "type": "string", - "description": "Entrypoint path to the Function. Defaults to \"functions/slug/index.ts\".", - "default": "" - }, - "static_files": { - "type": "array", - "items": { + "remotes": { + "anyOf": [ + { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "project_id": { "type": "string", - "description": "Static file glob for the function." + "description": "Remote project reference.", + "default": "" + }, + "analytics": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local Logflare service.", + "default": true + }, + "port": { + "type": "number", + "description": "Port to the local Logflare service.", + "default": 54327 + }, + "backend": { + "type": "string", + "enum": ["postgres", "bigquery"], + "description": "Configure one of the supported backends:\n\n- `postgres`\n- `bigquery`", + "default": "postgres" + }, + "vector_port": { + "type": "number", + "description": "Port to the local syslog ingest service." + }, + "gcp_project_id": { + "type": "string", + "description": "GCP project ID." + }, + "gcp_project_number": { + "type": "string", + "description": "GCP project number." + }, + "gcp_jwt_path": { + "type": "string", + "description": "Path to the GCP JWT file." + } + }, + "additionalProperties": false + }, + "api": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local PostgREST service.", + "default": true + }, + "port": { + "type": "number", + "description": "Port to use for the API URL.", + "default": 54321 + }, + "schemas": { + "type": "array", + "items": { + "type": "string", + "description": "Schemas to expose in your API. Tables, views and stored procedures in this schema will get API endpoints." + }, + "default": ["public", "graphql_public"] + }, + "extra_search_path": { + "type": "array", + "items": { + "type": "string", + "description": "Extra schemas to add to the search_path of every request." + }, + "default": ["public", "extensions"] + }, + "max_rows": { + "type": "number", + "description": "The maximum number of rows returned from a view, table, or stored procedure. Limits payload size for accidental or malicious requests.", + "default": 1000 + }, + "auto_expose_new_tables": { + "type": "boolean", + "description": "Controls whether newly-created tables, views, sequences and functions in the `public` schema by `postgres` are reachable through the Data API roles (`anon`, `authenticated`, `service_role`) without explicit GRANTs. When unset, new entities are auto-exposed, matching the cloud default. Set to `false` to revoke the default Data API privileges so new entities require explicit GRANTs, matching a cloud project with the \"Default privileges for new entities\" toggle turned off." + }, + "tls": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable HTTPS endpoints locally using a self-signed certificate.", + "default": false + }, + "cert_path": { + "type": "string", + "description": "Path to the self-signed certificate." + }, + "key_path": { + "type": "string", + "description": "Path to the self-signed certificate private key." + } + }, + "additionalProperties": false + }, + "external_url": { + "type": "string", + "description": "External URL for accessing the API server." + } + }, + "additionalProperties": false + }, + "auth": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local GoTrue service.", + "default": true + }, + "site_url": { + "type": "string", + "description": "The base URL of your website. Used as an allow-list for redirects and for constructing URLs used in emails.", + "default": "http://127.0.0.1:3000" + }, + "additional_redirect_urls": { + "type": "array", + "items": { + "type": "string", + "description": "A URL that auth providers are permitted to redirect to." + }, + "description": "A list of exact URLs that auth providers are permitted to redirect to post authentication.", + "default": ["https://127.0.0.1:3000"] + }, + "jwt_expiry": { + "type": "number", + "description": "How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one week).", + "default": 3600 + }, + "jwt_issuer": { + "type": "string", + "description": "JWT issuer URL." + }, + "signing_keys_path": { + "type": "string", + "description": "Path to the JWT signing keys file." + }, + "enable_refresh_token_rotation": { + "type": "boolean", + "description": "If disabled, the refresh token will never expire.", + "default": true + }, + "refresh_token_reuse_interval": { + "type": "number", + "description": "Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.", + "default": 10 + }, + "enable_manual_linking": { + "type": "boolean", + "description": "Allow/disallow testing manual linking of accounts.", + "default": false + }, + "enable_signup": { + "type": "boolean", + "description": "Allow/disallow new user signups to your project.", + "default": true + }, + "enable_anonymous_sign_ins": { + "type": "boolean", + "description": "Allow/disallow anonymous sign-ins to your project.", + "default": false + }, + "minimum_password_length": { + "type": "number", + "description": "Passwords shorter than this value will be rejected as weak.", + "default": 6 + }, + "password_requirements": { + "type": "string", + "enum": [ + "", + "letters_digits", + "lower_upper_letters_digits", + "lower_upper_letters_digits_symbols" + ], + "description": "Password character requirements.", + "default": "" + }, + "publishable_key": { + "type": "string", + "description": "Publishable key override." + }, + "secret_key": { + "type": "string", + "description": "Secret key override." + }, + "jwt_secret": { + "type": "string", + "description": "JWT secret override." + }, + "anon_key": { + "type": "string", + "description": "Anon key override." + }, + "service_role_key": { + "type": "string", + "description": "Service role key override." + }, + "rate_limit": { + "type": "object", + "properties": { + "email_sent": { + "type": "number", + "description": "Number of emails that can be sent per hour.", + "default": 2 + }, + "sms_sent": { + "type": "number", + "description": "Number of SMS messages that can be sent per hour.", + "default": 30 + }, + "anonymous_users": { + "type": "number", + "description": "Number of anonymous sign-ins that can be made per hour per IP address.", + "default": 30 + }, + "token_refresh": { + "type": "number", + "description": "Number of sessions that can be refreshed in a 5 minute interval per IP address.", + "default": 150 + }, + "sign_in_sign_ups": { + "type": "number", + "description": "Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address.", + "default": 30 + }, + "token_verifications": { + "type": "number", + "description": "Number of OTP or magic link verifications that can be made in a 5 minute interval per IP address.", + "default": 30 + }, + "web3": { + "type": "number", + "description": "Number of Web3 logins that can be made in a 5 minute interval per IP address.", + "default": 30 + } + }, + "additionalProperties": false + }, + "captcha": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable CAPTCHA verification.", + "default": false + }, + "provider": { + "type": "string", + "enum": ["hcaptcha", "turnstile"], + "description": "CAPTCHA provider to use." + }, + "secret": { + "type": "string", + "description": "Secret key for the CAPTCHA provider." + } + }, + "additionalProperties": false + }, + "hook": { + "type": "object", + "properties": { + "mfa_verification_attempt": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the mfa verification hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + }, + "secrets": { + "type": "string", + "description": "Secret value to pass to the function or endpoint." + } + }, + "additionalProperties": false + }, + "password_verification_attempt": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the password verification hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + }, + "secrets": { + "type": "string", + "description": "Secret value to pass to the function or endpoint." + } + }, + "additionalProperties": false + }, + "custom_access_token": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the custom access token hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + }, + "secrets": { + "type": "string", + "description": "Secret value to pass to the function or endpoint." + } + }, + "additionalProperties": false + }, + "send_sms": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the send sms hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + }, + "secrets": { + "type": "string", + "description": "Secret value to pass to the function or endpoint." + } + }, + "additionalProperties": false + }, + "send_email": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the send email hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + }, + "secrets": { + "type": "string", + "description": "Secret value to pass to the function or endpoint." + } + }, + "additionalProperties": false + }, + "before_user_created": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the before user created hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + }, + "secrets": { + "type": "string", + "description": "Secret value to pass to the function or endpoint." + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "mfa": { + "type": "object", + "properties": { + "totp": { + "type": "object", + "properties": { + "enroll_enabled": { + "type": "boolean", + "description": "Allow/disallow TOTP enrollment for users.", + "default": false + }, + "verify_enabled": { + "type": "boolean", + "description": "Allow/disallow TOTP verification for users.", + "default": false + } + }, + "additionalProperties": false + }, + "phone": { + "type": "object", + "properties": { + "enroll_enabled": { + "type": "boolean", + "description": "Allow/disallow phone enrollment for users.", + "default": false + }, + "verify_enabled": { + "type": "boolean", + "description": "Allow/disallow phone verification for users.", + "default": false + }, + "otp_length": { + "type": "number", + "description": "The length of the OTP code.", + "default": 6 + }, + "template": { + "type": "string", + "description": "The template to use for the phone message.", + "default": "Your code is {{ .Code }}" + }, + "max_frequency": { + "type": "string", + "description": "The maximum frequency of the phone messages.", + "default": "5s" + } + }, + "additionalProperties": false + }, + "web_authn": { + "type": "object", + "properties": { + "enroll_enabled": { + "type": "boolean", + "description": "Allow/disallow WebAuthn enrollment for users.", + "default": false + }, + "verify_enabled": { + "type": "boolean", + "description": "Allow/disallow WebAuthn verification for users.", + "default": false + } + }, + "additionalProperties": false + }, + "max_enrolled_factors": { + "type": "number", + "description": "The maximum number of MFA factors a user can enroll in.", + "default": 10 + } + }, + "additionalProperties": false + }, + "sessions": { + "type": "object", + "properties": { + "timebox": { + "type": "string", + "description": "The timebox for the user session." + }, + "inactivity_timeout": { + "type": "string", + "description": "The inactivity timeout for the user session." + } + }, + "additionalProperties": false, + "default": {} + }, + "email": { + "type": "object", + "properties": { + "enable_signup": { + "type": "boolean", + "description": "Allow/disallow new user signups via email to your project.", + "default": true + }, + "double_confirm_changes": { + "type": "boolean", + "description": "If enabled, a user will be required to confirm any email change on both the old and new email addresses.", + "default": true + }, + "enable_confirmations": { + "type": "boolean", + "description": "If enabled, users need to confirm their email address before signing in.", + "default": false + }, + "secure_password_change": { + "type": "boolean", + "description": "If enabled, users will need to reauthenticate or have logged in recently to change their password.", + "default": false + }, + "max_frequency": { + "type": "string", + "description": "Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.", + "default": "1s" + }, + "otp_length": { + "type": "number", + "description": "Number of characters used in the email OTP.", + "default": 6 + }, + "otp_expiry": { + "type": "number", + "description": "Number of seconds before the email OTP expires.", + "default": 3600 + }, + "smtp": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable SMTP for email delivery.", + "default": false + }, + "host": { + "type": "string", + "description": "Hostname or IP address of the SMTP server." + }, + "port": { + "type": "number", + "description": "Port number of the SMTP server." + }, + "user": { + "type": "string", + "description": "Username for authenticating with the SMTP server." + }, + "pass": { + "type": "string", + "description": "Password for authenticating with the SMTP server." + }, + "admin_email": { + "type": "string", + "description": "Email used as the sender for emails sent from the application." + }, + "sender_name": { + "type": "string", + "description": "Display name used as the sender for emails sent from the application." + } + }, + "additionalProperties": false + }, + "template": { + "anyOf": [ + { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "subject": { + "type": "string", + "description": "Subject line for the email template.", + "default": "" + }, + "content_path": { + "type": "string", + "description": "Path to the HTML template.", + "default": "" + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "description": "Custom email template configuration.", + "default": {} + }, + { + "type": "null" + } + ] + }, + "notification": { + "anyOf": [ + { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the notification email.", + "default": false + }, + "subject": { + "type": "string", + "description": "Subject line for the notification email.", + "default": "" + }, + "content_path": { + "type": "string", + "description": "Path to the HTML notification template.", + "default": "" + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "description": "Notification email configuration.", + "default": {} + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "sms": { + "type": "object", + "properties": { + "enable_signup": { + "type": "boolean", + "description": "Allow/disallow new user signups via SMS to your project.", + "default": false + }, + "enable_confirmations": { + "type": "boolean", + "description": "If enabled, users need to confirm their phone number before signing in.", + "default": false + }, + "template": { + "type": "string", + "description": "The template to use for the SMS message.", + "default": "Your code is {{ .Code }}" + }, + "max_frequency": { + "type": "string", + "description": "Controls the minimum amount of time that must pass before sending another sms otp.", + "default": "5s" + }, + "twilio": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable Twilio provider for phone login.", + "default": false + }, + "account_sid": { + "type": "string", + "description": "The account SID for the Twilio API.", + "default": "" + }, + "message_service_sid": { + "type": "string", + "description": "The message service SID for the Twilio API.", + "default": "" + }, + "auth_token": { + "type": "string", + "description": "The auth token for the Twilio API.", + "examples": ["env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"] + } + }, + "additionalProperties": false + }, + "twilio_verify": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable Twilio Verify provider for phone verification.", + "default": false + }, + "account_sid": { + "type": "string", + "description": "The account SID for the Twilio API." + }, + "message_service_sid": { + "type": "string", + "description": "The message service SID for the Twilio API." + }, + "auth_token": { + "type": "string", + "description": "The auth token for the Twilio API." + } + }, + "additionalProperties": false + }, + "messagebird": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable MessageBird provider for phone login.", + "default": false + }, + "originator": { + "type": "string", + "description": "The originator of the SMS message." + }, + "access_key": { + "type": "string", + "description": "The access key for the MessageBird API." + } + }, + "additionalProperties": false + }, + "textlocal": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable Textlocal provider for phone login.", + "default": false + }, + "sender": { + "type": "string", + "description": "The sender of the SMS message." + }, + "api_key": { + "type": "string", + "description": "The API key for the Textlocal API." + } + }, + "additionalProperties": false + }, + "vonage": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable Vonage provider for phone login.", + "default": false + }, + "from": { + "type": "string", + "description": "The sender of the SMS message." + }, + "api_key": { + "type": "string", + "description": "The API key for the Vonage API." + }, + "api_secret": { + "type": "string", + "description": "The API secret for the Vonage API." + } + }, + "additionalProperties": false + }, + "test_otp": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Use pre-defined map of phone number to OTP for testing." + } + }, + "additionalProperties": false + }, + "external": { + "type": "object", + "properties": { + "apple": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Apple OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Apple OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Apple OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Apple OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "azure": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Azure OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Azure OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Azure OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_AZURE_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Azure OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "bitbucket": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Bitbucket OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Bitbucket OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Bitbucket OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_BITBUCKET_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Bitbucket OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "discord": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Discord OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Discord OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Discord OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_DISCORD_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Discord OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "facebook": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Facebook OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Facebook OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Facebook OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_FACEBOOK_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Facebook OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "github": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the GitHub OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the GitHub OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the GitHub OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_GITHUB_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the GitHub OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "gitlab": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the GitLab OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the GitLab OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the GitLab OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_GITLAB_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "https://gitlab.com" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the GitLab OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "google": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Google OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Google OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Google OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_GOOGLE_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Google OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "kakao": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Kakao OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Kakao OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Kakao OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_KAKAO_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Kakao OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "keycloak": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Keycloak OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Keycloak OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Keycloak OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_KEYCLOAK_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "", + "examples": ["https://keycloak.example.com/realms/myrealm"] + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Keycloak OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "linkedin_oidc": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the LinkedIn OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the LinkedIn OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the LinkedIn OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_LINKEDIN_OIDC_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the LinkedIn OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "notion": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Notion OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Notion OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Notion OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_NOTION_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Notion OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "twitch": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Twitch OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Twitch OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Twitch OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_TWITCH_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Twitch OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "twitter": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Twitter OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Twitter OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Twitter OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_TWITTER_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Twitter OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "x": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the X OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the X OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the X OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_X_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the X OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "slack_oidc": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Slack OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Slack OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Slack OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_SLACK_OIDC_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Slack OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "spotify": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Spotify OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Spotify OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Spotify OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_SPOTIFY_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Spotify OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "workos": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the WorkOS OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the WorkOS OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the WorkOS OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_WORKOS_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the WorkOS OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + }, + "zoom": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Zoom OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Zoom OAuth provider.", + "default": "" + }, + "secret": { + "type": "string", + "description": "Client secret for the Zoom OAuth provider.\n\nDO NOT commit your OAuth provider secret to git. Use environment variable substitution instead.", + "examples": ["env(SUPABASE_AUTH_EXTERNAL_ZOOM_SECRET)"] + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Zoom OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "web3": { + "type": "object", + "properties": { + "solana": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this Web3 provider.", + "default": false + } + }, + "additionalProperties": false + }, + "ethereum": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this Web3 provider.", + "default": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "oauth_server": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable OAuth server functionality.", + "default": false + }, + "authorization_url_path": { + "type": "string", + "description": "Path for OAuth consent flow UI.", + "default": "/oauth/consent" + }, + "allow_dynamic_registration": { + "type": "boolean", + "description": "Allow dynamic client registration.", + "default": false + } + }, + "additionalProperties": false + }, + "third_party": { + "type": "object", + "properties": { + "firebase": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "project_id": { + "type": "string", + "description": "Firebase project ID." + } + }, + "additionalProperties": false + }, + "auth0": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "tenant": { + "type": "string", + "description": "Auth0 tenant." + }, + "tenant_region": { + "type": "string", + "description": "Auth0 tenant region." + } + }, + "additionalProperties": false + }, + "aws_cognito": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "user_pool_id": { + "type": "string", + "description": "AWS Cognito user pool ID." + }, + "user_pool_region": { + "type": "string", + "description": "AWS Cognito user pool region." + } + }, + "additionalProperties": false + }, + "clerk": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "domain": { + "type": "string", + "description": "Clerk domain." + } + }, + "additionalProperties": false + }, + "workos": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "issuer_url": { + "type": "string", + "description": "WorkOS issuer URL." + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false }, - "description": "Static files to bundle with the function.", - "default": [] - }, - "env": { - "type": "object", - "patternProperties": { - "^[A-Z_][A-Z0-9_]*$": { - "type": "string", - "allOf": [ - { - "pattern": "^env\\((.*)\\)$", - "description": "Reference to a project environment variable available to the Function." - } - ] - } + "db": { + "type": "object", + "properties": { + "port": { + "type": "number", + "description": "Port to use for the local database URL.", + "default": 54322 + }, + "shadow_port": { + "type": "number", + "description": "Port used by db diff command to initialize the shadow database.", + "default": 54320 + }, + "health_timeout": { + "type": "string", + "description": "Maximum amount of time to wait for health check when starting the local database.", + "default": "2m" + }, + "major_version": { + "type": "number", + "description": "The database major version to use. This has to be the same as your remote database's.", + "default": 17 + }, + "pooler": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local PgBouncer service.", + "default": false + }, + "port": { + "type": "number", + "description": "Port to use for the local connection pooler.", + "default": 54329 + }, + "pool_mode": { + "type": "string", + "enum": ["transaction", "session"], + "description": "Specifies when a server connection can be reused by other clients.", + "default": "transaction" + }, + "default_pool_size": { + "type": "number", + "description": "How many server connections to allow per user/database pair.", + "default": 20 + }, + "max_client_conn": { + "type": "number", + "description": "Maximum number of client connections allowed.", + "default": 100 + } + }, + "additionalProperties": false + }, + "migrations": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If disabled, migrations will be skipped during a db push or reset.", + "default": true + }, + "schema_paths": { + "type": "array", + "items": { + "type": "string", + "description": "Schema file path, directory, or glob relative to the supabase directory." + }, + "description": "Ordered list of schema files, directories, or glob patterns that describe your database.", + "default": [] + } + }, + "additionalProperties": false + }, + "seed": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable seeding the database with SQL files.", + "default": true + }, + "sql_paths": { + "type": "array", + "items": { + "type": "string", + "description": "Path to a SQL file used to seed the database." + }, + "description": "Ordered list of seed files to load during db reset.", + "default": ["./seed.sql"] + } + }, + "additionalProperties": false + }, + "settings": { + "type": "object", + "properties": { + "effective_cache_size": { + "type": "string" + }, + "logical_decoding_work_mem": { + "type": "string" + }, + "maintenance_work_mem": { + "type": "string" + }, + "max_connections": { + "type": "number" + }, + "max_locks_per_transaction": { + "type": "number" + }, + "max_parallel_maintenance_workers": { + "type": "number" + }, + "max_parallel_workers": { + "type": "number" + }, + "max_parallel_workers_per_gather": { + "type": "number" + }, + "max_replication_slots": { + "type": "number" + }, + "max_slot_wal_keep_size": { + "type": "string" + }, + "max_standby_archive_delay": { + "type": "string" + }, + "max_standby_streaming_delay": { + "type": "string" + }, + "max_wal_size": { + "type": "string" + }, + "max_wal_senders": { + "type": "number" + }, + "max_worker_processes": { + "type": "number" + }, + "session_replication_role": { + "type": "string", + "enum": ["origin", "replica", "local"], + "description": "Session replication role." + }, + "shared_buffers": { + "type": "string" + }, + "statement_timeout": { + "type": "string" + }, + "track_activity_query_size": { + "type": "string" + }, + "track_commit_timestamp": { + "type": "boolean" + }, + "wal_keep_size": { + "type": "string" + }, + "wal_sender_timeout": { + "type": "string" + }, + "work_mem": { + "type": "string" + } + }, + "additionalProperties": false + }, + "network_restrictions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable management of network restrictions.", + "default": false + }, + "allowed_cidrs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allowed IPv4 CIDR blocks.", + "default": ["0.0.0.0/0"] + }, + "allowed_cidrs_v6": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allowed IPv6 CIDR blocks.", + "default": ["::/0"] + } + }, + "additionalProperties": false + }, + "ssl_enforcement": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Reject non-secure connections to the database.", + "default": false + } + }, + "additionalProperties": false + }, + "vault": { + "type": "object", + "additionalProperties": { + "type": "string", + "description": "Vault secret value." + }, + "description": "Vault secrets." + } + }, + "additionalProperties": false }, - "description": "Environment variables from the project environment that this Function can access.", - "default": {} - } - }, - "additionalProperties": false - }, - { - "type": "null" - } - ] - } - }, - "description": "Function-specific configuration keyed by function slug.", - "default": {} - }, - "Objects_22": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable image transformation.", - "default": false - } - }, - "additionalProperties": false - }, - "Objects_23": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "type": "object", - "properties": { - "public": { - "type": "boolean", - "description": "Enable public access to the bucket.", - "default": false - }, - "file_size_limit": { - "anyOf": [ - { - "type": "string" + "edge_runtime": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local Edge Runtime service.", + "default": true + }, + "policy": { + "type": "string", + "enum": ["oneshot", "per_worker"], + "description": "Configure the supported request policy.", + "default": "per_worker" + }, + "inspector_port": { + "type": "number", + "description": "Port to run the Edge Functions inspector on.", + "default": 8083 + }, + "deno_version": { + "type": "number", + "description": "The Deno major version to use.", + "default": 2 + }, + "secrets": { + "type": "object", + "additionalProperties": { + "type": "string", + "description": "Secret value exposed to the edge runtime." + }, + "description": "Secrets exposed to the edge runtime." + } + }, + "additionalProperties": false + }, + "functions": { + "anyOf": [ + { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "anyOf": [ + { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Controls whether a function is deployed or served.", + "default": true + }, + "verify_jwt": { + "type": "boolean", + "description": "By default, deployed or locally served functions reject requests without a valid JWT.", + "default": true + }, + "import_map": { + "type": "string", + "description": "Import map file to use for the Function.", + "default": "" + }, + "entrypoint": { + "type": "string", + "description": "Entrypoint path to the Function. Defaults to \"functions/slug/index.ts\".", + "default": "" + }, + "static_files": { + "type": "array", + "items": { + "type": "string", + "description": "Static file glob for the function." + }, + "description": "Static files to bundle with the function.", + "default": [] + }, + "env": { + "type": "object", + "patternProperties": { + "^[A-Z_][A-Z0-9_]*$": { + "type": "string", + "pattern": "^env\\((.*)\\)$", + "description": "Reference to a project environment variable available to the Function." + } + }, + "description": "Environment variables from the project environment that this Function can access.", + "default": {} + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + } + }, + "description": "Function-specific configuration keyed by function slug.", + "default": {} + }, + { + "type": "null" + } + ] + }, + "local_smtp": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local SMTP testing server.", + "default": true + }, + "port": { + "type": "number", + "description": "Port to use for the email testing server web interface.\n\nEmails sent with the local dev setup are monitored and available from the web interface.", + "default": 54324 + }, + "smtp_port": { + "type": "number", + "description": "Optional SMTP port to expose for local testing." + }, + "pop3_port": { + "type": "number", + "description": "Optional POP3 port to expose for local testing." + }, + "admin_email": { + "type": "string", + "description": "Admin email address for test email sender metadata." + }, + "sender_name": { + "type": "string", + "description": "Sender name for test email sender metadata." + } + }, + "additionalProperties": false + }, + "realtime": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local Realtime service.", + "default": true + }, + "ip_version": { + "type": "string", + "enum": ["IPv4", "IPv6"], + "description": "Bind realtime via either IPv4 or IPv6.", + "default": "IPv4" + }, + "max_header_length": { + "type": "number", + "description": "Maximum length of the HTTP header.", + "default": 4096 + } + }, + "additionalProperties": false + }, + "storage": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local Storage service.", + "default": true + }, + "file_size_limit": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "image_transformation": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable image transformation.", + "default": false + } + }, + "additionalProperties": false + }, + "buckets": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "public": { + "type": "boolean", + "description": "Enable public access to the bucket.", + "default": false + }, + "file_size_limit": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + } + ] + }, + "allowed_mime_types": { + "type": "array", + "items": { + "type": "string", + "description": "A MIME type allowed for the bucket." + }, + "description": "The list of allowed MIME types for the bucket.", + "default": [] + }, + "objects_path": { + "type": "string", + "description": "The path to the objects in the bucket.", + "default": "" + } + }, + "additionalProperties": false + }, + { + "type": "null" + } + ] + }, + "description": "Storage buckets configuration." + }, + "s3_protocol": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Allow connections via S3 compatible clients.", + "default": true + } + }, + "additionalProperties": false + }, + "analytics": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable analytics buckets.", + "default": false + }, + "max_namespaces": { + "type": "number", + "description": "Maximum number of analytics namespaces.", + "default": 5 + }, + "max_tables": { + "type": "number", + "description": "Maximum number of analytics tables.", + "default": 10 + }, + "max_catalogs": { + "type": "number", + "description": "Maximum number of analytics catalogs.", + "default": 2 + }, + "buckets": { + "anyOf": [ + { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "object" + }, + { + "type": "array" + } + ] + }, + { + "type": "null" + } + ] + }, + "description": "Analytics bucket configuration.", + "default": {} + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "vector": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable vector buckets.", + "default": true + }, + "max_buckets": { + "type": "number", + "description": "Maximum number of vector buckets.", + "default": 10 + }, + "max_indexes": { + "type": "number", + "description": "Maximum number of vector indexes.", + "default": 5 + }, + "buckets": { + "anyOf": [ + { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "anyOf": [ + { + "type": "object" + }, + { + "type": "array" + } + ] + }, + { + "type": "null" + } + ] + }, + "description": "Vector bucket configuration.", + "default": {} + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false }, - { - "anyOf": [ - { - "type": "number" + "studio": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local Supabase Studio dashboard.", + "default": true }, - { - "$ref": "#/$defs/Union_" - } - ] - } - ] - }, - "allowed_mime_types": { - "type": "array", - "items": { - "type": "string", - "description": "A MIME type allowed for the bucket." - }, - "description": "The list of allowed MIME types for the bucket.", - "default": [] - }, - "objects_path": { - "type": "string", - "description": "The path to the objects in the bucket.", - "default": "" - } - }, - "additionalProperties": false - }, - { - "type": "null" - } - ] - }, - "description": "Storage buckets configuration." - }, - "Objects_24": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Allow connections via S3 compatible clients.", - "default": true - } - }, - "additionalProperties": false - }, - "Objects_25": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable analytics buckets.", - "default": false - }, - "max_namespaces": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "max_tables": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "max_catalogs": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "buckets": { - "anyOf": [ - { - "type": "object", - "additionalProperties": { - "anyOf": [ - { - "anyOf": [ - { - "type": "object" + "port": { + "type": "number", + "description": "Port to use for Supabase Studio.", + "default": 54323 }, - { - "type": "array" + "api_url": { + "type": "string", + "description": "External URL of the API server that frontend connects to.", + "default": "http://127.0.0.1" + }, + "openai_api_key": { + "type": "string", + "description": "OpenAI API key to use for Supabase AI in the Supabase Studio.", + "examples": ["env(OPENAI_API_KEY)"] } - ] + }, + "additionalProperties": false }, - { - "type": "null" - } - ] - }, - "description": "Analytics bucket configuration.", - "default": {} - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - }, - "Objects_26": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable vector buckets.", - "default": true - }, - "max_buckets": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "max_indexes": { - "anyOf": [ - { - "type": "number" - }, - { - "$ref": "#/$defs/Union_" - } - ] - }, - "buckets": { - "anyOf": [ - { - "type": "object", - "additionalProperties": { - "anyOf": [ - { + "workers": { "anyOf": [ { - "type": "object" + "type": "object", + "patternProperties": { + "^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$": { + "type": "object", + "properties": { + "runtime": { + "type": "string", + "description": "Runtime the worker is built on: `dockerfile` to build the directory's own\nDockerfile, or one of the catalog runtimes (`node`, `deno`). Guessed from\nmarker files when unset.", + "examples": ["node"] + }, + "size": { + "type": "string", + "description": "Instance size, denominated by memory. Each size implies its own vCPU count,\nso it is the one dial rather than two.", + "examples": ["2gb"] + }, + "instances": { + "type": "integer", + "minimum": 0, + "description": "Number of instances to run. Every deploy sends a complete spec, so a count\nrecorded here is what keeps a scaled worker scaled; `--instances` overrides\nit for one deploy. Defaults to 1.", + "examples": [3] + }, + "source": { + "type": "string", + "description": "Directory holding the worker's code, relative to the project root, when it\ndoes not live at `supabase/workers//`.", + "examples": ["packages/api"] + } + }, + "additionalProperties": false + } + }, + "description": "Worker-specific configuration keyed by worker name.", + "default": {} }, { - "type": "array" + "type": "null" } ] }, - { - "type": "null" + "experimental": { + "type": "object", + "properties": { + "orioledb_version": { + "type": "string", + "description": "Postgres storage engine version for OrioleDB." + }, + "s3_host": { + "type": "string", + "description": "S3 bucket URL.", + "examples": [".s3-.amazonaws.com", "env(S3_HOST)"] + }, + "s3_region": { + "type": "string", + "description": "S3 bucket region.", + "examples": ["us-east-1", "env(S3_REGION)"] + }, + "s3_access_key": { + "type": "string", + "description": "S3 access key.", + "examples": ["env(S3_ACCESS_KEY)"] + }, + "s3_secret_key": { + "type": "string", + "description": "S3 secret key.", + "examples": ["env(S3_SECRET_KEY)"] + }, + "webhooks": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable experimental webhooks.", + "default": false + } + }, + "additionalProperties": false + }, + "pgdelta": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use pg-delta as the schema diff engine for db diff / db pull / db remote commit. Set false to fall back to the legacy migra engine.", + "default": false + }, + "declarative_schema_path": { + "type": "string", + "description": "Directory under supabase/ where declarative schema files are written.", + "examples": ["./schemas"] + }, + "format_options": { + "type": "string", + "description": "JSON string passed through to pg-delta SQL formatting.", + "examples": [ + "{\"keywordCase\":\"upper\",\"indent\":2,\"maxWidth\":80,\"commaStyle\":\"trailing\"}" + ] + } + }, + "additionalProperties": false + }, + "inspect": { + "type": "object", + "properties": { + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Inspection query." + }, + "name": { + "type": "string", + "description": "Inspection rule name." + }, + "pass": { + "type": "string", + "description": "Success message." + }, + "fail": { + "type": "string", + "description": "Failure message." + } + }, + "additionalProperties": false + }, + "description": "Inspection rules.", + "default": [] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } - ] - }, - "description": "Vector bucket configuration.", - "default": {} - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - }, - "Objects_27": { - "type": "object", - "patternProperties": { - "^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$": { - "type": "object", - "properties": { - "runtime": { - "type": "string", - "description": "Runtime the worker is built on: `dockerfile` to build the directory's own\nDockerfile, or one of the catalog runtimes (`node`, `deno`). Guessed from\nmarker files when unset.", - "examples": [ - "node" - ] - }, - "size": { - "type": "string", - "description": "Instance size, denominated by memory. Each size implies its own vCPU count,\nso it is the one dial rather than two.", - "examples": [ - "2gb" - ] - }, - "instances": { - "type": "integer", - "allOf": [ - { - "minimum": 0, - "description": "Number of instances to run. Every deploy sends a complete spec, so a count\nrecorded here is what keeps a scaled worker scaled; `--instances` overrides\nit for one deploy. Defaults to 1.", - "examples": [ - 3 - ] - } - ] - }, - "source": { - "type": "string", - "description": "Directory holding the worker's code, relative to the project root, when it\ndoes not live at `supabase/workers//`.", - "examples": [ - "packages/api" - ] - } - }, - "additionalProperties": false - } - }, - "description": "Worker-specific configuration keyed by worker name.", - "default": {} - }, - "Objects_28": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Enable experimental webhooks.", - "default": false - } - }, - "additionalProperties": false - }, - "Objects_29": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "description": "Use pg-delta as the schema diff engine for db diff / db pull / db remote commit. Set false to fall back to the legacy migra engine.", - "default": false - }, - "declarative_schema_path": { - "type": "string", - "description": "Directory under supabase/ where declarative schema files are written.", - "examples": [ - "./schemas" - ] - }, - "format_options": { - "type": "string", - "description": "JSON string passed through to pg-delta SQL formatting.", - "examples": [ - "{\"keywordCase\":\"upper\",\"indent\":2,\"maxWidth\":80,\"commaStyle\":\"trailing\"}" - ] - } - }, - "additionalProperties": false - }, - "Objects_30": { - "type": "object", - "properties": { - "rules": { - "type": "array", - "items": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "Inspection query." - }, - "name": { - "type": "string", - "description": "Inspection rule name." - }, - "pass": { - "type": "string", - "description": "Success message." + }, + "additionalProperties": false }, - "fail": { - "type": "string", - "description": "Failure message." + { + "type": "null" } - }, - "additionalProperties": false + ] }, - "description": "Inspection rules.", - "default": [] + "description": "Remote branch-specific project configuration.", + "default": {} + }, + { + "type": "null" } - }, - "additionalProperties": false + ] } - } + }, + "additionalProperties": false } diff --git a/apps/docs/public/cli/project-config.schema.json b/apps/docs/public/cli/project-config.schema.json new file mode 100644 index 0000000000..d05346ae77 --- /dev/null +++ b/apps/docs/public/cli/project-config.schema.json @@ -0,0 +1,1972 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://supabase.com/docs/cli/project-config.schema.json", + "title": "Supabase hosted project config (ProjectConfig)", + "description": "The sparse, hosted-project subset of CliConfig that a Supabase project manages.", + "type": "object", + "properties": { + "api": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local PostgREST service.", + "default": true + }, + "port": { + "type": "number", + "description": "Port to use for the API URL.", + "default": 54321 + }, + "schemas": { + "type": "array", + "items": { + "type": "string", + "description": "Schemas to expose in your API. Tables, views and stored procedures in this schema will get API endpoints." + }, + "default": ["public", "graphql_public"] + }, + "extra_search_path": { + "type": "array", + "items": { + "type": "string", + "description": "Extra schemas to add to the search_path of every request." + }, + "default": ["public", "extensions"] + }, + "max_rows": { + "type": "number", + "description": "The maximum number of rows returned from a view, table, or stored procedure. Limits payload size for accidental or malicious requests.", + "default": 1000 + }, + "auto_expose_new_tables": { + "type": "boolean", + "description": "Controls whether newly-created tables, views, sequences and functions in the `public` schema by `postgres` are reachable through the Data API roles (`anon`, `authenticated`, `service_role`) without explicit GRANTs. When unset, new entities are auto-exposed, matching the cloud default. Set to `false` to revoke the default Data API privileges so new entities require explicit GRANTs, matching a cloud project with the \"Default privileges for new entities\" toggle turned off." + }, + "tls": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable HTTPS endpoints locally using a self-signed certificate.", + "default": false + }, + "cert_path": { + "type": "string", + "description": "Path to the self-signed certificate." + }, + "key_path": { + "type": "string", + "description": "Path to the self-signed certificate private key." + } + }, + "additionalProperties": true + }, + "external_url": { + "type": "string", + "description": "External URL for accessing the API server." + } + }, + "additionalProperties": true + }, + "auth": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local GoTrue service.", + "default": true + }, + "site_url": { + "type": "string", + "description": "The base URL of your website. Used as an allow-list for redirects and for constructing URLs used in emails.", + "default": "http://127.0.0.1:3000" + }, + "additional_redirect_urls": { + "type": "array", + "items": { + "type": "string", + "description": "A URL that auth providers are permitted to redirect to." + }, + "description": "A list of exact URLs that auth providers are permitted to redirect to post authentication.", + "default": ["https://127.0.0.1:3000"] + }, + "jwt_expiry": { + "type": "number", + "description": "How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one week).", + "default": 3600 + }, + "jwt_issuer": { + "type": "string", + "description": "JWT issuer URL." + }, + "signing_keys_path": { + "type": "string", + "description": "Path to the JWT signing keys file." + }, + "enable_refresh_token_rotation": { + "type": "boolean", + "description": "If disabled, the refresh token will never expire.", + "default": true + }, + "refresh_token_reuse_interval": { + "type": "number", + "description": "Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.", + "default": 10 + }, + "enable_manual_linking": { + "type": "boolean", + "description": "Allow/disallow testing manual linking of accounts.", + "default": false + }, + "enable_signup": { + "type": "boolean", + "description": "Allow/disallow new user signups to your project.", + "default": true + }, + "enable_anonymous_sign_ins": { + "type": "boolean", + "description": "Allow/disallow anonymous sign-ins to your project.", + "default": false + }, + "minimum_password_length": { + "type": "number", + "description": "Passwords shorter than this value will be rejected as weak.", + "default": 6 + }, + "password_requirements": { + "type": "string", + "enum": [ + "", + "letters_digits", + "lower_upper_letters_digits", + "lower_upper_letters_digits_symbols" + ], + "description": "Password character requirements.", + "default": "" + }, + "rate_limit": { + "type": "object", + "properties": { + "email_sent": { + "type": "number", + "description": "Number of emails that can be sent per hour.", + "default": 2 + }, + "sms_sent": { + "type": "number", + "description": "Number of SMS messages that can be sent per hour.", + "default": 30 + }, + "anonymous_users": { + "type": "number", + "description": "Number of anonymous sign-ins that can be made per hour per IP address.", + "default": 30 + }, + "token_refresh": { + "type": "number", + "description": "Number of sessions that can be refreshed in a 5 minute interval per IP address.", + "default": 150 + }, + "sign_in_sign_ups": { + "type": "number", + "description": "Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address.", + "default": 30 + }, + "token_verifications": { + "type": "number", + "description": "Number of OTP or magic link verifications that can be made in a 5 minute interval per IP address.", + "default": 30 + }, + "web3": { + "type": "number", + "description": "Number of Web3 logins that can be made in a 5 minute interval per IP address.", + "default": 30 + } + }, + "additionalProperties": true + }, + "captcha": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable CAPTCHA verification.", + "default": false + }, + "provider": { + "type": "string", + "enum": ["hcaptcha", "turnstile"], + "description": "CAPTCHA provider to use." + } + }, + "additionalProperties": true + }, + "hook": { + "type": "object", + "properties": { + "mfa_verification_attempt": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the mfa verification hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + } + }, + "additionalProperties": true + }, + "password_verification_attempt": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the password verification hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + } + }, + "additionalProperties": true + }, + "custom_access_token": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the custom access token hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + } + }, + "additionalProperties": true + }, + "send_sms": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the send sms hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + } + }, + "additionalProperties": true + }, + "send_email": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the send email hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + } + }, + "additionalProperties": true + }, + "before_user_created": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable or disable the before user created hook.", + "default": false + }, + "uri": { + "type": "string", + "description": "The URI of the postgres function or HTTP endpoint to call." + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "mfa": { + "type": "object", + "properties": { + "totp": { + "type": "object", + "properties": { + "enroll_enabled": { + "type": "boolean", + "description": "Allow/disallow TOTP enrollment for users.", + "default": false + }, + "verify_enabled": { + "type": "boolean", + "description": "Allow/disallow TOTP verification for users.", + "default": false + } + }, + "additionalProperties": true + }, + "phone": { + "type": "object", + "properties": { + "enroll_enabled": { + "type": "boolean", + "description": "Allow/disallow phone enrollment for users.", + "default": false + }, + "verify_enabled": { + "type": "boolean", + "description": "Allow/disallow phone verification for users.", + "default": false + }, + "otp_length": { + "type": "number", + "description": "The length of the OTP code.", + "default": 6 + }, + "template": { + "type": "string", + "description": "The template to use for the phone message.", + "default": "Your code is {{ .Code }}" + }, + "max_frequency": { + "type": "string", + "description": "The maximum frequency of the phone messages.", + "default": "5s" + } + }, + "additionalProperties": true + }, + "web_authn": { + "type": "object", + "properties": { + "enroll_enabled": { + "type": "boolean", + "description": "Allow/disallow WebAuthn enrollment for users.", + "default": false + }, + "verify_enabled": { + "type": "boolean", + "description": "Allow/disallow WebAuthn verification for users.", + "default": false + } + }, + "additionalProperties": true + }, + "max_enrolled_factors": { + "type": "number", + "description": "The maximum number of MFA factors a user can enroll in.", + "default": 10 + } + }, + "additionalProperties": true + }, + "sessions": { + "type": "object", + "properties": { + "timebox": { + "type": "string", + "description": "The timebox for the user session." + }, + "inactivity_timeout": { + "type": "string", + "description": "The inactivity timeout for the user session." + } + }, + "additionalProperties": true, + "default": {} + }, + "email": { + "type": "object", + "properties": { + "enable_signup": { + "type": "boolean", + "description": "Allow/disallow new user signups via email to your project.", + "default": true + }, + "double_confirm_changes": { + "type": "boolean", + "description": "If enabled, a user will be required to confirm any email change on both the old and new email addresses.", + "default": true + }, + "enable_confirmations": { + "type": "boolean", + "description": "If enabled, users need to confirm their email address before signing in.", + "default": false + }, + "secure_password_change": { + "type": "boolean", + "description": "If enabled, users will need to reauthenticate or have logged in recently to change their password.", + "default": false + }, + "max_frequency": { + "type": "string", + "description": "Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.", + "default": "1s" + }, + "otp_length": { + "type": "number", + "description": "Number of characters used in the email OTP.", + "default": 6 + }, + "otp_expiry": { + "type": "number", + "description": "Number of seconds before the email OTP expires.", + "default": 3600 + }, + "smtp": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable SMTP for email delivery.", + "default": false + }, + "host": { + "type": "string", + "description": "Hostname or IP address of the SMTP server." + }, + "port": { + "type": "number", + "description": "Port number of the SMTP server." + }, + "user": { + "type": "string", + "description": "Username for authenticating with the SMTP server." + }, + "admin_email": { + "type": "string", + "description": "Email used as the sender for emails sent from the application." + }, + "sender_name": { + "type": "string", + "description": "Display name used as the sender for emails sent from the application." + } + }, + "additionalProperties": true + }, + "template": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "subject": { + "type": "string", + "description": "Subject line for the email template.", + "default": "" + }, + "content_path": { + "type": "string", + "description": "Path to the HTML template.", + "default": "" + } + }, + "additionalProperties": true + }, + "description": "Custom email template configuration.", + "default": {} + }, + "notification": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the notification email.", + "default": false + }, + "subject": { + "type": "string", + "description": "Subject line for the notification email.", + "default": "" + }, + "content_path": { + "type": "string", + "description": "Path to the HTML notification template.", + "default": "" + } + }, + "additionalProperties": true + }, + "description": "Notification email configuration.", + "default": {} + } + }, + "additionalProperties": true + }, + "sms": { + "type": "object", + "properties": { + "enable_signup": { + "type": "boolean", + "description": "Allow/disallow new user signups via SMS to your project.", + "default": false + }, + "enable_confirmations": { + "type": "boolean", + "description": "If enabled, users need to confirm their phone number before signing in.", + "default": false + }, + "template": { + "type": "string", + "description": "The template to use for the SMS message.", + "default": "Your code is {{ .Code }}" + }, + "max_frequency": { + "type": "string", + "description": "Controls the minimum amount of time that must pass before sending another sms otp.", + "default": "5s" + }, + "twilio": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable Twilio provider for phone login.", + "default": false + }, + "account_sid": { + "type": "string", + "description": "The account SID for the Twilio API.", + "default": "" + }, + "message_service_sid": { + "type": "string", + "description": "The message service SID for the Twilio API.", + "default": "" + } + }, + "additionalProperties": true + }, + "twilio_verify": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable Twilio Verify provider for phone verification.", + "default": false + }, + "account_sid": { + "type": "string", + "description": "The account SID for the Twilio API." + }, + "message_service_sid": { + "type": "string", + "description": "The message service SID for the Twilio API." + } + }, + "additionalProperties": true + }, + "messagebird": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable MessageBird provider for phone login.", + "default": false + }, + "originator": { + "type": "string", + "description": "The originator of the SMS message." + } + }, + "additionalProperties": true + }, + "textlocal": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable Textlocal provider for phone login.", + "default": false + }, + "sender": { + "type": "string", + "description": "The sender of the SMS message." + } + }, + "additionalProperties": true + }, + "vonage": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable/disable Vonage provider for phone login.", + "default": false + }, + "from": { + "type": "string", + "description": "The sender of the SMS message." + }, + "api_key": { + "type": "string", + "description": "The API key for the Vonage API." + } + }, + "additionalProperties": true + }, + "test_otp": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Use pre-defined map of phone number to OTP for testing." + } + }, + "additionalProperties": true + }, + "external": { + "type": "object", + "properties": { + "apple": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Apple OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Apple OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Apple OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "azure": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Azure OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Azure OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Azure OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "bitbucket": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Bitbucket OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Bitbucket OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Bitbucket OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "discord": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Discord OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Discord OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Discord OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "facebook": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Facebook OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Facebook OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Facebook OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "github": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the GitHub OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the GitHub OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the GitHub OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "gitlab": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the GitLab OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the GitLab OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "https://gitlab.com" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the GitLab OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "google": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Google OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Google OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Google OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "kakao": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Kakao OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Kakao OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Kakao OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "keycloak": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Keycloak OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Keycloak OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "", + "examples": ["https://keycloak.example.com/realms/myrealm"] + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Keycloak OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "linkedin_oidc": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the LinkedIn OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the LinkedIn OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the LinkedIn OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "notion": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Notion OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Notion OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Notion OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "twitch": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Twitch OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Twitch OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Twitch OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "twitter": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Twitter OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Twitter OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Twitter OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "x": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the X OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the X OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the X OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "slack_oidc": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Slack OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Slack OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Slack OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "spotify": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Spotify OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Spotify OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Spotify OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "workos": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the WorkOS OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the WorkOS OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the WorkOS OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + }, + "zoom": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use the Zoom OAuth provider.", + "default": false + }, + "client_id": { + "type": "string", + "description": "Client ID for the Zoom OAuth provider.", + "default": "" + }, + "url": { + "type": "string", + "description": "The base URL used for constructing the URLs to request authorization and access tokens.", + "default": "" + }, + "redirect_uri": { + "type": "string", + "description": "The URI the Zoom OAuth2 provider will redirect to with the code and state values.", + "default": "" + }, + "skip_nonce_check": { + "type": "boolean", + "description": "If true, the nonce check will be skipped.", + "default": false + }, + "email_optional": { + "type": "boolean", + "description": "If true, authentication succeeds when the provider does not return an email address.", + "default": false + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "web3": { + "type": "object", + "properties": { + "solana": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this Web3 provider.", + "default": false + } + }, + "additionalProperties": true + }, + "ethereum": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this Web3 provider.", + "default": false + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "oauth_server": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable OAuth server functionality.", + "default": false + }, + "authorization_url_path": { + "type": "string", + "description": "Path for OAuth consent flow UI.", + "default": "/oauth/consent" + }, + "allow_dynamic_registration": { + "type": "boolean", + "description": "Allow dynamic client registration.", + "default": false + } + }, + "additionalProperties": true + }, + "third_party": { + "type": "object", + "properties": { + "firebase": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "project_id": { + "type": "string", + "description": "Firebase project ID." + } + }, + "additionalProperties": true + }, + "auth0": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "tenant": { + "type": "string", + "description": "Auth0 tenant." + }, + "tenant_region": { + "type": "string", + "description": "Auth0 tenant region." + } + }, + "additionalProperties": true + }, + "aws_cognito": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "user_pool_id": { + "type": "string", + "description": "AWS Cognito user pool ID." + }, + "user_pool_region": { + "type": "string", + "description": "AWS Cognito user pool region." + } + }, + "additionalProperties": true + }, + "clerk": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "domain": { + "type": "string", + "description": "Clerk domain." + } + }, + "additionalProperties": true + }, + "workos": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable this third-party auth provider.", + "default": false + }, + "issuer_url": { + "type": "string", + "description": "WorkOS issuer URL." + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "db": { + "type": "object", + "properties": { + "port": { + "type": "number", + "description": "Port to use for the local database URL.", + "default": 54322 + }, + "shadow_port": { + "type": "number", + "description": "Port used by db diff command to initialize the shadow database.", + "default": 54320 + }, + "health_timeout": { + "type": "string", + "description": "Maximum amount of time to wait for health check when starting the local database.", + "default": "2m" + }, + "major_version": { + "type": "number", + "description": "The database major version to use. This has to be the same as your remote database's.", + "default": 17 + }, + "pooler": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local PgBouncer service.", + "default": false + }, + "port": { + "type": "number", + "description": "Port to use for the local connection pooler.", + "default": 54329 + }, + "pool_mode": { + "type": "string", + "enum": ["transaction", "session"], + "description": "Specifies when a server connection can be reused by other clients.", + "default": "transaction" + }, + "default_pool_size": { + "type": "number", + "description": "How many server connections to allow per user/database pair.", + "default": 20 + }, + "max_client_conn": { + "type": "number", + "description": "Maximum number of client connections allowed.", + "default": 100 + } + }, + "additionalProperties": true + }, + "migrations": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "If disabled, migrations will be skipped during a db push or reset.", + "default": true + }, + "schema_paths": { + "type": "array", + "items": { + "type": "string", + "description": "Schema file path, directory, or glob relative to the supabase directory." + }, + "description": "Ordered list of schema files, directories, or glob patterns that describe your database.", + "default": [] + } + }, + "additionalProperties": true + }, + "seed": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable seeding the database with SQL files.", + "default": true + }, + "sql_paths": { + "type": "array", + "items": { + "type": "string", + "description": "Path to a SQL file used to seed the database." + }, + "description": "Ordered list of seed files to load during db reset.", + "default": ["./seed.sql"] + } + }, + "additionalProperties": true + }, + "settings": { + "type": "object", + "properties": { + "effective_cache_size": { + "type": "string" + }, + "logical_decoding_work_mem": { + "type": "string" + }, + "maintenance_work_mem": { + "type": "string" + }, + "max_connections": { + "type": "number" + }, + "max_locks_per_transaction": { + "type": "number" + }, + "max_parallel_maintenance_workers": { + "type": "number" + }, + "max_parallel_workers": { + "type": "number" + }, + "max_parallel_workers_per_gather": { + "type": "number" + }, + "max_replication_slots": { + "type": "number" + }, + "max_slot_wal_keep_size": { + "type": "string" + }, + "max_standby_archive_delay": { + "type": "string" + }, + "max_standby_streaming_delay": { + "type": "string" + }, + "max_wal_size": { + "type": "string" + }, + "max_wal_senders": { + "type": "number" + }, + "max_worker_processes": { + "type": "number" + }, + "session_replication_role": { + "type": "string", + "enum": ["origin", "replica", "local"], + "description": "Session replication role." + }, + "shared_buffers": { + "type": "string" + }, + "statement_timeout": { + "type": "string" + }, + "track_activity_query_size": { + "type": "string" + }, + "track_commit_timestamp": { + "type": "boolean" + }, + "wal_keep_size": { + "type": "string" + }, + "wal_sender_timeout": { + "type": "string" + }, + "work_mem": { + "type": "string" + } + }, + "additionalProperties": true + }, + "network_restrictions": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable management of network restrictions.", + "default": false + }, + "allowed_cidrs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allowed IPv4 CIDR blocks.", + "default": ["0.0.0.0/0"] + }, + "allowed_cidrs_v6": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allowed IPv6 CIDR blocks.", + "default": ["::/0"] + } + }, + "additionalProperties": true + }, + "ssl_enforcement": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Reject non-secure connections to the database.", + "default": false + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "realtime": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local Realtime service.", + "default": true + }, + "ip_version": { + "type": "string", + "enum": ["IPv4", "IPv6"], + "description": "Bind realtime via either IPv4 or IPv6.", + "default": "IPv4" + }, + "max_header_length": { + "type": "number", + "description": "Maximum length of the HTTP header.", + "default": 4096 + } + }, + "additionalProperties": true + }, + "storage": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable the local Storage service.", + "default": true + }, + "file_size_limit": { + "type": "string", + "description": "The maximum file size allowed.", + "default": "50MiB", + "examples": ["5MB", "500KB"] + }, + "image_transformation": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable image transformation.", + "default": false + } + }, + "additionalProperties": true + }, + "buckets": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "public": { + "type": "boolean", + "description": "Enable public access to the bucket.", + "default": false + }, + "file_size_limit": { + "type": "string", + "description": "The maximum file size allowed for the bucket.", + "default": "50MiB", + "examples": ["5MB", "500KB"] + }, + "allowed_mime_types": { + "type": "array", + "items": { + "type": "string", + "description": "A MIME type allowed for the bucket." + }, + "description": "The list of allowed MIME types for the bucket.", + "default": [] + }, + "objects_path": { + "type": "string", + "description": "The path to the objects in the bucket.", + "default": "" + } + }, + "additionalProperties": true + }, + "description": "Storage buckets configuration." + }, + "s3_protocol": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Allow connections via S3 compatible clients.", + "default": true + } + }, + "additionalProperties": true + }, + "analytics": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable analytics buckets.", + "default": false + }, + "max_namespaces": { + "type": "number", + "description": "Maximum number of analytics namespaces.", + "default": 5 + }, + "max_tables": { + "type": "number", + "description": "Maximum number of analytics tables.", + "default": 10 + }, + "max_catalogs": { + "type": "number", + "description": "Maximum number of analytics catalogs.", + "default": 2 + }, + "buckets": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "array" + } + ] + }, + "description": "Analytics bucket configuration.", + "default": {} + } + }, + "additionalProperties": true + }, + "vector": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable vector buckets.", + "default": true + }, + "max_buckets": { + "type": "number", + "description": "Maximum number of vector buckets.", + "default": 10 + }, + "max_indexes": { + "type": "number", + "description": "Maximum number of vector indexes.", + "default": 5 + }, + "buckets": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "array" + } + ] + }, + "description": "Vector bucket configuration.", + "default": {} + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + }, + "workers": { + "type": "object", + "patternProperties": { + "^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$": { + "type": "object", + "properties": { + "runtime": { + "type": "string", + "description": "Runtime the worker is built on: `dockerfile` to build the directory's own\nDockerfile, or one of the catalog runtimes (`node`, `deno`). Guessed from\nmarker files when unset.", + "examples": ["node"] + }, + "size": { + "type": "string", + "description": "Instance size, denominated by memory. Each size implies its own vCPU count,\nso it is the one dial rather than two.", + "examples": ["2gb"] + }, + "instances": { + "type": "integer", + "minimum": 0, + "description": "Number of instances to run. Every deploy sends a complete spec, so a count\nrecorded here is what keeps a scaled worker scaled; `--instances` overrides\nit for one deploy. Defaults to 1.", + "examples": [3] + }, + "source": { + "type": "string", + "description": "Directory holding the worker's code, relative to the project root, when it\ndoes not live at `supabase/workers//`.", + "examples": ["packages/api"] + } + }, + "additionalProperties": true + } + }, + "description": "Worker-specific configuration keyed by worker name.", + "default": {} + }, + "experimental": { + "type": "object", + "properties": { + "orioledb_version": { + "type": "string", + "description": "Postgres storage engine version for OrioleDB." + }, + "s3_host": { + "type": "string", + "description": "S3 bucket URL.", + "examples": [".s3-.amazonaws.com", "env(S3_HOST)"] + }, + "s3_region": { + "type": "string", + "description": "S3 bucket region.", + "examples": ["us-east-1", "env(S3_REGION)"] + }, + "webhooks": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable experimental webhooks.", + "default": false + } + }, + "additionalProperties": true + }, + "pgdelta": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Use pg-delta as the schema diff engine for db diff / db pull / db remote commit. Set false to fall back to the legacy migra engine.", + "default": false + }, + "declarative_schema_path": { + "type": "string", + "description": "Directory under supabase/ where declarative schema files are written.", + "examples": ["./schemas"] + }, + "format_options": { + "type": "string", + "description": "JSON string passed through to pg-delta SQL formatting.", + "examples": [ + "{\"keywordCase\":\"upper\",\"indent\":2,\"maxWidth\":80,\"commaStyle\":\"trailing\"}" + ] + } + }, + "additionalProperties": true + }, + "inspect": { + "type": "object", + "properties": { + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Inspection query." + }, + "name": { + "type": "string", + "description": "Inspection rule name." + }, + "pass": { + "type": "string", + "description": "Success message." + }, + "fail": { + "type": "string", + "description": "Failure message." + } + }, + "additionalProperties": true + }, + "description": "Inspection rules.", + "default": [] + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true +} diff --git a/docs/adr/0020-config-naming-vocabulary.md b/docs/adr/0020-config-naming-vocabulary.md index d505ec114d..2a1dfc84ce 100644 --- a/docs/adr/0020-config-naming-vocabulary.md +++ b/docs/adr/0020-config-naming-vocabulary.md @@ -44,9 +44,9 @@ and its CLI consumer: Prefix rule: `Cli*` names the local checkout side — what the CLI reads, writes, or resolves about itself on disk. A bare `Project*` name is reserved for the hosted Supabase project. Helpers that operate on config values follow the config family regardless of their inputs, not the shape of -whatever they're passed — `resolveCliConfigValue` and `MissingCliConfigValueError` are `Cli*`-named -even though both operate on plain config values, because the config they resolve or complain about -is the local-checkout document. +whatever they're passed — `resolveCliConfigValue` and `CliConfigParseError` are `Cli*`-named even +though one resolves a config value and the other reports a parse failure, because in both cases the +config in question is the local-checkout document. This convention is documented normatively in three places, so it is available wherever a session — human or agent — starts working in this repo: diff --git a/packages/config/AGENTS.md b/packages/config/AGENTS.md index 0c17e8147a..4ea94eb8b2 100644 --- a/packages/config/AGENTS.md +++ b/packages/config/AGENTS.md @@ -29,11 +29,12 @@ artifacts (`./schema.json`, `./project-schema.json`). (enforced by `src/monorepo-import-contract.unit.test.ts`). Exists solely for `apps/cli`'s own Go-parity call sites and contract-guard tests: `loadCliConfig`/`resolveCliConfigValue`/ `resolveCliConfigSubtree` — the SAME runtime functions `./effect` exports, re-typed here to - additionally accept the internal-only `goViperCompat` option (`InternalLoadCliConfigOptions`/ - `InternalResolveCliConfigOptions`) — plus the otherwise-internal registry data - (`AUTH_HOOK_NAMES`, `unmappedSecretApiPaths`, `projectConfigMappingRows`, - `ProjectConfigMappingRow`, `ProjectConfigApiAttributes`, `ENV_CAPTURE_REGEX`). Anything here can - change or vanish in any release. + additionally accept the internal-only `goViperCompat` option (`InternalLoadCliConfigOptions` for + `loadCliConfig`; `resolveCliConfigValue`/`resolveCliConfigSubtree`'s own widened options type, + `InternalResolveCliConfigOptions`, is package-internal and not itself re-exported) — plus the + otherwise-internal registry data (`AUTH_HOOK_NAMES`, `unmappedSecretApiPaths`, + `projectConfigMappingRows`, `ProjectConfigMappingRow`, `ProjectConfigApiAttributes`, + `ENV_CAPTURE_REGEX`). Anything here can change or vanish in any release. - `@supabase/config/schema.json` — generated JSON Schema (draft 2020-12) for `CliConfig` (a `dist/` build output). - `@supabase/config/project-schema.json` (CLI-2234) — generated JSON Schema (draft 2020-12) for @@ -117,8 +118,8 @@ either file. Run tests from this package with `bun --bun vitest run --project unit` (plain `node` vitest is broken here). Always run the relevant unit tests for what you changed before considering a task -done. Besides ordinary behavioral coverage, three tests enforce this package's own contracts and -must stay green after any entrypoint or type-surface change: +done. Besides ordinary behavioral coverage, the following contract tests enforce this package's +own guarantees and must stay green after any entrypoint or type-surface change: - `src/entrypoint-purity.unit.test.ts` — the pure-graph invariant above (also walked separately for `src/io-browser.ts`, the `browser` condition target for `./io`), plus pinned export-name diff --git a/packages/config/docs/cli-config-loading.md b/packages/config/docs/cli-config-loading.md index c8994dbd4a..2851c81976 100644 --- a/packages/config/docs/cli-config-loading.md +++ b/packages/config/docs/cli-config-loading.md @@ -240,9 +240,9 @@ An optional `goViperCompat` flag switches the `env(NAME)` matcher from the defau `SCREAMING_SNAKE_CASE`-only pattern to Go/viper's case-agnostic `^env\((.*)\)$` form; only the Go-parity legacy shell sets it. The public `resolveCliConfigValue`/`resolveCliConfigSubtree` on `.`/`./effect` take no options parameter at all (CLI-2234) — `goViperCompat` is internal-only, -typed on `InternalResolveCliConfigOptions` and exported from `@supabase/config/internal`, which -re-exports these same runtime functions typed to additionally accept it; `apps/cli`'s Go-parity -call sites import from there instead. +typed on `InternalResolveCliConfigOptions`, a package-internal type that is not itself exported. +`@supabase/config/internal` re-exports these same runtime functions re-typed to additionally +accept it; `apps/cli`'s Go-parity call sites import from there instead. Callers such as `functions serve`/`functions dev`, `secrets set`, and `start` call these resolvers on the subtrees they actually need (e.g. `auth`, `edge_runtime`, `functions`), so dormant diff --git a/packages/config/package.json b/packages/config/package.json index c4cb52673c..b68df5b39a 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -84,6 +84,7 @@ "@vitest/coverage-istanbul": "catalog:", "effect": "catalog:", "typescript": "catalog:", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "vitest": "catalog:" }, "peerDependencies": { diff --git a/packages/config/scripts/build.ts b/packages/config/scripts/build.ts index 019715e947..0aaec37bc8 100644 --- a/packages/config/scripts/build.ts +++ b/packages/config/scripts/build.ts @@ -83,7 +83,7 @@ async function renderJsonSchema(outputPath: string, json: Record, metadata: { readonly id: string; readonly title: string; readonly description: string }, ): Record { - const { $schema, ...rest } = document; + const { $schema, $id: _id, title: _title, description: _description, ...rest } = document; return { $schema, $id: metadata.id, diff --git a/packages/config/scripts/json-schema-postprocess.unit.test.ts b/packages/config/scripts/json-schema-postprocess.unit.test.ts index 4db8f73550..1bf52c6221 100644 --- a/packages/config/scripts/json-schema-postprocess.unit.test.ts +++ b/packages/config/scripts/json-schema-postprocess.unit.test.ts @@ -126,4 +126,29 @@ describe("withSchemaMetadata", () => { type: "object", }); }); + + test("caller-supplied metadata wins over a conflicting $id/title/description already on the document", () => { + const document = { + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://stale.example.com/old-schema.json", + title: "Stale title", + description: "Stale description.", + type: "object", + }; + + const result = withSchemaMetadata(document, { + id: "https://example.com/schema.json", + title: "Example", + description: "An example schema.", + }); + + expect(Object.keys(result)).toEqual(["$schema", "$id", "title", "description", "type"]); + expect(result).toEqual({ + $schema: "https://json-schema.org/draft/2020-12/schema", + $id: "https://example.com/schema.json", + title: "Example", + description: "An example schema.", + type: "object", + }); + }); }); diff --git a/packages/config/src/internal.ts b/packages/config/src/internal.ts index 22bdbf08ee..8734c5ecff 100644 --- a/packages/config/src/internal.ts +++ b/packages/config/src/internal.ts @@ -9,9 +9,11 @@ * `loadCliConfig`/`resolveCliConfigValue`/`resolveCliConfigSubtree` below are * the SAME runtime functions `./effect` exports, just re-typed here to widen * their options parameter to the internal-only, Go-parity `goViperCompat` - * knob (`InternalLoadCliConfigOptions`/`InternalResolveCliConfigOptions`) — - * this module otherwise only re-exports types and registry data, not - * independent implementations. + * knob (`InternalLoadCliConfigOptions` for `loadCliConfig`; + * `resolveCliConfigValue`/`resolveCliConfigSubtree`'s own widened options + * type is package-internal and not itself re-exported here) — this module + * otherwise only re-exports types and registry data, not independent + * implementations. */ export { ENV_CAPTURE_REGEX } from "./lib/env.ts"; export { AUTH_HOOK_NAMES, unmappedSecretApiPaths } from "./project-config/registry-auth.ts"; diff --git a/packages/config/src/lib/resolve.ts b/packages/config/src/lib/resolve.ts index 5c4a2cba70..fac62b90f9 100644 --- a/packages/config/src/lib/resolve.ts +++ b/packages/config/src/lib/resolve.ts @@ -146,9 +146,11 @@ export function resolveCliConfigValueAtPath( * `{ values }` directly instead of threading through the whole loaded object. * * Has no options parameter: this package's one resolver knob (`goViperCompat`) - * is internal-only — see `InternalResolveCliConfigOptions` in `../project.ts`, - * exported from `@supabase/config/internal`. Adding a public knob later is a - * non-breaking, additive change. + * is internal-only — see `InternalResolveCliConfigOptions` in `../project.ts`. + * That type is package-internal (not itself re-exported from + * `@supabase/config/internal`); only the `resolveCliConfigValue`/ + * `resolveCliConfigSubtree` functions widened to accept it are exported from + * there. Adding a public knob later is a non-breaking, additive change. */ export function resolveCliConfigValue( value: T, diff --git a/packages/config/src/project-config/project-schema.ts b/packages/config/src/project-config/project-schema.ts index b1aa0e6e8e..e3f53a1ef2 100644 --- a/packages/config/src/project-config/project-schema.ts +++ b/packages/config/src/project-config/project-schema.ts @@ -22,19 +22,21 @@ * `PropertySignature`/`IndexSignature` whose value AST carries the * `x-secret` annotation (ADR 0019 rule 5 — `fromConfigDocument`/ * `fromApiProjectConfig` never populate a secret leaf either), the same - * detection `../lib/secret-paths.ts`'s own walk uses. A container whose - * value type consists ENTIRELY of secret leaves (`db.vault`, a - * `Record`) ends up an empty `Objects` node this way - * (no surviving properties or index signatures) — `SchemaAST`'s own - * documented behavior for that shape is "accepts any value except - * `null`/`undefined`", the closest a schema can get to "this container - * held nothing but secrets, so nothing concrete is left to validate - * here" without special-casing an empty-object type JSON Schema has no - * way to express either. Two OTHER hosted-section leaves land on that - * same empty-`Objects` shape for an unrelated reason: + * detection `../lib/secret-paths.ts`'s own walk uses. When that + * stripping empties out an `Objects` node that ORIGINALLY had at least + * one property/index signature — a container whose value type consists + * ENTIRELY of secret leaves, e.g. `db.vault` (a `Record`) — the walk drops that property/index signature from its + * PARENT entirely instead of keeping an empty, accept-anything + * `Objects` node: an all-secret container is itself secret-shaped, the + * same as a single secret leaf, so `db.vault` never appears anywhere in + * `ProjectConfigSchema` at all. This is distinct from an `Objects` node + * that was ALREADY empty at the SOURCE level before any stripping — * `storage.analytics.buckets.*` and `storage.vector.buckets.*` are - * already `Schema.Struct({})` at the SOURCE level (`../storage.ts`) — - * genuinely empty structs, untouched by this walk's secret-stripping. + * genuinely empty `Schema.Struct({})`s (`../storage.ts`), untouched by + * this walk, and still pass through as accept-anything leaves — the + * derived schema must not be stricter than `CliConfigSchema` itself, + * which behaves identically for those two, genuinely-empty structs. * - Wraps every SURVIVING property in `optionalKey` (via * {@link toOptionalAst}), recursing into its type — mirroring * `DeepPartial`'s `{ readonly [K in keyof T]?: DeepPartial }` @@ -103,6 +105,31 @@ function isSecretAst(ast: SchemaAST.AST): boolean { return ast.annotations?.["x-secret"] === true; } +function hasObjectMembers(ast: SchemaAST.AST): boolean { + return ( + SchemaAST.isObjects(ast) && + (ast.propertySignatures.length > 0 || ast.indexSignatures.length > 0) + ); +} + +/** + * True when `original` was an `Objects` node with at least one member + * (property or index signature) before secret-stripping, and `transformed` — + * the same node's {@link toDeepOptionalHostedAst} result — ended up with + * none: every member was secret-shaped and got dropped, so the container + * itself is now secret-shaped too. Distinguishes that case from an `Objects` + * node that was ALREADY empty at the source level (`storage.analytics. + * buckets.*`/`storage.vector.buckets.*` — see this module's own doc + * comment), which must pass through unchanged rather than being treated as + * secret-shaped. + */ +function isAllSecretCollapsedContainer( + original: SchemaAST.AST, + transformed: SchemaAST.AST, +): boolean { + return hasObjectMembers(original) && !hasObjectMembers(transformed); +} + /** * Marks `ast` optional through the PUBLIC `Schema.optionalKey` combinator * (`Schema.optionalKey(Schema.make(ast)).ast`) rather than the internal @@ -130,24 +157,26 @@ function toOptionalAst(ast: SchemaAST.AST): SchemaAST.AST { */ function toDeepOptionalHostedAst(ast: SchemaAST.AST): SchemaAST.AST { if (SchemaAST.isObjects(ast)) { - const propertySignatures = ast.propertySignatures - .filter((property) => !isSecretAst(property.type)) - .map( - (property) => - new SchemaAST.PropertySignature( - property.name, - toOptionalAst(toDeepOptionalHostedAst(property.type)), - ), - ); - const indexSignatures = ast.indexSignatures - .filter((indexSignature) => !isSecretAst(indexSignature.type)) - .map( - (indexSignature) => - new SchemaAST.IndexSignature( - indexSignature.parameter, - toDeepOptionalHostedAst(indexSignature.type), - ), - ); + const propertySignatures = ast.propertySignatures.flatMap((property) => { + if (isSecretAst(property.type)) { + return []; + } + const transformedType = toDeepOptionalHostedAst(property.type); + if (isAllSecretCollapsedContainer(property.type, transformedType)) { + return []; + } + return [new SchemaAST.PropertySignature(property.name, toOptionalAst(transformedType))]; + }); + const indexSignatures = ast.indexSignatures.flatMap((indexSignature) => { + if (isSecretAst(indexSignature.type)) { + return []; + } + const transformedType = toDeepOptionalHostedAst(indexSignature.type); + if (isAllSecretCollapsedContainer(indexSignature.type, transformedType)) { + return []; + } + return [new SchemaAST.IndexSignature(indexSignature.parameter, transformedType)]; + }); return new SchemaAST.Objects( propertySignatures, indexSignatures, diff --git a/packages/config/src/project-config/project-schema.unit.test.ts b/packages/config/src/project-config/project-schema.unit.test.ts index 9bebcd60bb..53e2dced7c 100644 --- a/packages/config/src/project-config/project-schema.unit.test.ts +++ b/packages/config/src/project-config/project-schema.unit.test.ts @@ -72,6 +72,18 @@ describe("ProjectConfigSchema acceptance", () => { const projected = fromApiProjectConfig(apiEnvelope({ database: { major_version: 17 } })); expect(() => decodeProjectConfig(projected)).not.toThrow(); }); + + // `db.vault` is dropped from the schema entirely (an all-secret + // `Record` container, project-schema.ts's + // `isAllSecretCollapsedContainer`) rather than kept as an empty, + // accept-anything node — so under this schema's permissive-excess design + // (never `additionalProperties: false`, never `onExcessProperty: "error"`), + // a `db.vault` of ANY shape is simply excess input: it validates, but is + // silently dropped from the decoded result rather than rejected. + test("db.vault of any shape validates but is dropped, since the schema no longer knows the key", () => { + expect(decodeProjectConfig({ db: { vault: 42 } })).toEqual({ db: {} }); + expect(decodeProjectConfig({ db: { vault: {} } })).toEqual({ db: {} }); + }); }); describe("ProjectConfigSchema rejection", () => { @@ -146,17 +158,107 @@ describe("ProjectConfigSchema secret-strip exhaustiveness", () => { // (e.g. a whole section got dropped by an unrelated bug), `findAtPattern` // for the full secret path also returns `undefined` — indistinguishable, // from that assertion alone, from the secret leaf being correctly - // stripped. Asserting the parent path is still reachable rules that out. - test("the parent of every stripped x-secret path is still reachable", () => { + // stripped. Asserting the parent path is still reachable rules that out — + // EXCEPT for a known all-secret collapsed container (`db.vault`, a + // `Record` — project-schema.ts's + // `isAllSecretCollapsedContainer`), whose own immediate parent is dropped + // entirely rather than kept as an empty node. That one case is accepted + // explicitly (checking the GRANDPARENT is reachable instead, and that the + // container's own name no longer survives as a property there) rather + // than by walking arbitrarily far up the ancestor chain, which would mask + // an unrelated regression dropping some other, unexpected ancestor. + const KNOWN_ALL_SECRET_COLLAPSED_CONTAINER_PARENTS: ReadonlyArray> = [ + ["db", "vault"], + ]; + + test("the parent of every stripped x-secret path is still reachable, except a known all-secret collapsed container", () => { for (const pattern of reachablePatterns) { const parentPattern = pattern.slice(0, -1); const parent = parentPattern.length === 0 ? ProjectConfigSchema.ast : findAtPattern(ProjectConfigSchema.ast, parentPattern); - expect(parent, `parent of ${JSON.stringify(pattern)} vanished`).toBeDefined(); + + if (parent !== undefined) { + continue; + } + + const isKnownAllSecretContainer = KNOWN_ALL_SECRET_COLLAPSED_CONTAINER_PARENTS.some( + (known) => + known.length === parentPattern.length && + known.every((segment, index) => segment === parentPattern[index]), + ); + expect( + isKnownAllSecretContainer, + `parent of ${JSON.stringify(pattern)} vanished unexpectedly (not a known all-secret collapsed container)`, + ).toBe(true); + + const grandparentPattern = parentPattern.slice(0, -1); + const grandparent = + grandparentPattern.length === 0 + ? ProjectConfigSchema.ast + : findAtPattern(ProjectConfigSchema.ast, grandparentPattern); + expect(grandparent, `grandparent of ${JSON.stringify(pattern)} vanished`).toBeDefined(); + + const droppedName = parentPattern[parentPattern.length - 1]; + if (grandparent !== undefined && SchemaAST.isObjects(grandparent)) { + expect( + grandparent.propertySignatures.some((property) => property.name === droppedName), + ).toBe(false); + } } }); + + /** + * Recursively collects the dotted path of every reachable `Objects` node + * with zero properties AND zero index signatures — the shape both a + * genuinely source-empty struct (`storage.analytics.buckets.*`, + * `storage.vector.buckets.*` — see `project-schema.ts`'s own doc comment) + * and (before CLI-2234's fix) an all-secret collapsed container would + * produce. `db.vault` is dropped entirely rather than emptied now, so it + * must NOT appear in this list — this is the "double-check no OTHER + * container becomes stripped-empty besides vault" guard. + */ + function collectEmptyObjectPaths( + ast: SchemaAST.AST, + path: ReadonlyArray, + seen: Set, + into: string[], + ): void { + if (SchemaAST.isUnion(ast)) { + if (seen.has(ast)) { + return; + } + seen.add(ast); + for (const member of ast.types) { + collectEmptyObjectPaths(member, path, seen, into); + } + return; + } + if (!SchemaAST.isObjects(ast) || seen.has(ast)) { + return; + } + seen.add(ast); + if (ast.propertySignatures.length === 0 && ast.indexSignatures.length === 0) { + into.push(path.join(".")); + return; + } + for (const property of ast.propertySignatures) { + collectEmptyObjectPaths(property.type, [...path, String(property.name)], seen, into); + } + for (const indexSignature of ast.indexSignatures) { + collectEmptyObjectPaths(indexSignature.type, [...path, "*"], seen, into); + } + } + + test("no all-secret container besides db.vault collapses to an empty, accept-anything node", () => { + const emptyObjectPaths: string[] = []; + collectEmptyObjectPaths(ProjectConfigSchema.ast, [], new Set(), emptyObjectPaths); + + expect(emptyObjectPaths.toSorted()).toEqual( + ["storage.analytics.buckets.*", "storage.vector.buckets.*"].toSorted(), + ); + }); }); describe("ProjectConfigSchema hosted-section keys", () => { @@ -298,10 +400,8 @@ describe("toProjectConfigJsonSchema", () => { expect(document.properties.db.properties.pooler.required).toBeUndefined(); }); - test("the db.vault secret record collapses to a schema with no properties left to leak", () => { - const vault = document.properties.db.properties.vault; - expect(vault.properties).toBeUndefined(); - expect(vault.patternProperties).toBeUndefined(); + test("db.vault disappears from the schema entirely (an all-secret container is dropped, not emptied)", () => { + expect(Object.hasOwn(document.properties.db.properties, "vault")).toBe(false); }); test("is JSON-serializable and stable across two calls", () => { diff --git a/packages/config/vitest.config.ts b/packages/config/vitest.config.ts index 7a35ffa8d7..6c2cb8409a 100644 --- a/packages/config/vitest.config.ts +++ b/packages/config/vitest.config.ts @@ -1,6 +1,21 @@ +import { defaultClientConditions, defaultServerConditions } from "vite"; import { defineConfig } from "vitest/config"; +// This package publishes a `bun` export condition pointing at its +// TypeScript source (see package.json's `exports` map); without it, Vite's +// resolver falls through to the `default` condition and loads the built +// `dist/*.js` output instead — stale, or missing entirely on a fresh clone +// before the package has been built. Extending (not replacing) Vite's +// default condition lists keeps every other package's exports resolution +// unchanged. Required on every inline `test.projects` entry too: Vitest +// builds a separate Vite config per project and does not inherit these from +// the root config (see PR #6366 finding 0). +const workspacePackageResolve = { conditions: [...defaultClientConditions, "bun"] }; +const workspacePackageSsrResolve = { conditions: [...defaultServerConditions, "bun"] }; + export default defineConfig({ + resolve: workspacePackageResolve, + ssr: { resolve: workspacePackageSsrResolve }, test: { passWithNoTests: true, coverage: { @@ -13,6 +28,8 @@ export default defineConfig({ }, projects: [ { + resolve: workspacePackageResolve, + ssr: { resolve: workspacePackageSsrResolve }, test: { name: "unit", include: ["**/*.unit.test.ts"], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8372b826ff..5d76c74cd0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -240,6 +240,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vite: + specifier: ^6.0.0 || ^7.0.0 || ^8.0.0 + version: 8.1.4(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0) vitest: specifier: 'catalog:' version: 4.1.10(@types/node@26.2.0)(@vitest/coverage-istanbul@4.1.10)(vite@8.1.4(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)) @@ -431,6 +434,9 @@ importers: typescript: specifier: 'catalog:' version: 7.0.2 + vite: + specifier: ^6.0.0 || ^7.0.0 || ^8.0.0 + version: 8.1.4(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0) vitest: specifier: 'catalog:' version: 4.1.10(@types/node@26.2.0)(@vitest/coverage-istanbul@4.1.10)(vite@8.1.4(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0)) diff --git a/tools/config-api-compare.ts b/tools/config-api-compare.ts index 04253cac23..caa320481a 100644 --- a/tools/config-api-compare.ts +++ b/tools/config-api-compare.ts @@ -9,7 +9,10 @@ * Base ref resolution, in order: `--base`, then `GITHUB_BASE_REF` (prefixed * `origin/`), then `origin/develop`. Resolves `git merge-base HEAD `, * fetching `origin/` at depth 1 first when the ref is missing locally - * (a shallow CI clone only has the PR's own commits). + * (a shallow CI clone only has the PR's own commits). If HEAD's own checkout + * is also shallow, a depth-1 base fetch still can't produce a common + * ancestor — the tool then unshallows (or deepens) the checkout and retries + * once more before giving up and skipping the compare. * * Emits declarations twice with the same compiler settings — head from * `packages/config/src` directly, base from a `git archive` of the @@ -70,18 +73,33 @@ function resolveBaseRef(cliBase: string | undefined): string { return "origin/develop"; } +type MergeBaseResolution = + | { readonly kind: "resolved"; readonly sha: string } + | { readonly kind: "skip"; readonly reason: string }; + /** * Resolves `git merge-base HEAD `. A shallow CI checkout only has * the PR's own commits, so `` can be locally unresolvable — when * it's an `origin/` ref, fetch that branch at depth 1 and retry - * before giving up. A non-`origin/` ref (e.g. an explicit `--base `) - * that doesn't resolve locally is a caller error, not something this tool - * can fetch its way out of. + * before giving up. + * + * A depth-1 base fetch only helps when the base ref itself was simply never + * fetched; it cannot produce a common ancestor when HEAD's own checkout is + * shallow too (the `check` job's default `actions/checkout` depth), since + * neither side's shallow history reaches the other's. In that case, unshallow + * (or deepen, if `--unshallow` errors because the checkout is already + * complete) the repository, refetch the base ref in full, and retry once + * more. If a merge-base still can't be resolved, this is an advisory check — + * skip the compare instead of failing the tool. + * + * A non-`origin/` ref (e.g. an explicit `--base `) that doesn't resolve + * locally is a caller error, not something this tool can fetch its way out + * of. */ -async function resolveMergeBase(baseRef: string): Promise { +async function resolveMergeBase(baseRef: string): Promise { const attempt = await runGit(["merge-base", "HEAD", baseRef], repoRoot); if (attempt.exitCode === 0) { - return attempt.stdout.trim(); + return { kind: "resolved", sha: attempt.stdout.trim() }; } if (!baseRef.startsWith("origin/")) { @@ -105,13 +123,61 @@ async function resolveMergeBase(baseRef: string): Promise { } const retry = await runGit(["merge-base", "HEAD", baseRef], repoRoot); - if (retry.exitCode !== 0) { + if (retry.exitCode === 0) { + return { kind: "resolved", sha: retry.stdout.trim() }; + } + + const isShallow = await runGit(["rev-parse", "--is-shallow-repository"], repoRoot); + if (isShallow.stdout.trim() !== "true") { throw new Error( `could not resolve base ref "${baseRef}" even after fetching origin/${branchName}: ` + retry.stderr.trim(), ); } - return retry.stdout.trim(); + + console.warn( + `[config-api-compare] HEAD's own checkout is shallow, so a depth-1 ${baseRef} fetch can't ` + + "produce a common ancestor; unshallowing before retrying merge-base...", + ); + const unshallow = await runGit(["fetch", "--unshallow", "origin", branchName], repoRoot); + if (unshallow.exitCode !== 0) { + console.warn( + `[config-api-compare] git fetch --unshallow failed (${unshallow.stderr.trim()}); falling ` + + "back to git fetch --deepen=100000...", + ); + const deepen = await runGit(["fetch", "--deepen=100000", "origin"], repoRoot); + if (deepen.exitCode !== 0) { + return { + kind: "skip", + reason: + `could not unshallow (${unshallow.stderr.trim()}) or deepen ` + + `(${deepen.stderr.trim()}) the checkout to resolve a merge-base against ${baseRef}.`, + }; + } + } + + const fullFetch = await runGit( + ["fetch", "origin", `+${branchName}:refs/remotes/origin/${branchName}`], + repoRoot, + ); + if (fullFetch.exitCode !== 0) { + return { + kind: "skip", + reason: `could not fully fetch origin/${branchName} after unshallowing: ${fullFetch.stderr.trim()}.`, + }; + } + + const finalRetry = await runGit(["merge-base", "HEAD", baseRef], repoRoot); + if (finalRetry.exitCode === 0) { + return { kind: "resolved", sha: finalRetry.stdout.trim() }; + } + + return { + kind: "skip", + reason: + `could not resolve a merge-base between HEAD and ${baseRef} even after unshallowing ` + + `(git merge-base: ${finalRetry.stderr.trim()}).`, + }; } async function shortSha(rev: string): Promise { @@ -413,6 +479,19 @@ function renderMarkdownSummary( return lines.join("\n"); } +function renderShallowHistorySkippedSummary( + baseRef: string, + headLabel: string, + reason: string, +): string { + return [ + "## Config type-surface diff (advisory)", + "", + `⚠️ Compare skipped (shallow history): could not resolve a merge-base between \`${headLabel}\` ` + + `and \`${baseRef}\`: ${reason}`, + ].join("\n"); +} + function renderSkippedSummary(baseLabel: string, headLabel: string, baseEmit: EmitResult): string { return [ "## Config type-surface diff (advisory)", @@ -437,7 +516,16 @@ async function main(): Promise { const { values } = parseArgs({ options: { base: { type: "string" } } }); const baseRef = resolveBaseRef(values.base); - const mergeBase = await resolveMergeBase(baseRef); + const mergeBaseResolution = await resolveMergeBase(baseRef); + if (mergeBaseResolution.kind === "skip") { + const headLabel = await shortSha("HEAD"); + console.warn(`[config-api-compare] WARNING: ${mergeBaseResolution.reason}`); + await writeStepSummary( + renderShallowHistorySkippedSummary(baseRef, headLabel, mergeBaseResolution.reason), + ); + return 0; + } + const mergeBase = mergeBaseResolution.sha; const [baseLabel, headLabel] = await Promise.all([shortSha(mergeBase), shortSha("HEAD")]); console.log( `[config-api-compare] comparing merge-base ${baseLabel} (of ${baseRef}) against HEAD ${headLabel}...`, diff --git a/turbo.json b/turbo.json index a4f76a2b59..ba4a14ebe6 100644 --- a/turbo.json +++ b/turbo.json @@ -72,15 +72,20 @@ }, "@supabase/docs#generate": { "cache": true, - "dependsOn": ["supabase#build"], + "dependsOn": ["supabase#build", "@supabase/config#build"], "inputs": [ "$TURBO_DEFAULT$", "!content/docs/commands/**", "!public/cli/config.schema.json", + "!public/cli/project-config.schema.json", "$TURBO_ROOT$/.bun-version", "$TURBO_ROOT$/mise.lock" ], - "outputs": ["content/docs/commands/**", "public/cli/config.schema.json"] + "outputs": [ + "content/docs/commands/**", + "public/cli/config.schema.json", + "public/cli/project-config.schema.json" + ] }, "@supabase/docs#build": { "cache": true, @@ -89,6 +94,7 @@ "$TURBO_DEFAULT$", "!content/docs/commands/**", "!public/cli/config.schema.json", + "!public/cli/project-config.schema.json", "$TURBO_ROOT$/.bun-version", "$TURBO_ROOT$/mise.lock" ], From 98728256a07c47f5fba0710803d20106163759a0 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Sun, 30 Aug 2026 13:48:35 +0100 Subject: [PATCH 9/9] docs(config): correct the ./io error-contract and sync-export notes (CLI-2234) --- packages/config/README.md | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/config/README.md b/packages/config/README.md index dba81fdf56..22fac21442 100644 --- a/packages/config/README.md +++ b/packages/config/README.md @@ -261,8 +261,11 @@ specific, narrower validation contract — what it does and does not promise: `./io` re-exports this package's entire pure surface (`export * from "."`, the same way `./effect` does) alongside its seven Promise-returning functions, so one import from `@supabase/config/io` is -enough — no separate import from `.` needed to also name an error class, `CliConfigSchema`, or the -two synchronous resolvers (the only non-Promise members `./io` exports). +enough — no separate import from `.` needed to also name an error class, `CliConfigSchema`, or any +other pure export (those are all synchronous, exactly as on `.`). What `./io` adds on top — the +seven facade functions — is Promise-returning; among same-named `./effect` counterparts, only +`resolveCliConfigValue`/`resolveCliConfigSubtree` stay synchronous here, since they come from the +pure surface rather than the facade. `loadCliConfig`, `findCliProjectRoot`, `findCliProjectPaths`, and `loadCliProjectEnvironment` resolve to `null` — they never reject — when there is simply no project or config file to find. @@ -281,14 +284,21 @@ with any of: - `PlatformError` (from `effect/PlatformError`) — a host/OS failure surfaced by the underlying `FileSystem` service -Every one of these is a plain class (an Effect `Data.TaggedError`), so a catch block can -distinguish them with `instanceof`. Each carries structured fields instead of a prose message — -`error.message` is empty on every one of them except `ProjectConfigParseError` (the only class that -always sets it). Build user-facing text from the typed fields instead: `CliConfigParseError.path`/ -`.format`/`.cause`, `CliProjectEnvParseError.path`/`.line`, `DuplicateRemoteProjectIdError.message`/ -`InvalidRemoteProjectIdError.message` (these two DO set `message`, verbatim from Go), and -`ProjectConfigParseError.message`/`.reason`/`.apiPath`/`.detail`. A `CliConfigParseError`'s `.cause` -is typically a schema issue that itself carries line/column location info worth surfacing. +The four package-owned classes are plain Effect `Data.TaggedError` classes; `PlatformError` is +Effect's own error class rather than one of this package's — but all five are classes, so a catch +block can distinguish any of them with `instanceof`. What each carries: + +- `DuplicateRemoteProjectIdError` and `InvalidRemoteProjectIdError` set a real `error.message` + (verbatim the Go CLI's wording for the same failures). +- `PlatformError` sets `error.message` too, describing the failing filesystem operation, alongside + `.module`/`.method`/`.description`. +- `CliConfigParseError` and `CliProjectEnvParseError` carry structured fields instead of prose — + their `error.message` is empty. Build user-facing text from `CliConfigParseError.path`/`.format`/ + `.cause` (the `.cause` is typically a TOML or schema issue that itself carries line/column + location info worth surfacing) and `CliProjectEnvParseError.path`/`.line`. + +(`ProjectConfigParseError` is not part of this contract — it's thrown synchronously by the +`ProjectConfig` converters on the pure surface, and is documented in that section.) ```ts import {