diff --git a/packages/cli-v3/src/build/extensions.ts b/packages/cli-v3/src/build/extensions.ts index e03952282..750be7dc6 100644 --- a/packages/cli-v3/src/build/extensions.ts +++ b/packages/cli-v3/src/build/extensions.ts @@ -9,6 +9,7 @@ import { BuildManifest, BuildTarget } from "@trigger.dev/core/v3/schemas"; import * as esbuild from "esbuild"; import { logger } from "../utilities/logger.js"; import { resolveModule } from "./resolveModule.js"; +import { log, spinner } from "@clack/prompts"; export interface InternalBuildContext extends BuildContext { getLayers(): BuildLayer[]; @@ -98,6 +99,12 @@ export function createBuildContext( debug: (...args) => logger.debug(...args), log: (...args) => logger.log(...args), warn: (...args) => logger.warn(...args), + progress: (message) => log.message(message), + spinner: (message) => { + const $spinner = spinner(); + $spinner.start(message); + return $spinner; + }, }, }; } @@ -130,6 +137,8 @@ function applyLayerToManifest(layer: BuildLayer, manifest: BuildManifest): Build if (layer.deploy?.env) { manifest.deploy.env ??= {}; + manifest.deploy.sync ??= {}; + manifest.deploy.sync.env ??= {}; for (const [key, value] of Object.entries(layer.deploy.env)) { if (!value) { @@ -137,15 +146,11 @@ function applyLayerToManifest(layer: BuildLayer, manifest: BuildManifest): Build } if (layer.deploy.override || manifest.deploy.env[key] === undefined) { - let needsSyncing = manifest.deploy.needsSyncing; const existingValue = manifest.deploy.env[key]; if (existingValue !== value) { - needsSyncing = true; + manifest.deploy.sync.env[key] = value; } - - manifest.deploy.env[key] = value; - manifest.deploy.needsSyncing = needsSyncing; } } } diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index fd342ee6c..82e5aa7cf 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -1,15 +1,13 @@ -import { intro, log, outro } from "@clack/prompts"; -import { DEFAULT_RUNTIME, ResolvedConfig } from "@trigger.dev/core/v3/build"; -import { - BuildManifest, - InitializeDeploymentResponseBody, - TaskFile, -} from "@trigger.dev/core/v3/schemas"; +import { intro, outro } from "@clack/prompts"; import { CORE_VERSION } from "@trigger.dev/core/v3"; +import { DEFAULT_RUNTIME, ResolvedConfig } from "@trigger.dev/core/v3/build"; +import { BuildManifest, InitializeDeploymentResponseBody } from "@trigger.dev/core/v3/schemas"; import { Command, Option as CommandOption } from "commander"; +import { writeFile } from "node:fs/promises"; import { join, relative, resolve } from "node:path"; import { readPackageJSON, writePackageJSON } from "pkg-types"; import { z } from "zod"; +import { CliApiClient } from "../apiClient.js"; import { bundleWorker } from "../build/bundle.js"; import { createBuildContext, @@ -31,22 +29,7 @@ import { wrapCommandAction, } from "../cli/common.js"; import { loadConfig } from "../config.js"; -import { createTempDir, writeJSONFile } from "../utilities/fileSystem.js"; -import { printStandloneInitialBanner } from "../utilities/initialBanner.js"; -import { logger } from "../utilities/logger.js"; -import { getProjectClient } from "../utilities/session.js"; -import { getTmpDir } from "../utilities/tempDirectories.js"; -import { login } from "./login.js"; -import { updateTriggerPackages } from "./update.js"; -import { spinner } from "../utilities/windows.js"; -import { readFile, writeFile } from "node:fs/promises"; -import { VERSION } from "../version.js"; -import { resolveFileSources } from "../utilities/sourceFiles.js"; import { buildImage, generateContainerfile } from "../deploy/buildImage.js"; -import { buildManifestToJSON } from "../utilities/buildManifest.js"; -import { CliApiClient } from "../apiClient.js"; -import { chalkError, chalkWarning, cliLink } from "../utilities/cliOutput.js"; -import { docs, getInTouch } from "../utilities/links.js"; import { checkLogsForErrors, checkLogsForWarnings, @@ -54,6 +37,19 @@ import { printWarnings, saveLogs, } from "../deploy/logs.js"; +import { buildManifestToJSON } from "../utilities/buildManifest.js"; +import { chalkError, cliLink } from "../utilities/cliOutput.js"; +import { writeJSONFile } from "../utilities/fileSystem.js"; +import { printStandloneInitialBanner } from "../utilities/initialBanner.js"; +import { logger } from "../utilities/logger.js"; +import { getProjectClient } from "../utilities/session.js"; +import { resolveFileSources } from "../utilities/sourceFiles.js"; +import { getTmpDir } from "../utilities/tempDirectories.js"; +import { spinner } from "../utilities/windows.js"; +import { VERSION } from "../version.js"; +import { login } from "./login.js"; +import { updateTriggerPackages } from "./update.js"; +import { loadDotEnvVars, resolveDotEnvVars } from "../utilities/dotEnv.js"; const DeployCommandOptions = CommonCommandOptions.extend({ dryRun: z.boolean().default(false), @@ -210,6 +206,7 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { } const serverEnvVars = await projectClient.client.getEnvironmentVariables(resolvedConfig.project); + loadDotEnvVars(resolvedConfig.workingDir); const destination = getTmpDir(resolvedConfig.workingDir, "build", options.dryRun); const externalsExtension = createExternalsBuildExtension("deploy", resolvedConfig); @@ -293,18 +290,21 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { } if ( - buildManifest.deploy.env && - Object.keys(buildManifest.deploy.env).length > 0 && - buildManifest.deploy.needsSyncing + buildManifest.deploy.sync && + buildManifest.deploy.sync.env && + Object.keys(buildManifest.deploy.sync.env).length > 0 ) { + const numberOfEnvVars = Object.keys(buildManifest.deploy.sync.env).length; + const vars = numberOfEnvVars === 1 ? "var" : "vars"; + if (!options.skipSyncEnvVars) { const $spinner = spinner(); - $spinner.start("Syncing environment variables with the server"); + $spinner.start(`Syncing ${numberOfEnvVars} env ${vars} with the server`); const success = await syncEnvVarsWithServer( projectClient.client, resolvedConfig.project, options.env, - buildManifest.deploy.env + buildManifest.deploy.sync.env ); if (!success) { @@ -313,19 +313,19 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { deployment, { name: "SyncEnvVarsError", - message: "Failed to sync environment variables with the server", + message: `Failed to sync ${numberOfEnvVars} env ${vars} with the server`, }, "", $spinner ); } else { - $spinner.stop("Successfully synced environment variables with the server"); + $spinner.stop(`Successfully synced ${numberOfEnvVars} env ${vars} with the server`); } + } else { + logger.log( + "Skipping syncing env vars. The environment variables in your project have changed, but the --skip-sync-env-vars flag was provided." + ); } - - logger.log( - "Skipping syncing environment variables. The environment variables in your project have changed, but the --skip-sync-env-vars flag was provided." - ); } const version = deployment.version; diff --git a/packages/cli-v3/src/config.ts b/packages/cli-v3/src/config.ts index c762c5eab..21a561d81 100644 --- a/packages/cli-v3/src/config.ts +++ b/packages/cli-v3/src/config.ts @@ -1,4 +1,4 @@ -import { TriggerConfig } from "@trigger.dev/core/v3"; +import { ResolveEnvironmentVariablesFunction, TriggerConfig } from "@trigger.dev/core/v3"; import { DEFAULT_RUNTIME, ResolvedConfig } from "@trigger.dev/core/v3/build"; import * as c12 from "c12"; import { defu } from "defu"; @@ -8,7 +8,7 @@ import { basename, dirname, isAbsolute, join, relative } from "node:path"; import { findWorkspaceDir, resolveLockfile, resolvePackageJSON, resolveTSConfig } from "pkg-types"; import { generateCode, loadFile } from "./imports/magicast.js"; import { logger } from "./utilities/logger.js"; -import { additionalFiles, additionalPackages } from "@trigger.dev/core/v3/extensions"; +import { additionalFiles, additionalPackages, syncEnvVars } from "@trigger.dev/core/v3/extensions"; export type ResolveConfigOptions = { cwd?: string; @@ -99,6 +99,9 @@ export function configPlugin(resolvedConfig: ResolvedConfig): esbuild.Plugin | u options.build = {}; + // Remove export resolveEnvVars function as well + delete $mod.exports.resolveEnvVars; + const contents = generateCode($mod); logger.debug("trigger-config-strip.onLoad.contents", contents); @@ -247,7 +250,11 @@ function validateConfig(config: TriggerConfig, warn = true) { `The "resolveEnvVars" option is deprecated and will be removed. Use the "syncEnvVars" build extension instead. See https://trigger.dev/docs/trigger-config#syncEnvVars for more information.` ); - // + const resolveEnvVarsFn = config.resolveEnvVars as ResolveEnvironmentVariablesFunction; + + config.build ??= {}; + config.build.extensions ??= []; + config.build.extensions.push(adaptResolveEnvVarsToSyncEnvVarsExtension(resolveEnvVarsFn)); } if (config.runtime && config.runtime === "bun") { @@ -255,3 +262,20 @@ function validateConfig(config: TriggerConfig, warn = true) { logger.warn(`The "bun" runtime is currently experimental and may not work as expected.`); } } + +function adaptResolveEnvVarsToSyncEnvVarsExtension( + resolveEnvVarsFn: ResolveEnvironmentVariablesFunction +) { + return syncEnvVars( + async (ctx) => { + const resolveEnvVarsResult = await resolveEnvVarsFn(ctx); + + if (!resolveEnvVarsResult) { + return; + } + + return resolveEnvVarsResult.variables; + }, + { override: true } + ); +} diff --git a/packages/cli-v3/src/utilities/dotEnv.ts b/packages/cli-v3/src/utilities/dotEnv.ts index 0caa7e0de..616908c9c 100644 --- a/packages/cli-v3/src/utilities/dotEnv.ts +++ b/packages/cli-v3/src/utilities/dotEnv.ts @@ -19,3 +19,11 @@ export function resolveDotEnvVars(cwd?: string) { return result; } + +export function loadDotEnvVars(cwd?: string) { + dotenv.config({ + path: [".env", ".env.local", ".env.development.local"].map((p) => + resolve(cwd ?? process.cwd(), p) + ), + }); +} diff --git a/packages/core/src/v3/build/extensions.ts b/packages/core/src/v3/build/extensions.ts index 1c4908277..6f1b223a2 100644 --- a/packages/core/src/v3/build/extensions.ts +++ b/packages/core/src/v3/build/extensions.ts @@ -24,10 +24,17 @@ export interface BuildExtension { ) => Promise | undefined | void; } +export interface BuildSpinner { + stop: (message: string, code?: number) => void; + message: (message: string) => void; +} + export interface BuildLogger { debug: (...args: unknown[]) => void; log: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; + progress: (message: string) => void; + spinner: (message: string) => BuildSpinner; } export interface BuildContext { diff --git a/packages/core/src/v3/extensions/index.ts b/packages/core/src/v3/extensions/index.ts index 0fb48ab2f..570aa844a 100644 --- a/packages/core/src/v3/extensions/index.ts +++ b/packages/core/src/v3/extensions/index.ts @@ -2,3 +2,4 @@ export * from "./emitDecoratorMetadata.js"; export * from "./additionalFiles.js"; export * from "./additionalPackages.js"; export * from "./prisma.js"; +export * from "./syncEnvVars.js"; diff --git a/packages/core/src/v3/extensions/syncEnvVars.ts b/packages/core/src/v3/extensions/syncEnvVars.ts index bc0c5b8c0..50429ee81 100644 --- a/packages/core/src/v3/extensions/syncEnvVars.ts +++ b/packages/core/src/v3/extensions/syncEnvVars.ts @@ -1,4 +1,4 @@ -import { BuildExtension } from "../build/extensions.js"; +import { BuildContext, BuildExtension } from "../build/extensions.js"; export type SyncEnvVarsBody = Record | Array<{ name: string; value: string }>; @@ -77,14 +77,18 @@ export function syncEnvVars(fn: SyncEnvVarsFunction, options?: SyncEnvVarsOption return; } + const $spinner = context.logger.spinner("Invoking syncEnvVars callback"); + const result = await callSyncEnvVarsFn( fn, manifest.deploy.env ?? {}, manifest.environment, - context.config.project + context ); if (!result) { + $spinner.stop("No env vars detected"); + return; } @@ -100,6 +104,18 @@ export function syncEnvVars(fn: SyncEnvVarsFunction, options?: SyncEnvVarsOption {} as Record ); + const numberOfEnvVars = Object.keys(env).length; + + if (numberOfEnvVars === 0) { + $spinner.stop("No env vars detected"); + + return; + } else if (numberOfEnvVars === 1) { + $spinner.stop(`Found 1 env var`); + } else { + $spinner.stop(`Found ${numberOfEnvVars} env vars to sync`); + } + context.addLayer({ id: "sync-env-vars", deploy: { @@ -115,23 +131,26 @@ async function callSyncEnvVarsFn( syncEnvVarsFn: SyncEnvVarsFunction | undefined, env: Record, environment: string, - projectRef: string + context: BuildContext ): Promise | undefined> { if (syncEnvVarsFn && typeof syncEnvVarsFn === "function") { let resolvedEnvVars: Record = {}; + let result; - let result = syncEnvVarsFn({ - projectRef, - environment, - env, - }); + try { + result = await syncEnvVarsFn({ + projectRef: context.config.project, + environment, + env, + }); + } catch (error) { + context.logger.warn("Error calling syncEnvVars function", error); + } if (!result) { return; } - result = await result; - if (Array.isArray(result)) { for (const item of result) { if ( diff --git a/packages/core/src/v3/schemas/build.ts b/packages/core/src/v3/schemas/build.ts index 28a0c7592..133787c8f 100644 --- a/packages/core/src/v3/schemas/build.ts +++ b/packages/core/src/v3/schemas/build.ts @@ -44,7 +44,11 @@ export const BuildManifest = z.object({ }), deploy: z.object({ env: z.record(z.string()).optional(), - needsSyncing: z.boolean().optional(), + sync: z + .object({ + env: z.record(z.string()).optional(), + }) + .optional(), }), }); diff --git a/references/v3-catalog/trigger.config.ts b/references/v3-catalog/trigger.config.ts index a95234aad..e01be70e8 100644 --- a/references/v3-catalog/trigger.config.ts +++ b/references/v3-catalog/trigger.config.ts @@ -1,12 +1,34 @@ import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai"; import { defineConfig, ResolveEnvironmentVariablesFunction } from "@trigger.dev/sdk/v3"; import { emitDecoratorMetadata } from "@trigger.dev/sdk/v3/extensions"; +import { InfisicalClient } from "@infisical/sdk"; -export const resolveEnvVars: ResolveEnvironmentVariablesFunction = async ({ - projectRef, - env, - environment, -}) => {}; +export const resolveEnvVars: ResolveEnvironmentVariablesFunction = async (ctx) => { + if ( + process.env.INFISICAL_CLIENT_ID === undefined || + process.env.INFISICAL_CLIENT_SECRET === undefined || + process.env.INFISICAL_PROJECT_ID === undefined + ) { + return; + } + + const client = new InfisicalClient({ + clientId: process.env.INFISICAL_CLIENT_ID, + clientSecret: process.env.INFISICAL_CLIENT_SECRET, + }); + + const secrets = await client.listSecrets({ + environment: ctx.environment, + projectId: process.env.INFISICAL_PROJECT_ID, + }); + + return { + variables: secrets.map((secret) => ({ + name: secret.secretKey, + value: secret.secretValue, + })), + }; +}; export default defineConfig({ project: "yubjwjsfkxnylobaqvqz",