Smart configuration loader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: update dotenv assigned env variables on subsequent calls (#243)

authored by

Daniel Roe and committed by
GitHub
3e67bd48 0bb37f9c

+80 -8
+1 -1
.gitignore
··· 5 5 coverage 6 6 dist 7 7 types 8 - .tmp 8 + .tmp*
+36 -7
src/dotenv.ts
··· 51 51 interpolate: options.interpolate ?? true, 52 52 }); 53 53 54 + const dotenvVars = getDotEnvVars(targetEnvironment); 55 + 54 56 // Fill process.env 55 57 for (const key in environment) { 56 - if (!key.startsWith("_") && targetEnvironment[key] === undefined) { 58 + // Skip private variables 59 + if (key.startsWith("_")) { 60 + continue; 61 + } 62 + // Override if variables are not already set or come from `.env` 63 + if (targetEnvironment[key] === undefined || dotenvVars.has(key)) { 57 64 targetEnvironment[key] = environment[key]; 58 65 } 59 66 } ··· 66 73 const environment = Object.create(null); 67 74 68 75 const dotenvFile = resolve(options.cwd, options.fileName!); 76 + 77 + const dotenvVars = getDotEnvVars(options.env || {}); 78 + 79 + // Apply process.env 80 + Object.assign(environment, options.env); 69 81 70 82 if (statSync(dotenvFile, { throwIfNoEntry: false })?.isFile()) { 71 83 const parsed = dotenv.parse(await fsp.readFile(dotenvFile, "utf8")); 72 - Object.assign(environment, parsed); 73 - } 84 + for (const key in parsed) { 85 + if (key in environment && !dotenvVars.has(key)) { 86 + // do not override existing env variables 87 + continue; 88 + } 74 89 75 - // Apply process.env 76 - if (!options.env?._applied) { 77 - Object.assign(environment, options.env); 78 - environment._applied = true; 90 + environment[key] = parsed[key]; 91 + dotenvVars.add(key); 92 + } 79 93 } 80 94 81 95 // Interpolate env ··· 144 158 target[key] = interpolate(getValue(key)); 145 159 } 146 160 } 161 + 162 + // Internal: Keep track of which variables that are set by dotenv 163 + 164 + declare global { 165 + // eslint-disable-next-line no-var 166 + var __c12_dotenv_vars__: Map<Record<string, any>, Set<string>>; 167 + } 168 + 169 + function getDotEnvVars(targetEnvironment: Record<string, any>) { 170 + const globalRegistry = (globalThis.__c12_dotenv_vars__ ||= new Map()); 171 + if (!globalRegistry.has(targetEnvironment)) { 172 + globalRegistry.set(targetEnvironment, new Set()); 173 + } 174 + return globalRegistry.get(targetEnvironment)!; 175 + }
+43
test/dotenv.test.ts
··· 1 + import { fileURLToPath } from "node:url"; 2 + import { beforeEach, expect, it, describe, afterAll } from "vitest"; 3 + import { join, normalize } from "pathe"; 4 + import { mkdir, rm, writeFile } from "node:fs/promises"; 5 + import { setupDotenv } from "../src"; 6 + 7 + const tmpDir = normalize( 8 + fileURLToPath(new URL(".tmp-dotenv", import.meta.url)), 9 + ); 10 + const r = (path: string) => join(tmpDir, path); 11 + 12 + describe("update config file", () => { 13 + beforeEach(async () => { 14 + await rm(tmpDir, { recursive: true, force: true }); 15 + await mkdir(tmpDir, { recursive: true }); 16 + }); 17 + afterAll(async () => { 18 + await rm(tmpDir, { recursive: true, force: true }); 19 + }); 20 + it("should read .env file into process.env", async () => { 21 + await setupDotenv({ cwd: tmpDir }); 22 + expect(process.env.dotenv).toBeUndefined(); 23 + 24 + await writeFile(r(".env"), "dotenv=123"); 25 + await setupDotenv({ cwd: tmpDir }); 26 + expect(process.env.dotenv).toBe("123"); 27 + 28 + await writeFile(r(".env"), "dotenv=456"); 29 + await setupDotenv({ cwd: tmpDir }); 30 + expect(process.env.dotenv).toBe("456"); 31 + }); 32 + it("should not override OS environment values", async () => { 33 + process.env.override = "os"; 34 + 35 + await writeFile(r(".env"), "override=123"); 36 + await setupDotenv({ cwd: tmpDir }); 37 + expect(process.env.override).toBe("os"); 38 + 39 + await writeFile(r(".env"), "override=456"); 40 + await setupDotenv({ cwd: tmpDir }); 41 + expect(process.env.override).toBe("os"); 42 + }); 43 + });