-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsync.ts
More file actions
109 lines (92 loc) · 3.43 KB
/
Copy pathsync.ts
File metadata and controls
109 lines (92 loc) · 3.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
* Shared Doppler sync logic used by BOTH ways of syncing in this example:
*
* 1. the `syncDopplerEnvVars` build extension (deploy-time pull), and
* 2. the `sync-doppler-env-vars` task (webhook-driven, runtime pull).
*
* Both call {@link fetchSyncableDopplerSecrets} so there is exactly one place
* that knows how to talk to Doppler and which keys are syncable.
*/
export type DopplerSyncSource = {
/**
* A Doppler token used to read secrets. A read-only
* [service token](https://docs.doppler.com/docs/service-tokens) is recommended.
* Defaults to `process.env.DOPPLER_TOKEN`.
*/
dopplerToken?: string;
/**
* The Doppler project slug. Defaults to `process.env.DOPPLER_PROJECT`. Optional
* when using a service token already scoped to a single project + config.
*/
project?: string;
/**
* The Doppler config to read from. Overrides {@link configForEnvironment}.
*/
config?: string;
/**
* Map the Trigger.dev environment (`prod`, `staging`, `preview`, `dev`) to the
* Doppler config to read for that environment. Defaults to
* {@link defaultDopplerConfigForEnvironment}.
*/
configForEnvironment?: (environment: string) => string | undefined;
/**
* The Doppler API base URL. Defaults to `https://api.doppler.com`.
*/
apiUrl?: string;
};
/** Reserved metadata keys Doppler injects into every download; never synced. */
const DOPPLER_RESERVED_KEYS = new Set(["DOPPLER_PROJECT", "DOPPLER_CONFIG", "DOPPLER_ENVIRONMENT"]);
const DEFAULT_API_URL = "https://api.doppler.com";
/** Default mapping from a Trigger.dev environment to a Doppler config. */
export function defaultDopplerConfigForEnvironment(environment: string): string {
switch (environment) {
case "prod":
return "prd";
case "staging":
return "stg";
default:
return "dev";
}
}
/**
* Download every syncable secret from Doppler for the given Trigger.dev
* environment, with Doppler's reserved metadata keys removed. Returns a flat
* record of `{ KEY: value }`.
*/
export async function fetchSyncableDopplerSecrets(
environment: string,
source: DopplerSyncSource = {}
): Promise<Record<string, string>> {
const token = source.dopplerToken ?? process.env.DOPPLER_TOKEN;
if (!token) {
throw new Error(
"Doppler sync: no Doppler token found. Set the DOPPLER_TOKEN environment variable or pass `dopplerToken`."
);
}
const resolveConfig = source.configForEnvironment ?? defaultDopplerConfigForEnvironment;
const config = source.config ?? resolveConfig(environment);
const project = source.project ?? process.env.DOPPLER_PROJECT;
const url = new URL(`${source.apiUrl ?? DEFAULT_API_URL}/v3/configs/config/secrets/download`);
url.searchParams.set("format", "json");
if (project) url.searchParams.set("project", project);
if (config) url.searchParams.set("config", config);
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/json",
},
});
if (!response.ok) {
const body = await response.text().catch(() => "");
throw new Error(
`Doppler sync: Doppler API returned ${response.status} ${response.statusText}. ${body}`.trim()
);
}
const secrets = (await response.json()) as Record<string, string>;
const variables: Record<string, string> = {};
for (const [key, value] of Object.entries(secrets)) {
if (DOPPLER_RESERVED_KEYS.has(key)) continue;
variables[key] = value;
}
return variables;
}