diff --git a/.changeset/good-ligers-sit.md b/.changeset/good-ligers-sit.md new file mode 100644 index 0000000000..1704e72546 --- /dev/null +++ b/.changeset/good-ligers-sit.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/build": patch +--- + +Added a Vercel sync env vars extension. Given a Vercel projectId and access token it will sync Vercel env vars when deploying Trigger.dev tasks. diff --git a/packages/build/src/extensions/core.ts b/packages/build/src/extensions/core.ts index 2adddbaa06..d9ed52b5a0 100644 --- a/packages/build/src/extensions/core.ts +++ b/packages/build/src/extensions/core.ts @@ -3,3 +3,4 @@ export * from "./core/additionalPackages.js"; export * from "./core/syncEnvVars.js"; export * from "./core/aptGet.js"; export * from "./core/ffmpeg.js"; +export * from "./core/vercelSyncEnvVars.js"; diff --git a/packages/build/src/extensions/core/vercelSyncEnvVars.ts b/packages/build/src/extensions/core/vercelSyncEnvVars.ts new file mode 100644 index 0000000000..c6c6f3b11b --- /dev/null +++ b/packages/build/src/extensions/core/vercelSyncEnvVars.ts @@ -0,0 +1,83 @@ +import { BuildExtension } from "@trigger.dev/core/v3/build"; +import { syncEnvVars } from "../core.js"; + +export function syncVercelEnvVars( + options?: { projectId?: string; vercelAccessToken?: string }, +): BuildExtension { + const sync = syncEnvVars(async (ctx) => { + const projectId = options?.projectId ?? process.env.VERCEL_PROJECT_ID ?? + ctx.env.VERCEL_PROJECT_ID; + const vercelAccessToken = options?.vercelAccessToken ?? + process.env.VERCEL_ACCESS_TOKEN ?? + ctx.env.VERCEL_ACCESS_TOKEN; + + if (!projectId) { + throw new Error( + "vercelSyncEnvVars: you did not pass in a projectId or set the VERCEL_PROJECT_ID env var.", + ); + } + + if (!vercelAccessToken) { + throw new Error( + "vercelSyncEnvVars: you did not pass in a vercelAccessToken or set the VERCEL_ACCESS_TOKEN env var.", + ); + } + + const environmentMap = { + prod: "production", + staging: "preview", + dev: "development", + } as const; + + const vercelEnvironment = + environmentMap[ctx.environment as keyof typeof environmentMap]; + + if (!vercelEnvironment) { + throw new Error( + `Invalid environment '${ctx.environment}'. Expected 'prod', 'staging', or 'dev'.`, + ); + } + const vercelApiUrl = + `https://api.vercel.com/v8/projects/${projectId}/env?decrypt=true`; + + try { + const response = await fetch(vercelApiUrl, { + headers: { + Authorization: `Bearer ${vercelAccessToken}`, + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + const filteredEnvs = data.envs + .filter( + (env: { type: string; value: string; target: string[] }) => + env.value && + env.target.includes(vercelEnvironment), + ) + .map((env: { key: string; value: string }) => ({ + name: env.key, + value: env.value, + })); + + return filteredEnvs; + } catch (error) { + console.error( + "Error fetching or processing Vercel environment variables:", + error, + ); + throw error; // Re-throw the error to be handled by the caller + } + }); + + return { + name: "SyncVercelEnvVarsExtension", + async onBuildComplete(context, manifest) { + await sync.onBuildComplete?.(context, manifest); + }, + }; +}