From be9b51131db181e871354dcbc28b3928bb4d7b35 Mon Sep 17 00:00:00 2001 From: Hemachandar <132386067+hmacr@users.noreply.github.com> Date: Fri, 6 Oct 2023 18:48:58 +0530 Subject: [PATCH] feat: allow CLI dev to use injected environment variables (#532) * feat: allow CLI dev to use injected environment variables * Remove console.log --------- Co-authored-by: Eric Allam --- .changeset/tidy-toys-turn.md | 5 + packages/cli/src/commands/dev.ts | 6 +- .../cli/src/utils/getTriggerApiDetails.ts | 68 +++--------- packages/cli/src/utils/readEnvVariables.ts | 102 ++++++++++++++++++ 4 files changed, 125 insertions(+), 56 deletions(-) create mode 100644 .changeset/tidy-toys-turn.md create mode 100644 packages/cli/src/utils/readEnvVariables.ts diff --git a/.changeset/tidy-toys-turn.md b/.changeset/tidy-toys-turn.md new file mode 100644 index 0000000000..2f9a60e276 --- /dev/null +++ b/.changeset/tidy-toys-turn.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/cli": patch +--- + +allow CLI dev to use injected environment variable diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index 517ea99087..c6b1a20ad8 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -79,8 +79,8 @@ export async function devCommand(path: string, anyOptions: any) { telemetryClient.dev.failed("missing_api_key", resolvedOptions); return; } - const { apiUrl, envFile, apiKey } = apiDetails; - logger.success(`✔️ [trigger.dev] Found API Key in ${envFile} file`); + const { apiUrl, apiKey, apiKeySource } = apiDetails; + logger.success(`✔️ [trigger.dev] Found API Key in ${apiKeySource}`); //verify that the endpoint can be reached const verifiedEndpoint = await verifyEndpoint(resolvedOptions, endpointId, apiKey, framework); @@ -118,7 +118,7 @@ export async function devCommand(path: string, anyOptions: any) { const refreshedEndpointId = await getEndpointIdFromPackageJson(resolvedPath, resolvedOptions); // Read from env file to get the TRIGGER_API_KEY and TRIGGER_API_URL - const apiDetails = await getTriggerApiDetails(resolvedPath, envFile); + const apiDetails = await getTriggerApiDetails(resolvedPath, resolvedOptions.envFile); if (!apiDetails) { connectingSpinner.fail(`[trigger.dev] Failed to connect: Missing API Key`); diff --git a/packages/cli/src/utils/getTriggerApiDetails.ts b/packages/cli/src/utils/getTriggerApiDetails.ts index 7ce20105ce..274a9072b3 100644 --- a/packages/cli/src/utils/getTriggerApiDetails.ts +++ b/packages/cli/src/utils/getTriggerApiDetails.ts @@ -1,66 +1,23 @@ -import pathModule from "path"; -import { pathExists, readFile } from "./fileSystem"; import { logger } from "./logger"; -import dotenv from "dotenv"; import { CLOUD_API_URL } from "../consts"; import { checkApiKeyIsDevServer } from "./getApiKeyType"; - -export async function readEnvFilesWithBackups( - path: string, - envFile: string, - backups: string[] -): Promise<{ content: string; fileName: string } | undefined> { - const envFilePath = pathModule.join(path, envFile); - const envFileExists = await pathExists(envFilePath); - - if (envFileExists) { - const content = await readFile(envFilePath); - - return { content, fileName: envFile }; - } - - for (const backup of backups) { - const backupPath = pathModule.join(path, backup); - const backupExists = await pathExists(backupPath); - - if (backupExists) { - const content = await readFile(backupPath); - - return { content, fileName: backup }; - } - } - - return; -} +import { readEnvVariables } from "./readEnvVariables"; export async function getTriggerApiDetails(path: string, envFile: string) { - const resolvedEnvFile = await readEnvFilesWithBackups(path, envFile, [ - ".env", - ".env.local", - ".env.development.local", - ]); - - if (!resolvedEnvFile) { - logger.error(`You must add TRIGGER_API_KEY to your ${envFile} file.`); - return; - } - - const parsedEnvFile = dotenv.parse(resolvedEnvFile.content); - - if (!parsedEnvFile) { - logger.error(`You must add TRIGGER_API_KEY to your ${envFile} file.`); - return; - } + const envVarsToRead = ["TRIGGER_API_KEY", "TRIGGER_API_URL"]; + const resolvedEnvVars = await readEnvVariables(path, envFile, envVarsToRead); - const apiKey = parsedEnvFile.TRIGGER_API_KEY; - const apiUrl = parsedEnvFile.TRIGGER_API_URL; + const apiKey = resolvedEnvVars.TRIGGER_API_KEY; + const apiUrl = resolvedEnvVars.TRIGGER_API_URL; if (!apiKey) { - logger.error(`You must add TRIGGER_API_KEY to your ${envFile} file.`); + logger.error( + `You must add TRIGGER_API_KEY to your ${envFile} file or set as runtime environment variable.` + ); return; } - const result = checkApiKeyIsDevServer(apiKey); + const result = checkApiKeyIsDevServer(apiKey.value); if (!result.success) { if (result.type) { @@ -75,5 +32,10 @@ export async function getTriggerApiDetails(path: string, envFile: string) { return; } - return { apiKey, apiUrl: apiUrl ?? CLOUD_API_URL, envFile: resolvedEnvFile.fileName }; + return { + apiKey: apiKey.value, + apiUrl: apiUrl?.value ?? CLOUD_API_URL, + apiKeySource: + apiKey.source.type === "runtime" ? "process runtime" : `${apiKey.source.name} file`, + }; } diff --git a/packages/cli/src/utils/readEnvVariables.ts b/packages/cli/src/utils/readEnvVariables.ts new file mode 100644 index 0000000000..5ef8aa9b54 --- /dev/null +++ b/packages/cli/src/utils/readEnvVariables.ts @@ -0,0 +1,102 @@ +import pathModule from "path"; +import { pathExists, readFile } from "./fileSystem"; +import dotenv from "dotenv"; + +const ENV_FILES_FALLBACK = [".env", ".env.local", ".env.development.local"]; + +export type EnvVarSourceRuntime = { + type: "runtime"; +}; + +export type EnvVarSourceFile = { + type: "file"; + name: string; +}; + +export type EnvVarSource = EnvVarSourceRuntime | EnvVarSourceFile; + +export type EnvironmentVariable = { + value: string; + source: EnvVarSource; +}; + +export type EnvironmentVariables = { + [name: string]: EnvironmentVariable | undefined; +}; + +// Reads `varsToRead` from `process.env` and `envFile` (with fallbacks). +// `process.env` takes precedence over the `envFile`. +export async function readEnvVariables( + path: string, + envFile: string, + varsToRead: string[] +): Promise { + const resolvedEnvFile = await readEnvFilesWithBackups(path, envFile); + const parsedEnvFile = resolvedEnvFile + ? { output: dotenv.parse(resolvedEnvFile.content), filename: resolvedEnvFile.fileName } + : {}; + + return Object.fromEntries( + varsToRead.map((envVar) => [ + envVar, + readFromRuntime(envVar) ?? readFromFile(envVar, parsedEnvFile), + ]) + ); +} + +async function readEnvFilesWithBackups( + path: string, + envFile: string +): Promise<{ content: string; fileName: string } | undefined> { + const envFilePath = pathModule.join(path, envFile); + const envFileExists = await pathExists(envFilePath); + + if (envFileExists) { + const content = await readFile(envFilePath); + + return { content, fileName: envFile }; + } + + for (const fallBack of ENV_FILES_FALLBACK) { + const fallbackPath = pathModule.join(path, fallBack); + const fallbackExists = await pathExists(fallbackPath); + + if (fallbackExists) { + const content = await readFile(fallbackPath); + + return { content, fileName: fallBack }; + } + } + + return; +} + +function readFromRuntime(envVar: string): EnvironmentVariable | undefined { + const val = process.env[envVar]; + if (!val) { + return; + } + return { + value: val, + source: { + type: "runtime", + } as EnvVarSourceRuntime, + }; +} + +function readFromFile( + envVar: string, + parsedEnvFile: { output?: dotenv.DotenvParseOutput; filename?: string } +): EnvironmentVariable | undefined { + const val = parsedEnvFile.output ? parsedEnvFile.output[envVar] : undefined; + if (!val) { + return; + } + return { + value: val, + source: { + type: "file", + name: parsedEnvFile.filename, + } as EnvVarSourceFile, + }; +}