c12 0.1.0 → 0.1.1
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 +86 -1
- package/dist/index.cjs +59 -12
- package/dist/index.d.ts +12 -10
- package/dist/index.mjs +59 -13
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[![Github Actions][github-actions-src]][github-actions-href]
|
|
6
6
|
[![Codecov][codecov-src]][codecov-href]
|
|
7
7
|
|
|
8
|
-
> Smart
|
|
8
|
+
> Smart Configuration Loader
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
@@ -13,6 +13,7 @@
|
|
|
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 sourecs
|
|
16
17
|
|
|
17
18
|
## Usage
|
|
18
19
|
|
|
@@ -42,7 +43,11 @@ const { loadConfig } = require('c12')
|
|
|
42
43
|
Load configuration:
|
|
43
44
|
|
|
44
45
|
```js
|
|
46
|
+
// Get loaded config
|
|
45
47
|
const { config } = await loadConfig({})
|
|
48
|
+
|
|
49
|
+
// Get resolved config and extended layers
|
|
50
|
+
const { config, configFile, layers } = await loadConfig({})
|
|
46
51
|
```
|
|
47
52
|
|
|
48
53
|
## Loading priority
|
|
@@ -54,6 +59,7 @@ c12 merged config sources with [unjs/defu](https://github.com/unjs/defu) by belo
|
|
|
54
59
|
3. RC file in CWD
|
|
55
60
|
4. global RC file in user's home directory
|
|
56
61
|
5. default config passed by options
|
|
62
|
+
6. Extended config layers
|
|
57
63
|
|
|
58
64
|
## Options
|
|
59
65
|
|
|
@@ -93,6 +99,85 @@ Specify default configuration. It has the **lowest** priority.
|
|
|
93
99
|
|
|
94
100
|
Specify override configuration. It has the **highest** priority.
|
|
95
101
|
|
|
102
|
+
## Extending configuration
|
|
103
|
+
|
|
104
|
+
If resolved config contains a `extends` key, it will be used to extend configuration.
|
|
105
|
+
|
|
106
|
+
Extending can be nested and each layer can extend from one base or more.
|
|
107
|
+
|
|
108
|
+
Final config is merged result of extended options and user options with [unjs/defu](https://github.com/unjs/defu).
|
|
109
|
+
|
|
110
|
+
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.
|
|
111
|
+
If it starts with either of `github:`, `gitlab:`, `bitbucket:` or `https:`, c12 autmatically clones it.
|
|
112
|
+
|
|
113
|
+
For custom merging strategies, you can directly access each layer with `layers` property.
|
|
114
|
+
|
|
115
|
+
**Example:**
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
// config.ts
|
|
119
|
+
export default {
|
|
120
|
+
colors: {
|
|
121
|
+
primary: 'user_primary'
|
|
122
|
+
},
|
|
123
|
+
extends: [
|
|
124
|
+
'./theme',
|
|
125
|
+
'./config.dev.ts'
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
// config.dev.ts
|
|
132
|
+
export default {
|
|
133
|
+
dev: true
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
// theme/config.ts
|
|
139
|
+
export default {
|
|
140
|
+
extends: '../base',
|
|
141
|
+
colors: {
|
|
142
|
+
primary: 'theme_primary',
|
|
143
|
+
secondary: 'theme_secondary'
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
// base/config.ts
|
|
150
|
+
export default {
|
|
151
|
+
colors: {
|
|
152
|
+
primary: 'base_primary'
|
|
153
|
+
text: 'base_text'
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Loaded configuration would look like this:
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
{
|
|
162
|
+
dev: true,
|
|
163
|
+
colors: {
|
|
164
|
+
primary: 'user_primary',
|
|
165
|
+
secondary: 'theme_secondary',
|
|
166
|
+
text: 'base_text'
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Layers:
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
[
|
|
175
|
+
{ config: /* theme config */, configFile: /* path/to/theme/config.ts */, cwd: /* path/to/theme */ },
|
|
176
|
+
{ config: /* base config */, configFile: /* path/to/base/config.ts */, cwd: /* path/to/base */ },
|
|
177
|
+
{ config: /* dev config */, configFile: /* path/to/config.dev.ts */, cwd: /* path/ */ },
|
|
178
|
+
]
|
|
179
|
+
```
|
|
180
|
+
|
|
96
181
|
## 💻 Development
|
|
97
182
|
|
|
98
183
|
- Clone this repository
|
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const pathe = require('pathe');
|
|
7
7
|
const dotenv = require('dotenv');
|
|
8
|
+
const os = require('os');
|
|
8
9
|
const createJiti = require('jiti');
|
|
9
10
|
const rc9 = require('rc9');
|
|
10
11
|
const defu = require('defu');
|
|
@@ -24,6 +25,7 @@ function _interopNamespace(e) {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const dotenv__namespace = /*#__PURE__*/_interopNamespace(dotenv);
|
|
28
|
+
const os__default = /*#__PURE__*/_interopDefaultLegacy(os);
|
|
27
29
|
const createJiti__default = /*#__PURE__*/_interopDefaultLegacy(createJiti);
|
|
28
30
|
const rc9__namespace = /*#__PURE__*/_interopNamespace(rc9);
|
|
29
31
|
const defu__default = /*#__PURE__*/_interopDefaultLegacy(defu);
|
|
@@ -98,17 +100,22 @@ async function loadConfig(opts) {
|
|
|
98
100
|
opts.name = opts.name || "config";
|
|
99
101
|
opts.configFile = opts.configFile ?? (opts.name !== "config" ? `${opts.name}.config` : "config");
|
|
100
102
|
opts.rcFile = opts.rcFile ?? `.${opts.name}rc`;
|
|
101
|
-
const
|
|
102
|
-
config: {}
|
|
103
|
+
const r = {
|
|
104
|
+
config: {},
|
|
105
|
+
cwd: opts.cwd,
|
|
106
|
+
configFile: pathe.resolve(opts.cwd, opts.configFile),
|
|
107
|
+
layers: []
|
|
103
108
|
};
|
|
104
109
|
if (opts.dotenv) {
|
|
105
|
-
|
|
110
|
+
await setupDotenv({
|
|
106
111
|
cwd: opts.cwd,
|
|
107
112
|
...opts.dotenv === true ? {} : opts.dotenv
|
|
108
113
|
});
|
|
109
114
|
}
|
|
110
|
-
const { config,
|
|
111
|
-
|
|
115
|
+
const { config, configFile } = await loadConfigFile(opts.cwd, opts.configFile);
|
|
116
|
+
if (configFile) {
|
|
117
|
+
r.configFile = configFile;
|
|
118
|
+
}
|
|
112
119
|
const configRC = {};
|
|
113
120
|
if (opts.rcFile) {
|
|
114
121
|
if (opts.globalRc) {
|
|
@@ -116,21 +123,61 @@ async function loadConfig(opts) {
|
|
|
116
123
|
}
|
|
117
124
|
Object.assign(configRC, rc9__namespace.read({ name: opts.rcFile, dir: opts.cwd }));
|
|
118
125
|
}
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
r.config = defu__default(opts.overrides, config, configRC, opts.defaults);
|
|
127
|
+
await extendConfig(r.config, opts.configFile, opts.cwd);
|
|
128
|
+
r.layers = r.config._layers;
|
|
129
|
+
delete r.config._layers;
|
|
130
|
+
r.config = defu__default(r.config, ...r.layers.map((e) => e.config));
|
|
131
|
+
return r;
|
|
132
|
+
}
|
|
133
|
+
const GIT_PREFIXES = ["github:", "gitlab:", "bitbucket:", "https://"];
|
|
134
|
+
async function extendConfig(config, configFile, cwd) {
|
|
135
|
+
config._layers = config._layers || [];
|
|
136
|
+
const extendSources = (Array.isArray(config.extends) ? config.extends : [config.extends]).filter(Boolean);
|
|
137
|
+
delete config.extends;
|
|
138
|
+
for (let extendSource of extendSources) {
|
|
139
|
+
if (GIT_PREFIXES.some((prefix) => extendSource.startsWith(prefix))) {
|
|
140
|
+
const url = new URL(extendSource);
|
|
141
|
+
const subPath = url.pathname.split("/").slice(2).join("/");
|
|
142
|
+
const gitRepo = url.protocol + url.pathname.split("/").slice(0, 2).join("/");
|
|
143
|
+
const tmpdir = pathe.resolve(os__default.tmpdir(), "c12/", gitRepo.replace(/[#:@/\\]/g, "_"));
|
|
144
|
+
await fs.promises.rm(tmpdir, { recursive: true }).catch(() => {
|
|
145
|
+
});
|
|
146
|
+
const gittar = await import('gittar').then((r) => r.default || r);
|
|
147
|
+
const tarFile = await gittar.fetch(gitRepo);
|
|
148
|
+
await gittar.extract(tarFile, tmpdir);
|
|
149
|
+
extendSource = pathe.resolve(tmpdir, subPath);
|
|
150
|
+
}
|
|
151
|
+
const isDir = !pathe.extname(extendSource);
|
|
152
|
+
const _cwd = pathe.resolve(cwd, isDir ? extendSource : pathe.dirname(extendSource));
|
|
153
|
+
const _config = await loadConfigFile(_cwd, isDir ? configFile : extendSource);
|
|
154
|
+
if (!_config.config) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
await extendConfig(_config.config, configFile, _cwd);
|
|
158
|
+
config._layers.push({
|
|
159
|
+
config: _config.config,
|
|
160
|
+
cwd: _cwd,
|
|
161
|
+
configFile: _config.configFile
|
|
162
|
+
});
|
|
163
|
+
if (_config.config._layers) {
|
|
164
|
+
config._layers.push(..._config.config._layers);
|
|
165
|
+
delete _config.config._layers;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
121
168
|
}
|
|
122
169
|
const jiti = createJiti__default(null, { cache: false, interopDefault: true });
|
|
123
|
-
async function loadConfigFile(
|
|
170
|
+
async function loadConfigFile(cwd, configFile) {
|
|
124
171
|
const res = {
|
|
125
|
-
|
|
172
|
+
configFile: null,
|
|
126
173
|
config: null
|
|
127
174
|
};
|
|
128
|
-
if (!
|
|
175
|
+
if (!configFile) {
|
|
129
176
|
return res;
|
|
130
177
|
}
|
|
131
178
|
try {
|
|
132
|
-
res.
|
|
133
|
-
res.config = jiti(res.
|
|
179
|
+
res.configFile = jiti.resolve(pathe.resolve(cwd, configFile), { paths: [cwd] });
|
|
180
|
+
res.config = jiti(res.configFile);
|
|
134
181
|
if (typeof res.config === "function") {
|
|
135
182
|
res.config = await res.config();
|
|
136
183
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -34,22 +34,24 @@ declare function setupDotenv(options: DotenvOptions): Promise<Env>;
|
|
|
34
34
|
/** Load environment variables into an object. */
|
|
35
35
|
declare function loadDotenv(opts: DotenvOptions): Promise<Env>;
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
interface InputConfig extends Record<string, any> {
|
|
38
|
+
}
|
|
39
|
+
interface ResolvedConfig<T extends InputConfig = InputConfig> {
|
|
40
|
+
config: T;
|
|
41
|
+
cwd: string;
|
|
42
|
+
configFile: string;
|
|
43
|
+
layers: ResolvedConfig<T>[];
|
|
44
|
+
}
|
|
45
|
+
interface LoadConfigOptions<T extends InputConfig = InputConfig> {
|
|
39
46
|
name?: string;
|
|
40
47
|
cwd?: string;
|
|
41
|
-
configFile?:
|
|
48
|
+
configFile?: string;
|
|
42
49
|
rcFile?: false | string;
|
|
43
50
|
globalRc?: boolean;
|
|
44
51
|
dotenv?: boolean | DotenvOptions;
|
|
45
52
|
defaults?: T;
|
|
46
53
|
overrides?: T;
|
|
47
54
|
}
|
|
48
|
-
|
|
49
|
-
config: T;
|
|
50
|
-
configPath?: string;
|
|
51
|
-
env?: Record<string, any>;
|
|
52
|
-
}
|
|
53
|
-
declare function loadConfig<T extends ConfigT = ConfigT>(opts: LoadConfigOptions<T>): Promise<ResolvedConfig<T>>;
|
|
55
|
+
declare function loadConfig<T extends InputConfig = InputConfig>(opts: LoadConfigOptions<T>): Promise<ResolvedConfig<T>>;
|
|
54
56
|
|
|
55
|
-
export {
|
|
57
|
+
export { DotenvOptions, Env, InputConfig, LoadConfigOptions, ResolvedConfig, loadConfig, loadDotenv, setupDotenv };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, promises } from 'fs';
|
|
2
|
-
import { resolve } from 'pathe';
|
|
2
|
+
import { resolve, extname, dirname } from 'pathe';
|
|
3
3
|
import * as dotenv from 'dotenv';
|
|
4
|
+
import os from 'os';
|
|
4
5
|
import createJiti from 'jiti';
|
|
5
6
|
import * as rc9 from 'rc9';
|
|
6
7
|
import defu from 'defu';
|
|
@@ -75,17 +76,22 @@ async function loadConfig(opts) {
|
|
|
75
76
|
opts.name = opts.name || "config";
|
|
76
77
|
opts.configFile = opts.configFile ?? (opts.name !== "config" ? `${opts.name}.config` : "config");
|
|
77
78
|
opts.rcFile = opts.rcFile ?? `.${opts.name}rc`;
|
|
78
|
-
const
|
|
79
|
-
config: {}
|
|
79
|
+
const r = {
|
|
80
|
+
config: {},
|
|
81
|
+
cwd: opts.cwd,
|
|
82
|
+
configFile: resolve(opts.cwd, opts.configFile),
|
|
83
|
+
layers: []
|
|
80
84
|
};
|
|
81
85
|
if (opts.dotenv) {
|
|
82
|
-
|
|
86
|
+
await setupDotenv({
|
|
83
87
|
cwd: opts.cwd,
|
|
84
88
|
...opts.dotenv === true ? {} : opts.dotenv
|
|
85
89
|
});
|
|
86
90
|
}
|
|
87
|
-
const { config,
|
|
88
|
-
|
|
91
|
+
const { config, configFile } = await loadConfigFile(opts.cwd, opts.configFile);
|
|
92
|
+
if (configFile) {
|
|
93
|
+
r.configFile = configFile;
|
|
94
|
+
}
|
|
89
95
|
const configRC = {};
|
|
90
96
|
if (opts.rcFile) {
|
|
91
97
|
if (opts.globalRc) {
|
|
@@ -93,21 +99,61 @@ async function loadConfig(opts) {
|
|
|
93
99
|
}
|
|
94
100
|
Object.assign(configRC, rc9.read({ name: opts.rcFile, dir: opts.cwd }));
|
|
95
101
|
}
|
|
96
|
-
|
|
97
|
-
|
|
102
|
+
r.config = defu(opts.overrides, config, configRC, opts.defaults);
|
|
103
|
+
await extendConfig(r.config, opts.configFile, opts.cwd);
|
|
104
|
+
r.layers = r.config._layers;
|
|
105
|
+
delete r.config._layers;
|
|
106
|
+
r.config = defu(r.config, ...r.layers.map((e) => e.config));
|
|
107
|
+
return r;
|
|
108
|
+
}
|
|
109
|
+
const GIT_PREFIXES = ["github:", "gitlab:", "bitbucket:", "https://"];
|
|
110
|
+
async function extendConfig(config, configFile, cwd) {
|
|
111
|
+
config._layers = config._layers || [];
|
|
112
|
+
const extendSources = (Array.isArray(config.extends) ? config.extends : [config.extends]).filter(Boolean);
|
|
113
|
+
delete config.extends;
|
|
114
|
+
for (let extendSource of extendSources) {
|
|
115
|
+
if (GIT_PREFIXES.some((prefix) => extendSource.startsWith(prefix))) {
|
|
116
|
+
const url = new URL(extendSource);
|
|
117
|
+
const subPath = url.pathname.split("/").slice(2).join("/");
|
|
118
|
+
const gitRepo = url.protocol + url.pathname.split("/").slice(0, 2).join("/");
|
|
119
|
+
const tmpdir = resolve(os.tmpdir(), "c12/", gitRepo.replace(/[#:@/\\]/g, "_"));
|
|
120
|
+
await promises.rm(tmpdir, { recursive: true }).catch(() => {
|
|
121
|
+
});
|
|
122
|
+
const gittar = await import('gittar').then((r) => r.default || r);
|
|
123
|
+
const tarFile = await gittar.fetch(gitRepo);
|
|
124
|
+
await gittar.extract(tarFile, tmpdir);
|
|
125
|
+
extendSource = resolve(tmpdir, subPath);
|
|
126
|
+
}
|
|
127
|
+
const isDir = !extname(extendSource);
|
|
128
|
+
const _cwd = resolve(cwd, isDir ? extendSource : dirname(extendSource));
|
|
129
|
+
const _config = await loadConfigFile(_cwd, isDir ? configFile : extendSource);
|
|
130
|
+
if (!_config.config) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
await extendConfig(_config.config, configFile, _cwd);
|
|
134
|
+
config._layers.push({
|
|
135
|
+
config: _config.config,
|
|
136
|
+
cwd: _cwd,
|
|
137
|
+
configFile: _config.configFile
|
|
138
|
+
});
|
|
139
|
+
if (_config.config._layers) {
|
|
140
|
+
config._layers.push(..._config.config._layers);
|
|
141
|
+
delete _config.config._layers;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
98
144
|
}
|
|
99
145
|
const jiti = createJiti(null, { cache: false, interopDefault: true });
|
|
100
|
-
async function loadConfigFile(
|
|
146
|
+
async function loadConfigFile(cwd, configFile) {
|
|
101
147
|
const res = {
|
|
102
|
-
|
|
148
|
+
configFile: null,
|
|
103
149
|
config: null
|
|
104
150
|
};
|
|
105
|
-
if (!
|
|
151
|
+
if (!configFile) {
|
|
106
152
|
return res;
|
|
107
153
|
}
|
|
108
154
|
try {
|
|
109
|
-
res.
|
|
110
|
-
res.config = jiti(res.
|
|
155
|
+
res.configFile = jiti.resolve(resolve(cwd, configFile), { paths: [cwd] });
|
|
156
|
+
res.config = jiti(res.configFile);
|
|
111
157
|
if (typeof res.config === "function") {
|
|
112
158
|
res.config = await res.config();
|
|
113
159
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c12",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Smart Config Loader",
|
|
5
5
|
"repository": "unjs/c12",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"defu": "^5.0.1",
|
|
31
31
|
"dotenv": "^14.3.2",
|
|
32
|
+
"gittar": "^0.1.1",
|
|
32
33
|
"jiti": "^1.12.14",
|
|
33
34
|
"mlly": "^0.4.1",
|
|
34
35
|
"pathe": "^0.2.0",
|