alepha
Version:
Easy-to-use modern TypeScript framework for building many kind of applications.
93 lines (87 loc) • 3.27 kB
text/typescript
import { $inject } from "alepha";
import { $logger } from "alepha/logger";
import { FileSystemProvider } from "alepha/system";
export class EnvUtils {
protected readonly log = $logger();
protected readonly fs = $inject(FileSystemProvider);
/**
* Load environment variables from .env files into process.env.
*
* Variables that already exist in process.env are NOT overwritten,
* matching the standard dotenv convention where the shell environment
* takes precedence over .env file values.
*
* By default, it loads from ".env" and ".env.local".
* You can specify additional files to load, e.g. [".env", ".env.production"].
*/
public async loadEnv(
root: string,
files: string[] = [".env"],
): Promise<void> {
const vars = await this.parseEnv(root, files);
for (const [key, value] of Object.entries(vars)) {
if (process.env[key] === undefined) {
process.env[key] = value;
}
}
}
/**
* Parse environment variables from .env files without mutating process.env.
*
* Returns a merged record from all files (later files override earlier ones).
* For each file, also tries the `.local` variant (e.g. `.env.production.local`).
*/
public async parseEnv(
root: string,
files: string[] = [".env"],
): Promise<Record<string, string>> {
const result: Record<string, string> = {};
for (const it of files) {
for (const file of [it, `${it}.local`]) {
const envPath = this.fs.join(root, file);
try {
const buffer = await this.fs.readFile(envPath);
const envContent = buffer.toString("utf8");
for (const line of envContent.split("\n")) {
const [key, ...rest] = line.split("=");
if (key) {
const trimmedKey = key.trim();
if (trimmedKey && !trimmedKey.startsWith("#")) {
let value = rest.join("=").trim();
const last = value.length - 1;
if (
value.length >= 2 &&
value[0] === '"' &&
value[last] === '"'
) {
// Double-quoted: JSON-decode so escapes round-trip with a
// `JSON.stringify`-based writer (e.g. Rocket's
// `.env.<env>.local` overrides, which write JSON values like
// CLUB_CONFIG_JSON). Falls back to a naive strip when the
// body isn't valid JSON (e.g. a Windows path with `\`), so
// simple `KEY="value"` still behaves as before.
try {
value = JSON.parse(value);
} catch {
value = value.slice(1, -1);
}
} else if (
value.length >= 2 &&
value[0] === "'" &&
value[last] === "'"
) {
value = value.slice(1, -1);
}
result[trimmedKey] = value;
}
}
}
this.log.debug(`Parsed environment variables from ${envPath}`);
} catch {
this.log.debug(`No ${file} file found at ${envPath}, skipping.`);
}
}
}
return result;
}
}