Skip to content

Commit be9b511

Browse files
hmacrericallam
andauthored
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 <[email protected]>
1 parent 63a65da commit be9b511

File tree

4 files changed

+125
-56
lines changed

4 files changed

+125
-56
lines changed

.changeset/tidy-toys-turn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/cli": patch
3+
---
4+
5+
allow CLI dev to use injected environment variable

packages/cli/src/commands/dev.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ export async function devCommand(path: string, anyOptions: any) {
7979
telemetryClient.dev.failed("missing_api_key", resolvedOptions);
8080
return;
8181
}
82-
const { apiUrl, envFile, apiKey } = apiDetails;
83-
logger.success(`✔️ [trigger.dev] Found API Key in ${envFile} file`);
82+
const { apiUrl, apiKey, apiKeySource } = apiDetails;
83+
logger.success(`✔️ [trigger.dev] Found API Key in ${apiKeySource}`);
8484

8585
//verify that the endpoint can be reached
8686
const verifiedEndpoint = await verifyEndpoint(resolvedOptions, endpointId, apiKey, framework);
@@ -118,7 +118,7 @@ export async function devCommand(path: string, anyOptions: any) {
118118
const refreshedEndpointId = await getEndpointIdFromPackageJson(resolvedPath, resolvedOptions);
119119

120120
// Read from env file to get the TRIGGER_API_KEY and TRIGGER_API_URL
121-
const apiDetails = await getTriggerApiDetails(resolvedPath, envFile);
121+
const apiDetails = await getTriggerApiDetails(resolvedPath, resolvedOptions.envFile);
122122

123123
if (!apiDetails) {
124124
connectingSpinner.fail(`[trigger.dev] Failed to connect: Missing API Key`);
Lines changed: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,23 @@
1-
import pathModule from "path";
2-
import { pathExists, readFile } from "./fileSystem";
31
import { logger } from "./logger";
4-
import dotenv from "dotenv";
52
import { CLOUD_API_URL } from "../consts";
63
import { checkApiKeyIsDevServer } from "./getApiKeyType";
7-
8-
export async function readEnvFilesWithBackups(
9-
path: string,
10-
envFile: string,
11-
backups: string[]
12-
): Promise<{ content: string; fileName: string } | undefined> {
13-
const envFilePath = pathModule.join(path, envFile);
14-
const envFileExists = await pathExists(envFilePath);
15-
16-
if (envFileExists) {
17-
const content = await readFile(envFilePath);
18-
19-
return { content, fileName: envFile };
20-
}
21-
22-
for (const backup of backups) {
23-
const backupPath = pathModule.join(path, backup);
24-
const backupExists = await pathExists(backupPath);
25-
26-
if (backupExists) {
27-
const content = await readFile(backupPath);
28-
29-
return { content, fileName: backup };
30-
}
31-
}
32-
33-
return;
34-
}
4+
import { readEnvVariables } from "./readEnvVariables";
355

366
export async function getTriggerApiDetails(path: string, envFile: string) {
37-
const resolvedEnvFile = await readEnvFilesWithBackups(path, envFile, [
38-
".env",
39-
".env.local",
40-
".env.development.local",
41-
]);
42-
43-
if (!resolvedEnvFile) {
44-
logger.error(`You must add TRIGGER_API_KEY to your ${envFile} file.`);
45-
return;
46-
}
47-
48-
const parsedEnvFile = dotenv.parse(resolvedEnvFile.content);
49-
50-
if (!parsedEnvFile) {
51-
logger.error(`You must add TRIGGER_API_KEY to your ${envFile} file.`);
52-
return;
53-
}
7+
const envVarsToRead = ["TRIGGER_API_KEY", "TRIGGER_API_URL"];
8+
const resolvedEnvVars = await readEnvVariables(path, envFile, envVarsToRead);
549

55-
const apiKey = parsedEnvFile.TRIGGER_API_KEY;
56-
const apiUrl = parsedEnvFile.TRIGGER_API_URL;
10+
const apiKey = resolvedEnvVars.TRIGGER_API_KEY;
11+
const apiUrl = resolvedEnvVars.TRIGGER_API_URL;
5712

5813
if (!apiKey) {
59-
logger.error(`You must add TRIGGER_API_KEY to your ${envFile} file.`);
14+
logger.error(
15+
`You must add TRIGGER_API_KEY to your ${envFile} file or set as runtime environment variable.`
16+
);
6017
return;
6118
}
6219

63-
const result = checkApiKeyIsDevServer(apiKey);
20+
const result = checkApiKeyIsDevServer(apiKey.value);
6421

6522
if (!result.success) {
6623
if (result.type) {
@@ -75,5 +32,10 @@ export async function getTriggerApiDetails(path: string, envFile: string) {
7532
return;
7633
}
7734

78-
return { apiKey, apiUrl: apiUrl ?? CLOUD_API_URL, envFile: resolvedEnvFile.fileName };
35+
return {
36+
apiKey: apiKey.value,
37+
apiUrl: apiUrl?.value ?? CLOUD_API_URL,
38+
apiKeySource:
39+
apiKey.source.type === "runtime" ? "process runtime" : `${apiKey.source.name} file`,
40+
};
7941
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import pathModule from "path";
2+
import { pathExists, readFile } from "./fileSystem";
3+
import dotenv from "dotenv";
4+
5+
const ENV_FILES_FALLBACK = [".env", ".env.local", ".env.development.local"];
6+
7+
export type EnvVarSourceRuntime = {
8+
type: "runtime";
9+
};
10+
11+
export type EnvVarSourceFile = {
12+
type: "file";
13+
name: string;
14+
};
15+
16+
export type EnvVarSource = EnvVarSourceRuntime | EnvVarSourceFile;
17+
18+
export type EnvironmentVariable = {
19+
value: string;
20+
source: EnvVarSource;
21+
};
22+
23+
export type EnvironmentVariables = {
24+
[name: string]: EnvironmentVariable | undefined;
25+
};
26+
27+
// Reads `varsToRead` from `process.env` and `envFile` (with fallbacks).
28+
// `process.env` takes precedence over the `envFile`.
29+
export async function readEnvVariables(
30+
path: string,
31+
envFile: string,
32+
varsToRead: string[]
33+
): Promise<EnvironmentVariables> {
34+
const resolvedEnvFile = await readEnvFilesWithBackups(path, envFile);
35+
const parsedEnvFile = resolvedEnvFile
36+
? { output: dotenv.parse(resolvedEnvFile.content), filename: resolvedEnvFile.fileName }
37+
: {};
38+
39+
return Object.fromEntries(
40+
varsToRead.map((envVar) => [
41+
envVar,
42+
readFromRuntime(envVar) ?? readFromFile(envVar, parsedEnvFile),
43+
])
44+
);
45+
}
46+
47+
async function readEnvFilesWithBackups(
48+
path: string,
49+
envFile: string
50+
): Promise<{ content: string; fileName: string } | undefined> {
51+
const envFilePath = pathModule.join(path, envFile);
52+
const envFileExists = await pathExists(envFilePath);
53+
54+
if (envFileExists) {
55+
const content = await readFile(envFilePath);
56+
57+
return { content, fileName: envFile };
58+
}
59+
60+
for (const fallBack of ENV_FILES_FALLBACK) {
61+
const fallbackPath = pathModule.join(path, fallBack);
62+
const fallbackExists = await pathExists(fallbackPath);
63+
64+
if (fallbackExists) {
65+
const content = await readFile(fallbackPath);
66+
67+
return { content, fileName: fallBack };
68+
}
69+
}
70+
71+
return;
72+
}
73+
74+
function readFromRuntime(envVar: string): EnvironmentVariable | undefined {
75+
const val = process.env[envVar];
76+
if (!val) {
77+
return;
78+
}
79+
return {
80+
value: val,
81+
source: {
82+
type: "runtime",
83+
} as EnvVarSourceRuntime,
84+
};
85+
}
86+
87+
function readFromFile(
88+
envVar: string,
89+
parsedEnvFile: { output?: dotenv.DotenvParseOutput; filename?: string }
90+
): EnvironmentVariable | undefined {
91+
const val = parsedEnvFile.output ? parsedEnvFile.output[envVar] : undefined;
92+
if (!val) {
93+
return;
94+
}
95+
return {
96+
value: val,
97+
source: {
98+
type: "file",
99+
name: parsedEnvFile.filename,
100+
} as EnvVarSourceFile,
101+
};
102+
}

0 commit comments

Comments
 (0)