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 +88 -46
- package/dist/index.cjs +71 -20
- package/dist/index.d.ts +32 -11
- package/dist/index.mjs +65 -19
- package/package.json +17 -15
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
|
-
|
|
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
|
-
-
|
|
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
|
|
39
|
+
import { loadConfig } from "c12";
|
|
38
40
|
|
|
39
41
|
// CommonJS
|
|
40
|
-
const { loadConfig } = require(
|
|
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.
|
|
58
|
-
2.
|
|
59
|
+
1. Config overrides passed by options
|
|
60
|
+
2. Config file in CWD
|
|
59
61
|
3. RC file in CWD
|
|
60
|
-
4.
|
|
61
|
-
5.
|
|
62
|
-
6.
|
|
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.
|
|
71
|
+
Resolve configuration from this working directory. The default is `process.cwd()`
|
|
69
72
|
|
|
70
73
|
### `name`
|
|
71
74
|
|
|
72
|
-
Configuration base name.
|
|
75
|
+
Configuration base name. The default is `config`.
|
|
73
76
|
|
|
74
77
|
### `configName`
|
|
75
78
|
|
|
76
|
-
Configuration file name without extension
|
|
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
|
-
### `
|
|
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
|
-
|
|
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
|
|
123
|
-
If it starts with either
|
|
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:
|
|
150
|
+
primary: "user_primary",
|
|
134
151
|
},
|
|
135
|
-
extends: [
|
|
136
|
-
|
|
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:
|
|
166
|
+
extends: "../base",
|
|
153
167
|
colors: {
|
|
154
|
-
primary:
|
|
155
|
-
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
|
-
|
|
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-
|
|
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
|
|
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__*/
|
|
25
|
-
const
|
|
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
|
|
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 ||
|
|
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 (
|
|
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
|
-
|
|
203
|
-
)}\`
|
|
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.
|
|
265
|
+
await promises.rm(tmpDir, { recursive: true });
|
|
239
266
|
}
|
|
240
|
-
const
|
|
241
|
-
source =
|
|
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 = {
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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 {
|
|
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
|
|
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 (
|
|
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
|
-
|
|
187
|
-
)}\`
|
|
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
|
|
245
|
+
await rm(tmpDir, { recursive: true });
|
|
223
246
|
}
|
|
224
|
-
const
|
|
225
|
-
source =
|
|
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 = {
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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.
|
|
35
|
-
"jiti": "^1.
|
|
36
|
-
"mlly": "^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
|
|
40
|
+
"rc9": "^2.1.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
|
-
"@vitest/coverage-c8": "^0.
|
|
43
|
-
"changelogen": "^0.
|
|
44
|
-
"eslint": "^8.
|
|
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
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
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@
|
|
52
|
-
}
|
|
53
|
+
"packageManager": "pnpm@8.2.0"
|
|
54
|
+
}
|