diff --git a/apps/docs/public/cli/config.schema.json b/apps/docs/public/cli/config.schema.json index 87a9942059..e64d6aeb24 100644 --- a/apps/docs/public/cli/config.schema.json +++ b/apps/docs/public/cli/config.schema.json @@ -13,24 +13,56 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local Logflare service.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "port": { - "type": "number", "description": "Port to the local Logflare service.", - "default": 54327 + "default": 54327, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "backend": { - "type": "string", - "enum": ["postgres", "bigquery"], "description": "Configure one of the supported backends:\n\n- `postgres`\n- `bigquery`", - "default": "postgres" + "default": "postgres", + "anyOf": [ + { + "type": "string", + "enum": ["postgres", "bigquery"] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "vector_port": { - "type": "number", - "description": "Port to the local syslog ingest service." + "description": "Port to the local syslog ingest service.", + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "gcp_project_id": { "type": "string", @@ -51,47 +83,103 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local PostgREST service.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "port": { - "type": "number", "description": "Port to use for the API URL.", - "default": 54321 + "default": 54321, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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"] + "default": ["public", "graphql_public"], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "Schemas to expose in your API. Tables, views and stored procedures in this schema will get API endpoints." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "extra_search_path": { - "type": "array", - "items": { - "type": "string", - "description": "Extra schemas to add to the search_path of every request." - }, - "default": ["public", "extensions"] + "default": ["public", "extensions"], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "Extra schemas to add to the search_path of every request." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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 + "default": 1000, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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." + "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.", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "tls": { "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable HTTPS endpoints locally using a self-signed certificate.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "cert_path": { "type": "string", @@ -115,9 +203,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local GoTrue service.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "site_url": { "type": "string", @@ -125,18 +221,34 @@ "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"] + "default": ["https://127.0.0.1:3000"], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "A URL that auth providers are permitted to redirect to." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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 + "default": 3600, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "jwt_issuer": { "type": "string", @@ -147,45 +259,101 @@ "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 + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "refresh_token_reuse_interval": { - "type": "number", "description": "Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.", - "default": 10 + "default": 10, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "enable_manual_linking": { - "type": "boolean", "description": "Allow/disallow testing manual linking of accounts.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "enable_signup": { - "type": "boolean", "description": "Allow/disallow new user signups to your project.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "enable_anonymous_sign_ins": { - "type": "boolean", "description": "Allow/disallow anonymous sign-ins to your project.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "minimum_password_length": { - "type": "number", "description": "Passwords shorter than this value will be rejected as weak.", - "default": 6 + "default": 6, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "password_requirements": { - "type": "string", - "enum": [ - "", - "letters_digits", - "lower_upper_letters_digits", - "lower_upper_letters_digits_symbols" - ], "description": "Password character requirements.", - "default": "" + "default": "", + "anyOf": [ + { + "type": "string", + "enum": [ + "", + "letters_digits", + "lower_upper_letters_digits", + "lower_upper_letters_digits_symbols" + ] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "publishable_key": { "type": "string", @@ -216,39 +384,95 @@ "type": "object", "properties": { "email_sent": { - "type": "number", "description": "Number of emails that can be sent per hour.", - "default": 2 + "default": 2, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "sms_sent": { - "type": "number", "description": "Number of SMS messages that can be sent per hour.", - "default": 30 + "default": 30, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "anonymous_users": { - "type": "number", "description": "Number of anonymous sign-ins that can be made per hour per IP address.", - "default": 30 + "default": 30, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "token_refresh": { - "type": "number", "description": "Number of sessions that can be refreshed in a 5 minute interval per IP address.", - "default": 150 + "default": 150, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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 + "default": 30, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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 + "default": 30, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "web3": { - "type": "number", "description": "Number of Web3 logins that can be made in a 5 minute interval per IP address.", - "default": 30 + "default": 30, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -257,14 +481,30 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable CAPTCHA verification.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "provider": { - "type": "string", - "enum": ["hcaptcha", "turnstile"], - "description": "CAPTCHA provider to use." + "description": "CAPTCHA provider to use.", + "anyOf": [ + { + "type": "string", + "enum": ["hcaptcha", "turnstile"] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "secret": { "type": "string", @@ -281,9 +521,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable or disable the mfa verification hook.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "uri": { "type": "string", @@ -301,9 +549,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable or disable the password verification hook.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "uri": { "type": "string", @@ -321,9 +577,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable or disable the custom access token hook.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "uri": { "type": "string", @@ -341,9 +605,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable or disable the send sms hook.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "uri": { "type": "string", @@ -361,9 +633,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable or disable the send email hook.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "uri": { "type": "string", @@ -381,9 +661,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable or disable the before user created hook.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "uri": { "type": "string", @@ -407,14 +695,30 @@ "type": "object", "properties": { "enroll_enabled": { - "type": "boolean", "description": "Allow/disallow TOTP enrollment for users.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "verify_enabled": { - "type": "boolean", "description": "Allow/disallow TOTP verification for users.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -423,19 +727,43 @@ "type": "object", "properties": { "enroll_enabled": { - "type": "boolean", "description": "Allow/disallow phone enrollment for users.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "verify_enabled": { - "type": "boolean", "description": "Allow/disallow phone verification for users.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "otp_length": { - "type": "number", "description": "The length of the OTP code.", - "default": 6 + "default": 6, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "template": { "type": "string", @@ -454,22 +782,46 @@ "type": "object", "properties": { "enroll_enabled": { - "type": "boolean", "description": "Allow/disallow WebAuthn enrollment for users.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "verify_enabled": { - "type": "boolean", "description": "Allow/disallow WebAuthn verification for users.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false }, "max_enrolled_factors": { - "type": "number", "description": "The maximum number of MFA factors a user can enroll in.", - "default": 10 + "default": 10, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -493,24 +845,56 @@ "type": "object", "properties": { "enable_signup": { - "type": "boolean", "description": "Allow/disallow new user signups via email to your project.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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 + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "enable_confirmations": { - "type": "boolean", "description": "If enabled, users need to confirm their email address before signing in.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "secure_password_change": { - "type": "boolean", "description": "If enabled, users will need to reauthenticate or have logged in recently to change their password.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_frequency": { "type": "string", @@ -518,30 +902,62 @@ "default": "1s" }, "otp_length": { - "type": "number", "description": "Number of characters used in the email OTP.", - "default": 6 + "default": 6, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "otp_expiry": { - "type": "number", "description": "Number of seconds before the email OTP expires.", - "default": 3600 + "default": 3600, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "smtp": { "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable SMTP for email delivery.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "host": { "type": "string", "description": "Hostname or IP address of the SMTP server." }, "port": { - "type": "number", - "description": "Port number of the SMTP server." + "description": "Port number of the SMTP server.", + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "user": { "type": "string", @@ -608,9 +1024,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the notification email.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "subject": { "type": "string", @@ -645,14 +1069,30 @@ "type": "object", "properties": { "enable_signup": { - "type": "boolean", "description": "Allow/disallow new user signups via SMS to your project.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "enable_confirmations": { - "type": "boolean", "description": "If enabled, users need to confirm their phone number before signing in.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "template": { "type": "string", @@ -665,22 +1105,46 @@ "default": "5s" }, "otp_length": { - "type": "number", "description": "Number of characters used in the SMS OTP.", - "default": 6 + "default": 6, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "otp_expiry": { - "type": "number", "description": "Number of seconds before the SMS OTP expires.", - "default": 60 + "default": 60, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "twilio": { "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable/disable Twilio provider for phone login.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "account_sid": { "type": "string", @@ -709,9 +1173,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable/disable Twilio Verify provider for phone verification.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "account_sid": { "type": "string", @@ -733,9 +1205,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable/disable MessageBird provider for phone login.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "originator": { "type": "string", @@ -753,9 +1233,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable/disable Textlocal provider for phone login.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "sender": { "type": "string", @@ -773,9 +1261,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable/disable Vonage provider for phone login.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "from": { "type": "string", @@ -810,9 +1306,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Apple OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -836,14 +1340,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -852,9 +1372,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Azure OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -878,14 +1406,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -894,9 +1438,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Bitbucket OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -920,14 +1472,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -936,9 +1504,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Discord OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -962,14 +1538,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -978,9 +1570,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Facebook OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1004,14 +1604,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1020,9 +1636,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Figma OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1046,14 +1670,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1062,9 +1702,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the GitHub OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1088,14 +1736,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1104,9 +1768,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the GitLab OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1130,14 +1802,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1146,9 +1834,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Google OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1172,14 +1868,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1188,9 +1900,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Kakao OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1214,14 +1934,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1230,9 +1966,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Keycloak OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1257,14 +2001,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1273,9 +2033,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the LinkedIn OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1299,14 +2067,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1315,9 +2099,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Notion OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1341,14 +2133,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1357,9 +2165,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Twitch OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1383,14 +2199,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1399,9 +2231,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Twitter OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1425,14 +2265,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1441,9 +2297,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the X OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1467,14 +2331,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1483,9 +2363,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Slack OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1509,14 +2397,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1525,9 +2429,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Spotify OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1551,14 +2463,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1567,9 +2495,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the WorkOS OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1593,14 +2529,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1609,9 +2561,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Zoom OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -1635,14 +2595,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1657,9 +2633,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this Web3 provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1668,9 +2652,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this Web3 provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1682,9 +2674,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable OAuth server functionality.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "authorization_url_path": { "type": "string", @@ -1692,9 +2692,17 @@ "default": "/oauth/consent" }, "allow_dynamic_registration": { - "type": "boolean", "description": "Allow dynamic client registration.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1706,9 +2714,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this third-party auth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "project_id": { "type": "string", @@ -1721,9 +2737,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this third-party auth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "tenant": { "type": "string", @@ -1740,9 +2764,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this third-party auth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "user_pool_id": { "type": "string", @@ -1759,9 +2791,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this third-party auth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "domain": { "type": "string", @@ -1774,9 +2814,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this third-party auth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "issuer_url": { "type": "string", @@ -1795,14 +2843,30 @@ "type": "object", "properties": { "port": { - "type": "number", "description": "Port to use for the local database URL.", - "default": 54322 + "default": 54322, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "shadow_port": { - "type": "number", "description": "Port used by db diff command to initialize the shadow database.", - "default": 54320 + "default": 54320, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "health_timeout": { "type": "string", @@ -1810,9 +2874,17 @@ "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 + "default": 17, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "orioledb_version": { "type": "string", @@ -1822,30 +2894,70 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local PgBouncer service.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "port": { - "type": "number", "description": "Port to use for the local connection pooler.", - "default": 54329 + "default": 54329, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "pool_mode": { - "type": "string", - "enum": ["transaction", "session"], "description": "Specifies when a server connection can be reused by other clients.", - "default": "transaction" + "default": "transaction", + "anyOf": [ + { + "type": "string", + "enum": ["transaction", "session"] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "default_pool_size": { - "type": "number", "description": "How many server connections to allow per user/database pair.", - "default": 20 + "default": 20, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_client_conn": { - "type": "number", "description": "Maximum number of client connections allowed.", - "default": 100 + "default": 100, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1854,18 +2966,34 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "If disabled, migrations will be skipped during a db push or reset.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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": [] + "default": [], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "Schema file path, directory, or glob relative to the supabase directory." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1874,18 +3002,34 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable seeding the database with SQL files.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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"] + "default": ["./seed.sql"], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "Path to a SQL file used to seed the database." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1903,22 +3047,70 @@ "type": "string" }, "max_connections": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_locks_per_transaction": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_parallel_maintenance_workers": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_parallel_workers": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_parallel_workers_per_gather": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_replication_slots": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_slot_wal_keep_size": { "type": "string" @@ -1933,15 +3125,39 @@ "type": "string" }, "max_wal_senders": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_worker_processes": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "session_replication_role": { - "type": "string", - "enum": ["origin", "replica", "local"], - "description": "Session replication role." + "description": "Session replication role.", + "anyOf": [ + { + "type": "string", + "enum": ["origin", "replica", "local"] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "shared_buffers": { "type": "string" @@ -1953,7 +3169,15 @@ "type": "string" }, "track_commit_timestamp": { - "type": "boolean" + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "wal_keep_size": { "type": "string" @@ -1971,25 +3195,49 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable management of network restrictions.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "allowed_cidrs": { - "type": "array", - "items": { - "type": "string" - }, "description": "Allowed IPv4 CIDR blocks.", - "default": ["0.0.0.0/0"] + "default": ["0.0.0.0/0"], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "allowed_cidrs_v6": { - "type": "array", - "items": { - "type": "string" - }, "description": "Allowed IPv6 CIDR blocks.", - "default": ["::/0"] + "default": ["::/0"], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -1998,9 +3246,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Reject non-secure connections to the database.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -2021,25 +3277,57 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local Edge Runtime service.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "policy": { - "type": "string", - "enum": ["oneshot", "per_worker"], "description": "Configure the supported request policy.", - "default": "per_worker" + "default": "per_worker", + "anyOf": [ + { + "type": "string", + "enum": ["oneshot", "per_worker"] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "inspector_port": { - "type": "number", "description": "Port to run the Edge Functions inspector on.", - "default": 8083 + "default": 8083, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "deno_version": { - "type": "number", "description": "The Deno major version to use.", - "default": 2 + "default": 2, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "secrets": { "type": "object", @@ -2064,14 +3352,30 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Controls whether a function is deployed or served.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "verify_jwt": { - "type": "boolean", "description": "By default, deployed or locally served functions reject requests without a valid JWT.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "import_map": { "type": "string", @@ -2084,13 +3388,21 @@ "default": "" }, "static_files": { - "type": "array", - "items": { - "type": "string", - "description": "Static file glob for the function." - }, "description": "Static files to bundle with the function.", - "default": [] + "default": [], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "Static file glob for the function." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "env": { "type": "object", @@ -2125,22 +3437,54 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local SMTP testing server.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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 + "default": 54324, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "smtp_port": { - "type": "number", - "description": "Optional SMTP port to expose for local testing." + "description": "Optional SMTP port to expose for local testing.", + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "pop3_port": { - "type": "number", - "description": "Optional POP3 port to expose for local testing." + "description": "Optional POP3 port to expose for local testing.", + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "admin_email": { "type": "string", @@ -2157,20 +3501,44 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local Realtime service.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "ip_version": { - "type": "string", - "enum": ["IPv4", "IPv6"], "description": "Bind realtime via either IPv4 or IPv6.", - "default": "IPv4" + "default": "IPv4", + "anyOf": [ + { + "type": "string", + "enum": ["IPv4", "IPv6"] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_header_length": { - "type": "number", "description": "Maximum length of the HTTP header.", - "default": 4096 + "default": 4096, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -2179,9 +3547,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local Storage service.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "file_size_limit": { "anyOf": [ @@ -2190,6 +3566,10 @@ }, { "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" } ] }, @@ -2197,9 +3577,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable image transformation.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -2212,9 +3600,17 @@ "type": "object", "properties": { "public": { - "type": "boolean", "description": "Enable public access to the bucket.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "file_size_limit": { "anyOf": [ @@ -2223,17 +3619,29 @@ }, { "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" } ] }, "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": [] + "default": [], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "A MIME type allowed for the bucket." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "objects_path": { "type": "string", @@ -2254,9 +3662,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Allow connections via S3 compatible clients.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -2265,24 +3681,56 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable analytics buckets.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_namespaces": { - "type": "number", "description": "Maximum number of analytics namespaces.", - "default": 5 + "default": 5, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_tables": { - "type": "number", "description": "Maximum number of analytics tables.", - "default": 10 + "default": 10, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_catalogs": { - "type": "number", "description": "Maximum number of analytics catalogs.", - "default": 2 + "default": 2, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "buckets": { "anyOf": [ @@ -2320,19 +3768,43 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable vector buckets.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_buckets": { - "type": "number", "description": "Maximum number of vector buckets.", - "default": 10 + "default": 10, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_indexes": { - "type": "number", "description": "Maximum number of vector indexes.", - "default": 5 + "default": 5, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "buckets": { "anyOf": [ @@ -2373,14 +3845,30 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local Supabase Studio dashboard.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "port": { - "type": "number", "description": "Port to use for Supabase Studio.", - "default": 54323 + "default": 54323, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "api_url": { "type": "string", @@ -2420,10 +3908,18 @@ "examples": ["private"] }, "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 compute scaled; `--instances` overrides\nit for one deploy. Defaults to 1.", - "examples": [3] + "examples": [3], + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "source": { "type": "string", @@ -2431,12 +3927,20 @@ "examples": ["packages/api"] }, "exclude": { - "type": "array", - "items": { - "type": "string" - }, "description": "Patterns for paths to leave out of the uploaded build context, read the way\n`.gitignore` reads them: a pattern without `/` matches that name at any depth,\none with `/` is anchored at the compute's source directory, a trailing `/`\nmatches directories only, and `**` spans directories. Excluding a directory\nexcludes everything under it; re-inclusion (`!`) is not supported. Only the\npatterns recorded here are excluded, so a compute with no list uploads its source\ndirectory whole.", - "examples": [["node_modules", ".env"]] + "examples": [["node_modules", ".env"]], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -2454,12 +3958,28 @@ "type": "object", "properties": { "compute": { - "type": "boolean", - "description": "Enable the experimental compute command family." + "description": "Enable the experimental compute command family.", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "stack": { - "type": "boolean", - "description": "Use the new local stack backend for start, stop, and status, and for --local targets of the db, migration, test db, gen types, inspect, and pull command families." + "description": "Use the new local stack backend for start, stop, and status, and for --local targets of the db, migration, test db, gen types, inspect, and pull command families.", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "orioledb_version": { "type": "string", @@ -2491,9 +4011,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable experimental webhooks.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -2502,9 +4030,17 @@ "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 + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "declarative_schema_path": { "type": "string", @@ -2745,24 +4281,56 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local Logflare service.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "port": { - "type": "number", "description": "Port to the local Logflare service.", - "default": 54327 + "default": 54327, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "backend": { - "type": "string", - "enum": ["postgres", "bigquery"], "description": "Configure one of the supported backends:\n\n- `postgres`\n- `bigquery`", - "default": "postgres" + "default": "postgres", + "anyOf": [ + { + "type": "string", + "enum": ["postgres", "bigquery"] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "vector_port": { - "type": "number", - "description": "Port to the local syslog ingest service." + "description": "Port to the local syslog ingest service.", + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "gcp_project_id": { "type": "string", @@ -2783,47 +4351,103 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local PostgREST service.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "port": { - "type": "number", "description": "Port to use for the API URL.", - "default": 54321 + "default": 54321, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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"] + "default": ["public", "graphql_public"], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "Schemas to expose in your API. Tables, views and stored procedures in this schema will get API endpoints." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "extra_search_path": { - "type": "array", - "items": { - "type": "string", - "description": "Extra schemas to add to the search_path of every request." - }, - "default": ["public", "extensions"] + "default": ["public", "extensions"], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "Extra schemas to add to the search_path of every request." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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 + "default": 1000, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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." + "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.", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "tls": { "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable HTTPS endpoints locally using a self-signed certificate.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "cert_path": { "type": "string", @@ -2847,9 +4471,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local GoTrue service.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "site_url": { "type": "string", @@ -2857,18 +4489,34 @@ "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"] + "default": ["https://127.0.0.1:3000"], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "A URL that auth providers are permitted to redirect to." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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 + "default": 3600, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "jwt_issuer": { "type": "string", @@ -2879,45 +4527,101 @@ "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 + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "refresh_token_reuse_interval": { - "type": "number", "description": "Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.", - "default": 10 + "default": 10, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "enable_manual_linking": { - "type": "boolean", "description": "Allow/disallow testing manual linking of accounts.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "enable_signup": { - "type": "boolean", "description": "Allow/disallow new user signups to your project.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "enable_anonymous_sign_ins": { - "type": "boolean", "description": "Allow/disallow anonymous sign-ins to your project.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "minimum_password_length": { - "type": "number", "description": "Passwords shorter than this value will be rejected as weak.", - "default": 6 + "default": 6, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "password_requirements": { - "type": "string", - "enum": [ - "", - "letters_digits", - "lower_upper_letters_digits", - "lower_upper_letters_digits_symbols" - ], "description": "Password character requirements.", - "default": "" + "default": "", + "anyOf": [ + { + "type": "string", + "enum": [ + "", + "letters_digits", + "lower_upper_letters_digits", + "lower_upper_letters_digits_symbols" + ] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "publishable_key": { "type": "string", @@ -2948,39 +4652,95 @@ "type": "object", "properties": { "email_sent": { - "type": "number", "description": "Number of emails that can be sent per hour.", - "default": 2 + "default": 2, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "sms_sent": { - "type": "number", "description": "Number of SMS messages that can be sent per hour.", - "default": 30 + "default": 30, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "anonymous_users": { - "type": "number", "description": "Number of anonymous sign-ins that can be made per hour per IP address.", - "default": 30 + "default": 30, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "token_refresh": { - "type": "number", "description": "Number of sessions that can be refreshed in a 5 minute interval per IP address.", - "default": 150 + "default": 150, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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 + "default": 30, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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 + "default": 30, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "web3": { - "type": "number", "description": "Number of Web3 logins that can be made in a 5 minute interval per IP address.", - "default": 30 + "default": 30, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -2989,14 +4749,30 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable CAPTCHA verification.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "provider": { - "type": "string", - "enum": ["hcaptcha", "turnstile"], - "description": "CAPTCHA provider to use." + "description": "CAPTCHA provider to use.", + "anyOf": [ + { + "type": "string", + "enum": ["hcaptcha", "turnstile"] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "secret": { "type": "string", @@ -3013,9 +4789,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable or disable the mfa verification hook.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "uri": { "type": "string", @@ -3033,9 +4817,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable or disable the password verification hook.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "uri": { "type": "string", @@ -3053,9 +4845,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable or disable the custom access token hook.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "uri": { "type": "string", @@ -3073,9 +4873,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable or disable the send sms hook.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "uri": { "type": "string", @@ -3093,9 +4901,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable or disable the send email hook.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "uri": { "type": "string", @@ -3113,9 +4929,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable or disable the before user created hook.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "uri": { "type": "string", @@ -3139,14 +4963,30 @@ "type": "object", "properties": { "enroll_enabled": { - "type": "boolean", "description": "Allow/disallow TOTP enrollment for users.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "verify_enabled": { - "type": "boolean", "description": "Allow/disallow TOTP verification for users.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -3155,19 +4995,43 @@ "type": "object", "properties": { "enroll_enabled": { - "type": "boolean", "description": "Allow/disallow phone enrollment for users.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "verify_enabled": { - "type": "boolean", "description": "Allow/disallow phone verification for users.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "otp_length": { - "type": "number", "description": "The length of the OTP code.", - "default": 6 + "default": 6, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "template": { "type": "string", @@ -3186,22 +5050,46 @@ "type": "object", "properties": { "enroll_enabled": { - "type": "boolean", "description": "Allow/disallow WebAuthn enrollment for users.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "verify_enabled": { - "type": "boolean", "description": "Allow/disallow WebAuthn verification for users.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false }, "max_enrolled_factors": { - "type": "number", "description": "The maximum number of MFA factors a user can enroll in.", - "default": 10 + "default": 10, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -3225,24 +5113,56 @@ "type": "object", "properties": { "enable_signup": { - "type": "boolean", "description": "Allow/disallow new user signups via email to your project.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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 + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "enable_confirmations": { - "type": "boolean", "description": "If enabled, users need to confirm their email address before signing in.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "secure_password_change": { - "type": "boolean", "description": "If enabled, users will need to reauthenticate or have logged in recently to change their password.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_frequency": { "type": "string", @@ -3250,30 +5170,62 @@ "default": "1s" }, "otp_length": { - "type": "number", "description": "Number of characters used in the email OTP.", - "default": 6 + "default": 6, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "otp_expiry": { - "type": "number", "description": "Number of seconds before the email OTP expires.", - "default": 3600 + "default": 3600, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "smtp": { "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable SMTP for email delivery.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "host": { "type": "string", "description": "Hostname or IP address of the SMTP server." }, "port": { - "type": "number", - "description": "Port number of the SMTP server." + "description": "Port number of the SMTP server.", + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "user": { "type": "string", @@ -3340,9 +5292,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the notification email.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "subject": { "type": "string", @@ -3377,14 +5337,30 @@ "type": "object", "properties": { "enable_signup": { - "type": "boolean", "description": "Allow/disallow new user signups via SMS to your project.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "enable_confirmations": { - "type": "boolean", "description": "If enabled, users need to confirm their phone number before signing in.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "template": { "type": "string", @@ -3397,22 +5373,46 @@ "default": "5s" }, "otp_length": { - "type": "number", "description": "Number of characters used in the SMS OTP.", - "default": 6 + "default": 6, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "otp_expiry": { - "type": "number", "description": "Number of seconds before the SMS OTP expires.", - "default": 60 + "default": 60, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "twilio": { "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable/disable Twilio provider for phone login.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "account_sid": { "type": "string", @@ -3441,9 +5441,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable/disable Twilio Verify provider for phone verification.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "account_sid": { "type": "string", @@ -3465,9 +5473,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable/disable MessageBird provider for phone login.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "originator": { "type": "string", @@ -3485,9 +5501,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable/disable Textlocal provider for phone login.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "sender": { "type": "string", @@ -3505,9 +5529,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable/disable Vonage provider for phone login.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "from": { "type": "string", @@ -3542,9 +5574,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Apple OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -3568,14 +5608,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -3584,9 +5640,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Azure OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -3610,14 +5674,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -3626,9 +5706,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Bitbucket OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -3652,14 +5740,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -3668,9 +5772,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Discord OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -3694,14 +5806,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -3710,9 +5838,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Facebook OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -3736,14 +5872,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -3752,9 +5904,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Figma OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -3778,14 +5938,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -3794,9 +5970,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the GitHub OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -3820,14 +6004,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -3836,9 +6036,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the GitLab OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -3862,14 +6070,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -3878,9 +6102,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Google OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -3904,14 +6136,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -3920,9 +6168,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Kakao OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -3946,14 +6202,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -3962,9 +6234,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Keycloak OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -3989,14 +6269,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4005,9 +6301,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the LinkedIn OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -4031,14 +6335,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4047,9 +6367,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Notion OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -4073,14 +6401,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4089,9 +6433,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Twitch OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -4115,14 +6467,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4131,9 +6499,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Twitter OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -4157,14 +6533,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4173,9 +6565,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the X OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -4199,14 +6599,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4215,9 +6631,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Slack OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -4241,14 +6665,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4257,9 +6697,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Spotify OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -4283,14 +6731,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4299,9 +6763,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the WorkOS OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -4325,14 +6797,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4341,9 +6829,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Use the Zoom OAuth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "client_id": { "type": "string", @@ -4367,14 +6863,30 @@ "default": "" }, "skip_nonce_check": { - "type": "boolean", "description": "If true, the nonce check will be skipped.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "email_optional": { - "type": "boolean", "description": "If true, authentication succeeds when the provider does not return an email address.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4389,9 +6901,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this Web3 provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4400,9 +6920,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this Web3 provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4414,9 +6942,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable OAuth server functionality.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "authorization_url_path": { "type": "string", @@ -4424,9 +6960,17 @@ "default": "/oauth/consent" }, "allow_dynamic_registration": { - "type": "boolean", "description": "Allow dynamic client registration.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4438,9 +6982,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this third-party auth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "project_id": { "type": "string", @@ -4453,9 +7005,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this third-party auth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "tenant": { "type": "string", @@ -4472,9 +7032,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this third-party auth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "user_pool_id": { "type": "string", @@ -4491,9 +7059,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this third-party auth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "domain": { "type": "string", @@ -4506,9 +7082,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable this third-party auth provider.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "issuer_url": { "type": "string", @@ -4527,14 +7111,30 @@ "type": "object", "properties": { "port": { - "type": "number", "description": "Port to use for the local database URL.", - "default": 54322 + "default": 54322, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "shadow_port": { - "type": "number", "description": "Port used by db diff command to initialize the shadow database.", - "default": 54320 + "default": 54320, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "health_timeout": { "type": "string", @@ -4542,9 +7142,17 @@ "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 + "default": 17, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "orioledb_version": { "type": "string", @@ -4554,30 +7162,70 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local PgBouncer service.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "port": { - "type": "number", "description": "Port to use for the local connection pooler.", - "default": 54329 + "default": 54329, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "pool_mode": { - "type": "string", - "enum": ["transaction", "session"], "description": "Specifies when a server connection can be reused by other clients.", - "default": "transaction" + "default": "transaction", + "anyOf": [ + { + "type": "string", + "enum": ["transaction", "session"] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "default_pool_size": { - "type": "number", "description": "How many server connections to allow per user/database pair.", - "default": 20 + "default": 20, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_client_conn": { - "type": "number", "description": "Maximum number of client connections allowed.", - "default": 100 + "default": 100, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4586,18 +7234,34 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "If disabled, migrations will be skipped during a db push or reset.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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": [] + "default": [], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "Schema file path, directory, or glob relative to the supabase directory." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4606,18 +7270,34 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable seeding the database with SQL files.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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"] + "default": ["./seed.sql"], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "Path to a SQL file used to seed the database." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4635,22 +7315,70 @@ "type": "string" }, "max_connections": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_locks_per_transaction": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_parallel_maintenance_workers": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_parallel_workers": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_parallel_workers_per_gather": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_replication_slots": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_slot_wal_keep_size": { "type": "string" @@ -4665,15 +7393,39 @@ "type": "string" }, "max_wal_senders": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_worker_processes": { - "type": "number" + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "session_replication_role": { - "type": "string", - "enum": ["origin", "replica", "local"], - "description": "Session replication role." + "description": "Session replication role.", + "anyOf": [ + { + "type": "string", + "enum": ["origin", "replica", "local"] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "shared_buffers": { "type": "string" @@ -4685,7 +7437,15 @@ "type": "string" }, "track_commit_timestamp": { - "type": "boolean" + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "wal_keep_size": { "type": "string" @@ -4703,25 +7463,49 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable management of network restrictions.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "allowed_cidrs": { - "type": "array", - "items": { - "type": "string" - }, "description": "Allowed IPv4 CIDR blocks.", - "default": ["0.0.0.0/0"] + "default": ["0.0.0.0/0"], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "allowed_cidrs_v6": { - "type": "array", - "items": { - "type": "string" - }, "description": "Allowed IPv6 CIDR blocks.", - "default": ["::/0"] + "default": ["::/0"], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4730,9 +7514,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Reject non-secure connections to the database.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4753,25 +7545,57 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local Edge Runtime service.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "policy": { - "type": "string", - "enum": ["oneshot", "per_worker"], "description": "Configure the supported request policy.", - "default": "per_worker" + "default": "per_worker", + "anyOf": [ + { + "type": "string", + "enum": ["oneshot", "per_worker"] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "inspector_port": { - "type": "number", "description": "Port to run the Edge Functions inspector on.", - "default": 8083 + "default": 8083, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "deno_version": { - "type": "number", "description": "The Deno major version to use.", - "default": 2 + "default": 2, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "secrets": { "type": "object", @@ -4796,14 +7620,30 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Controls whether a function is deployed or served.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "verify_jwt": { - "type": "boolean", "description": "By default, deployed or locally served functions reject requests without a valid JWT.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "import_map": { "type": "string", @@ -4816,13 +7656,21 @@ "default": "" }, "static_files": { - "type": "array", - "items": { - "type": "string", - "description": "Static file glob for the function." - }, "description": "Static files to bundle with the function.", - "default": [] + "default": [], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "Static file glob for the function." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "env": { "type": "object", @@ -4857,22 +7705,54 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local SMTP testing server.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "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 + "default": 54324, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "smtp_port": { - "type": "number", - "description": "Optional SMTP port to expose for local testing." + "description": "Optional SMTP port to expose for local testing.", + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "pop3_port": { - "type": "number", - "description": "Optional POP3 port to expose for local testing." + "description": "Optional POP3 port to expose for local testing.", + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "admin_email": { "type": "string", @@ -4889,20 +7769,44 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local Realtime service.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "ip_version": { - "type": "string", - "enum": ["IPv4", "IPv6"], "description": "Bind realtime via either IPv4 or IPv6.", - "default": "IPv4" + "default": "IPv4", + "anyOf": [ + { + "type": "string", + "enum": ["IPv4", "IPv6"] + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_header_length": { - "type": "number", "description": "Maximum length of the HTTP header.", - "default": 4096 + "default": 4096, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4911,9 +7815,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local Storage service.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "file_size_limit": { "anyOf": [ @@ -4922,6 +7834,10 @@ }, { "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" } ] }, @@ -4929,9 +7845,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable image transformation.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4944,9 +7868,17 @@ "type": "object", "properties": { "public": { - "type": "boolean", "description": "Enable public access to the bucket.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "file_size_limit": { "anyOf": [ @@ -4955,17 +7887,29 @@ }, { "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" } ] }, "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": [] + "default": [], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string", + "description": "A MIME type allowed for the bucket." + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "objects_path": { "type": "string", @@ -4986,9 +7930,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Allow connections via S3 compatible clients.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -4997,24 +7949,56 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable analytics buckets.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_namespaces": { - "type": "number", "description": "Maximum number of analytics namespaces.", - "default": 5 + "default": 5, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_tables": { - "type": "number", "description": "Maximum number of analytics tables.", - "default": 10 + "default": 10, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_catalogs": { - "type": "number", "description": "Maximum number of analytics catalogs.", - "default": 2 + "default": 2, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "buckets": { "anyOf": [ @@ -5052,19 +8036,43 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable vector buckets.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_buckets": { - "type": "number", "description": "Maximum number of vector buckets.", - "default": 10 + "default": 10, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "max_indexes": { - "type": "number", "description": "Maximum number of vector indexes.", - "default": 5 + "default": 5, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "buckets": { "anyOf": [ @@ -5105,14 +8113,30 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable the local Supabase Studio dashboard.", - "default": true + "default": true, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "port": { - "type": "number", "description": "Port to use for Supabase Studio.", - "default": 54323 + "default": 54323, + "anyOf": [ + { + "type": "number" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "api_url": { "type": "string", @@ -5152,10 +8176,18 @@ "examples": ["private"] }, "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 compute scaled; `--instances` overrides\nit for one deploy. Defaults to 1.", - "examples": [3] + "examples": [3], + "anyOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "source": { "type": "string", @@ -5163,12 +8195,20 @@ "examples": ["packages/api"] }, "exclude": { - "type": "array", - "items": { - "type": "string" - }, "description": "Patterns for paths to leave out of the uploaded build context, read the way\n`.gitignore` reads them: a pattern without `/` matches that name at any depth,\none with `/` is anchored at the compute's source directory, a trailing `/`\nmatches directories only, and `**` spans directories. Excluding a directory\nexcludes everything under it; re-inclusion (`!`) is not supported. Only the\npatterns recorded here are excluded, so a compute with no list uploads its source\ndirectory whole.", - "examples": [["node_modules", ".env"]] + "examples": [["node_modules", ".env"]], + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -5186,12 +8226,28 @@ "type": "object", "properties": { "compute": { - "type": "boolean", - "description": "Enable the experimental compute command family." + "description": "Enable the experimental compute command family.", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "stack": { - "type": "boolean", - "description": "Use the new local stack backend for start, stop, and status, and for --local targets of the db, migration, test db, gen types, inspect, and pull command families." + "description": "Use the new local stack backend for start, stop, and status, and for --local targets of the db, migration, test db, gen types, inspect, and pull command families.", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "orioledb_version": { "type": "string", @@ -5223,9 +8279,17 @@ "type": "object", "properties": { "enabled": { - "type": "boolean", "description": "Enable experimental webhooks.", - "default": false + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] } }, "additionalProperties": false @@ -5234,9 +8298,17 @@ "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 + "default": false, + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^env\\((.*)\\)$" + } + ] }, "declarative_schema_path": { "type": "string", diff --git a/packages/config/AGENTS.md b/packages/config/AGENTS.md index f1c0b4eb45..46ae61ee30 100644 --- a/packages/config/AGENTS.md +++ b/packages/config/AGENTS.md @@ -50,7 +50,7 @@ artifacts (`./schema.json`, `./project-schema.json`). `apps/cli` needs them, and internal-only keeps the published semver surface unchanged). 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). + `dist/` build output); number, integer, boolean, enum and string-list fields also accept `env(VAR)` strings. - `@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`. diff --git a/packages/config/package.json b/packages/config/package.json index 8302128046..8fffa6469a 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -84,6 +84,7 @@ "@tsconfig/bun": "catalog:", "@types/bun": "catalog:", "@vitest/coverage-v8": "catalog:", + "ajv": "^8.20.0", "effect": "catalog:", "semantic-release": "catalog:", "typescript": "catalog:", diff --git a/packages/config/scripts/build-artifacts.unit.test.ts b/packages/config/scripts/build-artifacts.unit.test.ts index f8da54e665..3a7a50f404 100644 --- a/packages/config/scripts/build-artifacts.unit.test.ts +++ b/packages/config/scripts/build-artifacts.unit.test.ts @@ -75,7 +75,11 @@ describe("published JSON Schema artifacts", () => { throw new Error("expected properties.api.properties.max_rows to be an object"); } const maxRows = apiProperties["max_rows"]; - expect(maxRows["type"]).toBe("number"); + const anyOf = maxRows["anyOf"]; + if (!Array.isArray(anyOf) || !isRecord(anyOf[0])) { + throw new Error("expected max_rows to be an anyOf of the number and an env() string"); + } + expect(anyOf[0]["type"]).toBe("number"); expect(typeof maxRows["description"]).toBe("string"); expect(maxRows["default"]).toBe(1000); }); diff --git a/packages/config/src/base.ts b/packages/config/src/base.ts index 143762fd7f..6f6af2e211 100644 --- a/packages/config/src/base.ts +++ b/packages/config/src/base.ts @@ -83,7 +83,10 @@ export const CliConfigSchema = Schema.Struct({ }); export function toCliConfigJsonSchema() { - return toPublishedJsonSchemaDocument(CliConfigSchema, { onExcessProperty: "error" }); + return toPublishedJsonSchemaDocument(CliConfigSchema, { + onExcessProperty: "error", + acceptEnvReferences: true, + }); } export type CliConfig = typeof CliConfigSchema.Type; diff --git a/packages/config/src/json-schema-document.ts b/packages/config/src/json-schema-document.ts index 2fbc1cbe66..9fe55f4f53 100644 --- a/packages/config/src/json-schema-document.ts +++ b/packages/config/src/json-schema-document.ts @@ -1,23 +1,38 @@ import { Schema } from "effect"; +import { ENV_PATTERN } from "./lib/env.ts"; + +type Node = Record; + +const COERCED_TYPES = new Set(["number", "integer", "boolean"]); +const HOISTED_ANNOTATIONS = new Set(["title", "description", "default", "examples"]); +const DATA_KEYWORDS = new Set(["default", "examples", "enum", "const"]); +const SCHEMA_MAP_KEYWORDS = new Set(["properties", "patternProperties"]); /** * Renders a published JSON Schema document, carrying the `x-secret` marker through and keeping * pattern-keyed records open: the generator writes a boolean `additionalProperties` on them * (effect 4.0) with no per-schema setting, while the published documents never carried one there. + * With `acceptEnvReferences`, number, integer, boolean, string-enum and string-list leaves also + * accept the `env(VAR)` references the loader resolves. */ export function toPublishedJsonSchemaDocument( schema: Schema.Constraint, - options: { readonly onExcessProperty: "ignore" | "error" }, + options: { + readonly onExcessProperty: "ignore" | "error"; + readonly acceptEnvReferences?: boolean; + }, ) { const document = Schema.toJsonSchemaDocument(schema, { onExcessProperty: options.onExcessProperty, includeAnnotationKey: (key) => key === "x-secret", }); + const root = openPatternKeyedRecords(document.schema); + const definitions = openPatternKeyedRecords(document.definitions); return { $schema: "https://json-schema.org/draft/2020-12/schema", - ...openPatternKeyedRecords(document.schema), - ...(Object.keys(document.definitions).length > 0 - ? { $defs: openPatternKeyedRecords(document.definitions) } + ...(options.acceptEnvReferences ? acceptEnvReferences(root) : root), + ...(Object.keys(definitions).length > 0 + ? { $defs: options.acceptEnvReferences ? mapSchemas(definitions) : definitions } : {}), }; } @@ -37,3 +52,66 @@ function isRecordLike(node: Record): boolean { const patternProperties = node["patternProperties"]; return typeof patternProperties === "object" && patternProperties !== null; } + +function acceptEnvReferences(node: T): T { + if (Array.isArray(node)) return node.map(acceptEnvReferences) as T; + if (!isNode(node)) return node; + if (isEnvReferenceTarget(node)) return withEnvReferenceBranch(node) as T; + const out: Node = {}; + for (const [key, value] of Object.entries(node)) { + if (DATA_KEYWORDS.has(key)) out[key] = value; + else if (SCHEMA_MAP_KEYWORDS.has(key)) out[key] = mapSchemas(value); + else if (key === "anyOf" && Array.isArray(value)) out[key] = acceptEnvReferencesInUnion(value); + else out[key] = acceptEnvReferences(value); + } + return out as T; +} + +function mapSchemas(schemas: unknown): unknown { + if (!isNode(schemas)) return schemas; + return Object.fromEntries( + Object.entries(schemas).map(([name, schema]) => [name, acceptEnvReferences(schema)]), + ); +} + +function acceptEnvReferencesInUnion(members: ReadonlyArray): Array { + const accepted = members.map((member) => + isEnvReferenceTarget(member) ? member : acceptEnvReferences(member), + ); + const needsBranch = members.some(isEnvReferenceTarget) && !accepted.some(isEnvReferenceBranch); + return needsBranch ? [...accepted, envReferenceBranch()] : accepted; +} + +function withEnvReferenceBranch(node: Node): Node { + const outer: Node = {}; + const typed: Node = {}; + for (const [key, value] of Object.entries(node)) { + (HOISTED_ANNOTATIONS.has(key) ? outer : typed)[key] = value; + } + return { ...outer, anyOf: [typed, envReferenceBranch()] }; +} + +function envReferenceBranch(): Node { + return { type: "string", pattern: ENV_PATTERN }; +} + +function isEnvReferenceBranch(node: unknown): boolean { + if (!isNode(node) || node["type"] !== "string") return false; + const checks = Array.isArray(node["allOf"]) ? [node, ...node["allOf"]] : [node]; + return checks.some((check) => isNode(check) && check["pattern"] === ENV_PATTERN); +} + +function isEnvReferenceTarget(node: unknown): node is Node { + if (!isNode(node) || typeof node["type"] !== "string") return false; + if (COERCED_TYPES.has(node["type"])) return true; + if (node["type"] === "string") return !isFreeFormString(node); + return node["type"] === "array" && isFreeFormString(node["items"]); +} + +function isFreeFormString(node: unknown): boolean { + return isNode(node) && node["type"] === "string" && !("enum" in node); +} + +function isNode(value: unknown): value is Node { + return typeof value === "object" && value !== null && !Array.isArray(value); +} diff --git a/packages/config/src/json-schema-document.unit.test.ts b/packages/config/src/json-schema-document.unit.test.ts index 99eba9d5ee..1e0cb4c19c 100644 --- a/packages/config/src/json-schema-document.unit.test.ts +++ b/packages/config/src/json-schema-document.unit.test.ts @@ -1,8 +1,15 @@ -import { Schema } from "effect"; +import type { ErrorObject, ValidateFunction } from "ajv"; +import Ajv2020 from "ajv/dist/2020"; +import { Result, Schema, SchemaIssue } from "effect"; import { describe, expect, test } from "vitest"; -import { toCliConfigJsonSchema } from "./base.ts"; +import { CliConfigSchema, toCliConfigJsonSchema } from "./base.ts"; import { toPublishedJsonSchemaDocument } from "./json-schema-document.ts"; -import { env } from "./lib/env.ts"; +import { + ENV_PATTERN, + env, + interpolateEnvReferencesAgainstSchema, + isEnvReference, +} from "./lib/env.ts"; import { secretPathPatterns } from "./lib/secret-paths.ts"; import { toProjectConfigJsonSchema } from "./project-config/project-schema.ts"; @@ -17,6 +24,162 @@ const at = (node: unknown, ...path: ReadonlyArray): Node => { const isNode = (value: unknown): value is Node => typeof value === "object" && value !== null && !Array.isArray(value); +const DATA_KEYWORDS = new Set(["default", "examples", "enum", "const"]); +const SCHEMA_MAP_KEYWORDS = new Set(["properties", "patternProperties", "$defs"]); + +type Path = ReadonlyArray; + +const visitSchemas = ( + node: unknown, + visit: (node: Node, path: Path, siblings: ReadonlyArray | undefined) => void, + path: Path = [], + siblings?: ReadonlyArray, +): void => { + if (!isNode(node)) return; + visit(node, path, siblings); + for (const [keyword, value] of Object.entries(node)) { + if (DATA_KEYWORDS.has(keyword)) continue; + if (SCHEMA_MAP_KEYWORDS.has(keyword) && isNode(value)) { + for (const [name, child] of Object.entries(value)) { + visitSchemas(child, visit, [...path, keyword, name]); + } + } else if (Array.isArray(value)) { + value.forEach((child, index) => { + visitSchemas( + child, + visit, + [...path, keyword, index], + keyword === "anyOf" ? value : undefined, + ); + }); + } else { + visitSchemas(value, visit, [...path, keyword]); + } + } +}; + +const isEnvBranch = (node: unknown): boolean => + isNode(node) && + node.type === "string" && + typeof node.pattern === "string" && + Object.keys(node).length === 2; + +const collectEnvBranches = (document: unknown): Array => { + const found: Array = []; + visitSchemas(document, (node, _path, siblings) => { + if (siblings !== undefined && isEnvBranch(node)) found.push(node); + }); + return found; +}; + +const patternOf = (node: Node): string => { + if (typeof node.pattern !== "string") throw new Error("expected the node to carry a pattern"); + return node.pattern; +}; + +// $schema is dropped so the tests do not depend on which draft the header names. +const compile = (document: Node): ValidateFunction => { + const schema = Object.fromEntries(Object.entries(document).filter(([key]) => key !== "$schema")); + return new Ajv2020({ strict: false }).compile(schema); +}; + +const errorsFor = (validate: ValidateFunction, value: unknown): ReadonlyArray => + validate(value) ? [] : [...(validate.errors ?? [])]; + +const failingPaths = (validate: ValidateFunction, value: unknown): ReadonlyArray => [ + ...new Set(errorsFor(validate, value).map((error) => error.instancePath)), +]; + +const envPatternOf = (document: Node): string => + patternOf(at(document, "properties", "api", "properties", "max_rows", "anyOf", 1)); + +const RECORD_KEYS = ["sample", "SAMPLE"]; + +const collectLeaves = (node: unknown, path: Path = []): Array => { + if (!isNode(node)) return []; + const { properties, patternProperties, additionalProperties, anyOf, items } = node; + if (isNode(properties) || isNode(patternProperties) || isNode(additionalProperties)) { + return [ + ...Object.entries(isNode(properties) ? properties : {}).flatMap(([name, child]) => + collectLeaves(child, [...path, name]), + ), + ...Object.entries(isNode(patternProperties) ? patternProperties : {}).flatMap( + ([pattern, child]) => { + const key = RECORD_KEYS.find((candidate) => new RegExp(pattern, "u").test(candidate)); + return key === undefined ? [] : collectLeaves(child, [...path, key]); + }, + ), + ...collectLeaves(additionalProperties, [...path, "sample"]), + ]; + } + if (Array.isArray(anyOf) && anyOf.some((member) => isNode(member) && member.type === "object")) { + return anyOf.flatMap((member) => collectLeaves(member, path)); + } + if (isNode(items) && items.type === "object") return collectLeaves(items, [...path, 0]); + return [[path, node]]; +}; + +const envValueFor = (leaf: Node): string | undefined => { + const typed = Array.isArray(leaf.anyOf) ? at(leaf, "anyOf", 0) : leaf; + switch (typed.type) { + case "boolean": + return "false"; + case "number": + case "integer": + return "1"; + case "array": + return "a,b"; + case "string": + return Array.isArray(typed.enum) ? String(typed.enum.find((value) => value !== "")) : "x"; + default: + return undefined; + } +}; + +const documentAt = (path: Path, value: unknown): unknown => + path.reduceRight( + (child, key) => (typeof key === "number" ? [child] : { [key]: child }), + value, + ); + +const isAtOrBelow = (issuePath: ReadonlyArray, path: Path): boolean => + path.every((key, index) => String(issuePath[index]) === String(key)); + +const schemaAcceptsAt = (validate: ValidateFunction, path: Path, value: unknown): boolean => + !failingPaths(validate, documentAt(path, value)).some((pointer) => + isAtOrBelow(pointer.split("/").slice(1), path), + ); + +// Checks are off so only type acceptance is compared, not value rules such as port ranges. +const decodeCli = Schema.decodeUnknownResult(CliConfigSchema, { + errors: "all", + disableChecks: true, +}); +const toIssues = SchemaIssue.makeFormatterStandardSchemaV1(); + +const loaderAcceptsAt = ( + path: Path, + value: unknown, + env: Readonly>, +): boolean => { + const resolved = interpolateEnvReferencesAgainstSchema( + documentAt(path, value), + env, + CliConfigSchema, + { + goViperCompat: true, + }, + ); + const result = decodeCli(resolved); + if (Result.isSuccess(result)) return true; + return !toIssues(result.failure.issue).issues.some((issue) => + isAtOrBelow( + (issue.path ?? []).map((segment) => (typeof segment === "object" ? segment.key : segment)), + path, + ), + ); +}; + const collectSecretPaths = (document: unknown): Array => { const found = new Set(); const visit = (node: unknown, path: ReadonlyArray): void => { @@ -90,3 +253,398 @@ describe("published JSON Schema documents", () => { expect(JSON.stringify(toCliConfigJsonSchema())).not.toContain("x-env-deferred"); }); }); + +describe("env() references in the published CLI JSON Schema", () => { + const cli = toCliConfigJsonSchema(); + const validateCli = compile(cli); + + const baseline = { + project_id: "demo", + api: { port: 54321, max_rows: 1000 }, + auth: { enable_signup: true }, + db: { settings: { max_connections: 100 } }, + compute: { web: { instances: 2 } }, + storage: { file_size_limit: "50MiB" }, + remotes: { staging: { api: { max_rows: 500 }, compute: { web: { instances: 4 } } } }, + }; + + describe("validation", () => { + test("a document of plain literals validates", () => { + expect(errorsFor(validateCli, baseline)).toEqual([]); + }); + + test.each([ + ["api.max_rows", { api: { max_rows: "env(MAX_ROWS)" } }], + ["api.port", { api: { port: "env(API_PORT)" } }], + ["auth.enable_signup", { auth: { enable_signup: "env(ENABLE_SIGNUP)" } }], + ["db.settings.max_connections", { db: { settings: { max_connections: "env(MAX_CONN)" } } }], + ["compute..instances", { compute: { web: { instances: "env(WEB_INSTANCES)" } } }], + ["storage.file_size_limit", { storage: { file_size_limit: "env(FILE_SIZE_LIMIT)" } }], + ["db.pooler.pool_mode", { db: { pooler: { pool_mode: "env(POOL_MODE)" } } }], + ["realtime.ip_version", { realtime: { ip_version: "env(IP_VERSION)" } }], + [ + "auth.additional_redirect_urls", + { auth: { additional_redirect_urls: "env(REDIRECT_URLS)" } }, + ], + ["remotes..api.max_rows", { remotes: { staging: { api: { max_rows: "env(ROWS)" } } } }], + [ + "remotes..auth.enable_signup", + { remotes: { staging: { auth: { enable_signup: "env(SIGNUP)" } } } }, + ], + [ + "remotes..compute..instances", + { remotes: { staging: { compute: { web: { instances: "env(WEB)" } } } } }, + ], + ])("accepts env() on %s", (_field, document) => { + expect(errorsFor(validateCli, document)).toEqual([]); + }); + + test.each([ + ["a numeric string in a number field", { api: { max_rows: "1000" } }, "/api/max_rows"], + ["a word in a boolean field", { auth: { enable_signup: "yes" } }, "/auth/enable_signup"], + ["an unclosed env() reference", { api: { port: "env(PORT" } }, "/api/port"], + [ + "an env() reference with leading whitespace", + { auth: { enable_signup: " env(X)" } }, + "/auth/enable_signup", + ], + ["a boolean in a number field", { api: { max_rows: true } }, "/api/max_rows"], + [ + "an unknown value in an enum field", + { db: { pooler: { pool_mode: "statement" } } }, + "/db/pooler/pool_mode", + ], + [ + "a fractional count in an integer field", + { compute: { web: { instances: 1.5 } } }, + "/compute/web/instances", + ], + [ + "a negative count in a field with a minimum", + { compute: { web: { instances: -1 } } }, + "/compute/web/instances", + ], + [ + "a mistyped literal under a remote", + { remotes: { staging: { api: { port: "env(PORT" } } } }, + "/remotes/staging/api/port", + ], + [ + "a boolean in the string-or-number union", + { storage: { file_size_limit: true } }, + "/storage/file_size_limit", + ], + ])("rejects %s", (_scenario, document, path) => { + expect(validateCli(document)).toBe(false); + expect(failingPaths(validateCli, document)).toContain(path); + }); + }); + + describe("published structure", () => { + test("keeps hover docs on the outer node and the type in the first branch", () => { + const maxRows = at(cli, "properties", "api", "properties", "max_rows"); + expect(maxRows.description).toMatch(/maximum number of rows/); + expect(maxRows.default).toBe(1000); + expect(maxRows).not.toHaveProperty("type"); + expect(at(maxRows, "anyOf", 0)).toEqual({ type: "number" }); + }); + + test("keeps examples on the outer node and minimum in the typed branch of an integer leaf", () => { + const patterned = at(cli, "properties", "compute", "anyOf", 0, "patternProperties"); + const entries = Object.values(patterned); + expect(entries).toHaveLength(1); + const instances = at(entries[0], "properties", "instances"); + expect(instances.description).toMatch(/Number of instances/); + expect(instances.examples).toEqual([3]); + expect(instances).not.toHaveProperty("minimum"); + expect(at(instances, "anyOf", 0)).toEqual({ type: "integer", minimum: 0 }); + }); + + test("keeps hover docs on the outer node and the enum in the typed branch of a string enum", () => { + const poolMode = at( + cli, + "properties", + "db", + "properties", + "pooler", + "properties", + "pool_mode", + ); + expect(poolMode.description).toMatch(/server connection can be reused/); + expect(poolMode.default).toBe("transaction"); + expect(at(poolMode, "anyOf", 0)).toEqual({ + type: "string", + enum: ["transaction", "session"], + }); + expect(at(poolMode, "anyOf", 1)).toEqual({ type: "string", pattern: envPatternOf(cli) }); + }); + + test("appends a single env branch to a union without wrapping its number member", () => { + const fileSizeLimit = at(cli, "properties", "storage", "properties", "file_size_limit"); + expect(fileSizeLimit.anyOf).toHaveLength(3); + expect(at(fileSizeLimit, "anyOf", 0)).toEqual({ type: "string" }); + expect(at(fileSizeLimit, "anyOf", 1)).toEqual({ type: "number" }); + expect(at(fileSizeLimit, "anyOf", 2)).toEqual({ type: "string", pattern: envPatternOf(cli) }); + }); + }); + + describe("agreement with the loader's env() matching", () => { + const samples = [ + "env(MAX_ROWS)", + "env(max_rows)", + "env(_X1)", + "env(1ST)", + "env()", + "env(A B)", + "env(A-B)", + "env(日本語)", + "env((X))", + "env(env(X))", + "env(X))", + "ENV(X)", + "Env(X)", + "env(X", + "env X)", + " env(X)", + "env(X) ", + "prefix env(X)", + "env(X)suffix", + "env(A\nB)", + "env(X)\n", + "env", + "$(X)", + "${X}", + "1000", + "true", + "", + ]; + const validatePort = (value: unknown) => validateCli({ api: { port: value } }); + + test("a numeric field accepts exactly the strings the loader treats as references", () => { + expect(samples.filter((s) => validatePort(s) !== isEnvReference(s, true))).toEqual([]); + }); + + test("strict-mode references are a subset of what the schema accepts", () => { + expect(samples.filter((s) => isEnvReference(s, false) && !validatePort(s))).toEqual([]); + }); + + test("every env branch in the document carries the same pattern", () => { + const branches = collectEnvBranches(cli); + expect(branches.length).toBeGreaterThan(0); + expect(new Set(branches.map(patternOf))).toEqual(new Set([envPatternOf(cli)])); + }); + + test("every field accepts env() in the schema exactly when the loader resolves it", () => { + const port = ["api", "port"]; + expect(loaderAcceptsAt(port, "env(SAMPLE)", {})).toBe(false); + expect(loaderAcceptsAt(port, "env(SAMPLE)", { SAMPLE: "1" })).toBe(true); + + const leaves = collectLeaves(cli).flatMap(([path, leaf]) => { + const value = envValueFor(leaf); + return value === undefined ? [] : [{ path, value }]; + }); + const mismatches: Array = []; + for (const { path, value } of leaves) { + const schema = schemaAcceptsAt(validateCli, path, "env(SAMPLE)"); + const loader = loaderAcceptsAt(path, "env(SAMPLE)", { SAMPLE: value }); + if (schema !== loader) + mismatches.push(`${path.join(".")}: schema ${schema}, loader ${loader}`); + } + expect(leaves.length).toBeGreaterThan(500); + expect(mismatches).toEqual([]); + }, 60_000); + }); +}); + +describe("toPublishedJsonSchemaDocument acceptEnvReferences", () => { + const publish = (schema: Schema.Constraint, acceptEnvReferences?: boolean) => + toPublishedJsonSchemaDocument(schema, { + onExcessProperty: "error", + ...(acceptEnvReferences === undefined ? {} : { acceptEnvReferences }), + }); + const envBranch = { type: "string", pattern: ENV_PATTERN }; + + test("renders no env branch unless the option is on", () => { + const schema = Schema.Struct({ + count: Schema.Finite, + flag: Schema.Boolean, + whole: Schema.Int, + list: Schema.Array(Schema.Finite), + maybe: Schema.Union([Schema.Finite, Schema.Null]), + }); + const off = publish(schema); + expect(publish(schema, false)).toEqual(off); + expect(collectEnvBranches(off)).toEqual([]); + expect(JSON.stringify(off)).not.toContain('"pattern"'); + expect(collectEnvBranches(publish(schema, true))).toHaveLength(5); + }); + + test("wraps properties named after JSON Schema keywords without losing a key", () => { + const document = publish( + Schema.Struct({ type: Schema.Finite, default: Schema.Boolean, enum: Schema.Int }), + true, + ); + expect(Object.keys(at(document, "properties"))).toEqual(["type", "default", "enum"]); + expect(at(document, "required")).toEqual(["type", "default", "enum"]); + expect(at(document, "properties", "type").anyOf).toEqual([{ type: "number" }, envBranch]); + expect(at(document, "properties", "default").anyOf).toEqual([{ type: "boolean" }, envBranch]); + expect(at(document, "properties", "enum").anyOf).toEqual([{ type: "integer" }, envBranch]); + }); + + test("hoists title, description, default and examples out of the typed branch", () => { + const document = publish( + Schema.Struct({ + count: Schema.Finite.annotate({ + title: "Count", + description: "How many.", + default: 3, + examples: [4], + }), + }), + true, + ); + expect(at(document, "properties", "count")).toEqual({ + title: "Count", + description: "How many.", + default: 3, + examples: [4], + anyOf: [{ type: "number" }, envBranch], + }); + }); + + // The filter's toJsonSchema fragment is the one way to put object-valued const/enum into a document. + test("leaves default, examples, const and enum data alone even when it looks like a schema", () => { + const schema = Schema.Struct({ + annotated: Schema.Record(Schema.String, Schema.Unknown).annotate({ + default: { type: "number", nested: [{ type: "boolean" }] }, + examples: [{ type: "integer" }, { list: [{ type: "number" }] }], + }), + constrained: Schema.Record(Schema.String, Schema.Unknown).check( + Schema.makeFilter(() => true, { + toJsonSchema: () => ({ + const: { type: "number" }, + enum: [{ type: "boolean" }, { type: "number" }], + }), + }), + ), + }); + const off = publish(schema, false); + const on = publish(schema, true); + expect(at(on, "properties", "annotated").default).toEqual({ + type: "number", + nested: [{ type: "boolean" }], + }); + expect(at(on, "properties", "constrained", "allOf", 0).const).toEqual({ type: "number" }); + expect(on).toEqual(off); + expect(JSON.stringify(on)).toBe(JSON.stringify(off)); + }); + + test("wraps string enums but leaves plain and env() strings alone", () => { + const document = publish( + Schema.Struct({ + mode: Schema.Literals(["a", "b"]), + name: Schema.String, + token: env(), + }), + true, + ); + expect(at(document, "properties", "mode").anyOf).toEqual([ + { type: "string", enum: ["a", "b"] }, + envBranch, + ]); + expect(at(document, "properties", "name")).toEqual({ type: "string" }); + expect(at(document, "properties", "token")).toEqual(envBranch); + }); + + test("appends one env branch to a union and leaves its members unwrapped", () => { + const document = publish( + Schema.Struct({ + nullable: Schema.Union([Schema.Finite, Schema.Null]), + mixed: Schema.Union([Schema.Boolean, Schema.String]), + }), + true, + ); + expect(at(document, "properties", "nullable").anyOf).toEqual([ + { type: "number" }, + { type: "null" }, + envBranch, + ]); + expect(at(document, "properties", "mixed").anyOf).toEqual([ + { type: "boolean" }, + { type: "string" }, + envBranch, + ]); + }); + + test("does not add a second env branch to a union that already has one", () => { + const document = publish( + Schema.Struct({ + count: Schema.Union([Schema.Finite, env()]), + token: Schema.Union([Schema.Finite, env({ description: "Token", secret: true })]), + }), + true, + ); + expect(at(document, "properties", "count").anyOf).toEqual([{ type: "number" }, envBranch]); + const token = at(document, "properties", "token").anyOf as Array; + expect(token).toHaveLength(2); + expect(token[0]).toEqual({ type: "number" }); + }); + + test("wraps numbers and booleans inside arrays and record values", () => { + const document = publish( + Schema.Struct({ + counts: Schema.Array(Schema.Finite), + flags: Schema.Array(Schema.Boolean), + limits: Schema.Record(Schema.String, Schema.Finite), + }), + true, + ); + const validate = compile(document); + expect( + errorsFor(validate, { + counts: [1, "env(COUNT)"], + flags: [true, "env(FLAG)"], + limits: { a: 1, b: "env(LIMIT)" }, + }), + ).toEqual([]); + expect(failingPaths(validate, { counts: ["1"], flags: [], limits: {} })).toContain("/counts/0"); + expect(failingPaths(validate, { counts: [], flags: ["true"], limits: {} })).toContain( + "/flags/0", + ); + expect(failingPaths(validate, { counts: [], flags: [], limits: { a: "env(X" } })).toContain( + "/limits/a", + ); + }); + + test("accepts env() in place of a whole string list but not a list of enums", () => { + const document = publish( + Schema.Struct({ + names: Schema.Array(Schema.String), + modes: Schema.Array(Schema.Literals(["a", "b"])), + }), + true, + ); + const validate = compile(document); + expect(errorsFor(validate, { names: "env(NAMES)", modes: ["a", "env(MODE)"] })).toEqual([]); + expect(failingPaths(validate, { names: [], modes: "env(MODES)" })).toContain("/modes"); + }); + + test("wraps numbers and booleans in shared definitions", () => { + const Port = Schema.Finite.annotate({ identifier: "Port" }); + const Flag = Schema.Boolean.annotate({ identifier: "Flag" }); + const document = publish(Schema.Struct({ a: Port, b: Port, c: Flag }), true); + expect(at(document, "$defs", "Port").anyOf).toEqual([{ type: "number" }, envBranch]); + expect(at(document, "$defs", "Flag").anyOf).toEqual([{ type: "boolean" }, envBranch]); + const validate = compile(document); + expect(errorsFor(validate, { a: "env(A)", b: 2, c: "env(C)" })).toEqual([]); + expect(failingPaths(validate, { a: "x", b: 2, c: true })).toContain("/a"); + }); + + test("the project config schema carries no env-reference branch", () => { + const project = toProjectConfigJsonSchema(); + const maxRows = at(project, "properties", "api", "properties", "max_rows"); + expect(maxRows.type).toBe("number"); + expect(maxRows).not.toHaveProperty("anyOf"); + expect(collectEnvBranches(project)).toEqual([]); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8673924f0..e0be1578fc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -584,6 +584,9 @@ importers: '@vitest/coverage-v8': specifier: 'catalog:' version: 5.0.0(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.0))(vitest@5.0.0) + ajv: + specifier: ^8.20.0 + version: 8.20.0 effect: specifier: 'catalog:' version: 4.0.2