Smart configuration loader
0
fork

Configure Feed

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

feat: support expanding env with `_FILE` references (#297)

Co-authored-by: Vova Stelmashchuk <vovochkastelmashchuk@gmail.com>

authored by

Pooya Parsa
Vova Stelmashchuk
and committed by
GitHub
8165c0fa 8ccd8f50

+71 -1
+20 -1
README.md
··· 16 16 - `.jsonc`, `.json5`, `.yaml`, `.yml`, `.toml` config loader with [unjs/confbox](https://confbox.unjs.io) 17 17 - `.config/` directory support ([config dir proposal](https://github.com/pi0/config-dir)) 18 18 - `.rc` config support with [unjs/rc9](https://github.com/unjs/rc9) 19 - - `.env` support with [dotenv](https://www.npmjs.com/package/dotenv) 19 + - `.env` support with variable interpolation and `_FILE` references resolution 20 20 - Multiple sources merged with [unjs/defu](https://github.com/unjs/defu) 21 21 - Reads config from the nearest `package.json` file 22 22 - [Extends configurations](https://github.com/unjs/c12#extending-configuration) from multiple local or git sources ··· 137 137 138 138 console.log(config.config.connectionPoolMax); // "10" 139 139 console.log(config.config.databaseURL); // "<...localhost...>" 140 + ``` 141 + 142 + #### `expandFileReferences` 143 + 144 + Enabled by default. Environment variables ending with `_FILE` are resolved by reading the file at the specified path and assigning its trimmed content to the base key (without the `_FILE` suffix). This is useful for container secrets (e.g. Docker, Kubernetes) where sensitive values are mounted as files. Set to `false` to disable. 145 + 146 + ```ini 147 + # .env 148 + DATABASE_PASSWORD_FILE="/run/secrets/db_password" 149 + ``` 150 + 151 + ```ts 152 + import { loadConfig } from "c12"; 153 + 154 + const config = await loadConfig({ 155 + dotenv: true, 156 + }); 157 + 158 + // DATABASE_PASSWORD is now set to the contents of /run/secrets/db_password 140 159 ``` 141 160 142 161 ### `packageJson`
+35
src/dotenv.ts
··· 33 33 * An object describing environment variables (key, value pairs). 34 34 */ 35 35 env?: NodeJS.ProcessEnv; 36 + 37 + /** 38 + * Resolve `_FILE` suffixed environment variables by reading the file at the 39 + * specified path and assigning its trimmed content to the base key. 40 + * 41 + * This is useful for container secrets (e.g. Docker, Kubernetes) where 42 + * sensitive values are mounted as files. 43 + * 44 + * @default true 45 + * 46 + * @example 47 + * ```env 48 + * DATABASE_PASSWORD_FILE="/run/secrets/db_password" 49 + * # resolves to DATABASE_PASSWORD=<contents of /run/secrets/db_password> 50 + * ``` 51 + */ 52 + expandFileReferences?: boolean; 36 53 } 37 54 38 55 export type Env = typeof process.env; ··· 51 68 fileName: options.fileName ?? ".env", 52 69 env: targetEnvironment, 53 70 interpolate: options.interpolate ?? true, 71 + expandFileReferences: options.expandFileReferences ?? true, 54 72 }); 55 73 56 74 const dotenvVars = getDotEnvVars(targetEnvironment); ··· 95 113 } 96 114 environment[key] = parsed[key]; 97 115 dotenvVars.add(key); 116 + } 117 + } 118 + 119 + // Support _FILE environment variables 120 + if (options.expandFileReferences !== false) { 121 + for (const key in environment) { 122 + if (key.endsWith("_FILE")) { 123 + const targetKey = key.slice(0, -5); 124 + if (environment[targetKey] === undefined) { 125 + const filePath = environment[key]; 126 + if (filePath && statSync(filePath, { throwIfNoEntry: false })?.isFile()) { 127 + const value = readFileSync(filePath, "utf8"); 128 + environment[targetKey] = value.trim(); 129 + dotenvVars.add(targetKey); 130 + } 131 + } 132 + } 98 133 } 99 134 } 100 135
+16
test/dotenv.test.ts
··· 63 63 64 64 expect(process.env.humpty).toBe("dumpty"); 65 65 }); 66 + 67 + it("should support _FILE env vars by default", async () => { 68 + const secretPath = r(".secret"); 69 + await writeFile(secretPath, "my-secret-value"); 70 + process.env.TEST_SECRET_FILE = secretPath; 71 + 72 + await setupDotenv({ cwd: tmpDir }); 73 + expect(process.env.TEST_SECRET).toBe("my-secret-value"); 74 + }); 75 + 76 + it("should not expand _FILE env vars when disabled", async () => { 77 + process.env.TEST_SECRET_FILE = "normal-secret-into-key-with-file-suffix"; 78 + 79 + await setupDotenv({ cwd: tmpDir, expandFileReferences: false }); 80 + expect(process.env.TEST_SECRET_FILE).toBe("normal-secret-into-key-with-file-suffix"); 81 + }); 66 82 });