-
Notifications
You must be signed in to change notification settings - Fork 46
Expand file tree
/
Copy pathdotenv.ts
More file actions
147 lines (123 loc) · 3.84 KB
/
dotenv.ts
File metadata and controls
147 lines (123 loc) · 3.84 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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import { promises as fsp, existsSync } from "node:fs";
import { resolve } from "pathe";
import * as dotenv from "dotenv";
export interface DotenvOptions {
/**
* The project root directory (either absolute or relative to the current working directory).
*/
cwd: string;
/**
* What file to look in for environment variables (either absolute or relative
* to the current working directory). For example, `.env`.
*/
fileName?: string;
/**
* Whether to interpolate variables within .env.
*
* @example
* ```env
* BASE_DIR="/test"
* # resolves to "/test/further"
* ANOTHER_DIR="${BASE_DIR}/further"
* ```
*/
interpolate?: boolean;
/**
* An object describing environment variables (key, value pairs).
*/
// eslint-disable-next-line no-undef
env?: NodeJS.ProcessEnv;
}
export type Env = typeof process.env;
/**
* Load and interpolate environment variables into `process.env`.
* If you need more control (or access to the values), consider using `loadDotenv` instead
*
*/
export async function setupDotenv(options: DotenvOptions): Promise<Env> {
const targetEnvironment = options.env ?? process.env;
// Load env
const environment = await loadDotenv({
cwd: options.cwd,
fileName: options.fileName ?? ".env",
env: targetEnvironment,
interpolate: options.interpolate ?? true,
});
// Fill process.env
for (const key in environment) {
if (!key.startsWith("_") && targetEnvironment[key] === undefined) {
targetEnvironment[key] = environment[key];
}
}
return environment;
}
/** Load environment variables into an object. */
export async function loadDotenv(options: DotenvOptions): Promise<Env> {
const environment = Object.create(null);
const dotenvFile = resolve(options.cwd, options.fileName!);
if (existsSync(dotenvFile)) {
const parsed = dotenv.parse(await fsp.readFile(dotenvFile, "utf8"));
Object.assign(environment, parsed);
}
// Apply process.env
if (!options.env?._applied) {
Object.assign(environment, options.env);
environment._applied = true;
}
// Interpolate env
if (options.interpolate) {
interpolate(environment);
}
return environment;
}
// Based on https://github.com/motdotla/dotenv-expand
function interpolate(
target: Record<string, any>,
source: Record<string, any> = {},
parse = (v: any) => v,
) {
function getValue(key: string) {
// Source value 'wins' over target value
return source[key] === undefined ? target[key] : source[key];
}
function interpolate(value: unknown, parents: string[] = []): any {
if (typeof value !== "string") {
return value;
}
const matches: string[] = value.match(/(.?\${?(?:[\w:]+)?}?)/g) || [];
return parse(
// eslint-disable-next-line unicorn/no-array-reduce
matches.reduce((newValue, match) => {
const parts = /(.?)\${?([\w:]+)?}?/g.exec(match) || [];
const prefix = parts[1];
let value, replacePart: string;
if (prefix === "\\") {
replacePart = parts[0] || "";
value = replacePart.replace("\\$", "$");
} else {
const key = parts[2];
replacePart = (parts[0] || "").slice(prefix.length);
// Avoid recursion
if (parents.includes(key)) {
// eslint-disable-next-line no-console
console.warn(
`Please avoid recursive environment variables ( loop: ${parents.join(
" > ",
)} > ${key} )`,
);
return "";
}
value = getValue(key);
// Resolve recursive interpolations
value = interpolate(value, [...parents, key]);
}
return value === undefined
? newValue
: newValue.replace(replacePart, value);
}, value),
);
}
for (const key in target) {
target[key] = interpolate(getValue(key));
}
}