c12 1.1.2 → 1.3.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
@@ -1,19 +1,21 @@
1
- # c12
1
+ # ⚙️ c12
2
2
 
3
3
  [![npm version][npm-version-src]][npm-version-href]
4
4
  [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
- [![Github Actions][github-actions-src]][github-actions-href]
6
5
  [![Codecov][codecov-src]][codecov-href]
6
+ [![License][license-src]][license-href]
7
7
 
8
- > Smart Configuration Loader
8
+ Smart Configuration Loader.
9
9
 
10
10
  ## Features
11
11
 
12
- - JSON, CJS, Typescript and ESM config loader with [unjs/jiti](https://github.com/unjs/jiti)
12
+ - JSON, CJS, Typescript, and ESM config loader with [unjs/jiti](https://github.com/unjs/jiti)
13
13
  - RC config support with [unjs/rc9](https://github.com/unjs/rc9)
14
14
  - Multiple sources merged with [unjs/defu](https://github.com/unjs/defu)
15
15
  - `.env` support with [dotenv](https://www.npmjs.com/package/dotenv)
16
- - Support extending nested configurations from multiple local or git sources
16
+ - Reads config from the nearest `package.json` file
17
+ - [Extends configurations](https://github.com/unjs/c12#extending-configuration) from multiple local or git sources
18
+ - Overwrite with [environment-specific configuration](#environment-specific-configuration)
17
19
 
18
20
  ## Usage
19
21
 
@@ -34,48 +36,49 @@ Import:
34
36
 
35
37
  ```js
36
38
  // ESM
37
- import { loadConfig } from 'c12'
39
+ import { loadConfig } from "c12";
38
40
 
39
41
  // CommonJS
40
- const { loadConfig } = require('c12')
42
+ const { loadConfig } = require("c12");
41
43
  ```
42
44
 
43
45
  Load configuration:
44
46
 
45
47
  ```js
46
48
  // Get loaded config
47
- const { config } = await loadConfig({})
49
+ const { config } = await loadConfig({});
48
50
 
49
51
  // Get resolved config and extended layers
50
- const { config, configFile, layers } = await loadConfig({})
52
+ const { config, configFile, layers } = await loadConfig({});
51
53
  ```
52
54
 
53
55
  ## Loading priority
54
56
 
55
57
  c12 merged config sources with [unjs/defu](https://github.com/unjs/defu) by below order:
56
58
 
57
- 1. config overrides passed by options
58
- 2. config file in CWD
59
+ 1. Config overrides passed by options
60
+ 2. Config file in CWD
59
61
  3. RC file in CWD
60
- 4. global RC file in user's home directory
61
- 5. default config passed by options
62
- 6. Extended config layers
62
+ 4. Global RC file in user's home directory
63
+ 5. Config from `package.json`
64
+ 6. Default config passed by options
65
+ 7. Extended config layers
63
66
 
64
67
  ## Options
65
68
 
66
69
  ### `cwd`
67
70
 
68
- Resolve configuration from this working directory. Default is `process.cwd()`
71
+ Resolve configuration from this working directory. The default is `process.cwd()`
69
72
 
70
73
  ### `name`
71
74
 
72
- Configuration base name. Default is `config`.
75
+ Configuration base name. The default is `config`.
73
76
 
74
77
  ### `configName`
75
78
 
76
- Configuration file name without extension . Default is generated from `name` (name=foo => `foo.config`).
79
+ Configuration file name without extension. Default is generated from `name` (name=foo => `foo.config`).
77
80
 
78
- Set to `false` to avoid loading config file.
81
+ Set to `false` to avoid loading the config file.
79
82
 
80
83
  ### `rcFile`
81
84
 
@@ -85,12 +88,20 @@ Set to `false` to disable loading RC config.
85
88
 
86
89
  ### `globalRC`
87
90
 
88
- Load RC config from the workspace directory and user's home directory. Only enabled when `rcFile` is provided. Set to `false` to disable this functionality.
91
+ Load RC config from the workspace directory and the user's home directory. Only enabled when `rcFile` is provided. Set to `false` to disable this functionality.
89
92
 
90
93
  ### `dotenv`
91
94
 
92
95
  Loads `.env` file if enabled. It is disabled by default.
93
96
 
97
+ ### `packageJson`
98
+
99
+ Loads config from nearest `package.json` file. It is disabled by default.
100
+
101
+ If `true` value is passed, c12 uses `name` field from `package.json`.
102
+
103
+ You can also pass either a string or an array of strings as a value to use those fields.
104
+
94
105
  ### `defaults`
95
106
 
96
107
  Specify default configuration. It has the **lowest** priority and is applied **after extending** config.
@@ -99,7 +110,7 @@ Specify default configuration. It has the **lowest** priority and is applied **a
99
110
 
100
111
  Specify default configuration. It is applied **before** extending config.
101
112
 
102
- ### `overides`
113
+ ### `overrides`
103
114
 
104
115
  Specify override configuration. It has the **highest** priority and is applied **before extending** config.
105
116
 
@@ -111,16 +122,22 @@ Custom [unjs/jiti](https://github.com/unjs/jiti) instance used to import configu
111
122
 
112
123
  Custom [unjs/jiti](https://github.com/unjs/jiti) options to import configuration files.
113
124
 
125
+ ### `envName`
126
+
127
+ Environment name used for [environment specific configuration](#environment-specific-configuration).
128
+
129
+ The default is `process.env.NODE_ENV`. You can set `envName` to `false` or an empty string to disable the feature.
130
+
114
131
  ## Extending configuration
115
132
 
116
- If resolved config contains a `extends` key, it will be used to extend configuration.
133
+ If resolved config contains a `extends` key, it will be used to extend the configuration.
117
134
 
118
135
  Extending can be nested and each layer can extend from one base or more.
119
136
 
120
- Final config is merged result of extended options and user options with [unjs/defu](https://github.com/unjs/defu).
137
+ The final config is merged result of extended options and user options with [unjs/defu](https://github.com/unjs/defu).
121
138
 
122
- Each item in extends, is a string that can be either an absolute or relative path to current config file pointing to a config file for extending or directory containing config file.
123
- If it starts with either of `github:`, `gitlab:`, `bitbucket:` or `https:`, c12 autmatically clones it.
139
+ Each item in extends is a string that can be either an absolute or relative path to the current config file pointing to a config file for extending or the directory containing the config file.
140
+ If it starts with either `github:`, `gitlab:`, `bitbucket:`, or `https:`, c12 automatically clones it.
124
141
 
125
142
  For custom merging strategies, you can directly access each layer with `layers` property.
126
143
 
@@ -130,31 +147,28 @@ For custom merging strategies, you can directly access each layer with `layers`
130
147
  // config.ts
131
148
  export default {
132
149
  colors: {
133
- primary: 'user_primary'
150
+ primary: "user_primary",
134
151
  },
135
- extends: [
136
- './theme',
137
- './config.dev.ts'
138
- ]
139
- }
152
+ extends: ["./theme"],
153
+ };
140
154
  ```
141
155
 
142
156
  ```js
143
157
  // config.dev.ts
144
158
  export default {
145
- dev: true
146
- }
159
+ dev: true,
160
+ };
147
161
  ```
148
162
 
149
163
  ```js
150
164
  // theme/config.ts
151
165
  export default {
152
- extends: '../base',
166
+ extends: "../base",
153
167
  colors: {
154
- primary: 'theme_primary',
155
- secondary: 'theme_secondary'
156
- }
157
- }
168
+ primary: "theme_primary",
169
+ secondary: "theme_secondary",
170
+ },
171
+ };
158
172
  ```
159
173
 
160
174
  ```js
@@ -167,7 +181,7 @@ export default {
167
181
  }
168
182
  ```
169
183
 
170
- Loaded configuration would look like this:
184
+ The loaded configuration would look like this:
171
185
 
172
186
  ```js
173
187
  {
@@ -190,6 +204,36 @@ Layers:
190
204
  ]
191
205
  ```
192
206
 
207
+ ## Environment-specific configuration
208
+
209
+ Users can define environment-specific configuration using these config keys:
210
+
211
+ - `$test: {...}`
212
+ - `$development: {...}`
213
+ - `$production: {...}`
214
+ - `$env: { [env]: {...} }`
215
+
216
+ c12 tries to match [`envName`](#envname) and override environment config if specified.
217
+
218
+ **Note:** Environment will be applied when extending each configuration layer. This way layers can provide environment-specific configuration.
219
+
220
+ **Example:**
221
+
222
+ ```js
223
+ {
224
+ // Default configuration
225
+ logLevel: 'info',
226
+
227
+ // Environment overrides
228
+ $test: { logLevel: 'silent' },
229
+ $development: { logLevel: 'warning' },
230
+ $production: { logLevel: 'error' },
231
+ $env: {
232
+ staging: { logLevel: 'debug' }
233
+ }
234
+ }
235
+ ```
236
+
193
237
  ## 💻 Development
194
238
 
195
239
  - Clone this repository
@@ -202,14 +246,12 @@ Layers:
202
246
  Made with 💛 Published under [MIT License](./LICENSE).
203
247
 
204
248
  <!-- Badges -->
205
- [npm-version-src]: https://img.shields.io/npm/v/c12?style=flat-square
206
- [npm-version-href]: https://npmjs.com/package/c12
207
249
 
208
- [npm-downloads-src]: https://img.shields.io/npm/dm/c12?style=flat-square
250
+ [npm-version-src]: https://img.shields.io/npm/v/c12?style=flat&colorA=18181B&colorB=F0DB4F
251
+ [npm-version-href]: https://npmjs.com/package/c12
252
+ [npm-downloads-src]: https://img.shields.io/npm/dm/c12?style=flat&colorA=18181B&colorB=F0DB4F
209
253
  [npm-downloads-href]: https://npmjs.com/package/c12
210
-
211
- [github-actions-src]: https://img.shields.io/github/workflow/status/unjs/c12/ci/main?style=flat-square
212
- [github-actions-href]: https://github.com/unjs/c12/actions?query=workflow%3Aci
213
-
214
- [codecov-src]: https://img.shields.io/codecov/c/gh/unjs/c12/main?style=flat-square
254
+ [codecov-src]: https://img.shields.io/codecov/c/gh/unjs/c12/main?style=flat&colorA=18181B&colorB=F0DB4F
215
255
  [codecov-href]: https://codecov.io/gh/unjs/c12
256
+ [license-src]: https://img.shields.io/github/license/unjs/c12.svg?style=flat&colorA=18181B&colorB=F0DB4F
257
+ [license-href]: https://github.com/unjs/c12/blob/main/LICENSE
package/dist/index.cjs CHANGED
@@ -10,7 +10,10 @@ const rc9 = require('rc9');
10
10
  const defu = require('defu');
11
11
  const pkgTypes = require('pkg-types');
12
12
 
13
- function _interopNamespaceDefault(e) {
13
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
14
+
15
+ function _interopNamespaceCompat(e) {
16
+ if (e && typeof e === 'object' && 'default' in e) return e;
14
17
  const n = Object.create(null);
15
18
  if (e) {
16
19
  for (const k in e) {
@@ -21,8 +24,9 @@ function _interopNamespaceDefault(e) {
21
24
  return n;
22
25
  }
23
26
 
24
- const dotenv__namespace = /*#__PURE__*/_interopNamespaceDefault(dotenv);
25
- const rc9__namespace = /*#__PURE__*/_interopNamespaceDefault(rc9);
27
+ const dotenv__namespace = /*#__PURE__*/_interopNamespaceCompat(dotenv);
28
+ const createJiti__default = /*#__PURE__*/_interopDefaultCompat(createJiti);
29
+ const rc9__namespace = /*#__PURE__*/_interopNamespaceCompat(rc9);
26
30
 
27
31
  async function setupDotenv(options) {
28
32
  const targetEnvironment = options.env ?? process.env;
@@ -46,7 +50,7 @@ async function loadDotenv(options) {
46
50
  const parsed = dotenv__namespace.parse(await node_fs.promises.readFile(dotenvFile, "utf8"));
47
51
  Object.assign(environment, parsed);
48
52
  }
49
- if (!options.env._applied) {
53
+ if (!options.env?._applied) {
50
54
  Object.assign(environment, options.env);
51
55
  environment._applied = true;
52
56
  }
@@ -67,15 +71,15 @@ function interpolate(target, source = {}, parse = (v) => v) {
67
71
  return parse(
68
72
  // eslint-disable-next-line unicorn/no-array-reduce
69
73
  matches.reduce((newValue, match) => {
70
- const parts = /(.?)\${?([\w:]+)?}?/g.exec(match);
74
+ const parts = /(.?)\${?([\w:]+)?}?/g.exec(match) || [];
71
75
  const prefix = parts[1];
72
76
  let value2, replacePart;
73
77
  if (prefix === "\\") {
74
- replacePart = parts[0];
78
+ replacePart = parts[0] || "";
75
79
  value2 = replacePart.replace("\\$", "$");
76
80
  } else {
77
81
  const key = parts[2];
78
- replacePart = parts[0].slice(prefix.length);
82
+ replacePart = (parts[0] || "").slice(prefix.length);
79
83
  if (parents.includes(key)) {
80
84
  console.warn(
81
85
  `Please avoid recursive environment variables ( loop: ${parents.join(
@@ -99,6 +103,7 @@ function interpolate(target, source = {}, parse = (v) => v) {
99
103
  async function loadConfig(options) {
100
104
  options.cwd = pathe.resolve(process.cwd(), options.cwd || ".");
101
105
  options.name = options.name || "config";
106
+ options.envName = options.envName ?? process.env.NODE_ENV;
102
107
  options.configFile = options.configFile ?? (options.name !== "config" ? `${options.name}.config` : "config");
103
108
  options.rcFile = options.rcFile ?? `.${options.name}rc`;
104
109
  if (options.extend !== false) {
@@ -107,7 +112,7 @@ async function loadConfig(options) {
107
112
  ...options.extend
108
113
  };
109
114
  }
110
- options.jiti = options.jiti || createJiti(void 0, {
115
+ options.jiti = options.jiti || createJiti__default(void 0, {
111
116
  interopDefault: true,
112
117
  requireCache: false,
113
118
  esmResolve: true,
@@ -150,10 +155,21 @@ async function loadConfig(options) {
150
155
  rc9__namespace.read({ name: options.rcFile, dir: options.cwd })
151
156
  );
152
157
  }
158
+ const pkgJson = {};
159
+ if (options.packageJson) {
160
+ const keys = (Array.isArray(options.packageJson) ? options.packageJson : [
161
+ typeof options.packageJson === "string" ? options.packageJson : options.name
162
+ ]).filter((t) => t && typeof t === "string");
163
+ const pkgJsonFile = await pkgTypes.readPackageJSON(options.cwd).catch(() => {
164
+ });
165
+ const values = keys.map((key) => pkgJsonFile?.[key]);
166
+ Object.assign(pkgJson, defu.defu({}, ...values));
167
+ }
153
168
  r.config = defu.defu(
154
169
  options.overrides,
155
170
  config,
156
171
  configRC,
172
+ pkgJson,
157
173
  options.defaultConfig
158
174
  );
159
175
  if (options.extend) {
@@ -169,7 +185,8 @@ async function loadConfig(options) {
169
185
  cwd: void 0
170
186
  },
171
187
  { config, configFile: options.configFile, cwd: options.cwd },
172
- options.rcFile && { config: configRC, configFile: options.rcFile }
188
+ options.rcFile && { config: configRC, configFile: options.rcFile },
189
+ options.packageJson && { config: pkgJson, configFile: "package.json" }
173
190
  ].filter((l) => l && l.config);
174
191
  r.layers = [...baseLayers, ...r.layers];
175
192
  if (options.defaults) {
@@ -195,16 +212,26 @@ async function extendConfig(config, options) {
195
212
  );
196
213
  delete config[key];
197
214
  }
198
- for (const extendSource of extendSources) {
215
+ for (let extendSource of extendSources) {
216
+ const originalExtendSource = extendSource;
217
+ let sourceOptions = {};
218
+ if (extendSource.source) {
219
+ sourceOptions = extendSource.options || {};
220
+ extendSource = extendSource.source;
221
+ }
222
+ if (Array.isArray(extendSource)) {
223
+ sourceOptions = extendSource[1] || {};
224
+ extendSource = extendSource[0];
225
+ }
199
226
  if (typeof extendSource !== "string") {
200
227
  console.warn(
201
228
  `Cannot extend config from \`${JSON.stringify(
202
- extendSource
203
- )}\` (which should be a string) in ${options.cwd}`
229
+ originalExtendSource
230
+ )}\` in ${options.cwd}`
204
231
  );
205
232
  continue;
206
233
  }
207
- const _config = await resolveConfig(extendSource, options);
234
+ const _config = await resolveConfig(extendSource, options, sourceOptions);
208
235
  if (!_config.config) {
209
236
  console.warn(
210
237
  `Cannot extend config from \`${extendSource}\` in ${options.cwd}`
@@ -220,8 +247,8 @@ async function extendConfig(config, options) {
220
247
  }
221
248
  }
222
249
  const GIT_PREFIXES = ["github:", "gitlab:", "bitbucket:", "https://"];
223
- const NPM_PACKAGE_RE = /^(@[\da-z~-][\d._a-z~-]*\/)?[\da-z~-][\d._a-z~-]*$/;
224
- async function resolveConfig(source, options) {
250
+ const NPM_PACKAGE_RE = /^(@[\da-z~-][\d._a-z~-]*\/)?[\da-z~-][\d._a-z~-]*($|\/.*)/;
251
+ async function resolveConfig(source, options, sourceOptions = {}) {
225
252
  if (options.resolve) {
226
253
  const res2 = await options.resolve(source, options);
227
254
  if (res2) {
@@ -235,10 +262,10 @@ async function resolveConfig(source, options) {
235
262
  const name = gitRepo.replace(/[#/:@\\]/g, "_");
236
263
  const tmpDir = process.env.XDG_CACHE_HOME ? pathe.resolve(process.env.XDG_CACHE_HOME, "c12", name) : pathe.resolve(node_os.homedir(), ".cache/c12", name);
237
264
  if (node_fs.existsSync(tmpDir)) {
238
- await promises.rmdir(tmpDir, { recursive: true });
265
+ await promises.rm(tmpDir, { recursive: true });
239
266
  }
240
- const clonned = await downloadTemplate(source, { dir: tmpDir });
241
- source = clonned.dir;
267
+ const cloned = await downloadTemplate(source, { dir: tmpDir });
268
+ source = cloned.dir;
242
269
  }
243
270
  if (NPM_PACKAGE_RE.test(source)) {
244
271
  try {
@@ -251,7 +278,12 @@ async function resolveConfig(source, options) {
251
278
  if (isDir) {
252
279
  source = options.configFile;
253
280
  }
254
- const res = { config: void 0, cwd };
281
+ const res = {
282
+ config: void 0,
283
+ cwd,
284
+ source,
285
+ sourceOptions
286
+ };
255
287
  try {
256
288
  res.configFile = options.jiti.resolve(pathe.resolve(cwd, source), {
257
289
  paths: [cwd]
@@ -262,12 +294,31 @@ async function resolveConfig(source, options) {
262
294
  return res;
263
295
  }
264
296
  res.config = options.jiti(res.configFile);
265
- if (typeof res.config === "function") {
297
+ if (res.config instanceof Function) {
266
298
  res.config = await res.config();
267
299
  }
300
+ if (options.envName) {
301
+ const envConfig = {
302
+ ...res.config["$" + options.envName],
303
+ ...res.config.$env?.[options.envName]
304
+ };
305
+ if (Object.keys(envConfig).length > 0) {
306
+ res.config = defu.defu(envConfig, res.config);
307
+ }
308
+ }
309
+ res.meta = defu.defu(res.sourceOptions.meta, res.config.$meta);
310
+ delete res.config.$meta;
311
+ if (res.sourceOptions.overrides) {
312
+ res.config = defu.defu(res.sourceOptions.overrides, res.config);
313
+ }
268
314
  return res;
269
315
  }
270
316
 
317
+ function createDefineConfig() {
318
+ return (input) => input;
319
+ }
320
+
321
+ exports.createDefineConfig = createDefineConfig;
271
322
  exports.loadConfig = loadConfig;
272
323
  exports.loadDotenv = loadDotenv;
273
324
  exports.setupDotenv = setupDotenv;
package/dist/index.d.ts CHANGED
@@ -37,37 +37,58 @@ declare function setupDotenv(options: DotenvOptions): Promise<Env>;
37
37
  /** Load environment variables into an object. */
38
38
  declare function loadDotenv(options: DotenvOptions): Promise<Env>;
39
39
 
40
- interface InputConfig extends Record<string, any> {
40
+ interface ConfigLayerMeta {
41
+ name?: string;
42
+ [key: string]: any;
43
+ }
44
+ type UserInputConfig = Record<string, any>;
45
+ interface C12InputConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> {
46
+ $test?: T;
47
+ $development?: T;
48
+ $production?: T;
49
+ $env?: Record<string, T>;
50
+ $meta?: MT;
51
+ }
52
+ type InputConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> = C12InputConfig<T, MT> & T;
53
+ interface SourceOptions<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> {
54
+ meta?: MT;
55
+ overrides?: T;
56
+ [key: string]: any;
41
57
  }
42
- interface ConfigLayer<T extends InputConfig = InputConfig> {
58
+ interface ConfigLayer<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> {
43
59
  config: T | null;
60
+ source?: string;
61
+ sourceOptions?: SourceOptions<T, MT>;
62
+ meta?: MT;
44
63
  cwd?: string;
45
64
  configFile?: string;
46
65
  }
47
- interface ResolvedConfig<T extends InputConfig = InputConfig> extends ConfigLayer<T> {
48
- layers?: ConfigLayer<T>[];
66
+ interface ResolvedConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> extends ConfigLayer<T, MT> {
67
+ layers?: ConfigLayer<T, MT>[];
49
68
  cwd?: string;
50
69
  }
51
- interface ResolveConfigOptions {
52
- cwd: string;
53
- }
54
- interface LoadConfigOptions<T extends InputConfig = InputConfig> {
70
+ interface LoadConfigOptions<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> {
55
71
  name?: string;
56
72
  cwd?: string;
57
73
  configFile?: string;
58
74
  rcFile?: false | string;
59
75
  globalRc?: boolean;
60
76
  dotenv?: boolean | DotenvOptions;
77
+ envName?: string | false;
78
+ packageJson?: boolean | string | string[];
61
79
  defaults?: T;
62
80
  defaultConfig?: T;
63
81
  overrides?: T;
64
- resolve?: (id: string, options: LoadConfigOptions) => null | ResolvedConfig | Promise<ResolvedConfig | null>;
82
+ resolve?: (id: string, options: LoadConfigOptions<T, MT>) => null | undefined | ResolvedConfig<T, MT> | Promise<ResolvedConfig<T, MT> | undefined | null>;
65
83
  jiti?: JITI;
66
84
  jitiOptions?: JITIOptions;
67
85
  extend?: false | {
68
86
  extendKey?: string | string[];
69
87
  };
70
88
  }
71
- declare function loadConfig<T extends InputConfig = InputConfig>(options: LoadConfigOptions<T>): Promise<ResolvedConfig<T>>;
89
+ type DefineConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> = (input: InputConfig<T, MT>) => InputConfig<T, MT>;
90
+ declare function createDefineConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta>(): DefineConfig<T, MT>;
91
+
92
+ declare function loadConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta>(options: LoadConfigOptions<T, MT>): Promise<ResolvedConfig<T, MT>>;
72
93
 
73
- export { ConfigLayer, DotenvOptions, Env, InputConfig, LoadConfigOptions, ResolveConfigOptions, ResolvedConfig, loadConfig, loadDotenv, setupDotenv };
94
+ export { C12InputConfig, ConfigLayer, ConfigLayerMeta, DefineConfig, DotenvOptions, Env, InputConfig, LoadConfigOptions, ResolvedConfig, SourceOptions, UserInputConfig, createDefineConfig, loadConfig, loadDotenv, setupDotenv };
package/dist/index.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  import { existsSync, promises } from 'node:fs';
2
2
  import { resolve, extname, dirname } from 'pathe';
3
3
  import * as dotenv from 'dotenv';
4
- import { rmdir } from 'node:fs/promises';
4
+ import { rm } from 'node:fs/promises';
5
5
  import { homedir } from 'node:os';
6
6
  import createJiti from 'jiti';
7
7
  import * as rc9 from 'rc9';
8
8
  import { defu } from 'defu';
9
- import { findWorkspaceDir } from 'pkg-types';
9
+ import { findWorkspaceDir, readPackageJSON } from 'pkg-types';
10
10
 
11
11
  async function setupDotenv(options) {
12
12
  const targetEnvironment = options.env ?? process.env;
@@ -30,7 +30,7 @@ async function loadDotenv(options) {
30
30
  const parsed = dotenv.parse(await promises.readFile(dotenvFile, "utf8"));
31
31
  Object.assign(environment, parsed);
32
32
  }
33
- if (!options.env._applied) {
33
+ if (!options.env?._applied) {
34
34
  Object.assign(environment, options.env);
35
35
  environment._applied = true;
36
36
  }
@@ -51,15 +51,15 @@ function interpolate(target, source = {}, parse = (v) => v) {
51
51
  return parse(
52
52
  // eslint-disable-next-line unicorn/no-array-reduce
53
53
  matches.reduce((newValue, match) => {
54
- const parts = /(.?)\${?([\w:]+)?}?/g.exec(match);
54
+ const parts = /(.?)\${?([\w:]+)?}?/g.exec(match) || [];
55
55
  const prefix = parts[1];
56
56
  let value2, replacePart;
57
57
  if (prefix === "\\") {
58
- replacePart = parts[0];
58
+ replacePart = parts[0] || "";
59
59
  value2 = replacePart.replace("\\$", "$");
60
60
  } else {
61
61
  const key = parts[2];
62
- replacePart = parts[0].slice(prefix.length);
62
+ replacePart = (parts[0] || "").slice(prefix.length);
63
63
  if (parents.includes(key)) {
64
64
  console.warn(
65
65
  `Please avoid recursive environment variables ( loop: ${parents.join(
@@ -83,6 +83,7 @@ function interpolate(target, source = {}, parse = (v) => v) {
83
83
  async function loadConfig(options) {
84
84
  options.cwd = resolve(process.cwd(), options.cwd || ".");
85
85
  options.name = options.name || "config";
86
+ options.envName = options.envName ?? process.env.NODE_ENV;
86
87
  options.configFile = options.configFile ?? (options.name !== "config" ? `${options.name}.config` : "config");
87
88
  options.rcFile = options.rcFile ?? `.${options.name}rc`;
88
89
  if (options.extend !== false) {
@@ -134,10 +135,21 @@ async function loadConfig(options) {
134
135
  rc9.read({ name: options.rcFile, dir: options.cwd })
135
136
  );
136
137
  }
138
+ const pkgJson = {};
139
+ if (options.packageJson) {
140
+ const keys = (Array.isArray(options.packageJson) ? options.packageJson : [
141
+ typeof options.packageJson === "string" ? options.packageJson : options.name
142
+ ]).filter((t) => t && typeof t === "string");
143
+ const pkgJsonFile = await readPackageJSON(options.cwd).catch(() => {
144
+ });
145
+ const values = keys.map((key) => pkgJsonFile?.[key]);
146
+ Object.assign(pkgJson, defu({}, ...values));
147
+ }
137
148
  r.config = defu(
138
149
  options.overrides,
139
150
  config,
140
151
  configRC,
152
+ pkgJson,
141
153
  options.defaultConfig
142
154
  );
143
155
  if (options.extend) {
@@ -153,7 +165,8 @@ async function loadConfig(options) {
153
165
  cwd: void 0
154
166
  },
155
167
  { config, configFile: options.configFile, cwd: options.cwd },
156
- options.rcFile && { config: configRC, configFile: options.rcFile }
168
+ options.rcFile && { config: configRC, configFile: options.rcFile },
169
+ options.packageJson && { config: pkgJson, configFile: "package.json" }
157
170
  ].filter((l) => l && l.config);
158
171
  r.layers = [...baseLayers, ...r.layers];
159
172
  if (options.defaults) {
@@ -179,16 +192,26 @@ async function extendConfig(config, options) {
179
192
  );
180
193
  delete config[key];
181
194
  }
182
- for (const extendSource of extendSources) {
195
+ for (let extendSource of extendSources) {
196
+ const originalExtendSource = extendSource;
197
+ let sourceOptions = {};
198
+ if (extendSource.source) {
199
+ sourceOptions = extendSource.options || {};
200
+ extendSource = extendSource.source;
201
+ }
202
+ if (Array.isArray(extendSource)) {
203
+ sourceOptions = extendSource[1] || {};
204
+ extendSource = extendSource[0];
205
+ }
183
206
  if (typeof extendSource !== "string") {
184
207
  console.warn(
185
208
  `Cannot extend config from \`${JSON.stringify(
186
- extendSource
187
- )}\` (which should be a string) in ${options.cwd}`
209
+ originalExtendSource
210
+ )}\` in ${options.cwd}`
188
211
  );
189
212
  continue;
190
213
  }
191
- const _config = await resolveConfig(extendSource, options);
214
+ const _config = await resolveConfig(extendSource, options, sourceOptions);
192
215
  if (!_config.config) {
193
216
  console.warn(
194
217
  `Cannot extend config from \`${extendSource}\` in ${options.cwd}`
@@ -204,8 +227,8 @@ async function extendConfig(config, options) {
204
227
  }
205
228
  }
206
229
  const GIT_PREFIXES = ["github:", "gitlab:", "bitbucket:", "https://"];
207
- const NPM_PACKAGE_RE = /^(@[\da-z~-][\d._a-z~-]*\/)?[\da-z~-][\d._a-z~-]*$/;
208
- async function resolveConfig(source, options) {
230
+ const NPM_PACKAGE_RE = /^(@[\da-z~-][\d._a-z~-]*\/)?[\da-z~-][\d._a-z~-]*($|\/.*)/;
231
+ async function resolveConfig(source, options, sourceOptions = {}) {
209
232
  if (options.resolve) {
210
233
  const res2 = await options.resolve(source, options);
211
234
  if (res2) {
@@ -219,10 +242,10 @@ async function resolveConfig(source, options) {
219
242
  const name = gitRepo.replace(/[#/:@\\]/g, "_");
220
243
  const tmpDir = process.env.XDG_CACHE_HOME ? resolve(process.env.XDG_CACHE_HOME, "c12", name) : resolve(homedir(), ".cache/c12", name);
221
244
  if (existsSync(tmpDir)) {
222
- await rmdir(tmpDir, { recursive: true });
245
+ await rm(tmpDir, { recursive: true });
223
246
  }
224
- const clonned = await downloadTemplate(source, { dir: tmpDir });
225
- source = clonned.dir;
247
+ const cloned = await downloadTemplate(source, { dir: tmpDir });
248
+ source = cloned.dir;
226
249
  }
227
250
  if (NPM_PACKAGE_RE.test(source)) {
228
251
  try {
@@ -235,7 +258,12 @@ async function resolveConfig(source, options) {
235
258
  if (isDir) {
236
259
  source = options.configFile;
237
260
  }
238
- const res = { config: void 0, cwd };
261
+ const res = {
262
+ config: void 0,
263
+ cwd,
264
+ source,
265
+ sourceOptions
266
+ };
239
267
  try {
240
268
  res.configFile = options.jiti.resolve(resolve(cwd, source), {
241
269
  paths: [cwd]
@@ -246,10 +274,28 @@ async function resolveConfig(source, options) {
246
274
  return res;
247
275
  }
248
276
  res.config = options.jiti(res.configFile);
249
- if (typeof res.config === "function") {
277
+ if (res.config instanceof Function) {
250
278
  res.config = await res.config();
251
279
  }
280
+ if (options.envName) {
281
+ const envConfig = {
282
+ ...res.config["$" + options.envName],
283
+ ...res.config.$env?.[options.envName]
284
+ };
285
+ if (Object.keys(envConfig).length > 0) {
286
+ res.config = defu(envConfig, res.config);
287
+ }
288
+ }
289
+ res.meta = defu(res.sourceOptions.meta, res.config.$meta);
290
+ delete res.config.$meta;
291
+ if (res.sourceOptions.overrides) {
292
+ res.config = defu(res.sourceOptions.overrides, res.config);
293
+ }
252
294
  return res;
253
295
  }
254
296
 
255
- export { loadConfig, loadDotenv, setupDotenv };
297
+ function createDefineConfig() {
298
+ return (input) => input;
299
+ }
300
+
301
+ export { createDefineConfig, loadConfig, loadDotenv, setupDotenv };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c12",
3
- "version": "1.1.2",
3
+ "version": "1.3.0",
4
4
  "description": "Smart Config Loader",
5
5
  "repository": "unjs/c12",
6
6
  "license": "MIT",
@@ -26,27 +26,29 @@
26
26
  "lint:fix": "eslint --ext .ts,.js,.mjs,.cjs . --fix && prettier -w src test",
27
27
  "prepack": "unbuild",
28
28
  "release": "changelogen --release && npm publish && git push --follow-tags",
29
- "test": "vitest run --coverage"
29
+ "test": "vitest run --coverage && pnpm test:types",
30
+ "test:types": "tsc --noEmit"
30
31
  },
31
32
  "dependencies": {
32
33
  "defu": "^6.1.2",
33
34
  "dotenv": "^16.0.3",
34
- "giget": "^1.1.0",
35
- "jiti": "^1.17.1",
36
- "mlly": "^1.1.1",
35
+ "giget": "^1.1.2",
36
+ "jiti": "^1.18.2",
37
+ "mlly": "^1.2.0",
37
38
  "pathe": "^1.1.0",
38
39
  "pkg-types": "^1.0.2",
39
- "rc9": "^2.0.1"
40
+ "rc9": "^2.1.0"
40
41
  },
41
42
  "devDependencies": {
42
- "@vitest/coverage-c8": "^0.28.5",
43
- "changelogen": "^0.4.1",
44
- "eslint": "^8.34.0",
43
+ "@vitest/coverage-c8": "^0.30.1",
44
+ "changelogen": "^0.5.3",
45
+ "eslint": "^8.38.0",
45
46
  "eslint-config-unjs": "^0.1.0",
46
- "prettier": "^2.8.4",
47
- "typescript": "^4.9.5",
48
- "unbuild": "^1.1.1",
49
- "vitest": "^0.28.5"
47
+ "expect-type": "^0.15.0",
48
+ "prettier": "^2.8.7",
49
+ "typescript": "^5.0.4",
50
+ "unbuild": "^1.2.1",
51
+ "vitest": "^0.30.1"
50
52
  },
51
- "packageManager": "pnpm@7.27.0"
52
- }
53
+ "packageManager": "pnpm@8.2.0"
54
+ }