diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index f20c02f6a8..350463a5b0 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -3,1100 +3,1174 @@ import { BoolEnv } from "./utils/boolEnv"; import { isValidDatabaseUrl } from "./utils/db"; import { isValidRegex } from "./utils/regex"; -const EnvironmentSchema = z.object({ - NODE_ENV: z.union([z.literal("development"), z.literal("production"), z.literal("test")]), - DATABASE_URL: z - .string() - .refine( - isValidDatabaseUrl, - "DATABASE_URL is invalid, for details please check the additional output above this message." - ), - DATABASE_CONNECTION_LIMIT: z.coerce.number().int().default(10), - DATABASE_POOL_TIMEOUT: z.coerce.number().int().default(60), - DATABASE_CONNECTION_TIMEOUT: z.coerce.number().int().default(20), - DIRECT_URL: z - .string() - .refine( - isValidDatabaseUrl, - "DIRECT_URL is invalid, for details please check the additional output above this message." - ), - DATABASE_READ_REPLICA_URL: z.string().optional(), - SESSION_SECRET: z.string(), - MAGIC_LINK_SECRET: z.string(), - ENCRYPTION_KEY: z - .string() - .refine( - (val) => Buffer.from(val, "utf8").length === 32, - "ENCRYPTION_KEY must be exactly 32 bytes" - ), - WHITELISTED_EMAILS: z - .string() - .refine(isValidRegex, "WHITELISTED_EMAILS must be a valid regex.") - .optional(), - ADMIN_EMAILS: z.string().refine(isValidRegex, "ADMIN_EMAILS must be a valid regex.").optional(), - REMIX_APP_PORT: z.string().optional(), - LOGIN_ORIGIN: z.string().default("http://localhost:3030"), - APP_ORIGIN: z.string().default("http://localhost:3030"), - API_ORIGIN: z.string().optional(), - STREAM_ORIGIN: z.string().optional(), - ELECTRIC_ORIGIN: z.string().default("http://localhost:3060"), - // A comma separated list of electric origins to shard into different electric instances by environmentId - // example: "http://localhost:3060,http://localhost:3061,http://localhost:3062" - ELECTRIC_ORIGIN_SHARDS: z.string().optional(), - APP_ENV: z.string().default(process.env.NODE_ENV), - SERVICE_NAME: z.string().default("trigger.dev webapp"), - POSTHOG_PROJECT_KEY: z.string().default("phc_LFH7kJiGhdIlnO22hTAKgHpaKhpM8gkzWAFvHmf5vfS"), - TRIGGER_TELEMETRY_DISABLED: z.string().optional(), - AUTH_GITHUB_CLIENT_ID: z.string().optional(), - AUTH_GITHUB_CLIENT_SECRET: z.string().optional(), - EMAIL_TRANSPORT: z.enum(["resend", "smtp", "aws-ses"]).optional(), - FROM_EMAIL: z.string().optional(), - REPLY_TO_EMAIL: z.string().optional(), - RESEND_API_KEY: z.string().optional(), - SMTP_HOST: z.string().optional(), - SMTP_PORT: z.coerce.number().optional(), - SMTP_SECURE: BoolEnv.optional(), - SMTP_USER: z.string().optional(), - SMTP_PASSWORD: z.string().optional(), - - PLAIN_API_KEY: z.string().optional(), - WORKER_SCHEMA: z.string().default("graphile_worker"), - WORKER_CONCURRENCY: z.coerce.number().int().default(10), - WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), - WORKER_ENABLED: z.string().default("true"), - GRACEFUL_SHUTDOWN_TIMEOUT: z.coerce.number().int().default(60000), - DISABLE_SSE: z.string().optional(), - OPENAI_API_KEY: z.string().optional(), - - // Redis options - REDIS_HOST: z.string().optional(), - REDIS_READER_HOST: z.string().optional(), - REDIS_READER_PORT: z.coerce.number().optional(), - REDIS_PORT: z.coerce.number().optional(), - REDIS_USERNAME: z.string().optional(), - REDIS_PASSWORD: z.string().optional(), - REDIS_TLS_DISABLED: z.string().optional(), - - RATE_LIMIT_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - RATE_LIMIT_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - RATE_LIMIT_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - RATE_LIMIT_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - RATE_LIMIT_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - RATE_LIMIT_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - RATE_LIMIT_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), - RATE_LIMIT_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), - - CACHE_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - CACHE_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - CACHE_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - CACHE_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - CACHE_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - CACHE_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - CACHE_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), - CACHE_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), - - REALTIME_STREAMS_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - REALTIME_STREAMS_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - REALTIME_STREAMS_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - REALTIME_STREAMS_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - REALTIME_STREAMS_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - REALTIME_STREAMS_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - REALTIME_STREAMS_REDIS_TLS_DISABLED: z - .string() - .default(process.env.REDIS_TLS_DISABLED ?? "false"), - REALTIME_STREAMS_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), - - REALTIME_MAXIMUM_CREATED_AT_FILTER_AGE_IN_MS: z.coerce - .number() - .int() - .default(24 * 60 * 60 * 1000), // 1 day in milliseconds - - PUBSUB_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - PUBSUB_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - PUBSUB_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - PUBSUB_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - PUBSUB_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - PUBSUB_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - PUBSUB_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), - PUBSUB_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), - - DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT: z.coerce.number().int().default(100), - DEFAULT_ENV_EXECUTION_CONCURRENCY_BURST_FACTOR: z.coerce.number().default(1.0), - DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT: z.coerce.number().int().default(300), - DEFAULT_DEV_ENV_EXECUTION_ATTEMPTS: z.coerce.number().int().positive().default(1), - - //API Rate limiting - /** - * @example "60s" - * @example "1m" - * @example "1h" - * @example "1d" - * @example "1000ms" - * @example "1000s" - */ - API_RATE_LIMIT_REFILL_INTERVAL: z.string().default("10s"), // refill 250 tokens every 10 seconds - API_RATE_LIMIT_MAX: z.coerce.number().int().default(750), // allow bursts of 750 requests - API_RATE_LIMIT_REFILL_RATE: z.coerce.number().int().default(250), // refix 250 tokens every 10 seconds - API_RATE_LIMIT_REQUEST_LOGS_ENABLED: z.string().default("0"), - API_RATE_LIMIT_REJECTION_LOGS_ENABLED: z.string().default("1"), - API_RATE_LIMIT_LIMITER_LOGS_ENABLED: z.string().default("0"), - - API_RATE_LIMIT_JWT_WINDOW: z.string().default("1m"), - API_RATE_LIMIT_JWT_TOKENS: z.coerce.number().int().default(60), - - //v3 - PROVIDER_SECRET: z.string().default("provider-secret"), - COORDINATOR_SECRET: z.string().default("coordinator-secret"), - DEPOT_TOKEN: z.string().optional(), - DEPOT_ORG_ID: z.string().optional(), - DEPOT_REGION: z.string().default("us-east-1"), - - // Deployment registry (v3) - DEPLOY_REGISTRY_HOST: z.string().min(1), - DEPLOY_REGISTRY_USERNAME: z.string().optional(), - DEPLOY_REGISTRY_PASSWORD: z.string().optional(), - DEPLOY_REGISTRY_NAMESPACE: z.string().min(1).default("trigger"), - DEPLOY_REGISTRY_ECR_TAGS: z.string().optional(), // csv, for example: "key1=value1,key2=value2" - DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN: z.string().optional(), - DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID: z.string().optional(), - - // Deployment registry (v4) - falls back to v3 registry if not specified - V4_DEPLOY_REGISTRY_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.DEPLOY_REGISTRY_HOST) - .pipe(z.string().min(1)), // Ensure final type is required string - V4_DEPLOY_REGISTRY_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.DEPLOY_REGISTRY_USERNAME), - V4_DEPLOY_REGISTRY_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.DEPLOY_REGISTRY_PASSWORD), - V4_DEPLOY_REGISTRY_NAMESPACE: z - .string() - .optional() - .transform((v) => v ?? process.env.DEPLOY_REGISTRY_NAMESPACE) - .pipe(z.string().min(1).default("trigger")), // Ensure final type is required string - V4_DEPLOY_REGISTRY_ECR_TAGS: z - .string() - .optional() - .transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_TAGS), - V4_DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN: z - .string() - .optional() - .transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN), - V4_DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID: z - .string() - .optional() - .transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID), - - DEPLOY_IMAGE_PLATFORM: z.string().default("linux/amd64"), - DEPLOY_TIMEOUT_MS: z.coerce - .number() - .int() - .default(60 * 1000 * 8), // 8 minutes - - OBJECT_STORE_BASE_URL: z.string().optional(), - OBJECT_STORE_ACCESS_KEY_ID: z.string().optional(), - OBJECT_STORE_SECRET_ACCESS_KEY: z.string().optional(), - OBJECT_STORE_REGION: z.string().optional(), - OBJECT_STORE_SERVICE: z.string().default("s3"), - EVENTS_BATCH_SIZE: z.coerce.number().int().default(100), - EVENTS_BATCH_INTERVAL: z.coerce.number().int().default(1000), - EVENTS_DEFAULT_LOG_RETENTION: z.coerce.number().int().default(7), - EVENTS_MIN_CONCURRENCY: z.coerce.number().int().default(1), - EVENTS_MAX_CONCURRENCY: z.coerce.number().int().default(10), - EVENTS_MAX_BATCH_SIZE: z.coerce.number().int().default(500), - EVENTS_MEMORY_PRESSURE_THRESHOLD: z.coerce.number().int().default(5000), - EVENTS_LOAD_SHEDDING_THRESHOLD: z.coerce.number().int().default(100000), - EVENTS_LOAD_SHEDDING_ENABLED: z.string().default("1"), - SHARED_QUEUE_CONSUMER_POOL_SIZE: z.coerce.number().int().default(10), - SHARED_QUEUE_CONSUMER_INTERVAL_MS: z.coerce.number().int().default(100), - SHARED_QUEUE_CONSUMER_NEXT_TICK_INTERVAL_MS: z.coerce.number().int().default(100), - SHARED_QUEUE_CONSUMER_EMIT_RESUME_DEPENDENCY_TIMEOUT_MS: z.coerce.number().int().default(1000), - SHARED_QUEUE_CONSUMER_RESOLVE_PAYLOADS_BATCH_SIZE: z.coerce.number().int().default(25), - - MANAGED_WORKER_SECRET: z.string().default("managed-secret"), - - // Development OTEL environment variables - DEV_OTEL_EXPORTER_OTLP_ENDPOINT: z.string().optional(), - // If this is set to 1, then the below variables are used to configure the batch processor for spans and logs - DEV_OTEL_BATCH_PROCESSING_ENABLED: z.string().default("0"), - DEV_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE: z.string().default("64"), - DEV_OTEL_SPAN_SCHEDULED_DELAY_MILLIS: z.string().default("200"), - DEV_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS: z.string().default("30000"), - DEV_OTEL_SPAN_MAX_QUEUE_SIZE: z.string().default("512"), - DEV_OTEL_LOG_MAX_EXPORT_BATCH_SIZE: z.string().default("64"), - DEV_OTEL_LOG_SCHEDULED_DELAY_MILLIS: z.string().default("200"), - DEV_OTEL_LOG_EXPORT_TIMEOUT_MILLIS: z.string().default("30000"), - DEV_OTEL_LOG_MAX_QUEUE_SIZE: z.string().default("512"), - - PROD_OTEL_BATCH_PROCESSING_ENABLED: z.string().default("0"), - PROD_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE: z.string().default("64"), - PROD_OTEL_SPAN_SCHEDULED_DELAY_MILLIS: z.string().default("200"), - PROD_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS: z.string().default("30000"), - PROD_OTEL_SPAN_MAX_QUEUE_SIZE: z.string().default("512"), - PROD_OTEL_LOG_MAX_EXPORT_BATCH_SIZE: z.string().default("64"), - PROD_OTEL_LOG_SCHEDULED_DELAY_MILLIS: z.string().default("200"), - PROD_OTEL_LOG_EXPORT_TIMEOUT_MILLIS: z.string().default("30000"), - PROD_OTEL_LOG_MAX_QUEUE_SIZE: z.string().default("512"), - - TRIGGER_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: z.string().default("1024"), - TRIGGER_OTEL_LOG_ATTRIBUTE_COUNT_LIMIT: z.string().default("1024"), - TRIGGER_OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: z.string().default("131072"), - TRIGGER_OTEL_LOG_ATTRIBUTE_VALUE_LENGTH_LIMIT: z.string().default("131072"), - TRIGGER_OTEL_SPAN_EVENT_COUNT_LIMIT: z.string().default("10"), - TRIGGER_OTEL_LINK_COUNT_LIMIT: z.string().default("2"), - TRIGGER_OTEL_ATTRIBUTE_PER_LINK_COUNT_LIMIT: z.string().default("10"), - TRIGGER_OTEL_ATTRIBUTE_PER_EVENT_COUNT_LIMIT: z.string().default("10"), - - CHECKPOINT_THRESHOLD_IN_MS: z.coerce.number().int().default(30000), - - // Internal OTEL environment variables - INTERNAL_OTEL_TRACE_EXPORTER_URL: z.string().optional(), - INTERNAL_OTEL_TRACE_EXPORTER_AUTH_HEADERS: z.string().optional(), - INTERNAL_OTEL_TRACE_LOGGING_ENABLED: z.string().default("1"), - // this means 1/20 traces or 5% of traces will be sampled (sampled = recorded) - INTERNAL_OTEL_TRACE_SAMPLING_RATE: z.string().default("20"), - INTERNAL_OTEL_TRACE_INSTRUMENT_PRISMA_ENABLED: z.string().default("0"), - INTERNAL_OTEL_TRACE_DISABLED: z.string().default("0"), - - INTERNAL_OTEL_LOG_EXPORTER_URL: z.string().optional(), - INTERNAL_OTEL_METRIC_EXPORTER_URL: z.string().optional(), - INTERNAL_OTEL_METRIC_EXPORTER_AUTH_HEADERS: z.string().optional(), - INTERNAL_OTEL_METRIC_EXPORTER_ENABLED: z.string().default("0"), - INTERNAL_OTEL_METRIC_EXPORTER_INTERVAL_MS: z.coerce.number().int().default(30_000), - INTERNAL_OTEL_HOST_METRICS_ENABLED: BoolEnv.default(true), - INTERNAL_OTEL_NODEJS_METRICS_ENABLED: BoolEnv.default(true), - INTERNAL_OTEL_ADDITIONAL_DETECTORS_ENABLED: BoolEnv.default(true), - - ORG_SLACK_INTEGRATION_CLIENT_ID: z.string().optional(), - ORG_SLACK_INTEGRATION_CLIENT_SECRET: z.string().optional(), - - /** These enable the alerts feature in v3 */ - ALERT_EMAIL_TRANSPORT: z.enum(["resend", "smtp", "aws-ses"]).optional(), - ALERT_FROM_EMAIL: z.string().optional(), - ALERT_REPLY_TO_EMAIL: z.string().optional(), - ALERT_RESEND_API_KEY: z.string().optional(), - ALERT_SMTP_HOST: z.string().optional(), - ALERT_SMTP_PORT: z.coerce.number().optional(), - ALERT_SMTP_SECURE: BoolEnv.optional(), - ALERT_SMTP_USER: z.string().optional(), - ALERT_SMTP_PASSWORD: z.string().optional(), - ALERT_RATE_LIMITER_EMISSION_INTERVAL: z.coerce.number().int().default(2_500), - ALERT_RATE_LIMITER_BURST_TOLERANCE: z.coerce.number().int().default(10_000), - ALERT_RATE_LIMITER_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - ALERT_RATE_LIMITER_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - ALERT_RATE_LIMITER_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - ALERT_RATE_LIMITER_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - ALERT_RATE_LIMITER_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - ALERT_RATE_LIMITER_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - ALERT_RATE_LIMITER_REDIS_TLS_DISABLED: z - .string() - .default(process.env.REDIS_TLS_DISABLED ?? "false"), - ALERT_RATE_LIMITER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), - - LOOPS_API_KEY: z.string().optional(), - MARQS_DISABLE_REBALANCING: BoolEnv.default(false), - MARQS_VISIBILITY_TIMEOUT_MS: z.coerce - .number() - .int() - .default(60 * 1000 * 15), - MARQS_SHARED_QUEUE_LIMIT: z.coerce.number().int().default(1000), - MARQS_MAXIMUM_QUEUE_PER_ENV_COUNT: z.coerce.number().int().default(50), - MARQS_DEV_QUEUE_LIMIT: z.coerce.number().int().default(1000), - MARQS_MAXIMUM_NACK_COUNT: z.coerce.number().int().default(64), - MARQS_CONCURRENCY_LIMIT_BIAS: z.coerce.number().default(0.75), - MARQS_AVAILABLE_CAPACITY_BIAS: z.coerce.number().default(0.3), - MARQS_QUEUE_AGE_RANDOMIZATION_BIAS: z.coerce.number().default(0.25), - MARQS_REUSE_SNAPSHOT_COUNT: z.coerce.number().int().default(0), - MARQS_MAXIMUM_ENV_COUNT: z.coerce.number().int().optional(), - MARQS_SHARED_WORKER_QUEUE_CONSUMER_INTERVAL_MS: z.coerce.number().int().default(250), - MARQS_SHARED_WORKER_QUEUE_MAX_MESSAGE_COUNT: z.coerce.number().int().default(10), - - MARQS_SHARED_WORKER_QUEUE_EAGER_DEQUEUE_ENABLED: z.string().default("0"), - MARQS_WORKER_ENABLED: z.string().default("0"), - MARQS_WORKER_COUNT: z.coerce.number().int().default(2), - MARQS_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50), - MARQS_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(5), - MARQS_WORKER_POLL_INTERVAL_MS: z.coerce.number().int().default(100), - MARQS_WORKER_IMMEDIATE_POLL_INTERVAL_MS: z.coerce.number().int().default(100), - MARQS_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), - MARQS_SHARED_WORKER_QUEUE_COOLOFF_COUNT_THRESHOLD: z.coerce.number().int().default(10), - MARQS_SHARED_WORKER_QUEUE_COOLOFF_PERIOD_MS: z.coerce.number().int().default(5_000), - - PROD_TASK_HEARTBEAT_INTERVAL_MS: z.coerce.number().int().optional(), - - VERBOSE_GRAPHILE_LOGGING: z.string().default("false"), - V2_MARQS_ENABLED: z.string().default("0"), - V2_MARQS_CONSUMER_POOL_ENABLED: z.string().default("0"), - V2_MARQS_CONSUMER_POOL_SIZE: z.coerce.number().int().default(10), - V2_MARQS_CONSUMER_POLL_INTERVAL_MS: z.coerce.number().int().default(1000), - V2_MARQS_QUEUE_SELECTION_COUNT: z.coerce.number().int().default(36), - V2_MARQS_VISIBILITY_TIMEOUT_MS: z.coerce - .number() - .int() - .default(60 * 1000 * 15), - V2_MARQS_DEFAULT_ENV_CONCURRENCY: z.coerce.number().int().default(100), - V2_MARQS_VERBOSE: z.string().default("0"), - V3_MARQS_CONCURRENCY_MONITOR_ENABLED: z.string().default("0"), - V2_MARQS_CONCURRENCY_MONITOR_ENABLED: z.string().default("0"), - /* Usage settings */ - USAGE_EVENT_URL: z.string().optional(), - PROD_USAGE_HEARTBEAT_INTERVAL_MS: z.coerce.number().int().optional(), - - CENTS_PER_RUN: z.coerce.number().default(0), - - EVENT_LOOP_MONITOR_ENABLED: z.string().default("1"), - MAXIMUM_LIVE_RELOADING_EVENTS: z.coerce.number().int().default(1000), - MAXIMUM_TRACE_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(25_000), - MAXIMUM_TRACE_DETAILED_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(10_000), - TASK_PAYLOAD_OFFLOAD_THRESHOLD: z.coerce.number().int().default(524_288), // 512KB - TASK_PAYLOAD_MAXIMUM_SIZE: z.coerce.number().int().default(3_145_728), // 3MB - BATCH_TASK_PAYLOAD_MAXIMUM_SIZE: z.coerce.number().int().default(1_000_000), // 1MB - TASK_RUN_METADATA_MAXIMUM_SIZE: z.coerce.number().int().default(262_144), // 256KB - - MAXIMUM_DEV_QUEUE_SIZE: z.coerce.number().int().optional(), - MAXIMUM_DEPLOYED_QUEUE_SIZE: z.coerce.number().int().optional(), - MAX_BATCH_V2_TRIGGER_ITEMS: z.coerce.number().int().default(500), - MAX_BATCH_AND_WAIT_V2_TRIGGER_ITEMS: z.coerce.number().int().default(500), - - REALTIME_STREAM_VERSION: z.enum(["v1", "v2"]).default("v1"), - REALTIME_STREAM_MAX_LENGTH: z.coerce.number().int().default(1000), - REALTIME_STREAM_TTL: z.coerce - .number() - .int() - .default(60 * 60 * 24), // 1 day in seconds - BATCH_METADATA_OPERATIONS_FLUSH_INTERVAL_MS: z.coerce.number().int().default(1000), - BATCH_METADATA_OPERATIONS_FLUSH_ENABLED: z.string().default("1"), - BATCH_METADATA_OPERATIONS_FLUSH_LOGGING_ENABLED: z.string().default("1"), - - // Run Engine 2.0 - RUN_ENGINE_WORKER_COUNT: z.coerce.number().int().default(4), - RUN_ENGINE_TASKS_PER_WORKER: z.coerce.number().int().default(10), - RUN_ENGINE_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(10), - RUN_ENGINE_WORKER_POLL_INTERVAL: z.coerce.number().int().default(100), - RUN_ENGINE_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(100), - RUN_ENGINE_TIMEOUT_PENDING_EXECUTING: z.coerce.number().int().default(60_000), - RUN_ENGINE_TIMEOUT_PENDING_CANCEL: z.coerce.number().int().default(60_000), - RUN_ENGINE_TIMEOUT_EXECUTING: z.coerce.number().int().default(60_000), - RUN_ENGINE_TIMEOUT_EXECUTING_WITH_WAITPOINTS: z.coerce.number().int().default(60_000), - RUN_ENGINE_TIMEOUT_SUSPENDED: z.coerce - .number() - .int() - .default(60_000 * 10), - RUN_ENGINE_DEBUG_WORKER_NOTIFICATIONS: BoolEnv.default(false), - RUN_ENGINE_PARENT_QUEUE_LIMIT: z.coerce.number().int().default(1000), - RUN_ENGINE_CONCURRENCY_LIMIT_BIAS: z.coerce.number().default(0.75), - RUN_ENGINE_AVAILABLE_CAPACITY_BIAS: z.coerce.number().default(0.3), - RUN_ENGINE_QUEUE_AGE_RANDOMIZATION_BIAS: z.coerce.number().default(0.25), - RUN_ENGINE_REUSE_SNAPSHOT_COUNT: z.coerce.number().int().default(0), - RUN_ENGINE_MAXIMUM_ENV_COUNT: z.coerce.number().int().optional(), - RUN_ENGINE_RUN_QUEUE_SHARD_COUNT: z.coerce.number().int().default(4), - RUN_ENGINE_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), - RUN_ENGINE_RETRY_WARM_START_THRESHOLD_MS: z.coerce.number().int().default(30_000), - RUN_ENGINE_PROCESS_WORKER_QUEUE_DEBOUNCE_MS: z.coerce.number().int().default(200), - RUN_ENGINE_DEQUEUE_BLOCKING_TIMEOUT_SECONDS: z.coerce.number().int().default(10), - RUN_ENGINE_MASTER_QUEUE_CONSUMERS_INTERVAL_MS: z.coerce.number().int().default(1000), - RUN_ENGINE_MASTER_QUEUE_COOLOFF_PERIOD_MS: z.coerce.number().int().default(10_000), - RUN_ENGINE_MASTER_QUEUE_COOLOFF_COUNT_THRESHOLD: z.coerce.number().int().default(10), - RUN_ENGINE_MASTER_QUEUE_CONSUMER_DEQUEUE_COUNT: z.coerce.number().int().default(10), - RUN_ENGINE_CONCURRENCY_SWEEPER_SCAN_SCHEDULE: z.string().optional(), - RUN_ENGINE_CONCURRENCY_SWEEPER_PROCESS_MARKED_SCHEDULE: z.string().optional(), - RUN_ENGINE_CONCURRENCY_SWEEPER_SCAN_JITTER_IN_MS: z.coerce.number().int().optional(), - RUN_ENGINE_CONCURRENCY_SWEEPER_PROCESS_MARKED_JITTER_IN_MS: z.coerce.number().int().optional(), - - RUN_ENGINE_RUN_LOCK_DURATION: z.coerce.number().int().default(5000), - RUN_ENGINE_RUN_LOCK_AUTOMATIC_EXTENSION_THRESHOLD: z.coerce.number().int().default(1000), - RUN_ENGINE_RUN_LOCK_MAX_RETRIES: z.coerce.number().int().default(10), - RUN_ENGINE_RUN_LOCK_BASE_DELAY: z.coerce.number().int().default(100), - RUN_ENGINE_RUN_LOCK_MAX_DELAY: z.coerce.number().int().default(3000), - RUN_ENGINE_RUN_LOCK_BACKOFF_MULTIPLIER: z.coerce.number().default(1.8), - RUN_ENGINE_RUN_LOCK_JITTER_FACTOR: z.coerce.number().default(0.15), - RUN_ENGINE_RUN_LOCK_MAX_TOTAL_WAIT_TIME: z.coerce.number().int().default(15000), - - RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_MAX_COUNT: z.coerce.number().int().default(12), - RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_MAX_DELAY_MS: z.coerce - .number() - .int() - .default(60_000 * 60 * 6), - RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_INITIAL_DELAY_MS: z.coerce.number().int().default(60_000), - RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_FACTOR: z.coerce.number().default(2), - - RUN_ENGINE_WORKER_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - RUN_ENGINE_WORKER_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - RUN_ENGINE_WORKER_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - RUN_ENGINE_WORKER_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - RUN_ENGINE_WORKER_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - RUN_ENGINE_WORKER_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - RUN_ENGINE_WORKER_REDIS_TLS_DISABLED: z - .string() - .default(process.env.REDIS_TLS_DISABLED ?? "false"), - - RUN_ENGINE_RUN_QUEUE_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - RUN_ENGINE_RUN_QUEUE_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - RUN_ENGINE_RUN_QUEUE_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - RUN_ENGINE_RUN_QUEUE_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - RUN_ENGINE_RUN_QUEUE_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - RUN_ENGINE_RUN_QUEUE_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - RUN_ENGINE_RUN_QUEUE_REDIS_TLS_DISABLED: z - .string() - .default(process.env.REDIS_TLS_DISABLED ?? "false"), - - RUN_ENGINE_RUN_LOCK_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - RUN_ENGINE_RUN_LOCK_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - RUN_ENGINE_RUN_LOCK_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - RUN_ENGINE_RUN_LOCK_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - RUN_ENGINE_RUN_LOCK_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - RUN_ENGINE_RUN_LOCK_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - RUN_ENGINE_RUN_LOCK_REDIS_TLS_DISABLED: z - .string() - .default(process.env.REDIS_TLS_DISABLED ?? "false"), - - RUN_ENGINE_DEV_PRESENCE_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - RUN_ENGINE_DEV_PRESENCE_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - RUN_ENGINE_DEV_PRESENCE_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - RUN_ENGINE_DEV_PRESENCE_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - RUN_ENGINE_DEV_PRESENCE_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - RUN_ENGINE_DEV_PRESENCE_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - RUN_ENGINE_DEV_PRESENCE_REDIS_TLS_DISABLED: z - .string() - .default(process.env.REDIS_TLS_DISABLED ?? "false"), - - //API Rate limiting - /** - * @example "60s" - * @example "1m" - * @example "1h" - * @example "1d" - * @example "1000ms" - * @example "1000s" - */ - RUN_ENGINE_RATE_LIMIT_REFILL_INTERVAL: z.string().default("10s"), // refill 250 tokens every 10 seconds - RUN_ENGINE_RATE_LIMIT_MAX: z.coerce.number().int().default(1200), // allow bursts of 750 requests - RUN_ENGINE_RATE_LIMIT_REFILL_RATE: z.coerce.number().int().default(400), // refix 250 tokens every 10 seconds - RUN_ENGINE_RATE_LIMIT_REQUEST_LOGS_ENABLED: z.string().default("0"), - RUN_ENGINE_RATE_LIMIT_REJECTION_LOGS_ENABLED: z.string().default("1"), - RUN_ENGINE_RATE_LIMIT_LIMITER_LOGS_ENABLED: z.string().default("0"), - - RUN_ENGINE_RELEASE_CONCURRENCY_ENABLED: z.string().default("0"), - RUN_ENGINE_RELEASE_CONCURRENCY_DISABLE_CONSUMERS: z.string().default("0"), - RUN_ENGINE_RELEASE_CONCURRENCY_MAX_TOKENS_RATIO: z.coerce.number().default(1), - RUN_ENGINE_RELEASE_CONCURRENCY_RELEASINGS_MAX_AGE: z.coerce - .number() - .int() - .default(60_000 * 30), - RUN_ENGINE_RELEASE_CONCURRENCY_RELEASINGS_POLL_INTERVAL: z.coerce.number().int().default(60_000), - RUN_ENGINE_RELEASE_CONCURRENCY_MAX_RETRIES: z.coerce.number().int().default(3), - RUN_ENGINE_RELEASE_CONCURRENCY_CONSUMERS_COUNT: z.coerce.number().int().default(1), - RUN_ENGINE_RELEASE_CONCURRENCY_POLL_INTERVAL: z.coerce.number().int().default(500), - RUN_ENGINE_RELEASE_CONCURRENCY_BATCH_SIZE: z.coerce.number().int().default(10), - - RUN_ENGINE_WORKER_ENABLED: z.string().default("1"), - RUN_ENGINE_WORKER_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), - RUN_ENGINE_RUN_QUEUE_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), - - /** How long should the presence ttl last */ - DEV_PRESENCE_SSE_TIMEOUT: z.coerce.number().int().default(30_000), - DEV_PRESENCE_TTL_MS: z.coerce.number().int().default(5_000), - DEV_PRESENCE_POLL_MS: z.coerce.number().int().default(1_000), - /** How many ms to wait until dequeuing again, if there was a run last time */ - DEV_DEQUEUE_INTERVAL_WITH_RUN: z.coerce.number().int().default(250), - /** How many ms to wait until dequeuing again, if there was no run last time */ - DEV_DEQUEUE_INTERVAL_WITHOUT_RUN: z.coerce.number().int().default(1_000), - /** The max number of runs per API call that we'll dequeue in DEV */ - DEV_DEQUEUE_MAX_RUNS_PER_PULL: z.coerce.number().int().default(10), - - /** The maximum concurrent local run processes executing at once in dev */ - DEV_MAX_CONCURRENT_RUNS: z.coerce.number().int().default(25), - - /** The CLI should connect to this for dev runs */ - DEV_ENGINE_URL: z.string().default(process.env.APP_ORIGIN ?? "http://localhost:3030"), - - LEGACY_RUN_ENGINE_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), - LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), - LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(1), - LEGACY_RUN_ENGINE_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), - LEGACY_RUN_ENGINE_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50), - LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50), - LEGACY_RUN_ENGINE_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), - LEGACY_RUN_ENGINE_WORKER_LOG_LEVEL: z - .enum(["log", "error", "warn", "info", "debug"]) - .default("info"), - - LEGACY_RUN_ENGINE_WORKER_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - LEGACY_RUN_ENGINE_WORKER_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - LEGACY_RUN_ENGINE_WORKER_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - LEGACY_RUN_ENGINE_WORKER_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - LEGACY_RUN_ENGINE_WORKER_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - LEGACY_RUN_ENGINE_WORKER_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - LEGACY_RUN_ENGINE_WORKER_REDIS_TLS_DISABLED: z - .string() - .default(process.env.REDIS_TLS_DISABLED ?? "false"), - LEGACY_RUN_ENGINE_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), - - LEGACY_RUN_ENGINE_WAITING_FOR_DEPLOY_BATCH_SIZE: z.coerce.number().int().default(100), - LEGACY_RUN_ENGINE_WAITING_FOR_DEPLOY_BATCH_STAGGER_MS: z.coerce.number().int().default(1_000), - - COMMON_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), - COMMON_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), - COMMON_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10), - COMMON_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), - COMMON_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50), - COMMON_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50), - COMMON_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), - COMMON_WORKER_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), - - COMMON_WORKER_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - COMMON_WORKER_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - COMMON_WORKER_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - COMMON_WORKER_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - COMMON_WORKER_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - COMMON_WORKER_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - COMMON_WORKER_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), - COMMON_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), - - BATCH_TRIGGER_PROCESS_JOB_VISIBILITY_TIMEOUT_MS: z.coerce - .number() - .int() - .default(60_000 * 5), // 5 minutes - - BATCH_TRIGGER_CACHED_RUNS_CHECK_ENABLED: BoolEnv.default(false), - - BATCH_TRIGGER_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), - BATCH_TRIGGER_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), - BATCH_TRIGGER_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10), - BATCH_TRIGGER_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), - BATCH_TRIGGER_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50), - BATCH_TRIGGER_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(20), - BATCH_TRIGGER_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), - BATCH_TRIGGER_WORKER_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), - - BATCH_TRIGGER_WORKER_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - BATCH_TRIGGER_WORKER_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - BATCH_TRIGGER_WORKER_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - BATCH_TRIGGER_WORKER_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - BATCH_TRIGGER_WORKER_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - BATCH_TRIGGER_WORKER_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - BATCH_TRIGGER_WORKER_REDIS_TLS_DISABLED: z - .string() - .default(process.env.REDIS_TLS_DISABLED ?? "false"), - BATCH_TRIGGER_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), - - ADMIN_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), - ADMIN_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), - ADMIN_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10), - ADMIN_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), - ADMIN_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50), - ADMIN_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(20), - ADMIN_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), - ADMIN_WORKER_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), - - ADMIN_WORKER_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - ADMIN_WORKER_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - ADMIN_WORKER_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - ADMIN_WORKER_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - ADMIN_WORKER_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - ADMIN_WORKER_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - ADMIN_WORKER_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), - ADMIN_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), - - ALERTS_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), - ALERTS_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), - ALERTS_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10), - ALERTS_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), - ALERTS_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(100), - ALERTS_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50), - ALERTS_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), - ALERTS_WORKER_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), - - ALERTS_WORKER_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - ALERTS_WORKER_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - ALERTS_WORKER_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - ALERTS_WORKER_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - ALERTS_WORKER_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - ALERTS_WORKER_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - ALERTS_WORKER_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), - ALERTS_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), - - SCHEDULE_ENGINE_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), - SCHEDULE_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), - SCHEDULE_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), - SCHEDULE_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10), - SCHEDULE_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), - SCHEDULE_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50), - SCHEDULE_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50), - SCHEDULE_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(30_000), - SCHEDULE_WORKER_DISTRIBUTION_WINDOW_SECONDS: z.coerce.number().int().default(30), - - SCHEDULE_WORKER_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - SCHEDULE_WORKER_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - SCHEDULE_WORKER_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - SCHEDULE_WORKER_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - SCHEDULE_WORKER_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - SCHEDULE_WORKER_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - SCHEDULE_WORKER_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), - SCHEDULE_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), - - TASK_EVENT_PARTITIONING_ENABLED: z.string().default("0"), - TASK_EVENT_PARTITIONED_WINDOW_IN_SECONDS: z.coerce.number().int().default(60), // 1 minute - - QUEUE_SSE_AUTORELOAD_INTERVAL_MS: z.coerce.number().int().default(5_000), - QUEUE_SSE_AUTORELOAD_TIMEOUT_MS: z.coerce.number().int().default(60_000), - - SLACK_BOT_TOKEN: z.string().optional(), - SLACK_SIGNUP_REASON_CHANNEL_ID: z.string().optional(), - - // kapa.ai - KAPA_AI_WEBSITE_ID: z.string().optional(), - - // BetterStack - BETTERSTACK_API_KEY: z.string().optional(), - BETTERSTACK_STATUS_PAGE_ID: z.string().optional(), - - RUN_REPLICATION_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - RUN_REPLICATION_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - RUN_REPLICATION_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - RUN_REPLICATION_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - RUN_REPLICATION_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - RUN_REPLICATION_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - RUN_REPLICATION_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), - - RUN_REPLICATION_CLICKHOUSE_URL: z.string().optional(), - RUN_REPLICATION_ENABLED: z.string().default("0"), - RUN_REPLICATION_SLOT_NAME: z.string().default("task_runs_to_clickhouse_v1"), - RUN_REPLICATION_PUBLICATION_NAME: z.string().default("task_runs_to_clickhouse_v1_publication"), - RUN_REPLICATION_MAX_FLUSH_CONCURRENCY: z.coerce.number().int().default(2), - RUN_REPLICATION_FLUSH_INTERVAL_MS: z.coerce.number().int().default(1000), - RUN_REPLICATION_FLUSH_BATCH_SIZE: z.coerce.number().int().default(100), - RUN_REPLICATION_LEADER_LOCK_TIMEOUT_MS: z.coerce.number().int().default(30_000), - RUN_REPLICATION_LEADER_LOCK_EXTEND_INTERVAL_MS: z.coerce.number().int().default(10_000), - RUN_REPLICATION_ACK_INTERVAL_SECONDS: z.coerce.number().int().default(10), - RUN_REPLICATION_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), - RUN_REPLICATION_CLICKHOUSE_LOG_LEVEL: z - .enum(["log", "error", "warn", "info", "debug"]) - .default("info"), - RUN_REPLICATION_LEADER_LOCK_ADDITIONAL_TIME_MS: z.coerce.number().int().default(10_000), - RUN_REPLICATION_LEADER_LOCK_RETRY_INTERVAL_MS: z.coerce.number().int().default(500), - RUN_REPLICATION_WAIT_FOR_ASYNC_INSERT: z.string().default("0"), - RUN_REPLICATION_KEEP_ALIVE_ENABLED: z.string().default("0"), - RUN_REPLICATION_KEEP_ALIVE_IDLE_SOCKET_TTL_MS: z.coerce.number().int().optional(), - RUN_REPLICATION_MAX_OPEN_CONNECTIONS: z.coerce.number().int().default(10), - // Retry configuration for insert operations - RUN_REPLICATION_INSERT_MAX_RETRIES: z.coerce.number().int().default(3), - RUN_REPLICATION_INSERT_BASE_DELAY_MS: z.coerce.number().int().default(100), - RUN_REPLICATION_INSERT_MAX_DELAY_MS: z.coerce.number().int().default(2000), - RUN_REPLICATION_INSERT_STRATEGY: z.enum(["insert", "insert_async"]).default("insert"), - - // Clickhouse - CLICKHOUSE_URL: z.string(), - CLICKHOUSE_KEEP_ALIVE_ENABLED: z.string().default("1"), - CLICKHOUSE_KEEP_ALIVE_IDLE_SOCKET_TTL_MS: z.coerce.number().int().optional(), - CLICKHOUSE_MAX_OPEN_CONNECTIONS: z.coerce.number().int().default(10), - CLICKHOUSE_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), - CLICKHOUSE_COMPRESSION_REQUEST: z.string().default("1"), - - // Bootstrap - TRIGGER_BOOTSTRAP_ENABLED: z.string().default("0"), - TRIGGER_BOOTSTRAP_WORKER_GROUP_NAME: z.string().optional(), - TRIGGER_BOOTSTRAP_WORKER_TOKEN_PATH: z.string().optional(), - - // Machine presets - MACHINE_PRESETS_OVERRIDE_PATH: z.string().optional(), - - // CLI package tag (e.g. "latest", "v4-beta", "4.0.0") - used for setup commands - TRIGGER_CLI_TAG: z.string().default("latest"), - - HEALTHCHECK_DATABASE_DISABLED: z.string().default("0"), - - REQUEST_IDEMPOTENCY_REDIS_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_HOST), - REQUEST_IDEMPOTENCY_REDIS_READER_HOST: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_READER_HOST), - REQUEST_IDEMPOTENCY_REDIS_READER_PORT: z.coerce - .number() - .optional() - .transform( - (v) => - v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) - ), - REQUEST_IDEMPOTENCY_REDIS_PORT: z.coerce - .number() - .optional() - .transform((v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined)), - REQUEST_IDEMPOTENCY_REDIS_USERNAME: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_USERNAME), - REQUEST_IDEMPOTENCY_REDIS_PASSWORD: z - .string() - .optional() - .transform((v) => v ?? process.env.REDIS_PASSWORD), - REQUEST_IDEMPOTENCY_REDIS_TLS_DISABLED: z - .string() - .default(process.env.REDIS_TLS_DISABLED ?? "false"), - - REQUEST_IDEMPOTENCY_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), - - REQUEST_IDEMPOTENCY_TTL_IN_MS: z.coerce - .number() - .int() - .default(60_000 * 60 * 24), - - // Bulk action - BULK_ACTION_BATCH_SIZE: z.coerce.number().int().default(100), - BULK_ACTION_BATCH_DELAY_MS: z.coerce.number().int().default(200), - BULK_ACTION_SUBBATCH_CONCURRENCY: z.coerce.number().int().default(5), - - // AI Run Filter - AI_RUN_FILTER_MODEL: z.string().optional(), - - EVENT_LOOP_MONITOR_THRESHOLD_MS: z.coerce.number().int().default(100), - - VERY_SLOW_QUERY_THRESHOLD_MS: z.coerce.number().int().optional(), -}); +const GithubAppEnvSchema = z.preprocess( + (val) => { + const obj = val as any; + if (!obj || !obj.GITHUB_APP_ENABLED) { + return { ...obj, GITHUB_APP_ENABLED: "0" }; + } + return obj; + }, + z.discriminatedUnion("GITHUB_APP_ENABLED", [ + z.object({ + GITHUB_APP_ENABLED: z.literal("1"), + GITHUB_APP_ID: z.string(), + GITHUB_APP_PRIVATE_KEY: z.string(), + GITHUB_APP_WEBHOOK_SECRET: z.string(), + GITHUB_APP_SLUG: z.string(), + }), + z.object({ + GITHUB_APP_ENABLED: z.literal("0"), + }), + ]) +); + +const EnvironmentSchema = z + .object({ + NODE_ENV: z.union([z.literal("development"), z.literal("production"), z.literal("test")]), + DATABASE_URL: z + .string() + .refine( + isValidDatabaseUrl, + "DATABASE_URL is invalid, for details please check the additional output above this message." + ), + DATABASE_CONNECTION_LIMIT: z.coerce.number().int().default(10), + DATABASE_POOL_TIMEOUT: z.coerce.number().int().default(60), + DATABASE_CONNECTION_TIMEOUT: z.coerce.number().int().default(20), + DIRECT_URL: z + .string() + .refine( + isValidDatabaseUrl, + "DIRECT_URL is invalid, for details please check the additional output above this message." + ), + DATABASE_READ_REPLICA_URL: z.string().optional(), + SESSION_SECRET: z.string(), + MAGIC_LINK_SECRET: z.string(), + ENCRYPTION_KEY: z + .string() + .refine( + (val) => Buffer.from(val, "utf8").length === 32, + "ENCRYPTION_KEY must be exactly 32 bytes" + ), + WHITELISTED_EMAILS: z + .string() + .refine(isValidRegex, "WHITELISTED_EMAILS must be a valid regex.") + .optional(), + ADMIN_EMAILS: z.string().refine(isValidRegex, "ADMIN_EMAILS must be a valid regex.").optional(), + REMIX_APP_PORT: z.string().optional(), + LOGIN_ORIGIN: z.string().default("http://localhost:3030"), + APP_ORIGIN: z.string().default("http://localhost:3030"), + API_ORIGIN: z.string().optional(), + STREAM_ORIGIN: z.string().optional(), + ELECTRIC_ORIGIN: z.string().default("http://localhost:3060"), + // A comma separated list of electric origins to shard into different electric instances by environmentId + // example: "http://localhost:3060,http://localhost:3061,http://localhost:3062" + ELECTRIC_ORIGIN_SHARDS: z.string().optional(), + APP_ENV: z.string().default(process.env.NODE_ENV), + SERVICE_NAME: z.string().default("trigger.dev webapp"), + POSTHOG_PROJECT_KEY: z.string().default("phc_LFH7kJiGhdIlnO22hTAKgHpaKhpM8gkzWAFvHmf5vfS"), + TRIGGER_TELEMETRY_DISABLED: z.string().optional(), + AUTH_GITHUB_CLIENT_ID: z.string().optional(), + AUTH_GITHUB_CLIENT_SECRET: z.string().optional(), + EMAIL_TRANSPORT: z.enum(["resend", "smtp", "aws-ses"]).optional(), + FROM_EMAIL: z.string().optional(), + REPLY_TO_EMAIL: z.string().optional(), + RESEND_API_KEY: z.string().optional(), + SMTP_HOST: z.string().optional(), + SMTP_PORT: z.coerce.number().optional(), + SMTP_SECURE: BoolEnv.optional(), + SMTP_USER: z.string().optional(), + SMTP_PASSWORD: z.string().optional(), + + PLAIN_API_KEY: z.string().optional(), + WORKER_SCHEMA: z.string().default("graphile_worker"), + WORKER_CONCURRENCY: z.coerce.number().int().default(10), + WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), + WORKER_ENABLED: z.string().default("true"), + GRACEFUL_SHUTDOWN_TIMEOUT: z.coerce.number().int().default(60000), + DISABLE_SSE: z.string().optional(), + OPENAI_API_KEY: z.string().optional(), + + // Redis options + REDIS_HOST: z.string().optional(), + REDIS_READER_HOST: z.string().optional(), + REDIS_READER_PORT: z.coerce.number().optional(), + REDIS_PORT: z.coerce.number().optional(), + REDIS_USERNAME: z.string().optional(), + REDIS_PASSWORD: z.string().optional(), + REDIS_TLS_DISABLED: z.string().optional(), + + RATE_LIMIT_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + RATE_LIMIT_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + RATE_LIMIT_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + RATE_LIMIT_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + RATE_LIMIT_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + RATE_LIMIT_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + RATE_LIMIT_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), + RATE_LIMIT_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), + + CACHE_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + CACHE_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + CACHE_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + CACHE_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + CACHE_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + CACHE_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + CACHE_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), + CACHE_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), + + REALTIME_STREAMS_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + REALTIME_STREAMS_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + REALTIME_STREAMS_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + REALTIME_STREAMS_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + REALTIME_STREAMS_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + REALTIME_STREAMS_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + REALTIME_STREAMS_REDIS_TLS_DISABLED: z + .string() + .default(process.env.REDIS_TLS_DISABLED ?? "false"), + REALTIME_STREAMS_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), + + REALTIME_MAXIMUM_CREATED_AT_FILTER_AGE_IN_MS: z.coerce + .number() + .int() + .default(24 * 60 * 60 * 1000), // 1 day in milliseconds + + PUBSUB_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + PUBSUB_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + PUBSUB_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + PUBSUB_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + PUBSUB_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + PUBSUB_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + PUBSUB_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), + PUBSUB_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), + + DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT: z.coerce.number().int().default(100), + DEFAULT_ENV_EXECUTION_CONCURRENCY_BURST_FACTOR: z.coerce.number().default(1.0), + DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT: z.coerce.number().int().default(300), + DEFAULT_DEV_ENV_EXECUTION_ATTEMPTS: z.coerce.number().int().positive().default(1), + + //API Rate limiting + /** + * @example "60s" + * @example "1m" + * @example "1h" + * @example "1d" + * @example "1000ms" + * @example "1000s" + */ + API_RATE_LIMIT_REFILL_INTERVAL: z.string().default("10s"), // refill 250 tokens every 10 seconds + API_RATE_LIMIT_MAX: z.coerce.number().int().default(750), // allow bursts of 750 requests + API_RATE_LIMIT_REFILL_RATE: z.coerce.number().int().default(250), // refix 250 tokens every 10 seconds + API_RATE_LIMIT_REQUEST_LOGS_ENABLED: z.string().default("0"), + API_RATE_LIMIT_REJECTION_LOGS_ENABLED: z.string().default("1"), + API_RATE_LIMIT_LIMITER_LOGS_ENABLED: z.string().default("0"), + + API_RATE_LIMIT_JWT_WINDOW: z.string().default("1m"), + API_RATE_LIMIT_JWT_TOKENS: z.coerce.number().int().default(60), + + //v3 + PROVIDER_SECRET: z.string().default("provider-secret"), + COORDINATOR_SECRET: z.string().default("coordinator-secret"), + DEPOT_TOKEN: z.string().optional(), + DEPOT_ORG_ID: z.string().optional(), + DEPOT_REGION: z.string().default("us-east-1"), + + // Deployment registry (v3) + DEPLOY_REGISTRY_HOST: z.string().min(1), + DEPLOY_REGISTRY_USERNAME: z.string().optional(), + DEPLOY_REGISTRY_PASSWORD: z.string().optional(), + DEPLOY_REGISTRY_NAMESPACE: z.string().min(1).default("trigger"), + DEPLOY_REGISTRY_ECR_TAGS: z.string().optional(), // csv, for example: "key1=value1,key2=value2" + DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN: z.string().optional(), + DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID: z.string().optional(), + + // Deployment registry (v4) - falls back to v3 registry if not specified + V4_DEPLOY_REGISTRY_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_HOST) + .pipe(z.string().min(1)), // Ensure final type is required string + V4_DEPLOY_REGISTRY_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_USERNAME), + V4_DEPLOY_REGISTRY_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_PASSWORD), + V4_DEPLOY_REGISTRY_NAMESPACE: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_NAMESPACE) + .pipe(z.string().min(1).default("trigger")), // Ensure final type is required string + V4_DEPLOY_REGISTRY_ECR_TAGS: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_TAGS), + V4_DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN), + V4_DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID: z + .string() + .optional() + .transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID), + + DEPLOY_IMAGE_PLATFORM: z.string().default("linux/amd64"), + DEPLOY_TIMEOUT_MS: z.coerce + .number() + .int() + .default(60 * 1000 * 8), // 8 minutes + + OBJECT_STORE_BASE_URL: z.string().optional(), + OBJECT_STORE_ACCESS_KEY_ID: z.string().optional(), + OBJECT_STORE_SECRET_ACCESS_KEY: z.string().optional(), + OBJECT_STORE_REGION: z.string().optional(), + OBJECT_STORE_SERVICE: z.string().default("s3"), + EVENTS_BATCH_SIZE: z.coerce.number().int().default(100), + EVENTS_BATCH_INTERVAL: z.coerce.number().int().default(1000), + EVENTS_DEFAULT_LOG_RETENTION: z.coerce.number().int().default(7), + EVENTS_MIN_CONCURRENCY: z.coerce.number().int().default(1), + EVENTS_MAX_CONCURRENCY: z.coerce.number().int().default(10), + EVENTS_MAX_BATCH_SIZE: z.coerce.number().int().default(500), + EVENTS_MEMORY_PRESSURE_THRESHOLD: z.coerce.number().int().default(5000), + EVENTS_LOAD_SHEDDING_THRESHOLD: z.coerce.number().int().default(100000), + EVENTS_LOAD_SHEDDING_ENABLED: z.string().default("1"), + SHARED_QUEUE_CONSUMER_POOL_SIZE: z.coerce.number().int().default(10), + SHARED_QUEUE_CONSUMER_INTERVAL_MS: z.coerce.number().int().default(100), + SHARED_QUEUE_CONSUMER_NEXT_TICK_INTERVAL_MS: z.coerce.number().int().default(100), + SHARED_QUEUE_CONSUMER_EMIT_RESUME_DEPENDENCY_TIMEOUT_MS: z.coerce.number().int().default(1000), + SHARED_QUEUE_CONSUMER_RESOLVE_PAYLOADS_BATCH_SIZE: z.coerce.number().int().default(25), + + MANAGED_WORKER_SECRET: z.string().default("managed-secret"), + + // Development OTEL environment variables + DEV_OTEL_EXPORTER_OTLP_ENDPOINT: z.string().optional(), + // If this is set to 1, then the below variables are used to configure the batch processor for spans and logs + DEV_OTEL_BATCH_PROCESSING_ENABLED: z.string().default("0"), + DEV_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE: z.string().default("64"), + DEV_OTEL_SPAN_SCHEDULED_DELAY_MILLIS: z.string().default("200"), + DEV_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS: z.string().default("30000"), + DEV_OTEL_SPAN_MAX_QUEUE_SIZE: z.string().default("512"), + DEV_OTEL_LOG_MAX_EXPORT_BATCH_SIZE: z.string().default("64"), + DEV_OTEL_LOG_SCHEDULED_DELAY_MILLIS: z.string().default("200"), + DEV_OTEL_LOG_EXPORT_TIMEOUT_MILLIS: z.string().default("30000"), + DEV_OTEL_LOG_MAX_QUEUE_SIZE: z.string().default("512"), + + PROD_OTEL_BATCH_PROCESSING_ENABLED: z.string().default("0"), + PROD_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE: z.string().default("64"), + PROD_OTEL_SPAN_SCHEDULED_DELAY_MILLIS: z.string().default("200"), + PROD_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS: z.string().default("30000"), + PROD_OTEL_SPAN_MAX_QUEUE_SIZE: z.string().default("512"), + PROD_OTEL_LOG_MAX_EXPORT_BATCH_SIZE: z.string().default("64"), + PROD_OTEL_LOG_SCHEDULED_DELAY_MILLIS: z.string().default("200"), + PROD_OTEL_LOG_EXPORT_TIMEOUT_MILLIS: z.string().default("30000"), + PROD_OTEL_LOG_MAX_QUEUE_SIZE: z.string().default("512"), + + TRIGGER_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT: z.string().default("1024"), + TRIGGER_OTEL_LOG_ATTRIBUTE_COUNT_LIMIT: z.string().default("1024"), + TRIGGER_OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT: z.string().default("131072"), + TRIGGER_OTEL_LOG_ATTRIBUTE_VALUE_LENGTH_LIMIT: z.string().default("131072"), + TRIGGER_OTEL_SPAN_EVENT_COUNT_LIMIT: z.string().default("10"), + TRIGGER_OTEL_LINK_COUNT_LIMIT: z.string().default("2"), + TRIGGER_OTEL_ATTRIBUTE_PER_LINK_COUNT_LIMIT: z.string().default("10"), + TRIGGER_OTEL_ATTRIBUTE_PER_EVENT_COUNT_LIMIT: z.string().default("10"), + + CHECKPOINT_THRESHOLD_IN_MS: z.coerce.number().int().default(30000), + + // Internal OTEL environment variables + INTERNAL_OTEL_TRACE_EXPORTER_URL: z.string().optional(), + INTERNAL_OTEL_TRACE_EXPORTER_AUTH_HEADERS: z.string().optional(), + INTERNAL_OTEL_TRACE_LOGGING_ENABLED: z.string().default("1"), + // this means 1/20 traces or 5% of traces will be sampled (sampled = recorded) + INTERNAL_OTEL_TRACE_SAMPLING_RATE: z.string().default("20"), + INTERNAL_OTEL_TRACE_INSTRUMENT_PRISMA_ENABLED: z.string().default("0"), + INTERNAL_OTEL_TRACE_DISABLED: z.string().default("0"), + + INTERNAL_OTEL_LOG_EXPORTER_URL: z.string().optional(), + INTERNAL_OTEL_METRIC_EXPORTER_URL: z.string().optional(), + INTERNAL_OTEL_METRIC_EXPORTER_AUTH_HEADERS: z.string().optional(), + INTERNAL_OTEL_METRIC_EXPORTER_ENABLED: z.string().default("0"), + INTERNAL_OTEL_METRIC_EXPORTER_INTERVAL_MS: z.coerce.number().int().default(30_000), + INTERNAL_OTEL_HOST_METRICS_ENABLED: BoolEnv.default(true), + INTERNAL_OTEL_NODEJS_METRICS_ENABLED: BoolEnv.default(true), + INTERNAL_OTEL_ADDITIONAL_DETECTORS_ENABLED: BoolEnv.default(true), + + ORG_SLACK_INTEGRATION_CLIENT_ID: z.string().optional(), + ORG_SLACK_INTEGRATION_CLIENT_SECRET: z.string().optional(), + + /** These enable the alerts feature in v3 */ + ALERT_EMAIL_TRANSPORT: z.enum(["resend", "smtp", "aws-ses"]).optional(), + ALERT_FROM_EMAIL: z.string().optional(), + ALERT_REPLY_TO_EMAIL: z.string().optional(), + ALERT_RESEND_API_KEY: z.string().optional(), + ALERT_SMTP_HOST: z.string().optional(), + ALERT_SMTP_PORT: z.coerce.number().optional(), + ALERT_SMTP_SECURE: BoolEnv.optional(), + ALERT_SMTP_USER: z.string().optional(), + ALERT_SMTP_PASSWORD: z.string().optional(), + ALERT_RATE_LIMITER_EMISSION_INTERVAL: z.coerce.number().int().default(2_500), + ALERT_RATE_LIMITER_BURST_TOLERANCE: z.coerce.number().int().default(10_000), + ALERT_RATE_LIMITER_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + ALERT_RATE_LIMITER_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + ALERT_RATE_LIMITER_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + ALERT_RATE_LIMITER_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + ALERT_RATE_LIMITER_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + ALERT_RATE_LIMITER_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + ALERT_RATE_LIMITER_REDIS_TLS_DISABLED: z + .string() + .default(process.env.REDIS_TLS_DISABLED ?? "false"), + ALERT_RATE_LIMITER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), + + LOOPS_API_KEY: z.string().optional(), + MARQS_DISABLE_REBALANCING: BoolEnv.default(false), + MARQS_VISIBILITY_TIMEOUT_MS: z.coerce + .number() + .int() + .default(60 * 1000 * 15), + MARQS_SHARED_QUEUE_LIMIT: z.coerce.number().int().default(1000), + MARQS_MAXIMUM_QUEUE_PER_ENV_COUNT: z.coerce.number().int().default(50), + MARQS_DEV_QUEUE_LIMIT: z.coerce.number().int().default(1000), + MARQS_MAXIMUM_NACK_COUNT: z.coerce.number().int().default(64), + MARQS_CONCURRENCY_LIMIT_BIAS: z.coerce.number().default(0.75), + MARQS_AVAILABLE_CAPACITY_BIAS: z.coerce.number().default(0.3), + MARQS_QUEUE_AGE_RANDOMIZATION_BIAS: z.coerce.number().default(0.25), + MARQS_REUSE_SNAPSHOT_COUNT: z.coerce.number().int().default(0), + MARQS_MAXIMUM_ENV_COUNT: z.coerce.number().int().optional(), + MARQS_SHARED_WORKER_QUEUE_CONSUMER_INTERVAL_MS: z.coerce.number().int().default(250), + MARQS_SHARED_WORKER_QUEUE_MAX_MESSAGE_COUNT: z.coerce.number().int().default(10), + + MARQS_SHARED_WORKER_QUEUE_EAGER_DEQUEUE_ENABLED: z.string().default("0"), + MARQS_WORKER_ENABLED: z.string().default("0"), + MARQS_WORKER_COUNT: z.coerce.number().int().default(2), + MARQS_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50), + MARQS_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(5), + MARQS_WORKER_POLL_INTERVAL_MS: z.coerce.number().int().default(100), + MARQS_WORKER_IMMEDIATE_POLL_INTERVAL_MS: z.coerce.number().int().default(100), + MARQS_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), + MARQS_SHARED_WORKER_QUEUE_COOLOFF_COUNT_THRESHOLD: z.coerce.number().int().default(10), + MARQS_SHARED_WORKER_QUEUE_COOLOFF_PERIOD_MS: z.coerce.number().int().default(5_000), + + PROD_TASK_HEARTBEAT_INTERVAL_MS: z.coerce.number().int().optional(), + + VERBOSE_GRAPHILE_LOGGING: z.string().default("false"), + V2_MARQS_ENABLED: z.string().default("0"), + V2_MARQS_CONSUMER_POOL_ENABLED: z.string().default("0"), + V2_MARQS_CONSUMER_POOL_SIZE: z.coerce.number().int().default(10), + V2_MARQS_CONSUMER_POLL_INTERVAL_MS: z.coerce.number().int().default(1000), + V2_MARQS_QUEUE_SELECTION_COUNT: z.coerce.number().int().default(36), + V2_MARQS_VISIBILITY_TIMEOUT_MS: z.coerce + .number() + .int() + .default(60 * 1000 * 15), + V2_MARQS_DEFAULT_ENV_CONCURRENCY: z.coerce.number().int().default(100), + V2_MARQS_VERBOSE: z.string().default("0"), + V3_MARQS_CONCURRENCY_MONITOR_ENABLED: z.string().default("0"), + V2_MARQS_CONCURRENCY_MONITOR_ENABLED: z.string().default("0"), + /* Usage settings */ + USAGE_EVENT_URL: z.string().optional(), + PROD_USAGE_HEARTBEAT_INTERVAL_MS: z.coerce.number().int().optional(), + + CENTS_PER_RUN: z.coerce.number().default(0), + + EVENT_LOOP_MONITOR_ENABLED: z.string().default("1"), + MAXIMUM_LIVE_RELOADING_EVENTS: z.coerce.number().int().default(1000), + MAXIMUM_TRACE_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(25_000), + MAXIMUM_TRACE_DETAILED_SUMMARY_VIEW_COUNT: z.coerce.number().int().default(10_000), + TASK_PAYLOAD_OFFLOAD_THRESHOLD: z.coerce.number().int().default(524_288), // 512KB + TASK_PAYLOAD_MAXIMUM_SIZE: z.coerce.number().int().default(3_145_728), // 3MB + BATCH_TASK_PAYLOAD_MAXIMUM_SIZE: z.coerce.number().int().default(1_000_000), // 1MB + TASK_RUN_METADATA_MAXIMUM_SIZE: z.coerce.number().int().default(262_144), // 256KB + + MAXIMUM_DEV_QUEUE_SIZE: z.coerce.number().int().optional(), + MAXIMUM_DEPLOYED_QUEUE_SIZE: z.coerce.number().int().optional(), + MAX_BATCH_V2_TRIGGER_ITEMS: z.coerce.number().int().default(500), + MAX_BATCH_AND_WAIT_V2_TRIGGER_ITEMS: z.coerce.number().int().default(500), + + REALTIME_STREAM_VERSION: z.enum(["v1", "v2"]).default("v1"), + REALTIME_STREAM_MAX_LENGTH: z.coerce.number().int().default(1000), + REALTIME_STREAM_TTL: z.coerce + .number() + .int() + .default(60 * 60 * 24), // 1 day in seconds + BATCH_METADATA_OPERATIONS_FLUSH_INTERVAL_MS: z.coerce.number().int().default(1000), + BATCH_METADATA_OPERATIONS_FLUSH_ENABLED: z.string().default("1"), + BATCH_METADATA_OPERATIONS_FLUSH_LOGGING_ENABLED: z.string().default("1"), + + // Run Engine 2.0 + RUN_ENGINE_WORKER_COUNT: z.coerce.number().int().default(4), + RUN_ENGINE_TASKS_PER_WORKER: z.coerce.number().int().default(10), + RUN_ENGINE_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(10), + RUN_ENGINE_WORKER_POLL_INTERVAL: z.coerce.number().int().default(100), + RUN_ENGINE_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(100), + RUN_ENGINE_TIMEOUT_PENDING_EXECUTING: z.coerce.number().int().default(60_000), + RUN_ENGINE_TIMEOUT_PENDING_CANCEL: z.coerce.number().int().default(60_000), + RUN_ENGINE_TIMEOUT_EXECUTING: z.coerce.number().int().default(60_000), + RUN_ENGINE_TIMEOUT_EXECUTING_WITH_WAITPOINTS: z.coerce.number().int().default(60_000), + RUN_ENGINE_TIMEOUT_SUSPENDED: z.coerce + .number() + .int() + .default(60_000 * 10), + RUN_ENGINE_DEBUG_WORKER_NOTIFICATIONS: BoolEnv.default(false), + RUN_ENGINE_PARENT_QUEUE_LIMIT: z.coerce.number().int().default(1000), + RUN_ENGINE_CONCURRENCY_LIMIT_BIAS: z.coerce.number().default(0.75), + RUN_ENGINE_AVAILABLE_CAPACITY_BIAS: z.coerce.number().default(0.3), + RUN_ENGINE_QUEUE_AGE_RANDOMIZATION_BIAS: z.coerce.number().default(0.25), + RUN_ENGINE_REUSE_SNAPSHOT_COUNT: z.coerce.number().int().default(0), + RUN_ENGINE_MAXIMUM_ENV_COUNT: z.coerce.number().int().optional(), + RUN_ENGINE_RUN_QUEUE_SHARD_COUNT: z.coerce.number().int().default(4), + RUN_ENGINE_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), + RUN_ENGINE_RETRY_WARM_START_THRESHOLD_MS: z.coerce.number().int().default(30_000), + RUN_ENGINE_PROCESS_WORKER_QUEUE_DEBOUNCE_MS: z.coerce.number().int().default(200), + RUN_ENGINE_DEQUEUE_BLOCKING_TIMEOUT_SECONDS: z.coerce.number().int().default(10), + RUN_ENGINE_MASTER_QUEUE_CONSUMERS_INTERVAL_MS: z.coerce.number().int().default(1000), + RUN_ENGINE_MASTER_QUEUE_COOLOFF_PERIOD_MS: z.coerce.number().int().default(10_000), + RUN_ENGINE_MASTER_QUEUE_COOLOFF_COUNT_THRESHOLD: z.coerce.number().int().default(10), + RUN_ENGINE_MASTER_QUEUE_CONSUMER_DEQUEUE_COUNT: z.coerce.number().int().default(10), + RUN_ENGINE_CONCURRENCY_SWEEPER_SCAN_SCHEDULE: z.string().optional(), + RUN_ENGINE_CONCURRENCY_SWEEPER_PROCESS_MARKED_SCHEDULE: z.string().optional(), + RUN_ENGINE_CONCURRENCY_SWEEPER_SCAN_JITTER_IN_MS: z.coerce.number().int().optional(), + RUN_ENGINE_CONCURRENCY_SWEEPER_PROCESS_MARKED_JITTER_IN_MS: z.coerce.number().int().optional(), + + RUN_ENGINE_RUN_LOCK_DURATION: z.coerce.number().int().default(5000), + RUN_ENGINE_RUN_LOCK_AUTOMATIC_EXTENSION_THRESHOLD: z.coerce.number().int().default(1000), + RUN_ENGINE_RUN_LOCK_MAX_RETRIES: z.coerce.number().int().default(10), + RUN_ENGINE_RUN_LOCK_BASE_DELAY: z.coerce.number().int().default(100), + RUN_ENGINE_RUN_LOCK_MAX_DELAY: z.coerce.number().int().default(3000), + RUN_ENGINE_RUN_LOCK_BACKOFF_MULTIPLIER: z.coerce.number().default(1.8), + RUN_ENGINE_RUN_LOCK_JITTER_FACTOR: z.coerce.number().default(0.15), + RUN_ENGINE_RUN_LOCK_MAX_TOTAL_WAIT_TIME: z.coerce.number().int().default(15000), + + RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_MAX_COUNT: z.coerce.number().int().default(12), + RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_MAX_DELAY_MS: z.coerce + .number() + .int() + .default(60_000 * 60 * 6), + RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_INITIAL_DELAY_MS: z.coerce + .number() + .int() + .default(60_000), + RUN_ENGINE_SUSPENDED_HEARTBEAT_RETRIES_FACTOR: z.coerce.number().default(2), + + RUN_ENGINE_WORKER_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + RUN_ENGINE_WORKER_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + RUN_ENGINE_WORKER_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + RUN_ENGINE_WORKER_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + RUN_ENGINE_WORKER_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + RUN_ENGINE_WORKER_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + RUN_ENGINE_WORKER_REDIS_TLS_DISABLED: z + .string() + .default(process.env.REDIS_TLS_DISABLED ?? "false"), + + RUN_ENGINE_RUN_QUEUE_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + RUN_ENGINE_RUN_QUEUE_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + RUN_ENGINE_RUN_QUEUE_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + RUN_ENGINE_RUN_QUEUE_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + RUN_ENGINE_RUN_QUEUE_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + RUN_ENGINE_RUN_QUEUE_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + RUN_ENGINE_RUN_QUEUE_REDIS_TLS_DISABLED: z + .string() + .default(process.env.REDIS_TLS_DISABLED ?? "false"), + + RUN_ENGINE_RUN_LOCK_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + RUN_ENGINE_RUN_LOCK_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + RUN_ENGINE_RUN_LOCK_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + RUN_ENGINE_RUN_LOCK_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + RUN_ENGINE_RUN_LOCK_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + RUN_ENGINE_RUN_LOCK_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + RUN_ENGINE_RUN_LOCK_REDIS_TLS_DISABLED: z + .string() + .default(process.env.REDIS_TLS_DISABLED ?? "false"), + + RUN_ENGINE_DEV_PRESENCE_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + RUN_ENGINE_DEV_PRESENCE_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + RUN_ENGINE_DEV_PRESENCE_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + RUN_ENGINE_DEV_PRESENCE_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + RUN_ENGINE_DEV_PRESENCE_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + RUN_ENGINE_DEV_PRESENCE_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + RUN_ENGINE_DEV_PRESENCE_REDIS_TLS_DISABLED: z + .string() + .default(process.env.REDIS_TLS_DISABLED ?? "false"), + + //API Rate limiting + /** + * @example "60s" + * @example "1m" + * @example "1h" + * @example "1d" + * @example "1000ms" + * @example "1000s" + */ + RUN_ENGINE_RATE_LIMIT_REFILL_INTERVAL: z.string().default("10s"), // refill 250 tokens every 10 seconds + RUN_ENGINE_RATE_LIMIT_MAX: z.coerce.number().int().default(1200), // allow bursts of 750 requests + RUN_ENGINE_RATE_LIMIT_REFILL_RATE: z.coerce.number().int().default(400), // refix 250 tokens every 10 seconds + RUN_ENGINE_RATE_LIMIT_REQUEST_LOGS_ENABLED: z.string().default("0"), + RUN_ENGINE_RATE_LIMIT_REJECTION_LOGS_ENABLED: z.string().default("1"), + RUN_ENGINE_RATE_LIMIT_LIMITER_LOGS_ENABLED: z.string().default("0"), + + RUN_ENGINE_RELEASE_CONCURRENCY_ENABLED: z.string().default("0"), + RUN_ENGINE_RELEASE_CONCURRENCY_DISABLE_CONSUMERS: z.string().default("0"), + RUN_ENGINE_RELEASE_CONCURRENCY_MAX_TOKENS_RATIO: z.coerce.number().default(1), + RUN_ENGINE_RELEASE_CONCURRENCY_RELEASINGS_MAX_AGE: z.coerce + .number() + .int() + .default(60_000 * 30), + RUN_ENGINE_RELEASE_CONCURRENCY_RELEASINGS_POLL_INTERVAL: z.coerce + .number() + .int() + .default(60_000), + RUN_ENGINE_RELEASE_CONCURRENCY_MAX_RETRIES: z.coerce.number().int().default(3), + RUN_ENGINE_RELEASE_CONCURRENCY_CONSUMERS_COUNT: z.coerce.number().int().default(1), + RUN_ENGINE_RELEASE_CONCURRENCY_POLL_INTERVAL: z.coerce.number().int().default(500), + RUN_ENGINE_RELEASE_CONCURRENCY_BATCH_SIZE: z.coerce.number().int().default(10), + + RUN_ENGINE_WORKER_ENABLED: z.string().default("1"), + RUN_ENGINE_WORKER_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), + RUN_ENGINE_RUN_QUEUE_LOG_LEVEL: z + .enum(["log", "error", "warn", "info", "debug"]) + .default("info"), + + /** How long should the presence ttl last */ + DEV_PRESENCE_SSE_TIMEOUT: z.coerce.number().int().default(30_000), + DEV_PRESENCE_TTL_MS: z.coerce.number().int().default(5_000), + DEV_PRESENCE_POLL_MS: z.coerce.number().int().default(1_000), + /** How many ms to wait until dequeuing again, if there was a run last time */ + DEV_DEQUEUE_INTERVAL_WITH_RUN: z.coerce.number().int().default(250), + /** How many ms to wait until dequeuing again, if there was no run last time */ + DEV_DEQUEUE_INTERVAL_WITHOUT_RUN: z.coerce.number().int().default(1_000), + /** The max number of runs per API call that we'll dequeue in DEV */ + DEV_DEQUEUE_MAX_RUNS_PER_PULL: z.coerce.number().int().default(10), + + /** The maximum concurrent local run processes executing at once in dev */ + DEV_MAX_CONCURRENT_RUNS: z.coerce.number().int().default(25), + + /** The CLI should connect to this for dev runs */ + DEV_ENGINE_URL: z.string().default(process.env.APP_ORIGIN ?? "http://localhost:3030"), + + LEGACY_RUN_ENGINE_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), + LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), + LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(1), + LEGACY_RUN_ENGINE_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), + LEGACY_RUN_ENGINE_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50), + LEGACY_RUN_ENGINE_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50), + LEGACY_RUN_ENGINE_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), + LEGACY_RUN_ENGINE_WORKER_LOG_LEVEL: z + .enum(["log", "error", "warn", "info", "debug"]) + .default("info"), + + LEGACY_RUN_ENGINE_WORKER_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + LEGACY_RUN_ENGINE_WORKER_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + LEGACY_RUN_ENGINE_WORKER_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + LEGACY_RUN_ENGINE_WORKER_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + LEGACY_RUN_ENGINE_WORKER_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + LEGACY_RUN_ENGINE_WORKER_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + LEGACY_RUN_ENGINE_WORKER_REDIS_TLS_DISABLED: z + .string() + .default(process.env.REDIS_TLS_DISABLED ?? "false"), + LEGACY_RUN_ENGINE_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), + + LEGACY_RUN_ENGINE_WAITING_FOR_DEPLOY_BATCH_SIZE: z.coerce.number().int().default(100), + LEGACY_RUN_ENGINE_WAITING_FOR_DEPLOY_BATCH_STAGGER_MS: z.coerce.number().int().default(1_000), + + COMMON_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), + COMMON_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), + COMMON_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10), + COMMON_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), + COMMON_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50), + COMMON_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50), + COMMON_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), + COMMON_WORKER_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), + + COMMON_WORKER_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + COMMON_WORKER_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + COMMON_WORKER_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + COMMON_WORKER_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + COMMON_WORKER_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + COMMON_WORKER_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + COMMON_WORKER_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), + COMMON_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), + + BATCH_TRIGGER_PROCESS_JOB_VISIBILITY_TIMEOUT_MS: z.coerce + .number() + .int() + .default(60_000 * 5), // 5 minutes + + BATCH_TRIGGER_CACHED_RUNS_CHECK_ENABLED: BoolEnv.default(false), + + BATCH_TRIGGER_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), + BATCH_TRIGGER_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), + BATCH_TRIGGER_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10), + BATCH_TRIGGER_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), + BATCH_TRIGGER_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50), + BATCH_TRIGGER_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(20), + BATCH_TRIGGER_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), + BATCH_TRIGGER_WORKER_LOG_LEVEL: z + .enum(["log", "error", "warn", "info", "debug"]) + .default("info"), + + BATCH_TRIGGER_WORKER_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + BATCH_TRIGGER_WORKER_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + BATCH_TRIGGER_WORKER_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + BATCH_TRIGGER_WORKER_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + BATCH_TRIGGER_WORKER_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + BATCH_TRIGGER_WORKER_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + BATCH_TRIGGER_WORKER_REDIS_TLS_DISABLED: z + .string() + .default(process.env.REDIS_TLS_DISABLED ?? "false"), + BATCH_TRIGGER_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), + + ADMIN_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), + ADMIN_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), + ADMIN_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10), + ADMIN_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), + ADMIN_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50), + ADMIN_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(20), + ADMIN_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), + ADMIN_WORKER_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), + + ADMIN_WORKER_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + ADMIN_WORKER_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + ADMIN_WORKER_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + ADMIN_WORKER_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + ADMIN_WORKER_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + ADMIN_WORKER_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + ADMIN_WORKER_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), + ADMIN_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), + + ALERTS_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), + ALERTS_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), + ALERTS_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10), + ALERTS_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), + ALERTS_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(100), + ALERTS_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50), + ALERTS_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000), + ALERTS_WORKER_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), + + ALERTS_WORKER_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + ALERTS_WORKER_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + ALERTS_WORKER_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + ALERTS_WORKER_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + ALERTS_WORKER_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + ALERTS_WORKER_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + ALERTS_WORKER_REDIS_TLS_DISABLED: z.string().default(process.env.REDIS_TLS_DISABLED ?? "false"), + ALERTS_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), + + SCHEDULE_ENGINE_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), + SCHEDULE_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"), + SCHEDULE_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2), + SCHEDULE_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10), + SCHEDULE_WORKER_POLL_INTERVAL: z.coerce.number().int().default(1000), + SCHEDULE_WORKER_IMMEDIATE_POLL_INTERVAL: z.coerce.number().int().default(50), + SCHEDULE_WORKER_CONCURRENCY_LIMIT: z.coerce.number().int().default(50), + SCHEDULE_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(30_000), + SCHEDULE_WORKER_DISTRIBUTION_WINDOW_SECONDS: z.coerce.number().int().default(30), + + SCHEDULE_WORKER_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + SCHEDULE_WORKER_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + SCHEDULE_WORKER_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + SCHEDULE_WORKER_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + SCHEDULE_WORKER_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + SCHEDULE_WORKER_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + SCHEDULE_WORKER_REDIS_TLS_DISABLED: z + .string() + .default(process.env.REDIS_TLS_DISABLED ?? "false"), + SCHEDULE_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"), + + TASK_EVENT_PARTITIONING_ENABLED: z.string().default("0"), + TASK_EVENT_PARTITIONED_WINDOW_IN_SECONDS: z.coerce.number().int().default(60), // 1 minute + + QUEUE_SSE_AUTORELOAD_INTERVAL_MS: z.coerce.number().int().default(5_000), + QUEUE_SSE_AUTORELOAD_TIMEOUT_MS: z.coerce.number().int().default(60_000), + + SLACK_BOT_TOKEN: z.string().optional(), + SLACK_SIGNUP_REASON_CHANNEL_ID: z.string().optional(), + + // kapa.ai + KAPA_AI_WEBSITE_ID: z.string().optional(), + + // BetterStack + BETTERSTACK_API_KEY: z.string().optional(), + BETTERSTACK_STATUS_PAGE_ID: z.string().optional(), + + RUN_REPLICATION_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + RUN_REPLICATION_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + RUN_REPLICATION_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + RUN_REPLICATION_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + RUN_REPLICATION_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + RUN_REPLICATION_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + RUN_REPLICATION_REDIS_TLS_DISABLED: z + .string() + .default(process.env.REDIS_TLS_DISABLED ?? "false"), + + RUN_REPLICATION_CLICKHOUSE_URL: z.string().optional(), + RUN_REPLICATION_ENABLED: z.string().default("0"), + RUN_REPLICATION_SLOT_NAME: z.string().default("task_runs_to_clickhouse_v1"), + RUN_REPLICATION_PUBLICATION_NAME: z.string().default("task_runs_to_clickhouse_v1_publication"), + RUN_REPLICATION_MAX_FLUSH_CONCURRENCY: z.coerce.number().int().default(2), + RUN_REPLICATION_FLUSH_INTERVAL_MS: z.coerce.number().int().default(1000), + RUN_REPLICATION_FLUSH_BATCH_SIZE: z.coerce.number().int().default(100), + RUN_REPLICATION_LEADER_LOCK_TIMEOUT_MS: z.coerce.number().int().default(30_000), + RUN_REPLICATION_LEADER_LOCK_EXTEND_INTERVAL_MS: z.coerce.number().int().default(10_000), + RUN_REPLICATION_ACK_INTERVAL_SECONDS: z.coerce.number().int().default(10), + RUN_REPLICATION_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), + RUN_REPLICATION_CLICKHOUSE_LOG_LEVEL: z + .enum(["log", "error", "warn", "info", "debug"]) + .default("info"), + RUN_REPLICATION_LEADER_LOCK_ADDITIONAL_TIME_MS: z.coerce.number().int().default(10_000), + RUN_REPLICATION_LEADER_LOCK_RETRY_INTERVAL_MS: z.coerce.number().int().default(500), + RUN_REPLICATION_WAIT_FOR_ASYNC_INSERT: z.string().default("0"), + RUN_REPLICATION_KEEP_ALIVE_ENABLED: z.string().default("0"), + RUN_REPLICATION_KEEP_ALIVE_IDLE_SOCKET_TTL_MS: z.coerce.number().int().optional(), + RUN_REPLICATION_MAX_OPEN_CONNECTIONS: z.coerce.number().int().default(10), + // Retry configuration for insert operations + RUN_REPLICATION_INSERT_MAX_RETRIES: z.coerce.number().int().default(3), + RUN_REPLICATION_INSERT_BASE_DELAY_MS: z.coerce.number().int().default(100), + RUN_REPLICATION_INSERT_MAX_DELAY_MS: z.coerce.number().int().default(2000), + RUN_REPLICATION_INSERT_STRATEGY: z.enum(["insert", "insert_async"]).default("insert"), + + // Clickhouse + CLICKHOUSE_URL: z.string(), + CLICKHOUSE_KEEP_ALIVE_ENABLED: z.string().default("1"), + CLICKHOUSE_KEEP_ALIVE_IDLE_SOCKET_TTL_MS: z.coerce.number().int().optional(), + CLICKHOUSE_MAX_OPEN_CONNECTIONS: z.coerce.number().int().default(10), + CLICKHOUSE_LOG_LEVEL: z.enum(["log", "error", "warn", "info", "debug"]).default("info"), + CLICKHOUSE_COMPRESSION_REQUEST: z.string().default("1"), + + // Bootstrap + TRIGGER_BOOTSTRAP_ENABLED: z.string().default("0"), + TRIGGER_BOOTSTRAP_WORKER_GROUP_NAME: z.string().optional(), + TRIGGER_BOOTSTRAP_WORKER_TOKEN_PATH: z.string().optional(), + + // Machine presets + MACHINE_PRESETS_OVERRIDE_PATH: z.string().optional(), + + // CLI package tag (e.g. "latest", "v4-beta", "4.0.0") - used for setup commands + TRIGGER_CLI_TAG: z.string().default("latest"), + + HEALTHCHECK_DATABASE_DISABLED: z.string().default("0"), + + REQUEST_IDEMPOTENCY_REDIS_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_HOST), + REQUEST_IDEMPOTENCY_REDIS_READER_HOST: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_READER_HOST), + REQUEST_IDEMPOTENCY_REDIS_READER_PORT: z.coerce + .number() + .optional() + .transform( + (v) => + v ?? (process.env.REDIS_READER_PORT ? parseInt(process.env.REDIS_READER_PORT) : undefined) + ), + REQUEST_IDEMPOTENCY_REDIS_PORT: z.coerce + .number() + .optional() + .transform( + (v) => v ?? (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT) : undefined) + ), + REQUEST_IDEMPOTENCY_REDIS_USERNAME: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_USERNAME), + REQUEST_IDEMPOTENCY_REDIS_PASSWORD: z + .string() + .optional() + .transform((v) => v ?? process.env.REDIS_PASSWORD), + REQUEST_IDEMPOTENCY_REDIS_TLS_DISABLED: z + .string() + .default(process.env.REDIS_TLS_DISABLED ?? "false"), + + REQUEST_IDEMPOTENCY_LOG_LEVEL: z + .enum(["log", "error", "warn", "info", "debug"]) + .default("info"), + + REQUEST_IDEMPOTENCY_TTL_IN_MS: z.coerce + .number() + .int() + .default(60_000 * 60 * 24), + + // Bulk action + BULK_ACTION_BATCH_SIZE: z.coerce.number().int().default(100), + BULK_ACTION_BATCH_DELAY_MS: z.coerce.number().int().default(200), + BULK_ACTION_SUBBATCH_CONCURRENCY: z.coerce.number().int().default(5), + + // AI Run Filter + AI_RUN_FILTER_MODEL: z.string().optional(), + + EVENT_LOOP_MONITOR_THRESHOLD_MS: z.coerce.number().int().default(100), + + VERY_SLOW_QUERY_THRESHOLD_MS: z.coerce.number().int().optional(), + }) + .and(GithubAppEnvSchema); export type Environment = z.infer; export const env = EnvironmentSchema.parse(process.env); diff --git a/apps/webapp/app/routes/_app.github.callback/route.tsx b/apps/webapp/app/routes/_app.github.callback/route.tsx new file mode 100644 index 0000000000..44c7f37c13 --- /dev/null +++ b/apps/webapp/app/routes/_app.github.callback/route.tsx @@ -0,0 +1,121 @@ +import { type LoaderFunctionArgs } from "@remix-run/node"; +import { z } from "zod"; +import { validateGitHubAppInstallSession } from "~/services/gitHubSession.server"; +import { linkGitHubAppInstallation, updateGitHubAppInstallation } from "~/services/gitHub.server"; +import { logger } from "~/services/logger.server"; +import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server"; +import { tryCatch } from "@trigger.dev/core"; +import { $replica } from "~/db.server"; +import { requireUser } from "~/services/session.server"; +import { sanitizeRedirectPath } from "~/utils"; + +const QuerySchema = z.discriminatedUnion("setup_action", [ + z.object({ + setup_action: z.literal("install"), + installation_id: z.coerce.number(), + state: z.string(), + }), + z.object({ + setup_action: z.literal("update"), + installation_id: z.coerce.number(), + state: z.string(), + }), + z.object({ + setup_action: z.literal("request"), + state: z.string(), + }), +]); + +export async function loader({ request }: LoaderFunctionArgs) { + const url = new URL(request.url); + const queryParams = Object.fromEntries(url.searchParams); + const cookieHeader = request.headers.get("Cookie"); + + const result = QuerySchema.safeParse(queryParams); + + if (!result.success) { + logger.warn("GitHub App callback with invalid params", { + queryParams, + }); + return redirectWithErrorMessage("/", request, "Failed to install GitHub App"); + } + + const callbackData = result.data; + + const sessionResult = await validateGitHubAppInstallSession(cookieHeader, callbackData.state); + + if (!sessionResult.valid) { + logger.error("GitHub App callback with invalid session", { + callbackData, + error: sessionResult.error, + }); + + return redirectWithErrorMessage("/", request, "Failed to install GitHub App"); + } + + const { organizationId, redirectTo: unsafeRedirectTo } = sessionResult; + const redirectTo = sanitizeRedirectPath(unsafeRedirectTo); + + const user = await requireUser(request); + const org = await $replica.organization.findFirst({ + where: { id: organizationId, members: { some: { userId: user.id } }, deletedAt: null }, + orderBy: { createdAt: "desc" }, + select: { + id: true, + }, + }); + + if (!org) { + // the secure cookie approach should already protect against this + // just an additional check + logger.error("GitHub app installation attempt on unauthenticated org", { + userId: user.id, + organizationId, + }); + return redirectWithErrorMessage(redirectTo, request, "Failed to install GitHub App"); + } + + switch (callbackData.setup_action) { + case "install": { + const [error] = await tryCatch( + linkGitHubAppInstallation(callbackData.installation_id, organizationId) + ); + + if (error) { + logger.error("Failed to link GitHub App installation", { + error, + }); + return redirectWithErrorMessage(redirectTo, request, "Failed to install GitHub App"); + } + + return redirectWithSuccessMessage(redirectTo, request, "GitHub App installed successfully"); + } + + case "update": { + const [error] = await tryCatch(updateGitHubAppInstallation(callbackData.installation_id)); + + if (error) { + logger.error("Failed to update GitHub App installation", { + error, + }); + return redirectWithErrorMessage(redirectTo, request, "Failed to update GitHub App"); + } + + return redirectWithSuccessMessage(redirectTo, request, "GitHub App updated successfully"); + } + + case "request": { + // This happens when a non-admin user requests installation + // The installation_id won't be available until an admin approves + logger.info("GitHub App installation requested, awaiting approval", { + callbackData, + }); + + return redirectWithSuccessMessage(redirectTo, request, "GitHub App installation requested"); + } + + default: + callbackData satisfies never; + return redirectWithErrorMessage(redirectTo, request, "Failed to install GitHub App"); + } +} diff --git a/apps/webapp/app/routes/_app.github.install/route.tsx b/apps/webapp/app/routes/_app.github.install/route.tsx new file mode 100644 index 0000000000..42d68e5bec --- /dev/null +++ b/apps/webapp/app/routes/_app.github.install/route.tsx @@ -0,0 +1,52 @@ +import type { LoaderFunctionArgs } from "@remix-run/server-runtime"; +import { redirect } from "remix-typedjson"; +import { z } from "zod"; +import { $replica } from "~/db.server"; +import { createGitHubAppInstallSession } from "~/services/gitHubSession.server"; +import { requireUser } from "~/services/session.server"; +import { newOrganizationPath } from "~/utils/pathBuilder"; +import { logger } from "~/services/logger.server"; +import { sanitizeRedirectPath } from "~/utils"; + +const QuerySchema = z.object({ + org_slug: z.string(), + redirect_to: z.string().refine((value) => value === sanitizeRedirectPath(value), { + message: "Invalid redirect path", + }), +}); + +export const loader = async ({ request }: LoaderFunctionArgs) => { + const searchParams = new URL(request.url).searchParams; + const parsed = QuerySchema.safeParse(Object.fromEntries(searchParams)); + + if (!parsed.success) { + logger.warn("GitHub App installation redirect with invalid params", { + searchParams, + error: parsed.error, + }); + throw redirect("/"); + } + + const { org_slug, redirect_to } = parsed.data; + const user = await requireUser(request); + + const org = await $replica.organization.findFirst({ + where: { slug: org_slug, members: { some: { userId: user.id } }, deletedAt: null }, + orderBy: { createdAt: "desc" }, + select: { + id: true, + }, + }); + + if (!org) { + throw redirect(newOrganizationPath()); + } + + const { url, cookieHeader } = await createGitHubAppInstallSession(org.id, redirect_to); + + return redirect(url, { + headers: { + "Set-Cookie": cookieHeader, + }, + }); +}; diff --git a/apps/webapp/app/routes/auth.github.callback.tsx b/apps/webapp/app/routes/auth.github.callback.tsx index d85a68a472..ee8776ae81 100644 --- a/apps/webapp/app/routes/auth.github.callback.tsx +++ b/apps/webapp/app/routes/auth.github.callback.tsx @@ -5,11 +5,12 @@ import { getSession, redirectWithErrorMessage } from "~/models/message.server"; import { authenticator } from "~/services/auth.server"; import { commitSession } from "~/services/sessionStorage.server"; import { redirectCookie } from "./auth.github"; +import { sanitizeRedirectPath } from "~/utils"; export let loader: LoaderFunction = async ({ request }) => { const cookie = request.headers.get("Cookie"); const redirectValue = await redirectCookie.parse(cookie); - const redirectTo = redirectValue ?? "/"; + const redirectTo = sanitizeRedirectPath(redirectValue); const auth = await authenticator.authenticate("github", request, { failureRedirect: "/login", // If auth fails, the failureRedirect will be thrown as a Response diff --git a/apps/webapp/app/routes/auth.github.ts b/apps/webapp/app/routes/auth.github.ts index ec376153fc..a4adc7f28d 100644 --- a/apps/webapp/app/routes/auth.github.ts +++ b/apps/webapp/app/routes/auth.github.ts @@ -1,6 +1,4 @@ -import type { ActionFunction, LoaderFunction } from "@remix-run/node"; -import { createCookie } from "@remix-run/node"; -import { redirect } from "@remix-run/node"; +import { type ActionFunction, type LoaderFunction, redirect, createCookie } from "@remix-run/node"; import { authenticator } from "~/services/auth.server"; export let loader: LoaderFunction = () => redirect("/login"); diff --git a/apps/webapp/app/services/gitHub.server.ts b/apps/webapp/app/services/gitHub.server.ts new file mode 100644 index 0000000000..9e4c26a554 --- /dev/null +++ b/apps/webapp/app/services/gitHub.server.ts @@ -0,0 +1,135 @@ +import { App, type Octokit } from "octokit"; +import { env } from "../env.server"; +import { prisma } from "~/db.server"; +import { logger } from "./logger.server"; + +export const githubApp = + env.GITHUB_APP_ENABLED === "1" + ? new App({ + appId: env.GITHUB_APP_ID, + privateKey: env.GITHUB_APP_PRIVATE_KEY, + webhooks: { + secret: env.GITHUB_APP_WEBHOOK_SECRET, + }, + }) + : null; + +/** + * Links a GitHub App installation to a Trigger organization + */ +export async function linkGitHubAppInstallation( + installationId: number, + organizationId: string +): Promise { + if (!githubApp) { + throw new Error("GitHub App is not enabled"); + } + + const octokit = await githubApp.getInstallationOctokit(installationId); + const { data: installation } = await octokit.rest.apps.getInstallation({ + installation_id: installationId, + }); + + const repositories = await fetchInstallationRepositories(octokit, installationId); + + const repositorySelection = installation.repository_selection === "all" ? "ALL" : "SELECTED"; + + await prisma.githubAppInstallation.create({ + data: { + appInstallationId: installationId, + organizationId, + targetId: installation.target_id, + targetType: installation.target_type, + accountHandle: installation.account + ? "login" in installation.account + ? installation.account.login + : "slug" in installation.account + ? installation.account.slug + : "-" + : "-", + permissions: installation.permissions, + repositorySelection, + repositories: { + create: repositories, + }, + }, + }); +} + +/** + * Links a GitHub App installation to a Trigger organization + */ +export async function updateGitHubAppInstallation(installationId: number): Promise { + if (!githubApp) { + throw new Error("GitHub App is not enabled"); + } + + const octokit = await githubApp.getInstallationOctokit(installationId); + const { data: installation } = await octokit.rest.apps.getInstallation({ + installation_id: installationId, + }); + + const existingInstallation = await prisma.githubAppInstallation.findFirst({ + where: { appInstallationId: installationId }, + }); + + if (!existingInstallation) { + throw new Error("GitHub App installation not found"); + } + + const repositorySelection = installation.repository_selection === "all" ? "ALL" : "SELECTED"; + + // repos are updated asynchronously via webhook events + await prisma.githubAppInstallation.update({ + where: { id: existingInstallation?.id }, + data: { + appInstallationId: installationId, + targetId: installation.target_id, + targetType: installation.target_type, + accountHandle: installation.account + ? "login" in installation.account + ? installation.account.login + : "slug" in installation.account + ? installation.account.slug + : "-" + : "-", + permissions: installation.permissions, + suspendedAt: existingInstallation?.suspendedAt, + repositorySelection, + }, + }); +} + +async function fetchInstallationRepositories(octokit: Octokit, installationId: number) { + const iterator = octokit.paginate.iterator(octokit.rest.apps.listReposAccessibleToInstallation, { + installation_id: installationId, + per_page: 100, + }); + + const allRepos = []; + const maxPages = 3; + let pageCount = 0; + + for await (const { data } of iterator) { + pageCount++; + allRepos.push(...data); + + if (maxPages && pageCount >= maxPages) { + logger.warn("GitHub installation repository fetch truncated", { + installationId, + maxPages, + totalReposFetched: allRepos.length, + }); + break; + } + } + + return allRepos.map((repo) => ({ + githubId: repo.id, + name: repo.name, + fullName: repo.full_name, + htmlUrl: repo.html_url, + private: repo.private, + defaultBranch: repo.default_branch, + })); +} diff --git a/apps/webapp/app/services/gitHubSession.server.ts b/apps/webapp/app/services/gitHubSession.server.ts new file mode 100644 index 0000000000..499c1dff1a --- /dev/null +++ b/apps/webapp/app/services/gitHubSession.server.ts @@ -0,0 +1,124 @@ +import { createCookieSessionStorage } from "@remix-run/node"; +import { randomBytes } from "crypto"; +import { env } from "../env.server"; +import { logger } from "./logger.server"; + +const sessionStorage = createCookieSessionStorage({ + cookie: { + name: "__github_app_install", + httpOnly: true, + maxAge: 60 * 60, // 1 hour + path: "/", + sameSite: "lax", + secrets: [env.SESSION_SECRET], + secure: env.NODE_ENV === "production", + }, +}); + +/** + * Creates a secure session for GitHub App installation with organization tracking + */ +export async function createGitHubAppInstallSession( + organizationId: string, + redirectTo: string +): Promise<{ url: string; cookieHeader: string }> { + if (env.GITHUB_APP_ENABLED !== "1") { + throw new Error("GitHub App is not enabled"); + } + + const state = randomBytes(32).toString("hex"); + + const session = await sessionStorage.getSession(); + session.set("organizationId", organizationId); + session.set("redirectTo", redirectTo); + session.set("state", state); + session.set("createdAt", Date.now()); + + const githubAppSlug = env.GITHUB_APP_SLUG; + + // the state query param gets passed through to the installation callback + const url = `https://github.com/apps/${githubAppSlug}/installations/new?state=${state}`; + + const cookieHeader = await sessionStorage.commitSession(session); + + return { url, cookieHeader }; +} + +/** + * Validates and retrieves the GitHub App installation session + */ +export async function validateGitHubAppInstallSession( + cookieHeader: string | null, + state: string +): Promise< + { valid: true; organizationId: string; redirectTo: string } | { valid: false; error?: string } +> { + if (!cookieHeader) { + return { + valid: false, + error: "No installation session cookie found", + }; + } + + const session = await sessionStorage.getSession(cookieHeader); + + const sessionState = session.get("state"); + const organizationId = session.get("organizationId"); + const redirectTo = session.get("redirectTo"); + const createdAt = session.get("createdAt"); + + if (!sessionState || !organizationId || !createdAt || !redirectTo) { + logger.warn("GitHub App installation session missing required fields", { + hasState: !!sessionState, + hasOrgId: !!organizationId, + hasCreatedAt: !!createdAt, + hasRedirectTo: !!redirectTo, + }); + + return { + valid: false, + error: "invalid_session_data", + }; + } + + if (sessionState !== state) { + logger.warn("GitHub App installation state mismatch", { + expectedState: sessionState, + receivedState: state, + }); + return { + valid: false, + error: "state_mismatch", + }; + } + + const expirationTime = createdAt + 60 * 60 * 1000; + if (Date.now() > expirationTime) { + logger.warn("GitHub App installation session expired", { + createdAt: new Date(createdAt), + now: new Date(), + }); + return { + valid: false, + error: "session_expired", + }; + } + + return { + valid: true, + organizationId, + redirectTo, + }; +} + +/** + * Destroys the GitHub App installation cookie session + */ +export async function destroyGitHubAppInstallSession(cookieHeader: string | null): Promise { + if (!cookieHeader) { + return ""; + } + + const session = await sessionStorage.getSession(cookieHeader); + return await sessionStorage.destroySession(session); +} diff --git a/apps/webapp/app/utils.ts b/apps/webapp/app/utils.ts index 7dc407cec6..adb1829281 100644 --- a/apps/webapp/app/utils.ts +++ b/apps/webapp/app/utils.ts @@ -7,22 +7,38 @@ const DEFAULT_REDIRECT = "/"; * This should be used any time the redirect path is user-provided * (Like the query string on our login/signup pages). This avoids * open-redirect vulnerabilities. - * @param {string} to The redirect destination + * @param {string} path The redirect destination * @param {string} defaultRedirect The redirect to use if the to is unsafe. */ -export function safeRedirect( - to: FormDataEntryValue | string | null | undefined, +export function sanitizeRedirectPath( + path: string | undefined | null, defaultRedirect: string = DEFAULT_REDIRECT -) { - if (!to || typeof to !== "string") { +): string { + if (!path || typeof path !== "string") { return defaultRedirect; } - if (!to.startsWith("/") || to.startsWith("//")) { + if (!path.startsWith("/") || path.startsWith("//")) { return defaultRedirect; } - return to; + try { + // should not parse as a full URL + new URL(path); + return defaultRedirect; + } catch {} + + try { + // ensure it's a valid relative path + const url = new URL(path, "https://example.com"); + if (url.hostname !== "example.com") { + return defaultRedirect; + } + } catch { + return defaultRedirect; + } + + return path; } /** diff --git a/apps/webapp/package.json b/apps/webapp/package.json index a09bcc5a66..a029494240 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -160,6 +160,7 @@ "morgan": "^1.10.0", "nanoid": "3.3.8", "non.geist": "^1.0.2", + "octokit": "^3.2.1", "ohash": "^1.1.3", "openai": "^4.33.1", "p-limit": "^6.2.0", diff --git a/internal-packages/database/prisma/migrations/20250902133449_add_gh_installation_schema/migration.sql b/internal-packages/database/prisma/migrations/20250902133449_add_gh_installation_schema/migration.sql new file mode 100644 index 0000000000..78b5e6bf2c --- /dev/null +++ b/internal-packages/database/prisma/migrations/20250902133449_add_gh_installation_schema/migration.sql @@ -0,0 +1,46 @@ +CREATE TYPE "public"."GithubRepositorySelection" AS ENUM ('ALL', 'SELECTED'); + +CREATE TABLE "public"."GithubAppInstallation" ( + "id" TEXT NOT NULL, + "appInstallationId" BIGINT NOT NULL, + "targetId" BIGINT NOT NULL, + "targetType" TEXT NOT NULL, + "accountHandle" TEXT NOT NULL, + "permissions" JSONB, + "repositorySelection" "public"."GithubRepositorySelection" NOT NULL, + "installedBy" TEXT, + "organizationId" TEXT NOT NULL, + "deletedAt" TIMESTAMP(3), + "suspendedAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "GithubAppInstallation_pkey" PRIMARY KEY ("id") +); + +CREATE TABLE "public"."GithubRepository" ( + "id" TEXT NOT NULL, + "githubId" BIGINT NOT NULL, + "name" TEXT NOT NULL, + "fullName" TEXT NOT NULL, + "htmlUrl" TEXT NOT NULL, + "private" BOOLEAN NOT NULL, + "defaultBranch" TEXT NOT NULL, + "installationId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "GithubRepository_pkey" PRIMARY KEY ("id") +); + +CREATE UNIQUE INDEX "GithubAppInstallation_appInstallationId_key" ON "public"."GithubAppInstallation"("appInstallationId"); + +CREATE INDEX "GithubAppInstallation_organizationId_idx" ON "public"."GithubAppInstallation"("organizationId"); + +CREATE INDEX "GithubRepository_installationId_idx" ON "public"."GithubRepository"("installationId"); + +CREATE UNIQUE INDEX "GithubRepository_installationId_githubId_key" ON "public"."GithubRepository"("installationId", "githubId"); + +ALTER TABLE "public"."GithubAppInstallation" ADD CONSTRAINT "GithubAppInstallation_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "public"."Organization"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +ALTER TABLE "public"."GithubRepository" ADD CONSTRAINT "GithubRepository_installationId_fkey" FOREIGN KEY ("installationId") REFERENCES "public"."GithubAppInstallation"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/internal-packages/database/prisma/migrations/migration_lock.toml b/internal-packages/database/prisma/migrations/migration_lock.toml index 99e4f20090..044d57cdb0 100644 --- a/internal-packages/database/prisma/migrations/migration_lock.toml +++ b/internal-packages/database/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) +# It should be added in your version-control system (e.g., Git) provider = "postgresql" diff --git a/internal-packages/database/prisma/schema.prisma b/internal-packages/database/prisma/schema.prisma index be10d47f71..edcc82f535 100644 --- a/internal-packages/database/prisma/schema.prisma +++ b/internal-packages/database/prisma/schema.prisma @@ -210,6 +210,7 @@ model Organization { workerGroups WorkerInstanceGroup[] workerInstances WorkerInstance[] executionSnapshots TaskRunExecutionSnapshot[] + githubAppInstallations GithubAppInstallation[] } model OrgMember { @@ -2238,3 +2239,52 @@ model TaskEventPartitioned { // Used for getting all logs for a run @@index([runId]) } + +enum GithubRepositorySelection { + ALL + SELECTED +} + +model GithubAppInstallation { + id String @id @default(cuid()) + + appInstallationId BigInt @unique + targetId BigInt + targetType String + accountHandle String + permissions Json? + repositorySelection GithubRepositorySelection + installedBy String? + + organization Organization @relation(fields: [organizationId], references: [id]) + organizationId String + + repositories GithubRepository[] + + deletedAt DateTime? + suspendedAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([organizationId]) +} + +model GithubRepository { + id String @id @default(cuid()) + + githubId BigInt + name String + fullName String + htmlUrl String + private Boolean + defaultBranch String + + installation GithubAppInstallation @relation(fields: [installationId], references: [id], onDelete: Cascade, onUpdate: Cascade) + installationId String + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([installationId, githubId]) + @@index([installationId]) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4ddc72986..a2c6c79547 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -587,6 +587,9 @@ importers: non.geist: specifier: ^1.0.2 version: 1.0.2 + octokit: + specifier: ^3.2.1 + version: 3.2.2 ohash: specifier: ^1.1.3 version: 1.1.3 @@ -8546,6 +8549,262 @@ packages: which: 3.0.1 dev: true + /@octokit/app@14.1.0: + resolution: {integrity: sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-app': 6.1.4 + '@octokit/auth-unauthenticated': 5.0.1 + '@octokit/core': 5.2.2 + '@octokit/oauth-app': 6.1.0 + '@octokit/plugin-paginate-rest': 9.2.2(@octokit/core@5.2.2) + '@octokit/types': 12.6.0 + '@octokit/webhooks': 12.3.2 + dev: false + + /@octokit/auth-app@6.1.4: + resolution: {integrity: sha512-QkXkSOHZK4dA5oUqY5Dk3S+5pN2s1igPjEASNQV8/vgJgW034fQWR16u7VsNOK/EljA00eyjYF5mWNxWKWhHRQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-oauth-app': 7.1.0 + '@octokit/auth-oauth-user': 4.1.0 + '@octokit/request': 8.4.1 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 + deprecation: 2.3.1 + lru-cache: /@wolfy1339/lru-cache@11.0.2-patch.1 + universal-github-app-jwt: 1.2.0 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/auth-oauth-app@7.1.0: + resolution: {integrity: sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-oauth-device': 6.1.0 + '@octokit/auth-oauth-user': 4.1.0 + '@octokit/request': 8.4.1 + '@octokit/types': 13.10.0 + '@types/btoa-lite': 1.0.2 + btoa-lite: 1.0.0 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/auth-oauth-device@6.1.0: + resolution: {integrity: sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/oauth-methods': 4.1.0 + '@octokit/request': 8.4.1 + '@octokit/types': 13.10.0 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/auth-oauth-user@4.1.0: + resolution: {integrity: sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-oauth-device': 6.1.0 + '@octokit/oauth-methods': 4.1.0 + '@octokit/request': 8.4.1 + '@octokit/types': 13.10.0 + btoa-lite: 1.0.0 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/auth-token@4.0.0: + resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + engines: {node: '>= 18'} + dev: false + + /@octokit/auth-unauthenticated@5.0.1: + resolution: {integrity: sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==} + engines: {node: '>= 18'} + dependencies: + '@octokit/request-error': 5.1.1 + '@octokit/types': 12.6.0 + dev: false + + /@octokit/core@5.2.2: + resolution: {integrity: sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-token': 4.0.0 + '@octokit/graphql': 7.1.1 + '@octokit/request': 8.4.1 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 + before-after-hook: 2.2.3 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/endpoint@9.0.6: + resolution: {integrity: sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/types': 13.10.0 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/graphql@7.1.1: + resolution: {integrity: sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==} + engines: {node: '>= 18'} + dependencies: + '@octokit/request': 8.4.1 + '@octokit/types': 13.10.0 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/oauth-app@6.1.0: + resolution: {integrity: sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-oauth-app': 7.1.0 + '@octokit/auth-oauth-user': 4.1.0 + '@octokit/auth-unauthenticated': 5.0.1 + '@octokit/core': 5.2.2 + '@octokit/oauth-authorization-url': 6.0.2 + '@octokit/oauth-methods': 4.1.0 + '@types/aws-lambda': 8.10.152 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/oauth-authorization-url@6.0.2: + resolution: {integrity: sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==} + engines: {node: '>= 18'} + dev: false + + /@octokit/oauth-methods@4.1.0: + resolution: {integrity: sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/oauth-authorization-url': 6.0.2 + '@octokit/request': 8.4.1 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 + btoa-lite: 1.0.0 + dev: false + + /@octokit/openapi-types@20.0.0: + resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} + dev: false + + /@octokit/openapi-types@24.2.0: + resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==} + dev: false + + /@octokit/plugin-paginate-graphql@4.0.1(@octokit/core@5.2.2): + resolution: {integrity: sha512-R8ZQNmrIKKpHWC6V2gum4x9LG2qF1RxRjo27gjQcG3j+vf2tLsEfE7I/wRWEPzYMaenr1M+qDAtNcwZve1ce1A==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=5' + dependencies: + '@octokit/core': 5.2.2 + dev: false + + /@octokit/plugin-paginate-rest@11.4.4-cjs.2(@octokit/core@5.2.2): + resolution: {integrity: sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + dependencies: + '@octokit/core': 5.2.2 + '@octokit/types': 13.10.0 + dev: false + + /@octokit/plugin-paginate-rest@9.2.2(@octokit/core@5.2.2): + resolution: {integrity: sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + dependencies: + '@octokit/core': 5.2.2 + '@octokit/types': 12.6.0 + dev: false + + /@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1(@octokit/core@5.2.2): + resolution: {integrity: sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': ^5 + dependencies: + '@octokit/core': 5.2.2 + '@octokit/types': 13.10.0 + dev: false + + /@octokit/plugin-retry@6.1.0(@octokit/core@5.2.2): + resolution: {integrity: sha512-WrO3bvq4E1Xh1r2mT9w6SDFg01gFmP81nIG77+p/MqW1JeXXgL++6umim3t6x0Zj5pZm3rXAN+0HEjmmdhIRig==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + dependencies: + '@octokit/core': 5.2.2 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 + bottleneck: 2.19.5 + dev: false + + /@octokit/plugin-throttling@8.2.0(@octokit/core@5.2.2): + resolution: {integrity: sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': ^5.0.0 + dependencies: + '@octokit/core': 5.2.2 + '@octokit/types': 12.6.0 + bottleneck: 2.19.5 + dev: false + + /@octokit/request-error@5.1.1: + resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==} + engines: {node: '>= 18'} + dependencies: + '@octokit/types': 13.10.0 + deprecation: 2.3.1 + once: 1.4.0 + dev: false + + /@octokit/request@8.4.1: + resolution: {integrity: sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/endpoint': 9.0.6 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/types@12.6.0: + resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} + dependencies: + '@octokit/openapi-types': 20.0.0 + dev: false + + /@octokit/types@13.10.0: + resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} + dependencies: + '@octokit/openapi-types': 24.2.0 + dev: false + + /@octokit/webhooks-methods@4.1.0: + resolution: {integrity: sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==} + engines: {node: '>= 18'} + dev: false + + /@octokit/webhooks-types@7.6.1: + resolution: {integrity: sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw==} + dev: false + + /@octokit/webhooks@12.3.2: + resolution: {integrity: sha512-exj1MzVXoP7xnAcAB3jZ97pTvVPkQF9y6GA/dvYC47HV7vLv+24XRS6b/v/XnyikpEuvMhugEXdGtAlU086WkQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/request-error': 5.1.1 + '@octokit/webhooks-methods': 4.1.0 + '@octokit/webhooks-types': 7.6.1 + aggregate-error: 3.1.0 + dev: false + /@one-ini/wasm@0.1.1: resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} dev: false @@ -17538,6 +17797,10 @@ packages: resolution: {integrity: sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==} dev: true + /@types/aws-lambda@8.10.152: + resolution: {integrity: sha512-soT/c2gYBnT5ygwiHPmd9a1bftj462NWVk2tKCc1PYHSIacB2UwbTS2zYG4jzag1mRDuzg/OjtxQjQ2NKRB6Rw==} + dev: false + /@types/bcryptjs@2.4.2: resolution: {integrity: sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==} dev: true @@ -17549,6 +17812,10 @@ packages: '@types/node': 20.14.14 dev: true + /@types/btoa-lite@1.0.2: + resolution: {integrity: sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==} + dev: false + /@types/bun@1.1.6: resolution: {integrity: sha512-uJgKjTdX0GkWEHZzQzFsJkWp5+43ZS7HC8sZPFnOwnSo1AsNl2q9o2bFeS23disNDqbggEgyFkKCHl/w8iZsMA==} dependencies: @@ -17808,6 +18075,13 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/jsonwebtoken@9.0.10: + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + dependencies: + '@types/ms': 0.7.31 + '@types/node': 20.14.14 + dev: false + /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: @@ -17926,7 +18200,6 @@ packages: resolution: {integrity: sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==} dependencies: undici-types: 6.20.0 - dev: false /@types/nodemailer@6.4.17: resolution: {integrity: sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==} @@ -19012,6 +19285,11 @@ packages: xstate: 5.18.1 dev: false + /@wolfy1339/lru-cache@11.0.2-patch.1: + resolution: {integrity: sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==} + engines: {node: 18 >=18.20 || 20 || >=22} + dev: false + /@xobotyi/scrollbar-width@1.9.5: resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} dev: false @@ -19181,7 +19459,6 @@ packages: dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 - dev: true /aggregate-error@4.0.1: resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} @@ -19878,6 +20155,10 @@ packages: dependencies: tweetnacl: 0.14.5 + /before-after-hook@2.2.3: + resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} + dev: false + /better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -19958,6 +20239,10 @@ packages: - supports-color dev: false + /bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + dev: false + /bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} dev: false @@ -20023,6 +20308,10 @@ packages: update-browserslist-db: 1.1.3(browserslist@4.25.0) dev: true + /btoa-lite@1.0.0: + resolution: {integrity: sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==} + dev: false + /buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} dev: false @@ -20032,6 +20321,10 @@ packages: engines: {node: '>=8.0.0'} dev: true + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: false + /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -20453,7 +20746,6 @@ packages: /clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - dev: true /clean-stack@4.2.0: resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} @@ -21440,6 +21732,10 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + /deprecation@2.3.1: + resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + dev: false + /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -21702,6 +21998,12 @@ packages: safer-buffer: 2.1.2 dev: false + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /editorconfig@1.0.4: resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} engines: {node: '>=14'} @@ -21834,7 +22136,7 @@ packages: engines: {node: '>=10.13.0'} dependencies: graceful-fs: 4.2.11 - tapable: 2.2.1 + tapable: 2.2.2 /enquirer@2.3.6: resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} @@ -25124,7 +25426,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.14.14 + '@types/node': 22.13.9 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -25338,6 +25640,22 @@ packages: engines: {node: '>=0.10.0'} dev: false + /jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.2 + dev: false + /jsprim@1.4.2: resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} engines: {node: '>=0.6.0'} @@ -25361,6 +25679,21 @@ packages: engines: {node: '>=12.20'} dev: true + /jwa@1.4.2: + resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: false + + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + dependencies: + jwa: 1.4.2 + safe-buffer: 5.2.1 + dev: false + /keyv@3.1.0: resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} dependencies: @@ -25742,21 +26075,40 @@ packages: resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} dev: false + /lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + dev: false + /lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} dev: false + /lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + dev: false + /lodash.isfunction@3.0.9: resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} dev: false + /lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + dev: false + /lodash.isnil@4.0.0: resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} dev: false + /lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + dev: false + /lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - dev: true + + /lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: false /lodash.isundefined@3.0.1: resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} @@ -25769,6 +26121,10 @@ packages: resolution: {integrity: sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==} dev: false + /lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: false + /lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} dev: true @@ -27631,6 +27987,23 @@ packages: /obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + /octokit@3.2.2: + resolution: {integrity: sha512-7Abo3nADdja8l/aglU6Y3lpnHSfv0tw7gFPiqzry/yCU+2gTAX7R1roJ8hJrxIK+S1j+7iqRJXtmuHJ/UDsBhQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/app': 14.1.0 + '@octokit/core': 5.2.2 + '@octokit/oauth-app': 6.1.0 + '@octokit/plugin-paginate-graphql': 4.0.1(@octokit/core@5.2.2) + '@octokit/plugin-paginate-rest': 11.4.4-cjs.2(@octokit/core@5.2.2) + '@octokit/plugin-rest-endpoint-methods': 13.3.2-cjs.1(@octokit/core@5.2.2) + '@octokit/plugin-retry': 6.1.0(@octokit/core@5.2.2) + '@octokit/plugin-throttling': 8.2.0(@octokit/core@5.2.2) + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 + '@octokit/webhooks': 12.3.2 + dev: false + /ohash@1.1.3: resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==} dev: false @@ -32014,7 +32387,6 @@ packages: /tapable@2.2.2: resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} engines: {node: '>=6'} - dev: true /tar-fs@2.1.3: resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==} @@ -33000,7 +33372,6 @@ packages: /undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - dev: false /undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} @@ -33152,6 +33523,17 @@ packages: cookie: 0.6.0 dev: false + /universal-github-app-jwt@1.2.0: + resolution: {integrity: sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==} + dependencies: + '@types/jsonwebtoken': 9.0.10 + jsonwebtoken: 9.0.2 + dev: false + + /universal-user-agent@6.0.1: + resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + dev: false + /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'}