c12 4.0.0-beta.1 → 4.0.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -2
- package/dist/index.d.mts +17 -1
- package/dist/index.mjs +31 -5
- package/package.json +11 -3
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ c12 (pronounced as /siːtwelv/, like c-twelve) is a smart configuration loader.
|
|
|
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
|
|
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
|
|
@@ -139,6 +139,25 @@ console.log(config.config.connectionPoolMax); // "10"
|
|
|
139
139
|
console.log(config.config.databaseURL); // "<...localhost...>"
|
|
140
140
|
```
|
|
141
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
|
|
159
|
+
```
|
|
160
|
+
|
|
142
161
|
### `packageJson`
|
|
143
162
|
|
|
144
163
|
Loads config from nearest `package.json` file. It is disabled by default.
|
|
@@ -172,7 +191,9 @@ Custom import function used to load configuration files. By default, c12 uses na
|
|
|
172
191
|
```js
|
|
173
192
|
import { createJiti } from "jiti";
|
|
174
193
|
|
|
175
|
-
const jiti = createJiti(import.meta.url, {
|
|
194
|
+
const jiti = createJiti(import.meta.url, {
|
|
195
|
+
/* jiti options */
|
|
196
|
+
});
|
|
176
197
|
|
|
177
198
|
const { config } = await loadConfig({
|
|
178
199
|
import: (id) => jiti.import(id),
|
|
@@ -315,6 +336,14 @@ Layers:
|
|
|
315
336
|
|
|
316
337
|
## Extending config layer from remote sources
|
|
317
338
|
|
|
339
|
+
> [!NOTE]
|
|
340
|
+
> Extending from remote sources requires the [`giget`](https://giget.unjs.io) peer dependency to be installed.
|
|
341
|
+
>
|
|
342
|
+
> ```sh
|
|
343
|
+
> # ✨ Auto-detect
|
|
344
|
+
> npx nypm install giget
|
|
345
|
+
> ```
|
|
346
|
+
|
|
318
347
|
You can also extend configuration from remote sources such as npm or github.
|
|
319
348
|
|
|
320
349
|
In the repo, there should be a `config.ts` (or `config.{name}.ts`) file to be considered as a valid config layer.
|
package/dist/index.d.mts
CHANGED
|
@@ -31,6 +31,22 @@ interface DotenvOptions {
|
|
|
31
31
|
* An object describing environment variables (key, value pairs).
|
|
32
32
|
*/
|
|
33
33
|
env?: NodeJS.ProcessEnv;
|
|
34
|
+
/**
|
|
35
|
+
* Resolve `_FILE` suffixed environment variables by reading the file at the
|
|
36
|
+
* specified path and assigning its trimmed content to the base key.
|
|
37
|
+
*
|
|
38
|
+
* This is useful for container secrets (e.g. Docker, Kubernetes) where
|
|
39
|
+
* sensitive values are mounted as files.
|
|
40
|
+
*
|
|
41
|
+
* @default true
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```env
|
|
45
|
+
* DATABASE_PASSWORD_FILE="/run/secrets/db_password"
|
|
46
|
+
* # resolves to DATABASE_PASSWORD=<contents of /run/secrets/db_password>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
expandFileReferences?: boolean;
|
|
34
50
|
}
|
|
35
51
|
type Env = typeof process.env;
|
|
36
52
|
/**
|
|
@@ -168,4 +184,4 @@ interface WatchConfigOptions<T extends UserInputConfig = UserInputConfig, MT ext
|
|
|
168
184
|
}
|
|
169
185
|
declare function watchConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta>(options: WatchConfigOptions<T, MT>): Promise<ConfigWatcher<T, MT>>;
|
|
170
186
|
//#endregion
|
|
171
|
-
export { C12InputConfig, ConfigFunctionContext, ConfigLayer, ConfigLayerMeta, ConfigSource, ConfigWatcher, DefineConfig, DotenvOptions, Env, InputConfig, LoadConfigOptions, ResolvableConfig, ResolvableConfigContext, ResolvedConfig, SUPPORTED_EXTENSIONS, SourceOptions, UserInputConfig, WatchConfigOptions, createDefineConfig, loadConfig, loadDotenv, setupDotenv, watchConfig };
|
|
187
|
+
export { C12InputConfig, ConfigFunctionContext, ConfigLayer, ConfigLayerMeta, ConfigSource, type ConfigWatcher, DefineConfig, type DotenvOptions, type Env, InputConfig, LoadConfigOptions, ResolvableConfig, ResolvableConfigContext, ResolvedConfig, SUPPORTED_EXTENSIONS, SourceOptions, UserInputConfig, type WatchConfigOptions, createDefineConfig, loadConfig, loadDotenv, setupDotenv, watchConfig };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as debounce } from "./_chunks/libs/perfect-debounce.mjs";
|
|
2
|
-
import { existsSync,
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import * as nodeUtil from "node:util";
|
|
3
4
|
import { basename, dirname, extname, join, normalize, resolve } from "pathe";
|
|
4
|
-
import * as dotenv from "dotenv";
|
|
5
5
|
import { readFile, rm } from "node:fs/promises";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import { homedir } from "node:os";
|
|
@@ -15,7 +15,8 @@ async function setupDotenv(options) {
|
|
|
15
15
|
cwd: options.cwd,
|
|
16
16
|
fileName: options.fileName ?? ".env",
|
|
17
17
|
env: targetEnvironment,
|
|
18
|
-
interpolate: options.interpolate ?? true
|
|
18
|
+
interpolate: options.interpolate ?? true,
|
|
19
|
+
expandFileReferences: options.expandFileReferences ?? true
|
|
19
20
|
});
|
|
20
21
|
const dotenvVars = getDotEnvVars(targetEnvironment);
|
|
21
22
|
for (const key in environment) {
|
|
@@ -34,16 +35,39 @@ async function loadDotenv(options) {
|
|
|
34
35
|
for (const file of dotenvFiles) {
|
|
35
36
|
const dotenvFile = resolve(cwd, file);
|
|
36
37
|
if (!statSync(dotenvFile, { throwIfNoEntry: false })?.isFile()) continue;
|
|
37
|
-
const parsed =
|
|
38
|
+
const parsed = await readEnvFile(dotenvFile);
|
|
38
39
|
for (const key in parsed) {
|
|
39
40
|
if (key in environment && !dotenvVars.has(key)) continue;
|
|
40
41
|
environment[key] = parsed[key];
|
|
41
42
|
dotenvVars.add(key);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
45
|
+
if (options.expandFileReferences !== false) {
|
|
46
|
+
for (const key in environment) if (key.endsWith("_FILE")) {
|
|
47
|
+
const targetKey = key.slice(0, -5);
|
|
48
|
+
if (environment[targetKey] === void 0) {
|
|
49
|
+
const filePath = environment[key];
|
|
50
|
+
if (filePath && statSync(filePath, { throwIfNoEntry: false })?.isFile()) {
|
|
51
|
+
environment[targetKey] = readFileSync(filePath, "utf8").trim();
|
|
52
|
+
dotenvVars.add(targetKey);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
44
57
|
if (options.interpolate) interpolate(environment);
|
|
45
58
|
return environment;
|
|
46
59
|
}
|
|
60
|
+
let _parseEnv = nodeUtil.parseEnv;
|
|
61
|
+
async function readEnvFile(path) {
|
|
62
|
+
const src = readFileSync(path, "utf8");
|
|
63
|
+
if (!_parseEnv) try {
|
|
64
|
+
const dotenv = await import("dotenv");
|
|
65
|
+
_parseEnv = (src) => dotenv.parse(src);
|
|
66
|
+
} catch {
|
|
67
|
+
throw new Error("Failed to parse .env file: `node:util.parseEnv` is not available and `dotenv` package is not installed. Please upgrade your runtime or install `dotenv` as a dependency.");
|
|
68
|
+
}
|
|
69
|
+
return _parseEnv(src);
|
|
70
|
+
}
|
|
47
71
|
function interpolate(target, source = {}, parse = (v) => v) {
|
|
48
72
|
function getValue(key) {
|
|
49
73
|
return source[key] === void 0 ? target[key] : source[key];
|
|
@@ -263,7 +287,9 @@ async function resolveConfig(source, options, sourceOptions = {}) {
|
|
|
263
287
|
const customProviderKeys = Object.keys(sourceOptions.giget?.providers || {}).map((key) => `${key}:`);
|
|
264
288
|
const gigetPrefixes = customProviderKeys.length > 0 ? [...new Set([...customProviderKeys, ...GIGET_PREFIXES])] : GIGET_PREFIXES;
|
|
265
289
|
if (options.giget !== false && gigetPrefixes.some((prefix) => source.startsWith(prefix))) {
|
|
266
|
-
const { downloadTemplate } = await import("giget")
|
|
290
|
+
const { downloadTemplate } = await import("giget").catch((error) => {
|
|
291
|
+
throw new Error(`Extending config from \`${source}\` requires \`giget\` peer dependency to be installed.\n\nInstall it with: \`npx nypm i giget\``, { cause: error });
|
|
292
|
+
});
|
|
267
293
|
const { digest } = await import("./_chunks/libs/ohash.mjs").then((n) => n.n);
|
|
268
294
|
const cloneName = source.replace(/\W+/g, "_").split("_").splice(0, 3).join("_") + "_" + digest(source).slice(0, 10).replace(/[-_]/g, "");
|
|
269
295
|
let cloneDir;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c12",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.2",
|
|
4
4
|
"description": "Smart Config Loader",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "unjs/c12",
|
|
@@ -26,9 +26,7 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"confbox": "^0.2.4",
|
|
28
28
|
"defu": "^6.1.4",
|
|
29
|
-
"dotenv": "^17.2.4",
|
|
30
29
|
"exsolve": "^1.0.8",
|
|
31
|
-
"giget": "^3.1.2",
|
|
32
30
|
"pathe": "^2.0.3",
|
|
33
31
|
"pkg-types": "^2.3.0",
|
|
34
32
|
"rc9": "^3.0.0"
|
|
@@ -40,8 +38,10 @@
|
|
|
40
38
|
"automd": "^0.4.3",
|
|
41
39
|
"changelogen": "^0.6.2",
|
|
42
40
|
"chokidar": "^5.0.0",
|
|
41
|
+
"dotenv": "^17.2.4",
|
|
43
42
|
"eslint-config-unjs": "^0.6.2",
|
|
44
43
|
"expect-type": "^1.3.0",
|
|
44
|
+
"giget": "^3.1.2",
|
|
45
45
|
"jiti": "^2.6.1",
|
|
46
46
|
"magicast": "^0.5.2",
|
|
47
47
|
"obuild": "^0.4.27",
|
|
@@ -54,10 +54,15 @@
|
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"chokidar": "^5",
|
|
57
|
+
"dotenv": "*",
|
|
58
|
+
"giget": "*",
|
|
57
59
|
"jiti": "*",
|
|
58
60
|
"magicast": "*"
|
|
59
61
|
},
|
|
60
62
|
"peerDependenciesMeta": {
|
|
63
|
+
"dotenv": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
61
66
|
"magicast": {
|
|
62
67
|
"optional": true
|
|
63
68
|
},
|
|
@@ -66,6 +71,9 @@
|
|
|
66
71
|
},
|
|
67
72
|
"jiti": {
|
|
68
73
|
"optional": true
|
|
74
|
+
},
|
|
75
|
+
"giget": {
|
|
76
|
+
"optional": true
|
|
69
77
|
}
|
|
70
78
|
},
|
|
71
79
|
"packageManager": "pnpm@10.28.2"
|