@mudah-cli/config 0.8.0 → 0.9.2
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/LICENSE +21 -0
- package/README.md +39 -0
- package/dist/config-repository.d.ts +60 -4
- package/dist/config-repository.js +129 -7
- package/dist/config-repository.js.map +1 -1
- package/dist/env.d.ts +5 -1
- package/dist/env.js +29 -8
- package/dist/env.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/load-config-files.js.map +1 -1
- package/dist/paths.d.ts +11 -0
- package/dist/paths.js +45 -8
- package/dist/paths.js.map +1 -1
- package/dist/redact.d.ts +14 -0
- package/dist/redact.js +61 -0
- package/dist/redact.js.map +1 -0
- package/dist/remote.d.ts +47 -0
- package/dist/remote.js +266 -0
- package/dist/remote.js.map +1 -0
- package/dist/schema.d.ts +5 -0
- package/dist/schema.js +28 -4
- package/dist/schema.js.map +1 -1
- package/dist/secrets.d.ts +81 -0
- package/dist/secrets.js +302 -0
- package/dist/secrets.js.map +1 -0
- package/dist/watch.d.ts +36 -0
- package/dist/watch.js +57 -0
- package/dist/watch.js.map +1 -0
- package/package.json +15 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mudah Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @mudah-cli/config
|
|
2
|
+
|
|
3
|
+
Typed configuration repository, `.env` loading, and dotted-key access.
|
|
4
|
+
|
|
5
|
+
Part of [Mudah](https://github.com/thesimonharms/mudah) — an ergonomic, animation-rich CLI
|
|
6
|
+
framework. No Mudah dependencies; usable standalone.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install @mudah-cli/config
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { defineConfig, env, s } from '@mudah-cli/config';
|
|
18
|
+
|
|
19
|
+
export default defineConfig(
|
|
20
|
+
s.object({
|
|
21
|
+
url: s.string(),
|
|
22
|
+
pool: s.number().min(1).default(5),
|
|
23
|
+
}),
|
|
24
|
+
{
|
|
25
|
+
url: env('DATABASE_URL', 'sqlite:///local.db'),
|
|
26
|
+
pool: env('DB_POOL', 5),
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Pass a schema as the first argument and a bad value fails at import with every offending key
|
|
32
|
+
listed. `get('db.pool')` reads dotted keys; `mergeConfigFrom` merges defaults under existing
|
|
33
|
+
values (existing wins). `.env` loading is native (`process.loadEnvFile`) with typed parsing in
|
|
34
|
+
`env()`.
|
|
35
|
+
|
|
36
|
+
## Links
|
|
37
|
+
|
|
38
|
+
- [Full documentation](https://github.com/thesimonharms/mudah#readme)
|
|
39
|
+
- [License: MIT](LICENSE)
|
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
import { type Schema, type SchemaResult } from './schema.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where a dotted key last came from. Later layers win in the usual
|
|
4
|
+
* defaults < file < env < flag < remote < runtime stack; the repository
|
|
5
|
+
* records whichever layer the caller passes to `set` / `merge`.
|
|
6
|
+
*/
|
|
7
|
+
export type ConfigLayer = 'default' | 'file' | 'env' | 'flag' | 'remote' | 'runtime';
|
|
8
|
+
/** A single key's recorded layer and current value. */
|
|
9
|
+
export interface ConfigSource {
|
|
10
|
+
readonly layer: ConfigLayer;
|
|
11
|
+
readonly value: unknown;
|
|
12
|
+
}
|
|
13
|
+
/** One row of {@link ConfigRepository.precedence}. */
|
|
14
|
+
export interface PrecedenceRow {
|
|
15
|
+
readonly key: string;
|
|
16
|
+
readonly layer: ConfigLayer;
|
|
17
|
+
readonly value: unknown;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Render precedence rows as `key layer value` lines (two spaces between
|
|
21
|
+
* fields), e.g. `app.name file mudah`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatPrecedence(rows: ReadonlyArray<PrecedenceRow>): string[];
|
|
2
24
|
/**
|
|
3
25
|
* Dotted-key configuration repository.
|
|
4
26
|
*
|
|
@@ -8,8 +30,25 @@ import { type Schema, type SchemaResult } from './schema.js';
|
|
|
8
30
|
*/
|
|
9
31
|
export declare class ConfigRepository {
|
|
10
32
|
private root;
|
|
11
|
-
|
|
12
|
-
|
|
33
|
+
private onChange?;
|
|
34
|
+
private readonly layers;
|
|
35
|
+
private boundSchema;
|
|
36
|
+
/** Register a callback to be notified when any config value is mutated. */
|
|
37
|
+
onChangeNotification(cb: (key: string) => void): void;
|
|
38
|
+
/**
|
|
39
|
+
* Bind a schema used by `config:set` / `config:validate` to reject unknown
|
|
40
|
+
* keys and type violations. Pass `undefined` to clear.
|
|
41
|
+
*/
|
|
42
|
+
bindSchema(schema: Schema<unknown> | undefined): this;
|
|
43
|
+
/** The schema bound by {@link bindSchema}, if any. */
|
|
44
|
+
get schema(): Schema<unknown> | undefined;
|
|
45
|
+
set schema(schema: Schema<unknown> | undefined);
|
|
46
|
+
private notify;
|
|
47
|
+
/**
|
|
48
|
+
* Set a value at a dotted key, creating intermediate objects as needed.
|
|
49
|
+
* `layer` defaults to `'runtime'`.
|
|
50
|
+
*/
|
|
51
|
+
set(key: string, value: unknown, layer?: ConfigLayer): this;
|
|
13
52
|
/** Read a value at a dotted key, or `defaultValue` when missing. */
|
|
14
53
|
get<T = unknown>(key: string, defaultValue?: T): T;
|
|
15
54
|
/** Whether a dotted key holds a value. */
|
|
@@ -20,9 +59,17 @@ export declare class ConfigRepository {
|
|
|
20
59
|
all(): Record<string, unknown>;
|
|
21
60
|
/**
|
|
22
61
|
* Merge defaults under an existing config group. Values already present
|
|
23
|
-
* win; missing keys are filled in.
|
|
62
|
+
* win; missing keys are filled in. Newly filled keys are tagged with
|
|
63
|
+
* `layer` (default `'default'`).
|
|
24
64
|
*/
|
|
25
|
-
merge(key: string, defaults: Record<string, unknown
|
|
65
|
+
merge(key: string, defaults: Record<string, unknown>, layer?: ConfigLayer): this;
|
|
66
|
+
/**
|
|
67
|
+
* Which layer last set `key`. `undefined` when the key was never recorded
|
|
68
|
+
* (missing, or created only as an intermediate object).
|
|
69
|
+
*/
|
|
70
|
+
source(key: string): ConfigSource | undefined;
|
|
71
|
+
/** Every recorded key, sorted, with its layer and current value. */
|
|
72
|
+
precedence(): PrecedenceRow[];
|
|
26
73
|
/** Replace the entire configuration tree. */
|
|
27
74
|
clear(): this;
|
|
28
75
|
/**
|
|
@@ -33,4 +80,13 @@ export declare class ConfigRepository {
|
|
|
33
80
|
* confusing `undefined` three layers down.
|
|
34
81
|
*/
|
|
35
82
|
validate<T>(key: string, schema: Schema<T>): SchemaResult<T>;
|
|
83
|
+
/**
|
|
84
|
+
* Schema node at `key` when a schema is bound. `undefined` for unknown
|
|
85
|
+
* keys (or when nothing is bound).
|
|
86
|
+
*/
|
|
87
|
+
schemaAt(key: string): Schema<unknown> | undefined;
|
|
88
|
+
private recordLayer;
|
|
89
|
+
private recordFilled;
|
|
90
|
+
private pruneMissing;
|
|
91
|
+
private removeLayers;
|
|
36
92
|
}
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { assignPath, deepMerge, hasPath, isPlainObject, readPath, removePath } from './paths.js';
|
|
2
|
-
import { validateSchema } from './schema.js';
|
|
2
|
+
import { schemaAt, validateSchema } from './schema.js';
|
|
3
|
+
function joinKey(prefix, name) {
|
|
4
|
+
return prefix === '' ? name : `${prefix}.${name}`;
|
|
5
|
+
}
|
|
6
|
+
function formatValue(value) {
|
|
7
|
+
if (typeof value === 'string')
|
|
8
|
+
return value;
|
|
9
|
+
return JSON.stringify(value);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Render precedence rows as `key layer value` lines (two spaces between
|
|
13
|
+
* fields), e.g. `app.name file mudah`.
|
|
14
|
+
*/
|
|
15
|
+
export function formatPrecedence(rows) {
|
|
16
|
+
return rows.map((row) => `${row.key} ${row.layer} ${formatValue(row.value)}`);
|
|
17
|
+
}
|
|
3
18
|
/**
|
|
4
19
|
* Dotted-key configuration repository.
|
|
5
20
|
*
|
|
@@ -9,9 +24,40 @@ import { validateSchema } from './schema.js';
|
|
|
9
24
|
*/
|
|
10
25
|
export class ConfigRepository {
|
|
11
26
|
root = {};
|
|
12
|
-
|
|
13
|
-
|
|
27
|
+
onChange;
|
|
28
|
+
layers = new Map();
|
|
29
|
+
boundSchema;
|
|
30
|
+
/** Register a callback to be notified when any config value is mutated. */
|
|
31
|
+
onChangeNotification(cb) {
|
|
32
|
+
this.onChange = cb;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Bind a schema used by `config:set` / `config:validate` to reject unknown
|
|
36
|
+
* keys and type violations. Pass `undefined` to clear.
|
|
37
|
+
*/
|
|
38
|
+
bindSchema(schema) {
|
|
39
|
+
this.boundSchema = schema;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
/** The schema bound by {@link bindSchema}, if any. */
|
|
43
|
+
get schema() {
|
|
44
|
+
return this.boundSchema;
|
|
45
|
+
}
|
|
46
|
+
set schema(schema) {
|
|
47
|
+
this.boundSchema = schema;
|
|
48
|
+
}
|
|
49
|
+
notify(key) {
|
|
50
|
+
this.onChange?.(key);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Set a value at a dotted key, creating intermediate objects as needed.
|
|
54
|
+
* `layer` defaults to `'runtime'`.
|
|
55
|
+
*/
|
|
56
|
+
set(key, value, layer = 'runtime') {
|
|
14
57
|
assignPath(this.root, key, value);
|
|
58
|
+
this.recordLayer(key, value, layer);
|
|
59
|
+
this.pruneMissing(key);
|
|
60
|
+
this.notify(key);
|
|
15
61
|
return this;
|
|
16
62
|
}
|
|
17
63
|
/** Read a value at a dotted key, or `defaultValue` when missing. */
|
|
@@ -24,7 +70,12 @@ export class ConfigRepository {
|
|
|
24
70
|
}
|
|
25
71
|
/** Remove a dotted key. Returns whether anything was removed. */
|
|
26
72
|
delete(key) {
|
|
27
|
-
|
|
73
|
+
const removed = removePath(this.root, key);
|
|
74
|
+
if (removed) {
|
|
75
|
+
this.removeLayers(key);
|
|
76
|
+
this.notify(key);
|
|
77
|
+
}
|
|
78
|
+
return removed;
|
|
28
79
|
}
|
|
29
80
|
/** The entire configuration tree (live reference, use with care). */
|
|
30
81
|
all() {
|
|
@@ -32,17 +83,46 @@ export class ConfigRepository {
|
|
|
32
83
|
}
|
|
33
84
|
/**
|
|
34
85
|
* Merge defaults under an existing config group. Values already present
|
|
35
|
-
* win; missing keys are filled in.
|
|
86
|
+
* win; missing keys are filled in. Newly filled keys are tagged with
|
|
87
|
+
* `layer` (default `'default'`).
|
|
36
88
|
*/
|
|
37
|
-
merge(key, defaults) {
|
|
89
|
+
merge(key, defaults, layer = 'default') {
|
|
38
90
|
const existing = this.get(key, undefined);
|
|
91
|
+
// A scalar already set at this key wins outright — merging an object under
|
|
92
|
+
// it would silently discard the value the caller set.
|
|
93
|
+
if (existing !== undefined && !isPlainObject(existing))
|
|
94
|
+
return this;
|
|
39
95
|
const base = isPlainObject(existing) ? existing : {};
|
|
40
|
-
this.
|
|
96
|
+
assignPath(this.root, key, deepMerge(base, defaults));
|
|
97
|
+
this.recordFilled(key, isPlainObject(existing) ? existing : undefined, defaults, layer);
|
|
98
|
+
this.notify(key);
|
|
41
99
|
return this;
|
|
42
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Which layer last set `key`. `undefined` when the key was never recorded
|
|
103
|
+
* (missing, or created only as an intermediate object).
|
|
104
|
+
*/
|
|
105
|
+
source(key) {
|
|
106
|
+
const layer = this.layers.get(key);
|
|
107
|
+
if (layer === undefined)
|
|
108
|
+
return undefined;
|
|
109
|
+
return { layer, value: this.get(key) };
|
|
110
|
+
}
|
|
111
|
+
/** Every recorded key, sorted, with its layer and current value. */
|
|
112
|
+
precedence() {
|
|
113
|
+
return [...this.layers.keys()]
|
|
114
|
+
.sort()
|
|
115
|
+
.filter((key) => hasPath(this.root, key))
|
|
116
|
+
.map((key) => ({
|
|
117
|
+
key,
|
|
118
|
+
layer: this.layers.get(key),
|
|
119
|
+
value: this.get(key),
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
43
122
|
/** Replace the entire configuration tree. */
|
|
44
123
|
clear() {
|
|
45
124
|
this.root = {};
|
|
125
|
+
this.layers.clear();
|
|
46
126
|
return this;
|
|
47
127
|
}
|
|
48
128
|
/**
|
|
@@ -56,5 +136,47 @@ export class ConfigRepository {
|
|
|
56
136
|
const value = key === '' ? this.root : this.get(key, undefined);
|
|
57
137
|
return validateSchema(schema, value, key);
|
|
58
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Schema node at `key` when a schema is bound. `undefined` for unknown
|
|
141
|
+
* keys (or when nothing is bound).
|
|
142
|
+
*/
|
|
143
|
+
schemaAt(key) {
|
|
144
|
+
if (this.boundSchema === undefined)
|
|
145
|
+
return undefined;
|
|
146
|
+
return schemaAt(this.boundSchema, key);
|
|
147
|
+
}
|
|
148
|
+
recordLayer(key, value, layer) {
|
|
149
|
+
this.layers.set(key, layer);
|
|
150
|
+
if (isPlainObject(value)) {
|
|
151
|
+
for (const [name, child] of Object.entries(value)) {
|
|
152
|
+
this.recordLayer(joinKey(key, name), child, layer);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
recordFilled(prefix, existing, incoming, layer) {
|
|
157
|
+
for (const [name, value] of Object.entries(incoming)) {
|
|
158
|
+
const path = joinKey(prefix, name);
|
|
159
|
+
const had = existing?.[name];
|
|
160
|
+
if (isPlainObject(value)) {
|
|
161
|
+
this.recordFilled(path, isPlainObject(had) ? had : undefined, value, layer);
|
|
162
|
+
}
|
|
163
|
+
else if (had === undefined) {
|
|
164
|
+
this.layers.set(path, layer);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
pruneMissing(prefix) {
|
|
169
|
+
for (const key of [...this.layers.keys()]) {
|
|
170
|
+
if ((key === prefix || key.startsWith(`${prefix}.`)) && !hasPath(this.root, key)) {
|
|
171
|
+
this.layers.delete(key);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
removeLayers(prefix) {
|
|
176
|
+
for (const key of [...this.layers.keys()]) {
|
|
177
|
+
if (key === prefix || key.startsWith(`${prefix}.`))
|
|
178
|
+
this.layers.delete(key);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
59
181
|
}
|
|
60
182
|
//# sourceMappingURL=config-repository.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-repository.js","sourceRoot":"","sources":["../src/config-repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACjG,OAAO,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"config-repository.js","sourceRoot":"","sources":["../src/config-repository.ts"],"names":[],"mappings":"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"}
|
package/dist/env.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Load a `.env` file into `process.env` using the native
|
|
3
3
|
* `process.loadEnvFile`. Returns false when the file does not exist, so
|
|
4
|
-
* scaffolds work before a `.env` is created.
|
|
4
|
+
* scaffolds work before a `.env` is created. Any other failure (permissions,
|
|
5
|
+
* malformed content) is rethrown rather than reported as "missing".
|
|
5
6
|
*/
|
|
6
7
|
export declare function loadEnvFile(path?: string): boolean;
|
|
7
8
|
/**
|
|
8
9
|
* Read a typed environment variable. Strings are parsed to booleans
|
|
9
10
|
* (`true`/`false`), null, numbers, and JSON objects/arrays when they look
|
|
10
11
|
* like them; everything else passes through as a string.
|
|
12
|
+
*
|
|
13
|
+
* Values are classified on their trimmed form, but a value that stays a string
|
|
14
|
+
* is returned exactly as found, so surrounding whitespace is preserved.
|
|
11
15
|
*/
|
|
12
16
|
export declare function env<T = string>(key: string, defaultValue?: T): T | undefined;
|
package/dist/env.js
CHANGED
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Load a `.env` file into `process.env` using the native
|
|
3
3
|
* `process.loadEnvFile`. Returns false when the file does not exist, so
|
|
4
|
-
* scaffolds work before a `.env` is created.
|
|
4
|
+
* scaffolds work before a `.env` is created. Any other failure (permissions,
|
|
5
|
+
* malformed content) is rethrown rather than reported as "missing".
|
|
5
6
|
*/
|
|
6
7
|
export function loadEnvFile(path = '.env') {
|
|
7
8
|
try {
|
|
8
9
|
process.loadEnvFile(path);
|
|
9
10
|
return true;
|
|
10
11
|
}
|
|
11
|
-
catch {
|
|
12
|
-
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (error?.code === 'ENOENT')
|
|
14
|
+
return false;
|
|
15
|
+
throw error;
|
|
13
16
|
}
|
|
14
17
|
}
|
|
15
18
|
/**
|
|
16
19
|
* Read a typed environment variable. Strings are parsed to booleans
|
|
17
20
|
* (`true`/`false`), null, numbers, and JSON objects/arrays when they look
|
|
18
21
|
* like them; everything else passes through as a string.
|
|
22
|
+
*
|
|
23
|
+
* Values are classified on their trimmed form, but a value that stays a string
|
|
24
|
+
* is returned exactly as found, so surrounding whitespace is preserved.
|
|
19
25
|
*/
|
|
20
26
|
export function env(key, defaultValue) {
|
|
21
27
|
const raw = process.env[key];
|
|
@@ -24,18 +30,19 @@ export function env(key, defaultValue) {
|
|
|
24
30
|
return parseValue(raw);
|
|
25
31
|
}
|
|
26
32
|
function parseValue(raw) {
|
|
27
|
-
const
|
|
33
|
+
const text = raw.trim();
|
|
34
|
+
const lowered = text.toLowerCase();
|
|
28
35
|
if (lowered === 'true')
|
|
29
36
|
return true;
|
|
30
37
|
if (lowered === 'false')
|
|
31
38
|
return false;
|
|
32
39
|
if (lowered === 'null' || lowered === 'none')
|
|
33
40
|
return null;
|
|
34
|
-
if (
|
|
35
|
-
return Number(
|
|
36
|
-
if (
|
|
41
|
+
if (looksNumeric(text))
|
|
42
|
+
return Number(text);
|
|
43
|
+
if (text.startsWith('{') || text.startsWith('[')) {
|
|
37
44
|
try {
|
|
38
|
-
return JSON.parse(
|
|
45
|
+
return JSON.parse(text);
|
|
39
46
|
}
|
|
40
47
|
catch {
|
|
41
48
|
return raw;
|
|
@@ -43,4 +50,18 @@ function parseValue(raw) {
|
|
|
43
50
|
}
|
|
44
51
|
return raw;
|
|
45
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Whether `text` is a plain decimal number worth coercing. Zero-padded
|
|
55
|
+
* integers (`007`, `0123`) are deliberately excluded — they are almost always
|
|
56
|
+
* identifiers or codes, and coercing them silently drops the leading zeros.
|
|
57
|
+
*/
|
|
58
|
+
function looksNumeric(text) {
|
|
59
|
+
const match = /^-?(\d+)(\.\d+)?$/.exec(text);
|
|
60
|
+
if (!match)
|
|
61
|
+
return false;
|
|
62
|
+
const integerPart = match[1];
|
|
63
|
+
if (integerPart.length > 1 && integerPart.startsWith('0'))
|
|
64
|
+
return false;
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
46
67
|
//# sourceMappingURL=env.js.map
|
package/dist/env.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAI,GAAG,MAAM;IACvC,IAAI,CAAC;QACH,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA2C,EAAE,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAClF,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,GAAG,CAAa,GAAW,EAAE,YAAgB;IAC3D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,YAAY,CAAC;IAC3C,OAAO,UAAU,CAAI,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,UAAU,CAAI,GAAW;IAChC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACxB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACnC,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,IAAS,CAAC;IACzC,IAAI,OAAO,KAAK,OAAO;QAAE,OAAO,KAAU,CAAC;IAC3C,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,IAAS,CAAC;IAC/D,IAAI,YAAY,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC,IAAI,CAAM,CAAC;IACjD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,GAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IACD,OAAO,GAAQ,CAAC;AAClB,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,IAAY;IAChC,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;IAC9B,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACxE,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
export { ConfigRepository } from './config-repository.js';
|
|
1
|
+
export { type ConfigLayer, ConfigRepository, type ConfigSource, formatPrecedence, type PrecedenceRow, } from './config-repository.js';
|
|
2
2
|
export { defineConfig } from './define-config.js';
|
|
3
3
|
export { env, loadEnvFile } from './env.js';
|
|
4
|
-
export {
|
|
4
|
+
export { type LoadConfigOptions, loadConfigFiles } from './load-config-files.js';
|
|
5
5
|
export { deepMerge, isPlainObject } from './paths.js';
|
|
6
|
-
export {
|
|
6
|
+
export { REDACT_KEYS, type RedactOptions, redactSecrets } from './redact.js';
|
|
7
|
+
export { assertRemoteUrl, type LoadRemoteConfigOptions, loadRemoteConfig, REMOTE_CONFIG_CACHE_FILE, REMOTE_CONFIG_TTL_MS, resolveRemoteUrl, } from './remote.js';
|
|
8
|
+
export { type AnySchemaBuilder, type ArraySchemaBuilder, assertSchema, type BooleanSchemaBuilder, ConfigValidationError, type NumberSchemaBuilder, type ObjectSchemaBuilder, type ObjectShape, type Schema, type SchemaIssue, type SchemaResult, type StringSchemaBuilder, s, schemaAt, validateSchema, } from './schema.js';
|
|
9
|
+
export { createSecretStore, envSecretDriver, fileSecretDriver, type KeyringDriverOptions, type KeyringSpawn, type KeyringSpawnResult, keyringSecretDriver, type ResolveSecretOptions, resolveSecret, type SecretDriver, SecretStore, type SecretStoreOptions, } from './secrets.js';
|
|
10
|
+
export { type ConfigReloadSignalOptions, type ConfigWatchFn, installConfigReloadSignal, type SignalProcess, type WatchConfigOptions, watchConfig, } from './watch.js';
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
export { ConfigRepository } from './config-repository.js';
|
|
1
|
+
export { ConfigRepository, formatPrecedence, } from './config-repository.js';
|
|
2
2
|
export { defineConfig } from './define-config.js';
|
|
3
3
|
export { env, loadEnvFile } from './env.js';
|
|
4
4
|
export { loadConfigFiles } from './load-config-files.js';
|
|
5
5
|
export { deepMerge, isPlainObject } from './paths.js';
|
|
6
|
-
export {
|
|
6
|
+
export { REDACT_KEYS, redactSecrets } from './redact.js';
|
|
7
|
+
export { assertRemoteUrl, loadRemoteConfig, REMOTE_CONFIG_CACHE_FILE, REMOTE_CONFIG_TTL_MS, resolveRemoteUrl, } from './remote.js';
|
|
8
|
+
export { assertSchema, ConfigValidationError, s, schemaAt, validateSchema, } from './schema.js';
|
|
9
|
+
export { createSecretStore, envSecretDriver, fileSecretDriver, keyringSecretDriver, resolveSecret, SecretStore, } from './secrets.js';
|
|
10
|
+
export { installConfigReloadSignal, watchConfig, } from './watch.js';
|
|
7
11
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,gBAAgB,EAEhB,gBAAgB,GAEjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAA0B,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,WAAW,EAAsB,aAAa,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,EACL,eAAe,EAEf,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAGL,YAAY,EAEZ,qBAAqB,EAQrB,CAAC,EACD,QAAQ,EACR,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAIhB,mBAAmB,EAEnB,aAAa,EAEb,WAAW,GAEZ,MAAM,cAAc,CAAC;AACtB,OAAO,EAGL,yBAAyB,EAGzB,WAAW,GACZ,MAAM,YAAY,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-config-files.js","sourceRoot":"","sources":["../src/load-config-files.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"load-config-files.js","sourceRoot":"","sources":["../src/load-config-files.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAOzC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,OAAO,GAAsB,EAAE;IAE/B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,QAAQ,CAAC;IACpC,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAE7D,IAAI,OAAyB,CAAC;IAC9B,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAG,OAAO;SAClB,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,oBAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SAC1E,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SAC1B,IAAI,EAAE,CAAC;IAEV,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACnE,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;YACjC,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBACvC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;YACvB,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,OAAO,CAAC,KAAK,CAAC,2BAA2B,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/paths.d.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
2
|
+
/** Throw when a write key tries to address the prototype chain. */
|
|
3
|
+
export declare function assertSafeKey(key: string): void;
|
|
2
4
|
export declare function readPath(root: Record<string, unknown>, key: string, defaultValue?: unknown): unknown;
|
|
3
5
|
export declare function hasPath(root: Record<string, unknown>, key: string): boolean;
|
|
4
6
|
export declare function assignPath(root: Record<string, unknown>, key: string, value: unknown): void;
|
|
5
7
|
export declare function removePath(root: Record<string, unknown>, key: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Deep copy a config value so the repository never aliases the caller's
|
|
10
|
+
* objects. Plain objects and arrays are copied; other values (Date, Map,
|
|
11
|
+
* class instances, functions) are shared as-is — merging config should not
|
|
12
|
+
* silently clone arbitrary user objects.
|
|
13
|
+
*/
|
|
14
|
+
export declare function cloneConfigValue<T>(value: T): T;
|
|
6
15
|
/**
|
|
7
16
|
* Deep-merge where `target` wins: values already present in the target are
|
|
8
17
|
* kept, missing keys are filled in from `source` (mergeConfig semantics).
|
|
18
|
+
* Filled-in containers are copied, so the result never shares structure with
|
|
19
|
+
* `source`.
|
|
9
20
|
*/
|
|
10
21
|
export declare function deepMerge<T extends Record<string, unknown>>(target: T, source: Record<string, unknown>): T;
|
package/dist/paths.js
CHANGED
|
@@ -4,13 +4,27 @@ export function isPlainObject(value) {
|
|
|
4
4
|
const proto = Object.getPrototypeOf(value);
|
|
5
5
|
return proto === Object.prototype || proto === null;
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Segments that would walk (or write) the prototype chain. `Object.prototype`
|
|
9
|
+
* itself satisfies `isPlainObject` (its prototype is null), so a `__proto__`
|
|
10
|
+
* segment would otherwise let a dotted key reach the global prototype.
|
|
11
|
+
*/
|
|
12
|
+
const FORBIDDEN_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
13
|
+
/** Throw when a write key tries to address the prototype chain. */
|
|
14
|
+
export function assertSafeKey(key) {
|
|
15
|
+
for (const segment of segments(key)) {
|
|
16
|
+
if (FORBIDDEN_SEGMENTS.has(segment)) {
|
|
17
|
+
throw new Error(`[config] Refusing to use "${segment}" in config key "${key}" (prototype-pollution guard).`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
7
21
|
function segments(key) {
|
|
8
22
|
return key.split('.');
|
|
9
23
|
}
|
|
10
24
|
export function readPath(root, key, defaultValue) {
|
|
11
25
|
let current = root;
|
|
12
26
|
for (const segment of segments(key)) {
|
|
13
|
-
if (!isPlainObject(current))
|
|
27
|
+
if (!isPlainObject(current) || !Object.hasOwn(current, segment))
|
|
14
28
|
return defaultValue;
|
|
15
29
|
current = current[segment];
|
|
16
30
|
}
|
|
@@ -18,19 +32,20 @@ export function readPath(root, key, defaultValue) {
|
|
|
18
32
|
}
|
|
19
33
|
export function hasPath(root, key) {
|
|
20
34
|
let current = root;
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
if (!isPlainObject(current) || !(segment in current))
|
|
35
|
+
for (const segment of segments(key)) {
|
|
36
|
+
if (!isPlainObject(current) || !Object.hasOwn(current, segment))
|
|
24
37
|
return false;
|
|
25
38
|
current = current[segment];
|
|
26
39
|
}
|
|
27
40
|
return true;
|
|
28
41
|
}
|
|
29
42
|
export function assignPath(root, key, value) {
|
|
43
|
+
assertSafeKey(key);
|
|
30
44
|
const parts = segments(key);
|
|
31
45
|
let current = root;
|
|
32
46
|
for (const segment of parts.slice(0, -1)) {
|
|
33
|
-
|
|
47
|
+
// Descend only through own plain objects; never into a prototype.
|
|
48
|
+
const next = Object.hasOwn(current, segment) ? current[segment] : undefined;
|
|
34
49
|
if (!isPlainObject(next)) {
|
|
35
50
|
current[segment] = {};
|
|
36
51
|
}
|
|
@@ -43,20 +58,42 @@ export function removePath(root, key) {
|
|
|
43
58
|
const parts = segments(key);
|
|
44
59
|
let current = root;
|
|
45
60
|
for (const segment of parts.slice(0, -1)) {
|
|
46
|
-
const next = current[segment];
|
|
61
|
+
const next = Object.hasOwn(current, segment) ? current[segment] : undefined;
|
|
47
62
|
if (!isPlainObject(next))
|
|
48
63
|
return false;
|
|
49
64
|
current = next;
|
|
50
65
|
}
|
|
51
66
|
const last = parts[parts.length - 1];
|
|
52
|
-
if (!(last
|
|
67
|
+
if (!Object.hasOwn(current, last))
|
|
53
68
|
return false;
|
|
54
69
|
delete current[last];
|
|
55
70
|
return true;
|
|
56
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Deep copy a config value so the repository never aliases the caller's
|
|
74
|
+
* objects. Plain objects and arrays are copied; other values (Date, Map,
|
|
75
|
+
* class instances, functions) are shared as-is — merging config should not
|
|
76
|
+
* silently clone arbitrary user objects.
|
|
77
|
+
*/
|
|
78
|
+
export function cloneConfigValue(value) {
|
|
79
|
+
if (Array.isArray(value))
|
|
80
|
+
return value.map((entry) => cloneConfigValue(entry));
|
|
81
|
+
if (isPlainObject(value)) {
|
|
82
|
+
const out = {};
|
|
83
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
84
|
+
if (FORBIDDEN_SEGMENTS.has(key))
|
|
85
|
+
continue;
|
|
86
|
+
out[key] = cloneConfigValue(entry);
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
57
92
|
/**
|
|
58
93
|
* Deep-merge where `target` wins: values already present in the target are
|
|
59
94
|
* kept, missing keys are filled in from `source` (mergeConfig semantics).
|
|
95
|
+
* Filled-in containers are copied, so the result never shares structure with
|
|
96
|
+
* `source`.
|
|
60
97
|
*/
|
|
61
98
|
export function deepMerge(target, source) {
|
|
62
99
|
const result = { ...target };
|
|
@@ -66,7 +103,7 @@ export function deepMerge(target, source) {
|
|
|
66
103
|
result[key] = deepMerge(existing, value);
|
|
67
104
|
}
|
|
68
105
|
else if (existing === undefined) {
|
|
69
|
-
result[key] = value;
|
|
106
|
+
result[key] = cloneConfigValue(value);
|
|
70
107
|
}
|
|
71
108
|
}
|
|
72
109
|
return result;
|
package/dist/paths.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC3C,OAAO,KAAK,KAAK,MAAM,CAAC,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC;AACtD,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAA6B,EAAE,GAAW,EAAE,YAAsB;IACzF,IAAI,OAAO,GAAY,IAAI,CAAC;IAC5B,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACpC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC;YAAE,OAAO,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"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"}
|
package/dist/redact.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Key names whose values are treated as secrets and masked in dumps. */
|
|
2
|
+
export declare const REDACT_KEYS: ReadonlyArray<RegExp>;
|
|
3
|
+
export interface RedactOptions {
|
|
4
|
+
/** Override the set of sensitive key patterns. */
|
|
5
|
+
keys?: ReadonlyArray<RegExp>;
|
|
6
|
+
/** Replacement rendered in place of a redacted value. */
|
|
7
|
+
mask?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Recursively replace values whose key is sensitive. Scalars under matching
|
|
11
|
+
* keys become `mask`; objects/arrays recurse so sensitive leaves are masked
|
|
12
|
+
* regardless of depth. Non-sensitive scalars and arrays pass through unchanged.
|
|
13
|
+
*/
|
|
14
|
+
export declare function redactSecrets(value: unknown, options?: RedactOptions): unknown;
|