Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tidy-toys-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@trigger.dev/cli": patch
---

allow CLI dev to use injected environment variable
6 changes: 3 additions & 3 deletions packages/cli/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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`);
Expand Down
68 changes: 15 additions & 53 deletions packages/cli/src/utils/getTriggerApiDetails.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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`,
};
}
102 changes: 102 additions & 0 deletions packages/cli/src/utils/readEnvVariables.ts
Original file line number Diff line number Diff line change
@@ -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<EnvironmentVariables> {
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,
};
}