c12 3.1.0 → 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
@@ -34,35 +34,17 @@ c12 (pronounced as /siːtwelv/, like c-twelve) is a smart configuration loader.
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,8 +93,12 @@ 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;
95
97
  }
96
98
  type ConfigSource = "overrides" | "main" | "rc" | "packageJson" | "defaultConfig";
99
+ interface ConfigFunctionContext {
100
+ [key: string]: any;
101
+ }
97
102
  interface ResolvableConfigContext<T extends UserInputConfig = UserInputConfig> {
98
103
  configs: Record<ConfigSource, T | null | undefined>;
99
104
  rawConfigs: Record<ConfigSource, ResolvableConfig<T> | null | undefined>;
@@ -113,6 +118,8 @@ interface LoadConfigOptions<T extends UserInputConfig = UserInputConfig, MT exte
113
118
  defaultConfig?: ResolvableConfig<T>;
114
119
  overrides?: ResolvableConfig<T>;
115
120
  omit$Keys?: boolean;
121
+ /** Context passed to config functions */
122
+ context?: ConfigFunctionContext;
116
123
  resolve?: (id: string, options: LoadConfigOptions<T, MT>) => null | undefined | ResolvedConfig<T, MT> | Promise<ResolvedConfig<T, MT> | undefined | null>;
117
124
  jiti?: Jiti;
118
125
  jitiOptions?: JitiOptions;
@@ -121,6 +128,7 @@ interface LoadConfigOptions<T extends UserInputConfig = UserInputConfig, MT exte
121
128
  extend?: false | {
122
129
  extendKey?: string | string[];
123
130
  };
131
+ configFileRequired?: boolean;
124
132
  }
125
133
  type DefineConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> = (input: InputConfig<T, MT>) => InputConfig<T, MT>;
126
134
  declare function createDefineConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta>(): DefineConfig<T, MT>;
@@ -154,4 +162,4 @@ interface WatchConfigOptions<T extends UserInputConfig = UserInputConfig, MT ext
154
162
  declare function watchConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta>(options: WatchConfigOptions<T, MT>): Promise<ConfigWatcher<T, MT>>;
155
163
 
156
164
  export { SUPPORTED_EXTENSIONS, createDefineConfig, loadConfig, loadDotenv, setupDotenv, watchConfig };
157
- export type { C12InputConfig, ConfigLayer, ConfigLayerMeta, ConfigSource, 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.BXpNC6YI.mjs';
2
- export { a as loadDotenv, s as setupDotenv } from './shared/c12.BXpNC6YI.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,7 +149,8 @@ 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
155
  const rawConfigs = {
150
156
  overrides: options.overrides,
@@ -163,6 +169,7 @@ async function loadConfig(options) {
163
169
  if (_mainConfig.configFile) {
164
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;
@@ -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]();
@@ -367,7 +378,7 @@ async function resolveConfig(source, options, sourceOptions = {}) {
367
378
  });
368
379
  }
369
380
  if (typeof res.config === "function") {
370
- res.config = await res.config();
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.BXpNC6YI.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.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Smart Config Loader",
5
5
  "repository": "unjs/c12",
6
6
  "license": "MIT",
@@ -34,10 +34,10 @@
34
34
  "chokidar": "^4.0.3",
35
35
  "confbox": "^0.2.2",
36
36
  "defu": "^6.1.4",
37
- "dotenv": "^16.6.1",
37
+ "dotenv": "^17.2.1",
38
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",
@@ -45,17 +45,17 @@
45
45
  "rc9": "^2.1.2"
46
46
  },
47
47
  "devDependencies": {
48
- "@types/node": "^24.0.13",
48
+ "@types/node": "^24.1.0",
49
49
  "@vitest/coverage-v8": "^3.2.4",
50
50
  "automd": "^0.4.0",
51
51
  "changelogen": "^0.6.2",
52
- "eslint": "^9.31.0",
52
+ "eslint": "^9.32.0",
53
53
  "eslint-config-unjs": "^0.5.0",
54
54
  "expect-type": "^1.2.2",
55
55
  "magicast": "^0.3.5",
56
56
  "prettier": "^3.6.2",
57
57
  "typescript": "^5.8.3",
58
- "unbuild": "^3.5.0",
58
+ "unbuild": "^3.6.0",
59
59
  "vitest": "^3.2.4"
60
60
  },
61
61
  "peerDependencies": {