c12 1.2.0 → 1.4.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 +55 -13
- package/dist/index.cjs +106 -10
- package/dist/index.d.ts +49 -24
- package/dist/index.mjs +98 -8
- package/package.json +18 -13
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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
|
|
|
@@ -13,8 +13,10 @@
|
|
|
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
|
-
-
|
|
17
|
-
-
|
|
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)
|
|
19
|
+
- Config watcher with auto-reload and HMR support
|
|
18
20
|
|
|
19
21
|
## Usage
|
|
20
22
|
|
|
@@ -35,10 +37,10 @@ Import:
|
|
|
35
37
|
|
|
36
38
|
```js
|
|
37
39
|
// ESM
|
|
38
|
-
import { loadConfig } from "c12";
|
|
40
|
+
import { loadConfig, watchConfig } from "c12";
|
|
39
41
|
|
|
40
42
|
// CommonJS
|
|
41
|
-
const { loadConfig } = require("c12");
|
|
43
|
+
const { loadConfig, watchConfig } = require("c12");
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
Load configuration:
|
|
@@ -58,7 +60,7 @@ c12 merged config sources with [unjs/defu](https://github.com/unjs/defu) by belo
|
|
|
58
60
|
1. Config overrides passed by options
|
|
59
61
|
2. Config file in CWD
|
|
60
62
|
3. RC file in CWD
|
|
61
|
-
4. Global RC file in user's home directory
|
|
63
|
+
4. Global RC file in the user's home directory
|
|
62
64
|
5. Config from `package.json`
|
|
63
65
|
6. Default config passed by options
|
|
64
66
|
7. Extended config layers
|
|
@@ -233,6 +235,46 @@ c12 tries to match [`envName`](#envname) and override environment config if spec
|
|
|
233
235
|
}
|
|
234
236
|
```
|
|
235
237
|
|
|
238
|
+
## Watching Configuration
|
|
239
|
+
|
|
240
|
+
you can use `watchConfig` instead of `loadConfig` to load config and watch for changes, add and removals in all expected configuration paths and auto reload with new config.
|
|
241
|
+
|
|
242
|
+
### Lifecycle hooks
|
|
243
|
+
|
|
244
|
+
- `onWatch`: This function is always called when config is updated, added, or removed before attempting to reload the config.
|
|
245
|
+
- `acceptHMR`: By implementing this function, you can compare old and new functions and return `true` if a full reload is not needed.
|
|
246
|
+
- `onUpdate`: This function is always called after the new config is updated. If `acceptHMR` returns true, it will be skipped.
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import { watchConfig } from "c12";
|
|
250
|
+
|
|
251
|
+
const config = watchConfig({
|
|
252
|
+
cwd: ".",
|
|
253
|
+
// chokidarOptions: {}, // Default is { ignoreInitial: true }
|
|
254
|
+
// debounce: 200 // Default is 100. You can set it to false to disable debounced watcher
|
|
255
|
+
onWatch: (event) => {
|
|
256
|
+
console.log("[watcher]", event.type, event.path);
|
|
257
|
+
},
|
|
258
|
+
acceptHMR({ oldConfig, newConfig, getDiff }) {
|
|
259
|
+
const diff = getDiff();
|
|
260
|
+
if (diff.length === 0) {
|
|
261
|
+
console.log("No config changed detected!");
|
|
262
|
+
return true; // No changes!
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
onUpdate({ oldConfig, newConfig, getDiff }) {
|
|
266
|
+
const diff = getDiff();
|
|
267
|
+
console.log("Config updated:\n" + diff.map((i) => i.toJSON()).join("\n"));
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
console.log("watching config files:", config.watchingFiles);
|
|
272
|
+
console.log("initial config", config.config);
|
|
273
|
+
|
|
274
|
+
// Stop watcher when not needed anymore
|
|
275
|
+
// await config.unwatch();
|
|
276
|
+
```
|
|
277
|
+
|
|
236
278
|
## 💻 Development
|
|
237
279
|
|
|
238
280
|
- Clone this repository
|
|
@@ -246,11 +288,11 @@ Made with 💛 Published under [MIT License](./LICENSE).
|
|
|
246
288
|
|
|
247
289
|
<!-- Badges -->
|
|
248
290
|
|
|
249
|
-
[npm-version-src]: https://img.shields.io/npm/v/c12?style=flat
|
|
291
|
+
[npm-version-src]: https://img.shields.io/npm/v/c12?style=flat&colorA=18181B&colorB=F0DB4F
|
|
250
292
|
[npm-version-href]: https://npmjs.com/package/c12
|
|
251
|
-
[npm-downloads-src]: https://img.shields.io/npm/dm/c12?style=flat
|
|
293
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/c12?style=flat&colorA=18181B&colorB=F0DB4F
|
|
252
294
|
[npm-downloads-href]: https://npmjs.com/package/c12
|
|
253
|
-
[
|
|
254
|
-
[github-actions-href]: https://github.com/unjs/c12/actions?query=workflow%3Aci
|
|
255
|
-
[codecov-src]: https://img.shields.io/codecov/c/gh/unjs/c12/main?style=flat-square
|
|
295
|
+
[codecov-src]: https://img.shields.io/codecov/c/gh/unjs/c12/main?style=flat&colorA=18181B&colorB=F0DB4F
|
|
256
296
|
[codecov-href]: https://codecov.io/gh/unjs/c12
|
|
297
|
+
[license-src]: https://img.shields.io/github/license/unjs/c12.svg?style=flat&colorA=18181B&colorB=F0DB4F
|
|
298
|
+
[license-href]: https://github.com/unjs/c12/blob/main/LICENSE
|
package/dist/index.cjs
CHANGED
|
@@ -9,8 +9,14 @@ const createJiti = require('jiti');
|
|
|
9
9
|
const rc9 = require('rc9');
|
|
10
10
|
const defu = require('defu');
|
|
11
11
|
const pkgTypes = require('pkg-types');
|
|
12
|
+
const chokidar = require('chokidar');
|
|
13
|
+
const perfectDebounce = require('perfect-debounce');
|
|
14
|
+
const ohash = require('ohash');
|
|
12
15
|
|
|
13
|
-
function
|
|
16
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
17
|
+
|
|
18
|
+
function _interopNamespaceCompat(e) {
|
|
19
|
+
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
14
20
|
const n = Object.create(null);
|
|
15
21
|
if (e) {
|
|
16
22
|
for (const k in e) {
|
|
@@ -21,8 +27,9 @@ function _interopNamespaceDefault(e) {
|
|
|
21
27
|
return n;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
const dotenv__namespace = /*#__PURE__*/
|
|
25
|
-
const
|
|
30
|
+
const dotenv__namespace = /*#__PURE__*/_interopNamespaceCompat(dotenv);
|
|
31
|
+
const createJiti__default = /*#__PURE__*/_interopDefaultCompat(createJiti);
|
|
32
|
+
const rc9__namespace = /*#__PURE__*/_interopNamespaceCompat(rc9);
|
|
26
33
|
|
|
27
34
|
async function setupDotenv(options) {
|
|
28
35
|
const targetEnvironment = options.env ?? process.env;
|
|
@@ -46,7 +53,7 @@ async function loadDotenv(options) {
|
|
|
46
53
|
const parsed = dotenv__namespace.parse(await node_fs.promises.readFile(dotenvFile, "utf8"));
|
|
47
54
|
Object.assign(environment, parsed);
|
|
48
55
|
}
|
|
49
|
-
if (!options.env
|
|
56
|
+
if (!options.env?._applied) {
|
|
50
57
|
Object.assign(environment, options.env);
|
|
51
58
|
environment._applied = true;
|
|
52
59
|
}
|
|
@@ -67,15 +74,15 @@ function interpolate(target, source = {}, parse = (v) => v) {
|
|
|
67
74
|
return parse(
|
|
68
75
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
69
76
|
matches.reduce((newValue, match) => {
|
|
70
|
-
const parts = /(.?)\${?([\w:]+)?}?/g.exec(match);
|
|
77
|
+
const parts = /(.?)\${?([\w:]+)?}?/g.exec(match) || [];
|
|
71
78
|
const prefix = parts[1];
|
|
72
79
|
let value2, replacePart;
|
|
73
80
|
if (prefix === "\\") {
|
|
74
|
-
replacePart = parts[0];
|
|
81
|
+
replacePart = parts[0] || "";
|
|
75
82
|
value2 = replacePart.replace("\\$", "$");
|
|
76
83
|
} else {
|
|
77
84
|
const key = parts[2];
|
|
78
|
-
replacePart = parts[0].slice(prefix.length);
|
|
85
|
+
replacePart = (parts[0] || "").slice(prefix.length);
|
|
79
86
|
if (parents.includes(key)) {
|
|
80
87
|
console.warn(
|
|
81
88
|
`Please avoid recursive environment variables ( loop: ${parents.join(
|
|
@@ -108,7 +115,7 @@ async function loadConfig(options) {
|
|
|
108
115
|
...options.extend
|
|
109
116
|
};
|
|
110
117
|
}
|
|
111
|
-
options.jiti = options.jiti ||
|
|
118
|
+
options.jiti = options.jiti || createJiti__default(void 0, {
|
|
112
119
|
interopDefault: true,
|
|
113
120
|
requireCache: false,
|
|
114
121
|
esmResolve: true,
|
|
@@ -258,7 +265,7 @@ async function resolveConfig(source, options, sourceOptions = {}) {
|
|
|
258
265
|
const name = gitRepo.replace(/[#/:@\\]/g, "_");
|
|
259
266
|
const tmpDir = process.env.XDG_CACHE_HOME ? pathe.resolve(process.env.XDG_CACHE_HOME, "c12", name) : pathe.resolve(node_os.homedir(), ".cache/c12", name);
|
|
260
267
|
if (node_fs.existsSync(tmpDir)) {
|
|
261
|
-
await promises.
|
|
268
|
+
await promises.rm(tmpDir, { recursive: true });
|
|
262
269
|
}
|
|
263
270
|
const cloned = await downloadTemplate(source, { dir: tmpDir });
|
|
264
271
|
source = cloned.dir;
|
|
@@ -274,7 +281,12 @@ async function resolveConfig(source, options, sourceOptions = {}) {
|
|
|
274
281
|
if (isDir) {
|
|
275
282
|
source = options.configFile;
|
|
276
283
|
}
|
|
277
|
-
const res = {
|
|
284
|
+
const res = {
|
|
285
|
+
config: void 0,
|
|
286
|
+
cwd,
|
|
287
|
+
source,
|
|
288
|
+
sourceOptions
|
|
289
|
+
};
|
|
278
290
|
try {
|
|
279
291
|
res.configFile = options.jiti.resolve(pathe.resolve(cwd, source), {
|
|
280
292
|
paths: [cwd]
|
|
@@ -305,6 +317,90 @@ async function resolveConfig(source, options, sourceOptions = {}) {
|
|
|
305
317
|
return res;
|
|
306
318
|
}
|
|
307
319
|
|
|
320
|
+
function createDefineConfig() {
|
|
321
|
+
return (input) => input;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const eventMap = {
|
|
325
|
+
add: "created",
|
|
326
|
+
change: "updated",
|
|
327
|
+
unlink: "removed"
|
|
328
|
+
};
|
|
329
|
+
async function watchConfig(options) {
|
|
330
|
+
let config = await loadConfig(options);
|
|
331
|
+
const configName = options.name || "config";
|
|
332
|
+
const watchingFiles = [
|
|
333
|
+
...new Set(
|
|
334
|
+
(config.layers || []).filter((l) => l.cwd).flatMap((l) => [
|
|
335
|
+
...["ts", "js", "mjs", "cjs", "cts", "mts", "json"].map(
|
|
336
|
+
(ext) => pathe.resolve(l.cwd, (options.name || "config") + "." + ext)
|
|
337
|
+
),
|
|
338
|
+
l.source && pathe.resolve(l.cwd, l.source),
|
|
339
|
+
// TODO: Support watching rc from home and workspace
|
|
340
|
+
options.rcFile && pathe.resolve(
|
|
341
|
+
l.cwd,
|
|
342
|
+
typeof options.rcFile === "string" ? options.rcFile : `.${configName}rc`
|
|
343
|
+
),
|
|
344
|
+
options.packageJson && pathe.resolve(l.cwd, "package.json")
|
|
345
|
+
]).filter(Boolean)
|
|
346
|
+
)
|
|
347
|
+
];
|
|
348
|
+
const _fswatcher = chokidar.watch(watchingFiles, {
|
|
349
|
+
ignoreInitial: true,
|
|
350
|
+
...options.chokidarOptions
|
|
351
|
+
});
|
|
352
|
+
const onChange = async (event, path) => {
|
|
353
|
+
const type = eventMap[event];
|
|
354
|
+
if (!type) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (options.onWatch) {
|
|
358
|
+
await options.onWatch({
|
|
359
|
+
type,
|
|
360
|
+
path
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
const oldConfig = config;
|
|
364
|
+
const newConfig = await loadConfig(options);
|
|
365
|
+
config = newConfig;
|
|
366
|
+
const changeCtx = {
|
|
367
|
+
newConfig,
|
|
368
|
+
oldConfig,
|
|
369
|
+
getDiff: () => ohash.diff(oldConfig.config, config.config)
|
|
370
|
+
};
|
|
371
|
+
if (options.acceptHMR) {
|
|
372
|
+
const changeHandled = await options.acceptHMR(changeCtx);
|
|
373
|
+
if (changeHandled) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (options.onUpdate) {
|
|
378
|
+
await options.onUpdate(changeCtx);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
if (options.debounce !== false) {
|
|
382
|
+
_fswatcher.on("all", perfectDebounce.debounce(onChange, options.debounce));
|
|
383
|
+
} else {
|
|
384
|
+
_fswatcher.on("all", onChange);
|
|
385
|
+
}
|
|
386
|
+
const utils = {
|
|
387
|
+
watchingFiles,
|
|
388
|
+
unwatch: async () => {
|
|
389
|
+
await _fswatcher.close();
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
return new Proxy(utils, {
|
|
393
|
+
get(_, prop) {
|
|
394
|
+
if (prop in utils) {
|
|
395
|
+
return utils[prop];
|
|
396
|
+
}
|
|
397
|
+
return config[prop];
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
exports.createDefineConfig = createDefineConfig;
|
|
308
403
|
exports.loadConfig = loadConfig;
|
|
309
404
|
exports.loadDotenv = loadDotenv;
|
|
310
405
|
exports.setupDotenv = setupDotenv;
|
|
406
|
+
exports.watchConfig = watchConfig;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { JITI } from 'jiti';
|
|
2
2
|
import { JITIOptions } from 'jiti/dist/types';
|
|
3
|
+
import { WatchOptions } from 'chokidar';
|
|
4
|
+
import { diff } from 'ohash';
|
|
3
5
|
|
|
4
6
|
interface DotenvOptions {
|
|
5
7
|
/**
|
|
@@ -37,41 +39,37 @@ declare function setupDotenv(options: DotenvOptions): Promise<Env>;
|
|
|
37
39
|
/** Load environment variables into an object. */
|
|
38
40
|
declare function loadDotenv(options: DotenvOptions): Promise<Env>;
|
|
39
41
|
|
|
40
|
-
type UserInputConfig = Record<string, any>;
|
|
41
42
|
interface ConfigLayerMeta {
|
|
42
43
|
name?: string;
|
|
43
44
|
[key: string]: any;
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
$
|
|
48
|
-
$
|
|
49
|
-
$
|
|
50
|
-
$
|
|
51
|
-
|
|
52
|
-
interface InputConfig extends C12InputConfig, UserInputConfig {
|
|
46
|
+
type UserInputConfig = Record<string, any>;
|
|
47
|
+
interface C12InputConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> {
|
|
48
|
+
$test?: T;
|
|
49
|
+
$development?: T;
|
|
50
|
+
$production?: T;
|
|
51
|
+
$env?: Record<string, T>;
|
|
52
|
+
$meta?: MT;
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
type InputConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> = C12InputConfig<T, MT> & T;
|
|
55
|
+
interface SourceOptions<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> {
|
|
56
|
+
meta?: MT;
|
|
57
|
+
overrides?: T;
|
|
57
58
|
[key: string]: any;
|
|
58
59
|
}
|
|
59
|
-
interface ConfigLayer<T extends
|
|
60
|
+
interface ConfigLayer<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> {
|
|
60
61
|
config: T | null;
|
|
61
62
|
source?: string;
|
|
62
|
-
sourceOptions?: SourceOptions
|
|
63
|
-
meta?:
|
|
63
|
+
sourceOptions?: SourceOptions<T, MT>;
|
|
64
|
+
meta?: MT;
|
|
64
65
|
cwd?: string;
|
|
65
66
|
configFile?: string;
|
|
66
67
|
}
|
|
67
|
-
interface ResolvedConfig<T extends
|
|
68
|
-
layers?: ConfigLayer<T>[];
|
|
68
|
+
interface ResolvedConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> extends ConfigLayer<T, MT> {
|
|
69
|
+
layers?: ConfigLayer<T, MT>[];
|
|
69
70
|
cwd?: string;
|
|
70
71
|
}
|
|
71
|
-
interface
|
|
72
|
-
cwd: string;
|
|
73
|
-
}
|
|
74
|
-
interface LoadConfigOptions<T extends InputConfig = InputConfig> {
|
|
72
|
+
interface LoadConfigOptions<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> {
|
|
75
73
|
name?: string;
|
|
76
74
|
cwd?: string;
|
|
77
75
|
configFile?: string;
|
|
@@ -83,13 +81,40 @@ interface LoadConfigOptions<T extends InputConfig = InputConfig> {
|
|
|
83
81
|
defaults?: T;
|
|
84
82
|
defaultConfig?: T;
|
|
85
83
|
overrides?: T;
|
|
86
|
-
resolve?: (id: string, options: LoadConfigOptions) => null | ResolvedConfig | Promise<ResolvedConfig | null>;
|
|
84
|
+
resolve?: (id: string, options: LoadConfigOptions<T, MT>) => null | undefined | ResolvedConfig<T, MT> | Promise<ResolvedConfig<T, MT> | undefined | null>;
|
|
87
85
|
jiti?: JITI;
|
|
88
86
|
jitiOptions?: JITIOptions;
|
|
89
87
|
extend?: false | {
|
|
90
88
|
extendKey?: string | string[];
|
|
91
89
|
};
|
|
92
90
|
}
|
|
93
|
-
|
|
91
|
+
type DefineConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> = (input: InputConfig<T, MT>) => InputConfig<T, MT>;
|
|
92
|
+
declare function createDefineConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta>(): DefineConfig<T, MT>;
|
|
93
|
+
|
|
94
|
+
declare function loadConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta>(options: LoadConfigOptions<T, MT>): Promise<ResolvedConfig<T, MT>>;
|
|
95
|
+
|
|
96
|
+
type ConfigWatcher<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> = ResolvedConfig<T, MT> & {
|
|
97
|
+
watchingFiles: string[];
|
|
98
|
+
unwatch: () => Promise<void>;
|
|
99
|
+
};
|
|
100
|
+
interface WatchConfigOptions<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta> extends LoadConfigOptions<T, MT> {
|
|
101
|
+
chokidarOptions?: WatchOptions;
|
|
102
|
+
debounce?: false | number;
|
|
103
|
+
onWatch?: (event: {
|
|
104
|
+
type: "created" | "updated" | "removed";
|
|
105
|
+
path: string;
|
|
106
|
+
}) => void | Promise<void>;
|
|
107
|
+
acceptHMR?: (context: {
|
|
108
|
+
getDiff: () => ReturnType<typeof diff>;
|
|
109
|
+
newConfig: ResolvedConfig<T, MT>;
|
|
110
|
+
oldConfig: ResolvedConfig<T, MT>;
|
|
111
|
+
}) => void | boolean | Promise<void | boolean>;
|
|
112
|
+
onUpdate?: (context: {
|
|
113
|
+
getDiff: () => ReturnType<typeof diff>;
|
|
114
|
+
newConfig: ResolvedConfig<T, MT>;
|
|
115
|
+
oldConfig: ResolvedConfig<T, MT>;
|
|
116
|
+
}) => void | Promise<void>;
|
|
117
|
+
}
|
|
118
|
+
declare function watchConfig<T extends UserInputConfig = UserInputConfig, MT extends ConfigLayerMeta = ConfigLayerMeta>(options: WatchConfigOptions<T, MT>): Promise<ConfigWatcher<T, MT>>;
|
|
94
119
|
|
|
95
|
-
export { C12InputConfig, ConfigLayer, ConfigLayerMeta, DotenvOptions, Env, InputConfig, LoadConfigOptions,
|
|
120
|
+
export { C12InputConfig, ConfigLayer, ConfigLayerMeta, ConfigWatcher, DefineConfig, DotenvOptions, Env, InputConfig, LoadConfigOptions, ResolvedConfig, SourceOptions, UserInputConfig, WatchConfigOptions, createDefineConfig, loadConfig, loadDotenv, setupDotenv, watchConfig };
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
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
9
|
import { findWorkspaceDir, readPackageJSON } from 'pkg-types';
|
|
10
|
+
import { watch } from 'chokidar';
|
|
11
|
+
import { debounce } from 'perfect-debounce';
|
|
12
|
+
import { diff } from 'ohash';
|
|
10
13
|
|
|
11
14
|
async function setupDotenv(options) {
|
|
12
15
|
const targetEnvironment = options.env ?? process.env;
|
|
@@ -30,7 +33,7 @@ async function loadDotenv(options) {
|
|
|
30
33
|
const parsed = dotenv.parse(await promises.readFile(dotenvFile, "utf8"));
|
|
31
34
|
Object.assign(environment, parsed);
|
|
32
35
|
}
|
|
33
|
-
if (!options.env
|
|
36
|
+
if (!options.env?._applied) {
|
|
34
37
|
Object.assign(environment, options.env);
|
|
35
38
|
environment._applied = true;
|
|
36
39
|
}
|
|
@@ -51,15 +54,15 @@ function interpolate(target, source = {}, parse = (v) => v) {
|
|
|
51
54
|
return parse(
|
|
52
55
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
53
56
|
matches.reduce((newValue, match) => {
|
|
54
|
-
const parts = /(.?)\${?([\w:]+)?}?/g.exec(match);
|
|
57
|
+
const parts = /(.?)\${?([\w:]+)?}?/g.exec(match) || [];
|
|
55
58
|
const prefix = parts[1];
|
|
56
59
|
let value2, replacePart;
|
|
57
60
|
if (prefix === "\\") {
|
|
58
|
-
replacePart = parts[0];
|
|
61
|
+
replacePart = parts[0] || "";
|
|
59
62
|
value2 = replacePart.replace("\\$", "$");
|
|
60
63
|
} else {
|
|
61
64
|
const key = parts[2];
|
|
62
|
-
replacePart = parts[0].slice(prefix.length);
|
|
65
|
+
replacePart = (parts[0] || "").slice(prefix.length);
|
|
63
66
|
if (parents.includes(key)) {
|
|
64
67
|
console.warn(
|
|
65
68
|
`Please avoid recursive environment variables ( loop: ${parents.join(
|
|
@@ -242,7 +245,7 @@ async function resolveConfig(source, options, sourceOptions = {}) {
|
|
|
242
245
|
const name = gitRepo.replace(/[#/:@\\]/g, "_");
|
|
243
246
|
const tmpDir = process.env.XDG_CACHE_HOME ? resolve(process.env.XDG_CACHE_HOME, "c12", name) : resolve(homedir(), ".cache/c12", name);
|
|
244
247
|
if (existsSync(tmpDir)) {
|
|
245
|
-
await
|
|
248
|
+
await rm(tmpDir, { recursive: true });
|
|
246
249
|
}
|
|
247
250
|
const cloned = await downloadTemplate(source, { dir: tmpDir });
|
|
248
251
|
source = cloned.dir;
|
|
@@ -258,7 +261,12 @@ async function resolveConfig(source, options, sourceOptions = {}) {
|
|
|
258
261
|
if (isDir) {
|
|
259
262
|
source = options.configFile;
|
|
260
263
|
}
|
|
261
|
-
const res = {
|
|
264
|
+
const res = {
|
|
265
|
+
config: void 0,
|
|
266
|
+
cwd,
|
|
267
|
+
source,
|
|
268
|
+
sourceOptions
|
|
269
|
+
};
|
|
262
270
|
try {
|
|
263
271
|
res.configFile = options.jiti.resolve(resolve(cwd, source), {
|
|
264
272
|
paths: [cwd]
|
|
@@ -289,4 +297,86 @@ async function resolveConfig(source, options, sourceOptions = {}) {
|
|
|
289
297
|
return res;
|
|
290
298
|
}
|
|
291
299
|
|
|
292
|
-
|
|
300
|
+
function createDefineConfig() {
|
|
301
|
+
return (input) => input;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const eventMap = {
|
|
305
|
+
add: "created",
|
|
306
|
+
change: "updated",
|
|
307
|
+
unlink: "removed"
|
|
308
|
+
};
|
|
309
|
+
async function watchConfig(options) {
|
|
310
|
+
let config = await loadConfig(options);
|
|
311
|
+
const configName = options.name || "config";
|
|
312
|
+
const watchingFiles = [
|
|
313
|
+
...new Set(
|
|
314
|
+
(config.layers || []).filter((l) => l.cwd).flatMap((l) => [
|
|
315
|
+
...["ts", "js", "mjs", "cjs", "cts", "mts", "json"].map(
|
|
316
|
+
(ext) => resolve(l.cwd, (options.name || "config") + "." + ext)
|
|
317
|
+
),
|
|
318
|
+
l.source && resolve(l.cwd, l.source),
|
|
319
|
+
// TODO: Support watching rc from home and workspace
|
|
320
|
+
options.rcFile && resolve(
|
|
321
|
+
l.cwd,
|
|
322
|
+
typeof options.rcFile === "string" ? options.rcFile : `.${configName}rc`
|
|
323
|
+
),
|
|
324
|
+
options.packageJson && resolve(l.cwd, "package.json")
|
|
325
|
+
]).filter(Boolean)
|
|
326
|
+
)
|
|
327
|
+
];
|
|
328
|
+
const _fswatcher = watch(watchingFiles, {
|
|
329
|
+
ignoreInitial: true,
|
|
330
|
+
...options.chokidarOptions
|
|
331
|
+
});
|
|
332
|
+
const onChange = async (event, path) => {
|
|
333
|
+
const type = eventMap[event];
|
|
334
|
+
if (!type) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (options.onWatch) {
|
|
338
|
+
await options.onWatch({
|
|
339
|
+
type,
|
|
340
|
+
path
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
const oldConfig = config;
|
|
344
|
+
const newConfig = await loadConfig(options);
|
|
345
|
+
config = newConfig;
|
|
346
|
+
const changeCtx = {
|
|
347
|
+
newConfig,
|
|
348
|
+
oldConfig,
|
|
349
|
+
getDiff: () => diff(oldConfig.config, config.config)
|
|
350
|
+
};
|
|
351
|
+
if (options.acceptHMR) {
|
|
352
|
+
const changeHandled = await options.acceptHMR(changeCtx);
|
|
353
|
+
if (changeHandled) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (options.onUpdate) {
|
|
358
|
+
await options.onUpdate(changeCtx);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
if (options.debounce !== false) {
|
|
362
|
+
_fswatcher.on("all", debounce(onChange, options.debounce));
|
|
363
|
+
} else {
|
|
364
|
+
_fswatcher.on("all", onChange);
|
|
365
|
+
}
|
|
366
|
+
const utils = {
|
|
367
|
+
watchingFiles,
|
|
368
|
+
unwatch: async () => {
|
|
369
|
+
await _fswatcher.close();
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
return new Proxy(utils, {
|
|
373
|
+
get(_, prop) {
|
|
374
|
+
if (prop in utils) {
|
|
375
|
+
return utils[prop];
|
|
376
|
+
}
|
|
377
|
+
return config[prop];
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export { createDefineConfig, loadConfig, loadDotenv, setupDotenv, watchConfig };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c12",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Smart Config Loader",
|
|
5
5
|
"repository": "unjs/c12",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,27 +26,32 @@
|
|
|
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": {
|
|
33
|
+
"chokidar": "^3.5.3",
|
|
32
34
|
"defu": "^6.1.2",
|
|
33
35
|
"dotenv": "^16.0.3",
|
|
34
36
|
"giget": "^1.1.2",
|
|
35
|
-
"jiti": "^1.
|
|
37
|
+
"jiti": "^1.18.2",
|
|
36
38
|
"mlly": "^1.2.0",
|
|
39
|
+
"ohash": "^1.1.1",
|
|
37
40
|
"pathe": "^1.1.0",
|
|
41
|
+
"perfect-debounce": "^0.1.3",
|
|
38
42
|
"pkg-types": "^1.0.2",
|
|
39
|
-
"rc9": "^2.0
|
|
43
|
+
"rc9": "^2.1.0"
|
|
40
44
|
},
|
|
41
45
|
"devDependencies": {
|
|
42
|
-
"@vitest/coverage-c8": "^0.
|
|
43
|
-
"changelogen": "^0.5.
|
|
44
|
-
"eslint": "^8.
|
|
46
|
+
"@vitest/coverage-c8": "^0.30.1",
|
|
47
|
+
"changelogen": "^0.5.3",
|
|
48
|
+
"eslint": "^8.38.0",
|
|
45
49
|
"eslint-config-unjs": "^0.1.0",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
+
"expect-type": "^0.15.0",
|
|
51
|
+
"prettier": "^2.8.7",
|
|
52
|
+
"typescript": "^5.0.4",
|
|
53
|
+
"unbuild": "^1.2.1",
|
|
54
|
+
"vitest": "^0.30.1"
|
|
50
55
|
},
|
|
51
|
-
"packageManager": "pnpm@
|
|
52
|
-
}
|
|
56
|
+
"packageManager": "pnpm@8.3.0"
|
|
57
|
+
}
|