import type { AuthenticatedEnvironment } from "@trigger.dev/core/v3/auth/environment"; import { Prisma, type PrismaClient, type RuntimeEnvironmentType } from "@trigger.dev/database"; import { z } from "zod"; import { environmentFullTitle } from "~/components/environments/EnvironmentLabel"; import { $replica, $transaction, prisma, type PrismaReplicaClient } from "~/db.server"; import { env } from "~/env.server"; import { getSecretStore } from "~/services/secrets/secretStore.server"; import { deduplicateVariableArray } from "../deduplicateVariableArray.server"; import { removeBlacklistedVariables } from "../environmentVariableRules.server"; import { generateFriendlyId } from "../friendlyIdentifiers"; import { type CreateEnvironmentVariables, type CreateResult, type DeleteEnvironmentVariable, type DeleteEnvironmentVariableValue, type EditEnvironmentVariable, type EditEnvironmentVariableValue, type EnvironmentVariable, type EnvironmentVariableWithSecret, type ProjectEnvironmentVariable, type Repository, type Result, } from "./repository"; function secretKeyProjectPrefix(projectId: string) { return `environmentvariable:${projectId}:`; } function secretKeyEnvironmentPrefix(projectId: string, environmentId: string) { return `${secretKeyProjectPrefix(projectId)}${environmentId}:`; } function secretKey(projectId: string, environmentId: string, key: string) { return `${secretKeyEnvironmentPrefix(projectId, environmentId)}${key}`; } function parseSecretKey(key: string) { const parts = key.split(":"); return { projectId: parts[1], environmentId: parts[2], key: parts[3], }; } const SecretValue = z.object({ secret: z.string() }); export class EnvironmentVariablesRepository implements Repository { constructor( private prismaClient: PrismaClient = prisma, private replicaClient: PrismaReplicaClient = $replica ) {} async create(projectId: string, options: CreateEnvironmentVariables): Promise { const project = await this.prismaClient.project.findFirst({ where: { id: projectId, deletedAt: null, }, select: { environments: { select: { id: true, }, }, environmentVariables: { select: { key: true, values: { select: { environment: { select: { id: true, type: true }, }, }, }, }, }, }, }); if (!project) { return { success: false as const, error: "Project not found" }; } if (options.environmentIds.every((v) => !project.environments.some((e) => e.id === v))) { return { success: false as const, error: `Environment not found` }; } // Remove `TRIGGER_SECRET_KEY` or `TRIGGER_API_URL` let values = removeBlacklistedVariables(options.variables); const removedBlacklisted = values.length !== options.variables.length; //get rid of empty variables values = values.filter((v) => v.key.trim() !== "" && v.value.trim() !== ""); if (values.length === 0) { return { success: false as const, error: `You must set at least one valid variable.${ removedBlacklisted ? " All the variables submitted are not allowed." : "" }`, }; } //check if any of them exist in an environment we're setting if (!options.override) { const existingVariableKeys: { key: string; environments: RuntimeEnvironmentType[] }[] = []; for (const variable of values) { const existingVariable = project.environmentVariables.find((v) => v.key === variable.key); if ( existingVariable && existingVariable.values.some((v) => options.environmentIds.includes(v.environment.id)) ) { existingVariableKeys.push({ key: variable.key, environments: existingVariable.values .filter((v) => options.environmentIds.includes(v.environment.id)) .map((v) => v.environment.type), }); } } if (existingVariableKeys.length > 0) { return { success: false as const, error: `Some of the variables are already set for these environments. Set override to true to override them.`, variableErrors: existingVariableKeys.map((val) => ({ key: val.key, error: `Variable already set in ${val.environments .map((e) => environmentFullTitle({ type: e })) .join(", ")}.`, })), }; } } try { for (const variable of values) { const _result = await $transaction(this.prismaClient, "create env var", async (tx) => { const environmentVariable = await tx.environmentVariable.upsert({ where: { projectId_key: { key: variable.key, projectId, }, }, create: { key: variable.key, friendlyId: generateFriendlyId("envvar"), project: { connect: { id: projectId, }, }, }, update: {}, }); const secretStore = getSecretStore("DATABASE", { prismaClient: tx, }); // If parentEnvironmentId is provided and isSecret is not explicitly set, // look up if the parent has this variable marked as secret let inheritedIsSecret: boolean | undefined = undefined; if (options.isSecret === undefined && options.parentEnvironmentId) { const parentVariableValue = await tx.environmentVariableValue.findFirst({ where: { variableId: environmentVariable.id, environmentId: options.parentEnvironmentId, }, select: { isSecret: true, }, }); if (parentVariableValue?.isSecret) { inheritedIsSecret = true; } } const effectiveIsSecret = options.isSecret ?? inheritedIsSecret; //set the secret values and references for (const environmentId of options.environmentIds) { const key = secretKey(projectId, environmentId, variable.key); const existingValueRecord = await tx.environmentVariableValue.findFirst({ where: { variableId: environmentVariable.id, environmentId, }, }); // Check if value already exists and is the same, and no metadata change (e.g. isSecret toggle) const existingSecret = await secretStore.getSecret(SecretValue, key); const canSkip = existingSecret && existingSecret.secret === variable.value && existingValueRecord && (options.isSecret === undefined || existingValueRecord.isSecret === options.isSecret); if (canSkip) { continue; } //create the secret reference const secretReference = await tx.secretReference.upsert({ where: { key, }, create: { key, provider: "DATABASE", }, update: {}, }); if (existingValueRecord) { await tx.environmentVariableValue.update({ where: { id: existingValueRecord.id, }, data: { version: { increment: 1, }, ...(options.lastUpdatedBy ? { lastUpdatedBy: options.lastUpdatedBy } : {}), valueReferenceId: secretReference.id, ...(options.isSecret !== undefined ? { isSecret: options.isSecret, } : {}), }, }); } else { await tx.environmentVariableValue.create({ data: { variableId: environmentVariable.id, environmentId: environmentId, valueReferenceId: secretReference.id, isSecret: effectiveIsSecret, version: 1, lastUpdatedBy: options.lastUpdatedBy ? options.lastUpdatedBy : Prisma.JsonNull, }, }); } await secretStore.setSecret<{ secret: string }>(key, { secret: variable.value, }); } }); } return { success: true as const, }; } catch (error) { if (error instanceof Prisma.PrismaClientKnownRequestError) { // The error code for unique constraint violation in Prisma is P2002 if (error.code === "P2002") { return { success: false as const, error: `There was already an existing field`, }; } } return { success: false as const, error: error instanceof Error ? error.message : "Something went wrong", }; } } async edit(projectId: string, options: EditEnvironmentVariable): Promise { const project = await this.prismaClient.project.findFirst({ where: { id: projectId, deletedAt: null, }, select: { environments: { select: { id: true, }, }, }, }); if (!project) { return { success: false as const, error: "Project not found" }; } if (options.values.every((v) => !project.environments.some((e) => e.id === v.environmentId))) { return { success: false as const, error: `Environment not found` }; } //get rid of empty strings let values = options.values.filter((v) => v.value.trim() !== ""); //add in empty values for environments that don't have a value const environmentIds = project.environments.map((e) => e.id); if (!options.keepEmptyValues) { for (const environmentId of environmentIds) { if (!values.some((v) => v.environmentId === environmentId)) { values.push({ environmentId, value: "", }); } } } const environmentVariable = await this.prismaClient.environmentVariable.findFirst({ select: { id: true, key: true, }, where: { id: options.id, }, }); if (!environmentVariable) { return { success: false as const, error: "Environment variable not found" }; } try { await $transaction(this.prismaClient, "edit env var", async (tx) => { const secretStore = getSecretStore("DATABASE", { prismaClient: tx, }); //create the secret values and references for (const value of values) { const key = secretKey(projectId, value.environmentId, environmentVariable.key); const existingValue = await tx.environmentVariableValue.findFirst({ where: { variableId: environmentVariable.id, environmentId: value.environmentId, }, }); if (existingValue && existingValue.valueReferenceId) { if (value.value === "") { //delete the value await secretStore.deleteSecret(key); await tx.secretReference.delete({ where: { id: existingValue.valueReferenceId, }, }); await tx.environmentVariableValue.delete({ where: { variableId_environmentId: { variableId: environmentVariable.id, environmentId: value.environmentId, }, }, }); } else { await secretStore.setSecret<{ secret: string }>(key, { secret: value.value, }); await tx.environmentVariableValue.update({ where: { variableId_environmentId: { variableId: environmentVariable.id, environmentId: value.environmentId, }, }, data: { version: { increment: 1, }, lastUpdatedBy: options.lastUpdatedBy ? options.lastUpdatedBy : undefined, }, }); } continue; } //create the secret reference const secretReference = await tx.secretReference.create({ data: { key, provider: "DATABASE", }, }); const _variableValue = await tx.environmentVariableValue.create({ data: { variableId: environmentVariable.id, environmentId: value.environmentId, valueReferenceId: secretReference.id, version: 1, lastUpdatedBy: options.lastUpdatedBy ? options.lastUpdatedBy : Prisma.JsonNull, }, }); await secretStore.setSecret<{ secret: string }>(key, { secret: value.value, }); } }); return { success: true as const, }; } catch (error) { return { success: false as const, error: error instanceof Error ? error.message : "Something went wrong", }; } } async editValue(projectId: string, options: EditEnvironmentVariableValue): Promise { const project = await this.prismaClient.project.findFirst({ where: { id: projectId, deletedAt: null, }, select: { environments: { select: { id: true, }, }, }, }); if (!project) { return { success: false as const, error: "Project not found" }; } if (!project.environments.some((e) => e.id === options.environmentId)) { return { success: false as const, error: "Environment not found" }; } const environmentVariable = await this.prismaClient.environmentVariable.findFirst({ select: { id: true, key: true, values: { where: { environmentId: options.environmentId, }, select: { valueReferenceId: true, }, }, }, where: { id: options.id, }, }); if (!environmentVariable) { return { success: false as const, error: "Environment variable not found" }; } if (environmentVariable.values.length === 0) { return { success: false as const, error: "Environment variable value not found" }; } try { await $transaction(this.prismaClient, "edit env var value", async (tx) => { const secretStore = getSecretStore("DATABASE", { prismaClient: tx, }); const key = secretKey(projectId, options.environmentId, environmentVariable.key); await secretStore.setSecret<{ secret: string }>(key, { secret: options.value, }); await tx.environmentVariableValue.update({ where: { variableId_environmentId: { variableId: environmentVariable.id, environmentId: options.environmentId, }, }, data: { version: { increment: 1, }, lastUpdatedBy: options.lastUpdatedBy ? options.lastUpdatedBy : undefined, }, }); }); return { success: true as const, }; } catch (error) { return { success: false as const, error: error instanceof Error ? error.message : "Something went wrong", }; } } async getProject(projectId: string): Promise { const project = await this.prismaClient.project.findFirst({ where: { id: projectId, deletedAt: null, }, select: { environments: { select: { id: true, type: true, }, }, }, }); if (!project) { return []; } const secretStore = getSecretStore("DATABASE", { prismaClient: this.prismaClient, }); const secrets = await secretStore.getSecrets(SecretValue, secretKeyProjectPrefix(projectId)); const values = secrets.map((secret) => { const { projectId, environmentId, key } = parseSecretKey(secret.key); return { projectId, environmentId, key, value: secret.value.secret, }; }); //now group the values together by key and environment ID into ProjectEnvironmentVariable[] //and add the type of environment to the result const results: ProjectEnvironmentVariable[] = []; for (const value of values) { const environment = project.environments.find((e) => e.id === value.environmentId); if (!environment) { throw new Error("Environment not found"); } const existing = results.find((r) => r.key === value.key); if (existing) { existing.values.push({ value: value.value, environment: { id: value.environmentId, type: environment.type, }, }); } else { results.push({ key: value.key, values: [ { value: value.value, environment: { id: value.environmentId, type: environment.type, }, }, ], }); } } return results; } async getVariableValuesForKeys( projectId: string, items: Array<{ environmentId: string; key: string }> ): Promise> { if (items.length === 0) { return new Map(); } const uniqueItems = new Map(); for (const item of items) { uniqueItems.set(`${item.environmentId}:${item.key}`, item); } const secretStore = getSecretStore("DATABASE", { prismaClient: this.replicaClient, }); const storeKeys = Array.from(uniqueItems.values()).map((item) => secretKey(projectId, item.environmentId, item.key) ); const secrets = await secretStore.getSecretsByKeys(SecretValue, storeKeys); const secretsByStoreKey = new Map(secrets.map((secret) => [secret.key, secret.value.secret])); const values = new Map(); for (const item of uniqueItems.values()) { const storeKey = secretKey(projectId, item.environmentId, item.key); const value = secretsByStoreKey.get(storeKey); if (value !== undefined) { values.set(`${item.environmentId}:${item.key}`, value); } } return values; } async getEnvironmentWithRedactedSecrets( projectId: string, environmentId: string, parentEnvironmentId?: string ): Promise { const variables = await this.getEnvironment(projectId, environmentId, parentEnvironmentId); // Get the keys of all secret variables const secretValues = await this.replicaClient.environmentVariableValue.findMany({ where: { environmentId: parentEnvironmentId ? { in: [environmentId, parentEnvironmentId] } : environmentId, isSecret: true, }, select: { variable: { select: { key: true, }, }, }, }); const secretVarKeys = secretValues.map((r) => r.variable.key); // Filter out secret variables return variables.map((v) => { if (secretVarKeys.includes(v.key)) { return { key: v.key, value: "", isSecret: true, }; } return { key: v.key, value: v.value, isSecret: false }; }); } async getEnvironment( projectId: string, environmentId: string, parentEnvironmentId?: string ): Promise { const project = await this.prismaClient.project.findFirst({ where: { id: projectId, deletedAt: null, }, select: { environments: { select: { id: true, }, where: { id: environmentId, }, }, }, }); if (!project || project.environments.length === 0) { return []; } return this.getEnvironmentVariables(projectId, environmentId, parentEnvironmentId); } async #getSecretEnvironmentVariables( projectId: string, environmentId: string, parentEnvironmentId?: string ): Promise { const secretStore = getSecretStore("DATABASE", { prismaClient: this.prismaClient, }); const parentSecrets = parentEnvironmentId ? await secretStore.getSecrets( SecretValue, secretKeyEnvironmentPrefix(projectId, parentEnvironmentId) ) : []; const childSecrets = await secretStore.getSecrets( SecretValue, secretKeyEnvironmentPrefix(projectId, environmentId) ); // Merge the secrets, we want child ones to override parent ones const mergedSecrets = new Map(); for (const secret of parentSecrets) { const { key: parsedKey } = parseSecretKey(secret.key); mergedSecrets.set(parsedKey, secret.value.secret); } for (const secret of childSecrets) { const { key: parsedKey } = parseSecretKey(secret.key); mergedSecrets.set(parsedKey, secret.value.secret); } const merged = Array.from(mergedSecrets.entries()).map(([key, value]) => { return { key, value, }; }); return removeBlacklistedVariables(merged); } async getEnvironmentVariables( projectId: string, environmentId: string, parentEnvironmentId?: string ): Promise { return this.#getSecretEnvironmentVariables(projectId, environmentId, parentEnvironmentId); } async delete(projectId: string, options: DeleteEnvironmentVariable): Promise { const project = await this.prismaClient.project.findFirst({ where: { id: projectId, deletedAt: null, }, select: { environments: { select: { id: true, }, }, }, }); if (!project) { return { success: false as const, error: "Project not found" }; } const environmentVariable = await this.prismaClient.environmentVariable.findFirst({ select: { id: true, key: true, values: { select: { id: true, environmentId: true, valueReference: { select: { key: true, }, }, }, }, }, where: { id: options.id, }, }); if (!environmentVariable) { return { success: false as const, error: "Environment variable not found" }; } try { await $transaction(this.prismaClient, "delete env var", async (tx) => { await tx.environmentVariable.delete({ where: { id: options.id, }, }); const secretStore = getSecretStore("DATABASE", { prismaClient: tx, }); //delete the secret values and references for (const value of environmentVariable.values) { const key = secretKey(projectId, value.environmentId, environmentVariable.key); await secretStore.deleteSecret(key); if (value.valueReference) { await tx.secretReference.delete({ where: { key: value.valueReference.key, }, }); } } }); return { success: true as const, }; } catch (error) { return { success: false as const, error: error instanceof Error ? error.message : "Something went wrong", }; } } async deleteValue(projectId: string, options: DeleteEnvironmentVariableValue): Promise { const project = await this.prismaClient.project.findFirst({ where: { id: projectId, deletedAt: null, }, select: { environments: { select: { id: true, }, }, }, }); if (!project) { return { success: false as const, error: "Project not found" }; } const environmentVariable = await this.prismaClient.environmentVariable.findFirst({ select: { id: true, key: true, values: { select: { id: true, environmentId: true, valueReference: { select: { key: true, }, }, }, }, }, where: { id: options.id, }, }); if (!environmentVariable) { return { success: false as const, error: "Environment variable not found" }; } const value = environmentVariable.values.find((v) => v.environmentId === options.environmentId); if (!value) { return { success: false as const, error: "Environment variable value not found" }; } // If this is the last value, delete the whole variable if (environmentVariable.values.length === 1) { return this.delete(projectId, { id: options.id }); } try { await $transaction(this.prismaClient, "delete env var value", async (tx) => { const secretStore = getSecretStore("DATABASE", { prismaClient: tx, }); const key = secretKey(projectId, options.environmentId, environmentVariable.key); await secretStore.deleteSecret(key); if (value.valueReference) { await tx.secretReference.delete({ where: { key: value.valueReference.key, }, }); } await tx.environmentVariableValue.delete({ where: { id: value.id, }, }); }); return { success: true as const, }; } catch (error) { return { success: false as const, error: error instanceof Error ? error.message : "Something went wrong", }; } } } export const RuntimeEnvironmentForEnvRepoPayload = { select: { id: true, slug: true, type: true, projectId: true, apiKey: true, organizationId: true, branchName: true, builtInEnvironmentVariableOverrides: true, }, } as const; // Derived from the slim AuthenticatedEnvironment so a full AE satisfies // this type — the legacy Prisma payload had `builtInEnvironmentVariableOverrides` // as Prisma's JsonValue, which is a subtype of `unknown` in the slim // shape, causing assignability errors in the JWT/queue paths that pass // AE values straight through. Using Pick aligns them. export type RuntimeEnvironmentForEnvRepo = Pick< AuthenticatedEnvironment, | "id" | "slug" | "type" | "projectId" | "apiKey" | "organizationId" | "branchName" | "builtInEnvironmentVariableOverrides" >; export const environmentVariablesRepository = new EnvironmentVariablesRepository(); export async function resolveVariablesForEnvironment( runtimeEnvironment: RuntimeEnvironmentForEnvRepo, parentEnvironment?: RuntimeEnvironmentForEnvRepo ) { let projectSecrets = await environmentVariablesRepository.getEnvironmentVariables( runtimeEnvironment.projectId, runtimeEnvironment.id, parentEnvironment?.id ); projectSecrets = renameVariables(projectSecrets, { OTEL_RESOURCE_ATTRIBUTES: "CUSTOM_OTEL_RESOURCE_ATTRIBUTES", }); const overridableTriggerVariables = await resolveOverridableTriggerVariables(runtimeEnvironment); const builtInVariables = runtimeEnvironment.type === "DEVELOPMENT" ? await resolveBuiltInDevVariables(runtimeEnvironment) : await resolveBuiltInProdVariables(runtimeEnvironment, parentEnvironment); const overridableOtelVariables = runtimeEnvironment.type === "DEVELOPMENT" ? await resolveOverridableOtelDevVariables(runtimeEnvironment) : []; const result = deduplicateVariableArray([ ...overridableTriggerVariables, ...overridableOtelVariables, ...projectSecrets, ...builtInVariables, ]); return result; } function renameVariables(variables: EnvironmentVariable[], renameMap: Record) { return variables.map((variable) => { return { ...variable, key: renameMap[variable.key] ?? variable.key, }; }); } async function resolveOverridableTriggerVariables( runtimeEnvironment: RuntimeEnvironmentForEnvRepo ) { let result: Array = [ { key: "TRIGGER_REALTIME_STREAM_VERSION", value: env.REALTIME_STREAM_VERSION, }, ]; return result; } async function resolveBuiltInDevVariables(runtimeEnvironment: RuntimeEnvironmentForEnvRepo) { let result: Array = [ { key: "TRIGGER_OTEL_EXPORTER_OTLP_ENDPOINT", value: env.DEV_OTEL_EXPORTER_OTLP_ENDPOINT ?? `${env.APP_ORIGIN.replace(/\/$/, "")}/otel`, }, { key: "TRIGGER_API_URL", value: env.API_ORIGIN ?? env.APP_ORIGIN, }, { key: "TRIGGER_STREAM_URL", value: env.STREAM_ORIGIN ?? env.API_ORIGIN ?? env.APP_ORIGIN, }, ]; if (env.DEV_OTEL_METRICS_ENDPOINT) { result.push({ key: "TRIGGER_OTEL_METRICS_ENDPOINT", value: env.DEV_OTEL_METRICS_ENDPOINT, }); } if (env.DEV_OTEL_METRICS_EXPORT_INTERVAL_MILLIS) { result.push({ key: "TRIGGER_OTEL_METRICS_EXPORT_INTERVAL_MILLIS", value: env.DEV_OTEL_METRICS_EXPORT_INTERVAL_MILLIS, }); } if (env.DEV_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS) { result.push({ key: "TRIGGER_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS", value: env.DEV_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS, }); } if (env.DEV_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS) { result.push({ key: "TRIGGER_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS", value: env.DEV_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS, }); } if (env.DEV_OTEL_BATCH_PROCESSING_ENABLED === "1") { result = result.concat([ { key: "TRIGGER_OTEL_BATCH_PROCESSING_ENABLED", value: "1", }, { key: "TRIGGER_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE", value: env.DEV_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE, }, { key: "TRIGGER_OTEL_SPAN_SCHEDULED_DELAY_MILLIS", value: env.DEV_OTEL_SPAN_SCHEDULED_DELAY_MILLIS, }, { key: "TRIGGER_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS", value: env.DEV_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS, }, { key: "TRIGGER_OTEL_SPAN_MAX_QUEUE_SIZE", value: env.DEV_OTEL_SPAN_MAX_QUEUE_SIZE, }, { key: "TRIGGER_OTEL_LOG_MAX_EXPORT_BATCH_SIZE", value: env.DEV_OTEL_LOG_MAX_EXPORT_BATCH_SIZE, }, { key: "TRIGGER_OTEL_LOG_SCHEDULED_DELAY_MILLIS", value: env.DEV_OTEL_LOG_SCHEDULED_DELAY_MILLIS, }, { key: "TRIGGER_OTEL_LOG_EXPORT_TIMEOUT_MILLIS", value: env.DEV_OTEL_LOG_EXPORT_TIMEOUT_MILLIS, }, { key: "TRIGGER_OTEL_LOG_MAX_QUEUE_SIZE", value: env.DEV_OTEL_LOG_MAX_QUEUE_SIZE, }, { key: "OTEL_BATCH_PROCESSING_ENABLED", value: "1", }, { key: "OTEL_SPAN_MAX_EXPORT_BATCH_SIZE", value: env.DEV_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE, }, { key: "OTEL_SPAN_SCHEDULED_DELAY_MILLIS", value: env.DEV_OTEL_SPAN_SCHEDULED_DELAY_MILLIS, }, { key: "OTEL_SPAN_EXPORT_TIMEOUT_MILLIS", value: env.DEV_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS, }, { key: "OTEL_SPAN_MAX_QUEUE_SIZE", value: env.DEV_OTEL_SPAN_MAX_QUEUE_SIZE, }, { key: "OTEL_LOG_MAX_EXPORT_BATCH_SIZE", value: env.DEV_OTEL_LOG_MAX_EXPORT_BATCH_SIZE, }, { key: "OTEL_LOG_SCHEDULED_DELAY_MILLIS", value: env.DEV_OTEL_LOG_SCHEDULED_DELAY_MILLIS, }, { key: "OTEL_LOG_EXPORT_TIMEOUT_MILLIS", value: env.DEV_OTEL_LOG_EXPORT_TIMEOUT_MILLIS, }, { key: "OTEL_LOG_MAX_QUEUE_SIZE", value: env.DEV_OTEL_LOG_MAX_QUEUE_SIZE, }, ]); } // Dev branches set branchName too, so carry it to the task via the same // TRIGGER_PREVIEW_BRANCH var the prod path uses. if (runtimeEnvironment.branchName) { result = result.concat([ { key: "TRIGGER_PREVIEW_BRANCH", value: runtimeEnvironment.branchName, }, ]); } const commonVariables = await resolveCommonBuiltInVariables(runtimeEnvironment); return [...result, ...commonVariables]; } async function resolveOverridableOtelDevVariables( runtimeEnvironment: RuntimeEnvironmentForEnvRepo ) { let result: Array = [ { key: "OTEL_EXPORTER_OTLP_ENDPOINT", value: env.DEV_OTEL_EXPORTER_OTLP_ENDPOINT ?? `${env.APP_ORIGIN.replace(/\/$/, "")}/otel`, }, ]; return result; } async function resolveBuiltInProdVariables( runtimeEnvironment: RuntimeEnvironmentForEnvRepo, parentEnvironment?: RuntimeEnvironmentForEnvRepo ) { let result: Array = [ { key: "TRIGGER_SECRET_KEY", value: parentEnvironment?.apiKey ?? runtimeEnvironment.apiKey, }, { key: "TRIGGER_API_URL", value: env.API_ORIGIN ?? env.APP_ORIGIN, }, { key: "TRIGGER_STREAM_URL", value: env.STREAM_ORIGIN ?? env.API_ORIGIN ?? env.APP_ORIGIN, }, { key: "TRIGGER_RUNTIME_WAIT_THRESHOLD_IN_MS", value: String(env.CHECKPOINT_THRESHOLD_IN_MS), }, { key: "TRIGGER_ORG_ID", value: runtimeEnvironment.organizationId, }, ]; if (runtimeEnvironment.branchName) { result = result.concat([ { key: "TRIGGER_PREVIEW_BRANCH", value: runtimeEnvironment.branchName, }, ]); } if (env.PROD_OTEL_METRICS_EXPORT_INTERVAL_MILLIS) { result.push({ key: "TRIGGER_OTEL_METRICS_EXPORT_INTERVAL_MILLIS", value: env.PROD_OTEL_METRICS_EXPORT_INTERVAL_MILLIS, }); } if (env.PROD_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS) { result.push({ key: "TRIGGER_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS", value: env.PROD_OTEL_METRICS_EXPORT_TIMEOUT_MILLIS, }); } if (env.PROD_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS) { result.push({ key: "TRIGGER_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS", value: env.PROD_OTEL_METRICS_COLLECTION_INTERVAL_MILLIS, }); } if (env.PROD_OTEL_BATCH_PROCESSING_ENABLED === "1") { result = result.concat([ { key: "TRIGGER_OTEL_BATCH_PROCESSING_ENABLED", value: "1", }, { key: "TRIGGER_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE", value: env.PROD_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE, }, { key: "TRIGGER_OTEL_SPAN_SCHEDULED_DELAY_MILLIS", value: env.PROD_OTEL_SPAN_SCHEDULED_DELAY_MILLIS, }, { key: "TRIGGER_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS", value: env.PROD_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS, }, { key: "TRIGGER_OTEL_SPAN_MAX_QUEUE_SIZE", value: env.PROD_OTEL_SPAN_MAX_QUEUE_SIZE, }, { key: "TRIGGER_OTEL_LOG_MAX_EXPORT_BATCH_SIZE", value: env.PROD_OTEL_LOG_MAX_EXPORT_BATCH_SIZE, }, { key: "TRIGGER_OTEL_LOG_SCHEDULED_DELAY_MILLIS", value: env.PROD_OTEL_LOG_SCHEDULED_DELAY_MILLIS, }, { key: "TRIGGER_OTEL_LOG_EXPORT_TIMEOUT_MILLIS", value: env.PROD_OTEL_LOG_EXPORT_TIMEOUT_MILLIS, }, { key: "TRIGGER_OTEL_LOG_MAX_QUEUE_SIZE", value: env.PROD_OTEL_LOG_MAX_QUEUE_SIZE, }, { key: "OTEL_BATCH_PROCESSING_ENABLED", value: "1", }, { key: "OTEL_SPAN_MAX_EXPORT_BATCH_SIZE", value: env.PROD_OTEL_SPAN_MAX_EXPORT_BATCH_SIZE, }, { key: "OTEL_SPAN_SCHEDULED_DELAY_MILLIS", value: env.PROD_OTEL_SPAN_SCHEDULED_DELAY_MILLIS, }, { key: "OTEL_SPAN_EXPORT_TIMEOUT_MILLIS", value: env.PROD_OTEL_SPAN_EXPORT_TIMEOUT_MILLIS, }, { key: "OTEL_SPAN_MAX_QUEUE_SIZE", value: env.PROD_OTEL_SPAN_MAX_QUEUE_SIZE, }, { key: "OTEL_LOG_MAX_EXPORT_BATCH_SIZE", value: env.PROD_OTEL_LOG_MAX_EXPORT_BATCH_SIZE, }, { key: "OTEL_LOG_SCHEDULED_DELAY_MILLIS", value: env.PROD_OTEL_LOG_SCHEDULED_DELAY_MILLIS, }, { key: "OTEL_LOG_EXPORT_TIMEOUT_MILLIS", value: env.PROD_OTEL_LOG_EXPORT_TIMEOUT_MILLIS, }, { key: "OTEL_LOG_MAX_QUEUE_SIZE", value: env.PROD_OTEL_LOG_MAX_QUEUE_SIZE, }, ]); } if (env.PROD_USAGE_HEARTBEAT_INTERVAL_MS && env.USAGE_EVENT_URL) { result = result.concat([ { key: "USAGE_HEARTBEAT_INTERVAL_MS", value: String(env.PROD_USAGE_HEARTBEAT_INTERVAL_MS), }, { key: "USAGE_EVENT_URL", value: env.USAGE_EVENT_URL, }, ]); } if (env.PROD_TASK_HEARTBEAT_INTERVAL_MS) { result = result.concat([ { key: "HEARTBEAT_INTERVAL_MS", value: String(env.PROD_TASK_HEARTBEAT_INTERVAL_MS), }, ]); } const commonVariables = await resolveCommonBuiltInVariables(runtimeEnvironment); return [...result, ...commonVariables]; } async function resolveCommonBuiltInVariables( runtimeEnvironment: RuntimeEnvironmentForEnvRepo ): Promise> { return [ { key: "TRIGGER_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", value: resolveBuiltInEnvironmentVariableOverrides( "TRIGGER_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", runtimeEnvironment, String(env.TRIGGER_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT) ), }, { key: "TRIGGER_OTEL_LOG_ATTRIBUTE_COUNT_LIMIT", value: resolveBuiltInEnvironmentVariableOverrides( "TRIGGER_OTEL_LOG_ATTRIBUTE_COUNT_LIMIT", runtimeEnvironment, String(env.TRIGGER_OTEL_LOG_ATTRIBUTE_COUNT_LIMIT) ), }, { key: "TRIGGER_OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", value: resolveBuiltInEnvironmentVariableOverrides( "TRIGGER_OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", runtimeEnvironment, String(env.TRIGGER_OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT) ), }, { key: "TRIGGER_OTEL_LOG_ATTRIBUTE_VALUE_LENGTH_LIMIT", value: resolveBuiltInEnvironmentVariableOverrides( "TRIGGER_OTEL_LOG_ATTRIBUTE_VALUE_LENGTH_LIMIT", runtimeEnvironment, String(env.TRIGGER_OTEL_LOG_ATTRIBUTE_VALUE_LENGTH_LIMIT) ), }, { key: "TRIGGER_OTEL_SPAN_EVENT_COUNT_LIMIT", value: resolveBuiltInEnvironmentVariableOverrides( "TRIGGER_OTEL_SPAN_EVENT_COUNT_LIMIT", runtimeEnvironment, String(env.TRIGGER_OTEL_SPAN_EVENT_COUNT_LIMIT) ), }, { key: "TRIGGER_OTEL_LINK_COUNT_LIMIT", value: resolveBuiltInEnvironmentVariableOverrides( "TRIGGER_OTEL_LINK_COUNT_LIMIT", runtimeEnvironment, String(env.TRIGGER_OTEL_LINK_COUNT_LIMIT) ), }, { key: "TRIGGER_OTEL_ATTRIBUTE_PER_LINK_COUNT_LIMIT", value: resolveBuiltInEnvironmentVariableOverrides( "TRIGGER_OTEL_ATTRIBUTE_PER_LINK_COUNT_LIMIT", runtimeEnvironment, String(env.TRIGGER_OTEL_ATTRIBUTE_PER_LINK_COUNT_LIMIT) ), }, { key: "TRIGGER_OTEL_ATTRIBUTE_PER_EVENT_COUNT_LIMIT", value: resolveBuiltInEnvironmentVariableOverrides( "TRIGGER_OTEL_ATTRIBUTE_PER_EVENT_COUNT_LIMIT", runtimeEnvironment, String(env.TRIGGER_OTEL_ATTRIBUTE_PER_EVENT_COUNT_LIMIT) ), }, { key: "TRIGGER_WAIT_UNTIL_TIMEOUT_MS", value: resolveBuiltInEnvironmentVariableOverrides( "TRIGGER_WAIT_UNTIL_TIMEOUT_MS", runtimeEnvironment, String(env.WAIT_UNTIL_TIMEOUT_MS) ), }, ]; } function resolveBuiltInEnvironmentVariableOverrides( key: string, runtimeEnvironment: RuntimeEnvironmentForEnvRepo, defaultValue: string ) { const overrides = runtimeEnvironment.builtInEnvironmentVariableOverrides; if (!overrides) { return defaultValue; } if ( !Array.isArray(overrides) && typeof overrides === "object" && overrides !== null && key in overrides ) { const value = (overrides as Record)[key]; if (typeof value === "string") { return value; } } return defaultValue; }