c12 3.0.4 → 3.2.0

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 CHANGED
@@ -27,42 +27,24 @@ c12 (pronounced as /siːtwelv/, like c-twelve) is a smart configuration loader.
27
27
  ## 🦴 Used by
28
28
 
29
29
  - [Nuxt](https://nuxt.com/)
30
- - [Nitro](https://nitro.unjs.io/)
30
+ - [Nitro](https://nitro.build/)
31
31
  - [Unbuild](https://unbuild.unjs.io)
32
32
  - [Automd](https://automd.unjs.io)
33
33
  - [Changelogen](https://changelogen.unjs.io)
34
34
  - [RemixKit](https://github.com/jrestall/remix-kit)
35
35
  - [Hey API](https://github.com/hey-api/openapi-ts)
36
36
  - [kysely-ctl](https://github.com/kysely-org/kysely-ctl)
37
+ - [Prisma](https://github.com/prisma/prisma)
37
38
 
38
39
  ## Usage
39
40
 
40
41
  Install package:
41
42
 
42
- <!-- automd:pm-install -->
43
-
44
43
  ```sh
45
44
  # ✨ Auto-detect
46
45
  npx nypm install c12
47
-
48
- # npm
49
- npm install c12
50
-
51
- # yarn
52
- yarn add c12
53
-
54
- # pnpm
55
- pnpm install c12
56
-
57
- # bun
58
- bun install c12
59
-
60
- # deno
61
- deno install c12
62
46
  ```
63
47
 
64
- <!-- /automd -->
65
-
66
48
  Import:
67
49
 
68
50
  ```js
@@ -171,10 +153,17 @@ Environment name used for [environment specific configuration](#environment-spec
171
153
 
172
154
  The default is `process.env.NODE_ENV`. You can set `envName` to `false` or an empty string to disable the feature.
173
155
 
156
+ ### `context`
157
+
158
+ Context object passed to dynamic config functions.
159
+
174
160
  ### `resolve`
175
161
 
176
162
  You can define a custom function that resolves the config.
177
163
 
164
+ ### `configFileRequired`
165
+
166
+ If this option is set to `true`, loader fails if the main config file does not exists.
178
167
 
179
168
  ## Extending configuration
180
169
 
@@ -386,30 +375,11 @@ Update or create a new configuration files.
386
375
 
387
376
  Add `magicast` peer dependency:
388
377
 
389
- <!-- automd:pm-install name="magicast" dev -->
390
-
391
378
  ```sh
392
379
  # ✨ Auto-detect
393
380
  npx nypm install -D magicast
394
-
395
- # npm
396
- npm install -D magicast
397
-
398
- # yarn
399
- yarn add -D magicast
400
-
401
- # pnpm
402
- pnpm install -D magicast
403
-
404
- # bun
405
- bun install -D magicast
406
-
407
- # deno
408
- deno install --dev magicast
409
381
  ```
410
382
 
411
- <!-- /automd -->
412
-
413
383
  Import util from `c12/update`
414
384
 
415
385
  ```js
@@ -430,6 +400,28 @@ const { configFile, created } = await updateConfig({
430
400
  console.log(`Config file ${created ? "created" : "updated"} in ${configFile}`);
431
401
  ```
432
402
 
403
+ ## Configuration functions
404
+
405
+ You can use a function to define your configuration dynamically based on context.
406
+
407
+ ```ts
408
+ // config.ts
409
+ export default (ctx) => {
410
+ return {
411
+ apiUrl: ctx?.dev ? "http://localhost:3000" : "https://api.example.com",
412
+ };
413
+ };
414
+ ```
415
+
416
+ ```ts
417
+ // Usage
418
+ import { loadConfig } from "c12";
419
+
420
+ const config = await loadConfig({
421
+ context: { dev: true },
422
+ });
423
+ ```
424
+
433
425
  ## Contribution
434
426
 
435
427
  <details>
package/dist/index.d.mts CHANGED
@@ -9,10 +9,11 @@ interface DotenvOptions {
9
9
  */
10
10
  cwd: string;
11
11
  /**
12
- * What file to look in for environment variables (either absolute or relative
12
+ * What file or files to look in for environment variables (either absolute or relative
13
13
  * to the current working directory). For example, `.env`.
14
+ * With the array type, the order enforce the env loading priority (last one overrides).
14
15
  */
15
- fileName?: string;
16
+ fileName?: string | string[];
16
17
  /**
17
18
  * Whether to interpolate variables within .env.
18
19
  *
@@ -92,9 +93,15 @@ interface ResolvedConfig<T extends UserInputConfig = UserInputConfig, MT extends
92
93
  config: T;
93
94
  layers?: ConfigLayer<T, MT>[];
94
95
  cwd?: string;
96
+ _configFile?: string;
97
+ }
98
+ type ConfigSource = "overrides" | "main" | "rc" | "packageJson" | "defaultConfig";
99
+ interface ConfigFunctionContext {
100
+ [key: string]: any;
95
101
  }
96
102
  interface ResolvableConfigContext<T extends UserInputConfig = UserInputConfig> {
97
- configs: Record<"overrides" | "main" | "rc" | "packageJson" | "defaultConfig", T | null | undefined>;
103
+ configs: Record<ConfigSource, T | null | undefined>;
104
+ rawConfigs: Record<ConfigSource, ResolvableConfig<T> | null | undefined>;
98
105
  }
99
106
  type MaybePromise<T> = T | Promise<T>;
100
107
  type ResolvableConfig<T extends UserInputConfig = UserInputConfig> = MaybePromise<T | null | undefined> | ((ctx: ResolvableConfigContext<T>) => MaybePromise<T | null | undefined>);
@@ -111,6 +118,8 @@ interface LoadConfigOptions<T extends UserInputConfig = UserInputConfig, MT exte
111
118
  defaultConfig?: ResolvableConfig<T>;
112
119
  overrides?: ResolvableConfig<T>;
113
120
  omit$Keys?: boolean;
121
+ /** Context passed to config functions */
122
+ context?: ConfigFunctionContext;
114
123
  resolve?: (id: string, options: LoadConfigOptions<T, MT>) => null | undefined | ResolvedConfig<T, MT> | Promise<ResolvedConfig<T, MT> | undefined | null>;
115
124
  jiti?: Jiti;
116
125
  jitiOptions?: JitiOptions;
@@ -119,6 +128,7 @@ interface LoadConfigOptions<T extends UserInputConfig = UserInputConfig, MT exte
119
128
  extend?: false | {
120
129
  extendKey?: string | string[];
121
130
  };
131
+ configFileRequired?: boolean;
122
132
  }
123
133
  type DefineConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> = (input: InputConfig<T, MT>) => InputConfig<T, MT>;
124
134
  declare function createDefineConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta>(): DefineConfig<T, MT>;
@@ -152,4 +162,4 @@ interface WatchConfigOptions<T extends UserInputConfig = UserInputConfig, MT ext
152
162
  declare function watchConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta>(options: WatchConfigOptions<T, MT>): Promise<ConfigWatcher<T, MT>>;
153
163
 
154
164
  export { SUPPORTED_EXTENSIONS, createDefineConfig, loadConfig, loadDotenv, setupDotenv, watchConfig };
155
- export type { C12InputConfig, ConfigLayer, ConfigLayerMeta, ConfigWatcher, DefineConfig, DotenvOptions, Env, InputConfig, LoadConfigOptions, ResolvableConfig, ResolvableConfigContext, ResolvedConfig, SourceOptions, UserInputConfig, WatchConfigOptions };
165
+ export type { C12InputConfig, ConfigFunctionContext, ConfigLayer, ConfigLayerMeta, ConfigSource, ConfigWatcher, DefineConfig, DotenvOptions, Env, InputConfig, LoadConfigOptions, ResolvableConfig, ResolvableConfigContext, ResolvedConfig, SourceOptions, UserInputConfig, WatchConfigOptions };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { l as loadConfig, S as SUPPORTED_EXTENSIONS } from './shared/c12.CEGFwQZa.mjs';
2
- export { a as loadDotenv, s as setupDotenv } from './shared/c12.CEGFwQZa.mjs';
1
+ import { l as loadConfig, S as SUPPORTED_EXTENSIONS } from './shared/c12.DDjD4HmS.mjs';
2
+ export { a as loadDotenv, s as setupDotenv } from './shared/c12.DDjD4HmS.mjs';
3
3
  import { debounce } from 'perfect-debounce';
4
4
  import { resolve } from 'pathe';
5
5
  import 'node:fs';
@@ -31,10 +31,15 @@ async function setupDotenv(options) {
31
31
  }
32
32
  async function loadDotenv(options) {
33
33
  const environment = /* @__PURE__ */ Object.create(null);
34
- const dotenvFile = resolve(options.cwd, options.fileName);
34
+ const _fileName = options.fileName || ".env";
35
+ const dotenvFiles = typeof _fileName === "string" ? [_fileName] : _fileName;
35
36
  const dotenvVars = getDotEnvVars(options.env || {});
36
37
  Object.assign(environment, options.env);
37
- if (statSync(dotenvFile, { throwIfNoEntry: false })?.isFile()) {
38
+ for (const file of dotenvFiles) {
39
+ const dotenvFile = resolve(options.cwd, file);
40
+ if (!statSync(dotenvFile, { throwIfNoEntry: false })?.isFile()) {
41
+ continue;
42
+ }
38
43
  const parsed = dotenv.parse(await promises.readFile(dotenvFile, "utf8"));
39
44
  for (const key in parsed) {
40
45
  if (key in environment && !dotenvVars.has(key)) {
@@ -144,9 +149,10 @@ async function loadConfig(options) {
144
149
  config: {},
145
150
  cwd: options.cwd,
146
151
  configFile: resolve(options.cwd, options.configFile),
147
- layers: []
152
+ layers: [],
153
+ _configFile: void 0
148
154
  };
149
- const _configs = {
155
+ const rawConfigs = {
150
156
  overrides: options.overrides,
151
157
  main: void 0,
152
158
  rc: void 0,
@@ -161,8 +167,9 @@ async function loadConfig(options) {
161
167
  }
162
168
  const _mainConfig = await resolveConfig(".", options);
163
169
  if (_mainConfig.configFile) {
164
- _configs.main = _mainConfig.config;
170
+ rawConfigs.main = _mainConfig.config;
165
171
  r.configFile = _mainConfig.configFile;
172
+ r._configFile = _mainConfig._configFile;
166
173
  }
167
174
  if (_mainConfig.meta) {
168
175
  r.meta = _mainConfig.meta;
@@ -178,7 +185,7 @@ async function loadConfig(options) {
178
185
  }
179
186
  rcSources.push(rc9.readUser({ name: options.rcFile, dir: options.cwd }));
180
187
  }
181
- _configs.rc = _merger({}, ...rcSources);
188
+ rawConfigs.rc = _merger({}, ...rcSources);
182
189
  }
183
190
  if (options.packageJson) {
184
191
  const keys = (Array.isArray(options.packageJson) ? options.packageJson : [
@@ -187,12 +194,12 @@ async function loadConfig(options) {
187
194
  const pkgJsonFile = await readPackageJSON(options.cwd).catch(() => {
188
195
  });
189
196
  const values = keys.map((key) => pkgJsonFile?.[key]);
190
- _configs.packageJson = _merger({}, ...values);
197
+ rawConfigs.packageJson = _merger({}, ...values);
191
198
  }
192
199
  const configs = {};
193
- for (const key in _configs) {
194
- const value = _configs[key];
195
- configs[key] = await (typeof value === "function" ? value({ configs }) : value);
200
+ for (const key in rawConfigs) {
201
+ const value = rawConfigs[key];
202
+ configs[key] = await (typeof value === "function" ? value({ configs, rawConfigs }) : value);
196
203
  }
197
204
  r.config = _merger(
198
205
  configs.overrides,
@@ -231,6 +238,9 @@ async function loadConfig(options) {
231
238
  }
232
239
  }
233
240
  }
241
+ if (options.configFileRequired && !r._configFile) {
242
+ throw new Error(`Required config (${r.configFile}) cannot be resolved.`);
243
+ }
234
244
  return r;
235
245
  }
236
246
  async function extendConfig(config, options) {
@@ -356,6 +366,7 @@ async function resolveConfig(source, options, sourceOptions = {}) {
356
366
  if (!existsSync(res.configFile)) {
357
367
  return res;
358
368
  }
369
+ res._configFile = res.configFile;
359
370
  const configFileExt = extname(res.configFile) || "";
360
371
  if (configFileExt in ASYNC_LOADERS) {
361
372
  const asyncLoader = await ASYNC_LOADERS[configFileExt]();
@@ -366,8 +377,8 @@ async function resolveConfig(source, options, sourceOptions = {}) {
366
377
  default: true
367
378
  });
368
379
  }
369
- if (res.config instanceof Function) {
370
- res.config = await res.config();
380
+ if (typeof res.config === "function") {
381
+ res.config = await res.config(options.context);
371
382
  }
372
383
  if (options.envName) {
373
384
  const envConfig = {
package/dist/update.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { resolveModulePath } from 'exsolve';
2
- import { S as SUPPORTED_EXTENSIONS } from './shared/c12.CEGFwQZa.mjs';
2
+ import { S as SUPPORTED_EXTENSIONS } from './shared/c12.DDjD4HmS.mjs';
3
3
  import { join, normalize } from 'pathe';
4
4
  import { mkdir, writeFile, readFile } from 'node:fs/promises';
5
5
  import { dirname, extname } from 'node:path';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c12",
3
- "version": "3.0.4",
3
+ "version": "3.2.0",
4
4
  "description": "Smart Config Loader",
5
5
  "repository": "unjs/c12",
6
6
  "license": "MIT",
@@ -34,29 +34,29 @@
34
34
  "chokidar": "^4.0.3",
35
35
  "confbox": "^0.2.2",
36
36
  "defu": "^6.1.4",
37
- "dotenv": "^16.5.0",
38
- "exsolve": "^1.0.5",
37
+ "dotenv": "^17.2.1",
38
+ "exsolve": "^1.0.7",
39
39
  "giget": "^2.0.0",
40
- "jiti": "^2.4.2",
40
+ "jiti": "^2.5.1",
41
41
  "ohash": "^2.0.11",
42
42
  "pathe": "^2.0.3",
43
43
  "perfect-debounce": "^1.0.0",
44
- "pkg-types": "^2.1.0",
44
+ "pkg-types": "^2.2.0",
45
45
  "rc9": "^2.1.2"
46
46
  },
47
47
  "devDependencies": {
48
- "@types/node": "^22.15.18",
49
- "@vitest/coverage-v8": "^3.1.3",
48
+ "@types/node": "^24.1.0",
49
+ "@vitest/coverage-v8": "^3.2.4",
50
50
  "automd": "^0.4.0",
51
- "changelogen": "^0.6.1",
52
- "eslint": "^9.27.0",
53
- "eslint-config-unjs": "^0.4.2",
54
- "expect-type": "^1.2.1",
51
+ "changelogen": "^0.6.2",
52
+ "eslint": "^9.32.0",
53
+ "eslint-config-unjs": "^0.5.0",
54
+ "expect-type": "^1.2.2",
55
55
  "magicast": "^0.3.5",
56
- "prettier": "^3.5.3",
56
+ "prettier": "^3.6.2",
57
57
  "typescript": "^5.8.3",
58
- "unbuild": "^3.5.0",
59
- "vitest": "^3.1.3"
58
+ "unbuild": "^3.6.0",
59
+ "vitest": "^3.2.4"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "magicast": "^0.3.5"
@@ -66,5 +66,5 @@
66
66
  "optional": true
67
67
  }
68
68
  },
69
- "packageManager": "pnpm@10.11.0"
69
+ "packageManager": "pnpm@10.13.1"
70
70
  }