@optique/config 1.0.0-dev.908 → 1.0.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/dist/index.cjs +258 -361
- package/dist/index.d.cts +29 -36
- package/dist/index.d.ts +29 -36
- package/dist/index.js +259 -356
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,305 +1,46 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { getAnnotations } from "@optique/core/annotations";
|
|
4
|
+
import { defineTraits, delegateSuggestNodes, inheritAnnotations, injectAnnotations, mapModeValue, mapSourceMetadata, wrapForMode } from "@optique/core/extension";
|
|
4
5
|
import { message } from "@optique/core/message";
|
|
5
|
-
import { mapModeValue, wrapForMode } from "@optique/core/mode-dispatch";
|
|
6
6
|
|
|
7
7
|
//#region src/index.ts
|
|
8
|
-
const deferPromptUntilConfigResolvesKey = Symbol.for("@optique/config/deferPromptUntilResolved");
|
|
9
|
-
const phase1ConfigAnnotationsKey = Symbol.for("@optique/config/phase1PromptAnnotations");
|
|
10
|
-
const phase2UndefinedParsedValueKey = Symbol.for("@optique/config/phase2UndefinedParsedValue");
|
|
11
|
-
const deferredPromptValueKey = Symbol.for("@optique/inquirer/deferredPromptValue");
|
|
12
|
-
/**
|
|
13
|
-
* Internal registry for active config data during config context execution.
|
|
14
|
-
* This is a workaround for the limitation that object() doesn't propagate
|
|
15
|
-
* annotations to child field parsers.
|
|
16
|
-
* @internal
|
|
17
|
-
*/
|
|
18
|
-
const activeConfigRegistry = /* @__PURE__ */ new Map();
|
|
19
|
-
/**
|
|
20
|
-
* Internal registry for active config metadata during config context execution.
|
|
21
|
-
* @internal
|
|
22
|
-
*/
|
|
23
|
-
const activeConfigMetaRegistry = /* @__PURE__ */ new Map();
|
|
24
8
|
const phase1ConfigAnnotationMarker = Symbol("@optique/config/phase1Annotation");
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return value != null && typeof value === "object" && phase2UndefinedParsedValueKey in value;
|
|
30
|
-
}
|
|
31
|
-
function isPlainObject(value) {
|
|
32
|
-
const proto = Object.getPrototypeOf(value);
|
|
33
|
-
return proto === Object.prototype || proto === null;
|
|
9
|
+
function getTypeName(value) {
|
|
10
|
+
if (value === null) return "null";
|
|
11
|
+
if (Array.isArray(value)) return "array";
|
|
12
|
+
return typeof value;
|
|
34
13
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
function containsDeferredPromptValuesInOwnProperties(value, seen) {
|
|
40
|
-
for (const key of Reflect.ownKeys(value)) {
|
|
41
|
-
if (shouldSkipCollectionOwnKey(value, key)) continue;
|
|
42
|
-
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
43
|
-
if (descriptor != null && "value" in descriptor && containsDeferredPromptValues(descriptor.value, seen)) return true;
|
|
44
|
-
}
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
function copySanitizedOwnProperties(source, target, seen) {
|
|
48
|
-
for (const key of Reflect.ownKeys(source)) {
|
|
49
|
-
if (shouldSkipCollectionOwnKey(source, key)) continue;
|
|
50
|
-
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
51
|
-
if (descriptor == null) continue;
|
|
52
|
-
if ("value" in descriptor) descriptor.value = stripDeferredPromptValues(descriptor.value, seen);
|
|
53
|
-
Object.defineProperty(target, key, descriptor);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function containsDeferredPromptValues(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
57
|
-
if (isDeferredPromptValue(value)) return true;
|
|
58
|
-
if (value == null || typeof value !== "object") return false;
|
|
59
|
-
if (seen.has(value)) return false;
|
|
60
|
-
seen.add(value);
|
|
61
|
-
if (Array.isArray(value)) {
|
|
62
|
-
if (value.some((item) => containsDeferredPromptValues(item, seen))) return true;
|
|
63
|
-
return containsDeferredPromptValuesInOwnProperties(value, seen);
|
|
64
|
-
}
|
|
65
|
-
if (value instanceof Set) {
|
|
66
|
-
for (const entryValue of value) if (containsDeferredPromptValues(entryValue, seen)) return true;
|
|
67
|
-
return containsDeferredPromptValuesInOwnProperties(value, seen);
|
|
68
|
-
}
|
|
69
|
-
if (value instanceof Map) {
|
|
70
|
-
for (const [key, entryValue] of value) if (containsDeferredPromptValues(key, seen) || containsDeferredPromptValues(entryValue, seen)) return true;
|
|
71
|
-
return containsDeferredPromptValuesInOwnProperties(value, seen);
|
|
72
|
-
}
|
|
73
|
-
return containsDeferredPromptValuesInOwnProperties(value, seen);
|
|
74
|
-
}
|
|
75
|
-
const SANITIZE_FAILED = Symbol("sanitizeFailed");
|
|
76
|
-
const activeSanitizations = /* @__PURE__ */ new WeakMap();
|
|
77
|
-
function callWithSanitizedOwnProperties(target, fn, args, strip, seen) {
|
|
78
|
-
let active = activeSanitizations.get(target);
|
|
79
|
-
if (active != null) active.count++;
|
|
80
|
-
else {
|
|
81
|
-
const saved = /* @__PURE__ */ new Map();
|
|
82
|
-
const sanitizedValues = /* @__PURE__ */ new Map();
|
|
83
|
-
for (const key of Reflect.ownKeys(target)) {
|
|
84
|
-
const desc = Object.getOwnPropertyDescriptor(target, key);
|
|
85
|
-
if (desc != null && "value" in desc) {
|
|
86
|
-
let stripped;
|
|
87
|
-
try {
|
|
88
|
-
stripped = strip(desc.value, seen);
|
|
89
|
-
} catch {
|
|
90
|
-
for (const [k, d] of saved) try {
|
|
91
|
-
Object.defineProperty(target, k, d);
|
|
92
|
-
} catch {}
|
|
93
|
-
return SANITIZE_FAILED;
|
|
94
|
-
}
|
|
95
|
-
if (stripped !== desc.value) try {
|
|
96
|
-
Object.defineProperty(target, key, {
|
|
97
|
-
...desc,
|
|
98
|
-
value: stripped
|
|
99
|
-
});
|
|
100
|
-
saved.set(key, desc);
|
|
101
|
-
sanitizedValues.set(key, stripped);
|
|
102
|
-
} catch {
|
|
103
|
-
for (const [k, d] of saved) try {
|
|
104
|
-
Object.defineProperty(target, k, d);
|
|
105
|
-
} catch {}
|
|
106
|
-
return SANITIZE_FAILED;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
active = {
|
|
111
|
-
saved,
|
|
112
|
-
sanitizedValues,
|
|
113
|
-
count: 1
|
|
114
|
-
};
|
|
115
|
-
activeSanitizations.set(target, active);
|
|
116
|
-
}
|
|
117
|
-
function release() {
|
|
118
|
-
active.count--;
|
|
119
|
-
if (active.count === 0) {
|
|
120
|
-
activeSanitizations.delete(target);
|
|
121
|
-
for (const [key, desc] of active.saved) try {
|
|
122
|
-
const current = Object.getOwnPropertyDescriptor(target, key);
|
|
123
|
-
if (current == null) continue;
|
|
124
|
-
if ("value" in current && current.value !== active.sanitizedValues.get(key)) continue;
|
|
125
|
-
Object.defineProperty(target, key, desc);
|
|
126
|
-
} catch {}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
let result;
|
|
130
|
-
try {
|
|
131
|
-
result = fn.apply(target, args);
|
|
132
|
-
} catch (e) {
|
|
133
|
-
release();
|
|
134
|
-
throw e;
|
|
135
|
-
}
|
|
136
|
-
if (result instanceof Promise) return result.then((v) => {
|
|
137
|
-
release();
|
|
138
|
-
return strip(v, seen);
|
|
139
|
-
}, (e) => {
|
|
140
|
-
release();
|
|
141
|
-
throw e;
|
|
142
|
-
});
|
|
143
|
-
release();
|
|
144
|
-
return strip(result, seen);
|
|
145
|
-
}
|
|
146
|
-
function callMethodOnSanitizedTarget(fn, proxy, target, args, strip, seen) {
|
|
147
|
-
const result = callWithSanitizedOwnProperties(target, fn, args, strip, seen);
|
|
148
|
-
if (result !== SANITIZE_FAILED) return result;
|
|
149
|
-
const fallback = fn.apply(proxy, args);
|
|
150
|
-
if (fallback instanceof Promise) return fallback.then((v) => strip(v, seen));
|
|
151
|
-
return strip(fallback, seen);
|
|
152
|
-
}
|
|
153
|
-
function createSanitizedNonPlainView(value, seen) {
|
|
154
|
-
const methodCache = /* @__PURE__ */ new Map();
|
|
155
|
-
const proxy = new Proxy(value, {
|
|
156
|
-
get(target, key, receiver) {
|
|
157
|
-
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
158
|
-
if (descriptor != null && "value" in descriptor) {
|
|
159
|
-
if (!descriptor.configurable && !descriptor.writable) return descriptor.value;
|
|
160
|
-
const val = stripDeferredPromptValues(descriptor.value, seen);
|
|
161
|
-
if (typeof val === "function") {
|
|
162
|
-
if (!descriptor.configurable && !descriptor.writable || /^class[\s{]/.test(Function.prototype.toString.call(val))) return val;
|
|
163
|
-
const cached = methodCache.get(key);
|
|
164
|
-
if (cached != null && cached.fn === val) return cached.wrapper;
|
|
165
|
-
const wrapper = function(...args) {
|
|
166
|
-
if (this !== proxy) return stripDeferredPromptValues(val.apply(this, args), seen);
|
|
167
|
-
return callMethodOnSanitizedTarget(val, proxy, target, args, stripDeferredPromptValues, seen);
|
|
168
|
-
};
|
|
169
|
-
methodCache.set(key, {
|
|
170
|
-
fn: val,
|
|
171
|
-
wrapper
|
|
172
|
-
});
|
|
173
|
-
return wrapper;
|
|
174
|
-
}
|
|
175
|
-
return val;
|
|
176
|
-
}
|
|
177
|
-
let isAccessor = false;
|
|
178
|
-
for (let proto = target; proto != null; proto = Object.getPrototypeOf(proto)) {
|
|
179
|
-
const d = Object.getOwnPropertyDescriptor(proto, key);
|
|
180
|
-
if (d != null) {
|
|
181
|
-
isAccessor = "get" in d;
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
const result = Reflect.get(target, key, receiver);
|
|
186
|
-
if (typeof result === "function") {
|
|
187
|
-
if (/^class[\s{]/.test(Function.prototype.toString.call(result))) return result;
|
|
188
|
-
if (!isAccessor) {
|
|
189
|
-
const cached = methodCache.get(key);
|
|
190
|
-
if (cached != null && cached.fn === result) return cached.wrapper;
|
|
191
|
-
const wrapper = function(...args) {
|
|
192
|
-
if (this !== proxy) return stripDeferredPromptValues(result.apply(this, args), seen);
|
|
193
|
-
return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValues, seen);
|
|
194
|
-
};
|
|
195
|
-
methodCache.set(key, {
|
|
196
|
-
fn: result,
|
|
197
|
-
wrapper
|
|
198
|
-
});
|
|
199
|
-
return wrapper;
|
|
200
|
-
}
|
|
201
|
-
return function(...args) {
|
|
202
|
-
if (this !== proxy) return stripDeferredPromptValues(result.apply(this, args), seen);
|
|
203
|
-
return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValues, seen);
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
return stripDeferredPromptValues(result, seen);
|
|
207
|
-
},
|
|
208
|
-
getOwnPropertyDescriptor(target, key) {
|
|
209
|
-
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
210
|
-
if (descriptor == null || !("value" in descriptor)) return descriptor;
|
|
211
|
-
if (!descriptor.configurable && !descriptor.writable) return descriptor;
|
|
212
|
-
return {
|
|
213
|
-
...descriptor,
|
|
214
|
-
value: stripDeferredPromptValues(descriptor.value, seen)
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
seen.set(value, proxy);
|
|
219
|
-
return proxy;
|
|
220
|
-
}
|
|
221
|
-
function stripDeferredPromptValues(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
222
|
-
if (isDeferredPromptValue(value)) return void 0;
|
|
223
|
-
if (value == null || typeof value !== "object") return value;
|
|
224
|
-
const cached = seen.get(value);
|
|
225
|
-
if (cached !== void 0) return cached;
|
|
226
|
-
if (Array.isArray(value)) {
|
|
227
|
-
if (!containsDeferredPromptValues(value)) return value;
|
|
228
|
-
const clone$1 = new Array(value.length);
|
|
229
|
-
seen.set(value, clone$1);
|
|
230
|
-
for (let i = 0; i < value.length; i++) clone$1[i] = stripDeferredPromptValues(value[i], seen);
|
|
231
|
-
copySanitizedOwnProperties(value, clone$1, seen);
|
|
232
|
-
return clone$1;
|
|
233
|
-
}
|
|
234
|
-
if (value instanceof Set) {
|
|
235
|
-
if (!containsDeferredPromptValues(value)) return value;
|
|
236
|
-
const clone$1 = /* @__PURE__ */ new Set();
|
|
237
|
-
seen.set(value, clone$1);
|
|
238
|
-
for (const entryValue of value) clone$1.add(stripDeferredPromptValues(entryValue, seen));
|
|
239
|
-
copySanitizedOwnProperties(value, clone$1, seen);
|
|
240
|
-
return clone$1;
|
|
241
|
-
}
|
|
242
|
-
if (value instanceof Map) {
|
|
243
|
-
if (!containsDeferredPromptValues(value)) return value;
|
|
244
|
-
const clone$1 = /* @__PURE__ */ new Map();
|
|
245
|
-
seen.set(value, clone$1);
|
|
246
|
-
for (const [key, entryValue] of value) clone$1.set(stripDeferredPromptValues(key, seen), stripDeferredPromptValues(entryValue, seen));
|
|
247
|
-
copySanitizedOwnProperties(value, clone$1, seen);
|
|
248
|
-
return clone$1;
|
|
249
|
-
}
|
|
250
|
-
if (!isPlainObject(value)) return containsDeferredPromptValues(value) ? createSanitizedNonPlainView(value, seen) : value;
|
|
251
|
-
if (!containsDeferredPromptValues(value)) return value;
|
|
252
|
-
const clone = Object.create(Object.getPrototypeOf(value));
|
|
253
|
-
seen.set(value, clone);
|
|
254
|
-
for (const key of Reflect.ownKeys(value)) {
|
|
255
|
-
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
256
|
-
if (descriptor == null) continue;
|
|
257
|
-
if ("value" in descriptor) descriptor.value = stripDeferredPromptValues(descriptor.value, seen);
|
|
258
|
-
Object.defineProperty(clone, key, descriptor);
|
|
259
|
-
}
|
|
260
|
-
return clone;
|
|
14
|
+
function isPromiseLike(value) {
|
|
15
|
+
return value != null && (typeof value === "object" || typeof value === "function") && "then" in value && typeof value.then === "function";
|
|
261
16
|
}
|
|
262
17
|
/**
|
|
263
|
-
*
|
|
264
|
-
*
|
|
18
|
+
* Detects native Promises and cross-realm Promises. Cross-realm
|
|
19
|
+
* Promises fail `instanceof Promise` but still carry the spec-required
|
|
20
|
+
* `Symbol.toStringTag === "Promise"`. Domain objects that merely
|
|
21
|
+
* define a `then()` method are not matched unless they also set
|
|
22
|
+
* `Symbol.toStringTag` to `"Promise"`.
|
|
265
23
|
*/
|
|
266
|
-
function
|
|
267
|
-
|
|
24
|
+
function isPromise(value) {
|
|
25
|
+
if (value instanceof Promise) return true;
|
|
26
|
+
if (value == null || typeof value !== "object" && typeof value !== "function") return false;
|
|
27
|
+
return isPromiseLike(value) && value[Symbol.toStringTag] === "Promise";
|
|
268
28
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
29
|
+
function validateLoadResult(loaded) {
|
|
30
|
+
if (loaded == null) return void 0;
|
|
31
|
+
if (typeof loaded !== "object" || Array.isArray(loaded)) throw new TypeError(`Expected load() to return an object, but got: ${getTypeName(loaded)}.`);
|
|
32
|
+
if (!("config" in loaded)) throw new TypeError("Expected load() result to have a config property.");
|
|
33
|
+
const result = loaded;
|
|
34
|
+
if (isPromise(result.config)) throw new TypeError("Expected config in load() result to not be a Promise. Resolve the Promise before returning.");
|
|
35
|
+
if (isPromise(result.meta)) throw new TypeError("Expected meta in load() result to not be a Promise. Resolve the Promise before returning.");
|
|
36
|
+
return loaded;
|
|
275
37
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Sets active config metadata for a context.
|
|
285
|
-
* @internal
|
|
286
|
-
*/
|
|
287
|
-
function setActiveConfigMeta(contextId, meta) {
|
|
288
|
-
activeConfigMetaRegistry.set(contextId, meta);
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* Gets active config metadata for a context.
|
|
292
|
-
* @internal
|
|
293
|
-
*/
|
|
294
|
-
function getActiveConfigMeta(contextId) {
|
|
295
|
-
return activeConfigMetaRegistry.get(contextId);
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Clears active config metadata for a context.
|
|
299
|
-
* @internal
|
|
300
|
-
*/
|
|
301
|
-
function clearActiveConfigMeta(contextId) {
|
|
302
|
-
activeConfigMetaRegistry.delete(contextId);
|
|
38
|
+
function isStandardSchema(value) {
|
|
39
|
+
if (value == null || typeof value !== "object" && typeof value !== "function") return false;
|
|
40
|
+
if (!("~standard" in value)) return false;
|
|
41
|
+
const standard = value["~standard"];
|
|
42
|
+
if (standard == null || typeof standard !== "object" && typeof standard !== "function") return false;
|
|
43
|
+
return typeof standard.validate === "function";
|
|
303
44
|
}
|
|
304
45
|
function isErrnoException(error) {
|
|
305
46
|
return typeof error === "object" && error !== null && "code" in error;
|
|
@@ -325,13 +66,24 @@ function validateWithSchema(schema, rawData) {
|
|
|
325
66
|
*
|
|
326
67
|
* The config context implements the `SourceContext` interface and can be used
|
|
327
68
|
* with `runWith()` from *@optique/core* or `run()`/`runAsync()` from
|
|
328
|
-
* *@optique/run* to provide configuration file support.
|
|
69
|
+
* *@optique/run* to provide configuration file support. Each runner call
|
|
70
|
+
* receives its own annotation snapshot, so the same `ConfigContext`
|
|
71
|
+
* instance can be reused safely across independent or concurrent runs.
|
|
72
|
+
* When calling `context.getAnnotations()` manually, omit the request for a
|
|
73
|
+
* phase-1 snapshot or pass `{ phase: "phase2", parsed }` for a phase-two
|
|
74
|
+
* snapshot, then thread the returned annotations into low-level APIs such
|
|
75
|
+
* as `parse()`, `parseAsync()`, `parser.complete()`, `suggest()`, or
|
|
76
|
+
* `getDocPage()`. Calling `getAnnotations()` by itself does not affect
|
|
77
|
+
* later parses unless those returned annotations are explicitly threaded
|
|
78
|
+
* into a low-level API call.
|
|
329
79
|
*
|
|
330
80
|
* @template T The output type of the config schema.
|
|
331
81
|
* @template TConfigMeta The metadata type for config sources.
|
|
332
82
|
* @param options Configuration options including schema and optional file
|
|
333
83
|
* parser.
|
|
334
84
|
* @returns A config context that can be used with `bindConfig()` and runners.
|
|
85
|
+
* @throws {TypeError} If `schema` is not a valid Standard Schema object.
|
|
86
|
+
* @throws {TypeError} If `fileParser` is provided but is not a function.
|
|
335
87
|
* @since 0.10.0
|
|
336
88
|
*
|
|
337
89
|
* @example
|
|
@@ -348,46 +100,58 @@ function validateWithSchema(schema, rawData) {
|
|
|
348
100
|
* ```
|
|
349
101
|
*/
|
|
350
102
|
function createConfigContext(options) {
|
|
103
|
+
const rawSchema = options.schema;
|
|
104
|
+
if (!isStandardSchema(rawSchema)) throw new TypeError(`Expected schema to be a Standard Schema object, but got: ${getTypeName(rawSchema)}.`);
|
|
105
|
+
const rawFileParser = options.fileParser;
|
|
106
|
+
if (rawFileParser !== void 0 && typeof rawFileParser !== "function") throw new TypeError(`Expected fileParser to be a function, but got: ${getTypeName(rawFileParser)}.`);
|
|
351
107
|
const contextId = Symbol(`@optique/config:${Math.random()}`);
|
|
352
108
|
const context = {
|
|
353
109
|
id: contextId,
|
|
354
|
-
schema:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (
|
|
110
|
+
schema: rawSchema,
|
|
111
|
+
phase: "two-pass",
|
|
112
|
+
getInternalAnnotations(request, annotations) {
|
|
113
|
+
if (request.phase === "phase1") return { [contextId]: phase1ConfigAnnotationMarker };
|
|
358
114
|
return Object.getOwnPropertySymbols(annotations).includes(contextId) ? void 0 : { [contextId]: void 0 };
|
|
359
115
|
},
|
|
360
|
-
getAnnotations(
|
|
361
|
-
if (
|
|
116
|
+
getAnnotations(request, runtimeOptions) {
|
|
117
|
+
if (request === void 0) return {};
|
|
118
|
+
if (request === null || typeof request !== "object" || !("phase" in request) || request.phase !== "phase1" && request.phase !== "phase2" || request.phase === "phase2" && !("parsed" in request)) throw new TypeError(`Expected getAnnotations() to receive no request or a SourceContextRequest ({ phase: "phase1" } or { phase: "phase2", parsed }), but got: ${getTypeName(request)}.`);
|
|
119
|
+
if (request.phase === "phase1") return {};
|
|
362
120
|
const opts = runtimeOptions;
|
|
363
121
|
if (!opts || !opts.getConfigPath && !opts.load) throw new TypeError("Either getConfigPath or load must be provided in the runner options when using ConfigContext.");
|
|
364
|
-
|
|
365
|
-
|
|
122
|
+
if (opts.load !== void 0 && typeof opts.load !== "function") throw new TypeError(`Expected load to be a function, but got: ${getTypeName(opts.load)}.`);
|
|
123
|
+
if (!opts.load && opts.getConfigPath !== void 0 && typeof opts.getConfigPath !== "function") throw new TypeError(`Expected getConfigPath to be a function, but got: ${getTypeName(opts.getConfigPath)}.`);
|
|
124
|
+
const parsedPlaceholder = request.parsed;
|
|
125
|
+
const emptyAnnotations = () => ({});
|
|
366
126
|
const buildAnnotations = (configData, configMeta) => {
|
|
367
|
-
if (configData === void 0 || configData === null) return
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
data: configData,
|
|
373
|
-
meta: configMeta
|
|
374
|
-
} };
|
|
375
|
-
}
|
|
127
|
+
if (configData === void 0 || configData === null) return emptyAnnotations();
|
|
128
|
+
if (configMeta !== void 0) return { [contextId]: {
|
|
129
|
+
data: configData,
|
|
130
|
+
meta: configMeta
|
|
131
|
+
} };
|
|
376
132
|
return { [contextId]: { data: configData } };
|
|
377
133
|
};
|
|
378
134
|
const validateAndBuildAnnotations = (rawData, configMeta) => {
|
|
379
|
-
const validated = validateWithSchema(
|
|
135
|
+
const validated = validateWithSchema(rawSchema, rawData);
|
|
380
136
|
if (validated instanceof Promise) return validated.then((configData) => buildAnnotations(configData, configMeta));
|
|
381
137
|
return buildAnnotations(validated, configMeta);
|
|
382
138
|
};
|
|
383
139
|
if (opts.load) {
|
|
384
140
|
const loaded = opts.load(parsedPlaceholder);
|
|
385
|
-
if (loaded
|
|
386
|
-
|
|
141
|
+
if (isPromise(loaded)) return Promise.resolve(loaded).then((resolved) => {
|
|
142
|
+
const validated$1 = validateLoadResult(resolved);
|
|
143
|
+
if (validated$1 === void 0) return emptyAnnotations();
|
|
144
|
+
return validateAndBuildAnnotations(validated$1.config, validated$1.meta);
|
|
145
|
+
});
|
|
146
|
+
if (isPromiseLike(loaded)) throw new TypeError("Expected load() to return a plain object or Promise, but got a thenable. Use a real Promise instead.");
|
|
147
|
+
const validated = validateLoadResult(loaded);
|
|
148
|
+
if (validated === void 0) return emptyAnnotations();
|
|
149
|
+
return validateAndBuildAnnotations(validated.config, validated.meta);
|
|
387
150
|
}
|
|
388
151
|
if (opts.getConfigPath) {
|
|
389
152
|
const configPath = opts.getConfigPath(parsedPlaceholder);
|
|
390
|
-
if (
|
|
153
|
+
if (configPath !== void 0 && typeof configPath !== "string") throw new TypeError(`Expected getConfigPath() to return a string or undefined, but got: ${getTypeName(configPath)}.`);
|
|
154
|
+
if (!configPath) return emptyAnnotations();
|
|
391
155
|
const absoluteConfigPath = resolve(configPath);
|
|
392
156
|
const singleFileMeta = {
|
|
393
157
|
configDir: dirname(absoluteConfigPath),
|
|
@@ -396,24 +160,21 @@ function createConfigContext(options) {
|
|
|
396
160
|
try {
|
|
397
161
|
const contents = readFileSync(absoluteConfigPath);
|
|
398
162
|
let rawData;
|
|
399
|
-
if (
|
|
163
|
+
if (rawFileParser) rawData = rawFileParser(contents);
|
|
400
164
|
else {
|
|
401
165
|
const text = new TextDecoder().decode(contents);
|
|
402
166
|
rawData = JSON.parse(text);
|
|
403
167
|
}
|
|
404
168
|
return validateAndBuildAnnotations(rawData, singleFileMeta);
|
|
405
169
|
} catch (error) {
|
|
406
|
-
if (isErrnoException(error) && error.code === "ENOENT") return
|
|
170
|
+
if (isErrnoException(error) && error.code === "ENOENT") return emptyAnnotations();
|
|
407
171
|
if (error instanceof SyntaxError) throw new Error(`Failed to parse config file ${absoluteConfigPath}: ${error.message}`);
|
|
408
172
|
throw error;
|
|
409
173
|
}
|
|
410
174
|
}
|
|
411
|
-
return
|
|
175
|
+
return emptyAnnotations();
|
|
412
176
|
},
|
|
413
|
-
[Symbol.dispose]() {
|
|
414
|
-
clearActiveConfig(contextId);
|
|
415
|
-
clearActiveConfigMeta(contextId);
|
|
416
|
-
}
|
|
177
|
+
[Symbol.dispose]() {}
|
|
417
178
|
};
|
|
418
179
|
return context;
|
|
419
180
|
}
|
|
@@ -433,6 +194,12 @@ function createConfigContext(options) {
|
|
|
433
194
|
* @param parser The parser to bind to config values.
|
|
434
195
|
* @param options Binding options including context, key, and default.
|
|
435
196
|
* @returns A new parser with config fallback behavior.
|
|
197
|
+
* @throws {TypeError} If `key` is not a property key or function.
|
|
198
|
+
* @throws {Error} If the inner parser's {@link Parser.validateValue} hook
|
|
199
|
+
* throws while re-validating a fallback value (a
|
|
200
|
+
* config-sourced value or the configured `default`) —
|
|
201
|
+
* the hook can run even when no CLI tokens are parsed
|
|
202
|
+
* (see issue #414).
|
|
436
203
|
* @since 0.10.0
|
|
437
204
|
*
|
|
438
205
|
* @example
|
|
@@ -449,16 +216,19 @@ function createConfigContext(options) {
|
|
|
449
216
|
* ```
|
|
450
217
|
*/
|
|
451
218
|
function bindConfig(parser, options) {
|
|
219
|
+
const keyType = typeof options.key;
|
|
220
|
+
if (keyType !== "string" && keyType !== "number" && keyType !== "symbol" && keyType !== "function") throw new TypeError(`Expected key to be a property key or function, but got: ${getTypeName(options.key)}.`);
|
|
452
221
|
const configBindStateKey = Symbol("@optique/config/bindState");
|
|
453
222
|
function isConfigBindState(value) {
|
|
454
223
|
return value != null && typeof value === "object" && configBindStateKey in value;
|
|
455
224
|
}
|
|
456
|
-
function shouldDeferPromptUntilConfigResolves(state) {
|
|
225
|
+
function shouldDeferPromptUntilConfigResolves(state, _exec) {
|
|
457
226
|
const annotations = getAnnotations(state);
|
|
458
227
|
return annotations?.[options.context.id] === phase1ConfigAnnotationMarker;
|
|
459
228
|
}
|
|
229
|
+
const getSuggestInnerState = (state) => isConfigBindState(state) ? state.cliState === void 0 ? inheritAnnotations(state, parser.initialState) : state.cliState : state;
|
|
460
230
|
const boundParser = {
|
|
461
|
-
|
|
231
|
+
mode: parser.mode,
|
|
462
232
|
$valueType: parser.$valueType,
|
|
463
233
|
$stateType: parser.$stateType,
|
|
464
234
|
priority: parser.priority,
|
|
@@ -466,7 +236,13 @@ function bindConfig(parser, options) {
|
|
|
466
236
|
type: "optional",
|
|
467
237
|
terms: parser.usage
|
|
468
238
|
}] : parser.usage,
|
|
239
|
+
leadingNames: parser.leadingNames,
|
|
240
|
+
acceptingAnyToken: parser.acceptingAnyToken,
|
|
469
241
|
initialState: parser.initialState,
|
|
242
|
+
getSuggestRuntimeNodes(state, path) {
|
|
243
|
+
const innerState = getSuggestInnerState(state);
|
|
244
|
+
return delegateSuggestNodes(parser, boundParser, state, path, innerState);
|
|
245
|
+
},
|
|
470
246
|
parse: (context) => {
|
|
471
247
|
const annotations = getAnnotations(context.state);
|
|
472
248
|
const innerState = isConfigBindState(context.state) ? context.state.hasCliValue ? context.state.cliState : parser.initialState : context.state;
|
|
@@ -477,14 +253,14 @@ function bindConfig(parser, options) {
|
|
|
477
253
|
const processResult = (result) => {
|
|
478
254
|
if (result.success) {
|
|
479
255
|
const cliConsumed = result.consumed.length > 0;
|
|
480
|
-
const newState$1 = {
|
|
256
|
+
const newState$1 = injectAnnotations({
|
|
481
257
|
[configBindStateKey]: true,
|
|
482
258
|
hasCliValue: cliConsumed,
|
|
483
|
-
cliState: result.next.state
|
|
484
|
-
|
|
485
|
-
};
|
|
259
|
+
cliState: result.next.state
|
|
260
|
+
}, annotations);
|
|
486
261
|
return {
|
|
487
262
|
success: true,
|
|
263
|
+
...result.provisional ? { provisional: true } : {},
|
|
488
264
|
next: {
|
|
489
265
|
...result.next,
|
|
490
266
|
state: newState$1
|
|
@@ -493,11 +269,10 @@ function bindConfig(parser, options) {
|
|
|
493
269
|
};
|
|
494
270
|
}
|
|
495
271
|
if (result.consumed > 0) return result;
|
|
496
|
-
const newState = {
|
|
272
|
+
const newState = injectAnnotations({
|
|
497
273
|
[configBindStateKey]: true,
|
|
498
|
-
hasCliValue: false
|
|
499
|
-
|
|
500
|
-
};
|
|
274
|
+
hasCliValue: false
|
|
275
|
+
}, annotations);
|
|
501
276
|
return {
|
|
502
277
|
success: true,
|
|
503
278
|
next: {
|
|
@@ -507,53 +282,181 @@ function bindConfig(parser, options) {
|
|
|
507
282
|
consumed: []
|
|
508
283
|
};
|
|
509
284
|
};
|
|
510
|
-
return mapModeValue(parser
|
|
285
|
+
return mapModeValue(parser.mode, parser.parse(innerContext), processResult);
|
|
511
286
|
},
|
|
512
|
-
complete: (state) => {
|
|
513
|
-
if (isConfigBindState(state) && state.hasCliValue) return parser.complete(state.cliState);
|
|
514
|
-
return
|
|
287
|
+
complete: (state, exec) => {
|
|
288
|
+
if (isConfigBindState(state) && state.hasCliValue) return parser.complete(state.cliState, exec);
|
|
289
|
+
return getConfigOrDefault(state, options, parser.mode, parser);
|
|
515
290
|
},
|
|
516
|
-
suggest:
|
|
517
|
-
|
|
291
|
+
suggest: (context, prefix) => {
|
|
292
|
+
const innerState = getSuggestInnerState(context.state);
|
|
293
|
+
const innerContext = innerState !== context.state ? {
|
|
294
|
+
...context,
|
|
295
|
+
state: innerState
|
|
296
|
+
} : context;
|
|
297
|
+
return parser.suggest(innerContext, prefix);
|
|
298
|
+
},
|
|
299
|
+
shouldDeferCompletion: shouldDeferPromptUntilConfigResolves,
|
|
518
300
|
getDocFragments(state, upperDefaultValue) {
|
|
519
301
|
const defaultValue = upperDefaultValue ?? options.default;
|
|
520
302
|
return parser.getDocFragments(state, defaultValue);
|
|
521
303
|
}
|
|
522
304
|
};
|
|
305
|
+
defineTraits(boundParser, {
|
|
306
|
+
inheritsAnnotations: true,
|
|
307
|
+
completesFromSource: true
|
|
308
|
+
});
|
|
309
|
+
if ("placeholder" in parser) Object.defineProperty(boundParser, "placeholder", {
|
|
310
|
+
get() {
|
|
311
|
+
return parser.placeholder;
|
|
312
|
+
},
|
|
313
|
+
configurable: true,
|
|
314
|
+
enumerable: false
|
|
315
|
+
});
|
|
316
|
+
if (typeof parser.normalizeValue === "function") Object.defineProperty(boundParser, "normalizeValue", {
|
|
317
|
+
value: parser.normalizeValue.bind(parser),
|
|
318
|
+
configurable: true,
|
|
319
|
+
enumerable: false
|
|
320
|
+
});
|
|
321
|
+
if (typeof parser.validateValue === "function") Object.defineProperty(boundParser, "validateValue", {
|
|
322
|
+
value: parser.validateValue.bind(parser),
|
|
323
|
+
configurable: true,
|
|
324
|
+
enumerable: false
|
|
325
|
+
});
|
|
326
|
+
const dependencyMetadata = mapSourceMetadata(parser, (sourceMetadata) => ({
|
|
327
|
+
...sourceMetadata,
|
|
328
|
+
getMissingSourceValue: sourceMetadata.preservesSourceValue !== false && options.default !== void 0 ? () => {
|
|
329
|
+
if (typeof parser.validateValue === "function") return parser.validateValue(options.default);
|
|
330
|
+
return {
|
|
331
|
+
success: true,
|
|
332
|
+
value: options.default
|
|
333
|
+
};
|
|
334
|
+
} : void 0,
|
|
335
|
+
extractSourceValue: (state) => {
|
|
336
|
+
if (!isConfigBindState(state)) {
|
|
337
|
+
if (sourceMetadata.preservesSourceValue) return getConfigSourceValue(state, options, state, sourceMetadata.extractSourceValue, parser);
|
|
338
|
+
return sourceMetadata.extractSourceValue(state);
|
|
339
|
+
}
|
|
340
|
+
if (state.hasCliValue) return sourceMetadata.extractSourceValue(state.cliState);
|
|
341
|
+
const fallbackState = state.cliState ?? state;
|
|
342
|
+
if (!sourceMetadata.preservesSourceValue) return sourceMetadata.extractSourceValue(fallbackState);
|
|
343
|
+
return getConfigSourceValue(state, options, fallbackState, sourceMetadata.extractSourceValue, parser);
|
|
344
|
+
}
|
|
345
|
+
}));
|
|
346
|
+
if (dependencyMetadata != null) Object.defineProperty(boundParser, "dependencyMetadata", {
|
|
347
|
+
value: dependencyMetadata,
|
|
348
|
+
configurable: true,
|
|
349
|
+
enumerable: false
|
|
350
|
+
});
|
|
523
351
|
return boundParser;
|
|
524
352
|
}
|
|
525
353
|
/**
|
|
526
354
|
* Helper function to get value from config or default.
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
355
|
+
* Reads only from explicit annotations carried by the current parse state.
|
|
356
|
+
*
|
|
357
|
+
* When `innerParser.validateValue` is available, the returned fallback
|
|
358
|
+
* value is routed through it so that the inner CLI parser's constraints
|
|
359
|
+
* (regex patterns, numeric bounds, choices, etc.) are enforced on
|
|
360
|
+
* config-sourced values and configured defaults (see issue #414). If
|
|
361
|
+
* `innerParser` is absent or does not implement `validateValue` (for
|
|
362
|
+
* example, the inner parser is wrapped in `map()`), the value is
|
|
363
|
+
* returned unchanged to preserve existing behavior.
|
|
364
|
+
*
|
|
365
|
+
* @throws {TypeError} If the key callback returns a Promise or thenable.
|
|
366
|
+
* @throws {Error} Propagates errors thrown by
|
|
367
|
+
* `innerParser.validateValue()` (via
|
|
368
|
+
* {@link validateFallbackValue}) while revalidating a
|
|
369
|
+
* config-sourced value or the configured `default`
|
|
370
|
+
* against the inner CLI parser's constraints (see
|
|
371
|
+
* issue #414).
|
|
530
372
|
*/
|
|
531
|
-
function getConfigOrDefault(state, options) {
|
|
373
|
+
function getConfigOrDefault(state, options, mode, innerParser) {
|
|
532
374
|
const annotations = getAnnotations(state);
|
|
533
375
|
const contextId = options.context.id;
|
|
534
376
|
const annotationValue = annotations?.[contextId];
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
if (configData === void 0 || configData === null) {
|
|
538
|
-
configData = getActiveConfig(contextId);
|
|
539
|
-
configMeta = getActiveConfigMeta(contextId);
|
|
540
|
-
}
|
|
377
|
+
const configData = annotationValue?.data;
|
|
378
|
+
const configMeta = annotationValue?.meta;
|
|
541
379
|
let configValue;
|
|
542
|
-
if (configData !== void 0 && configData !== null) if (typeof options.key === "function")
|
|
543
|
-
|
|
544
|
-
|
|
380
|
+
if (configData !== void 0 && configData !== null) if (typeof options.key === "function") {
|
|
381
|
+
configValue = options.key(configData, configMeta);
|
|
382
|
+
if (configValue != null && (typeof configValue === "object" || typeof configValue === "function") && "then" in configValue && typeof configValue.then === "function") throw new TypeError("The key callback must return a synchronous value, but got a thenable.");
|
|
383
|
+
} else configValue = configData[options.key];
|
|
384
|
+
if (configValue !== void 0) return validateFallbackValue(mode, innerParser, configValue);
|
|
385
|
+
if (options.default !== void 0) return validateFallbackValue(mode, innerParser, options.default);
|
|
386
|
+
return wrapForMode(mode, {
|
|
387
|
+
success: false,
|
|
388
|
+
error: message`Missing required configuration value.`
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Routes a (successful) fallback value through the inner parser's
|
|
393
|
+
* `validateValue()` hook, or returns the value unchanged when no
|
|
394
|
+
* validator is available. See {@link getConfigOrDefault} for context.
|
|
395
|
+
*
|
|
396
|
+
* @throws {Error} Propagates errors thrown by
|
|
397
|
+
* `innerParser.validateValue()` while revalidating the
|
|
398
|
+
* fallback value against the inner CLI parser's
|
|
399
|
+
* constraints (see issue #414). When the hook returns
|
|
400
|
+
* a failed {@link Result} the failure is propagated
|
|
401
|
+
* through the return value; only an actual exception
|
|
402
|
+
* thrown by the hook escapes through this path.
|
|
403
|
+
*/
|
|
404
|
+
function validateFallbackValue(mode, innerParser, value) {
|
|
405
|
+
if (innerParser == null || typeof innerParser.validateValue !== "function") return wrapForMode(mode, {
|
|
545
406
|
success: true,
|
|
546
|
-
value
|
|
407
|
+
value
|
|
408
|
+
});
|
|
409
|
+
return innerParser.validateValue(value);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Resolves a config-backed dependency source with fallback priority.
|
|
413
|
+
*
|
|
414
|
+
* This first checks annotations via {@link getConfigOrDefault}. If no
|
|
415
|
+
* config-backed value is available, it falls back to `options.default` and
|
|
416
|
+
* finally delegates to the wrapped parser's source extractor.
|
|
417
|
+
*
|
|
418
|
+
* When `innerParser` exposes `validateValue`, the returned fallback is
|
|
419
|
+
* routed through it so that the inner CLI parser's constraints are
|
|
420
|
+
* enforced on config-sourced source values and configured defaults
|
|
421
|
+
* (see issue #414). This helper is only invoked from the
|
|
422
|
+
* `preservesSourceValue: true` branch in {@link bindConfig}, so the
|
|
423
|
+
* source value type is guaranteed to equal `TValue`.
|
|
424
|
+
*
|
|
425
|
+
* @param state The wrapper state, which may carry config annotations.
|
|
426
|
+
* @param options The binding options with lookup and default settings.
|
|
427
|
+
* @param innerState The unwrapped inner state for delegated extraction.
|
|
428
|
+
* @param extractInnerSourceValue The wrapped parser's source extractor.
|
|
429
|
+
* @param innerParser The wrapped parser, used to revalidate fallback values.
|
|
430
|
+
* @returns The resolved source value, an async source value, or `undefined`.
|
|
431
|
+
* @throws {TypeError} If {@link getConfigOrDefault} rejects a thenable-returning
|
|
432
|
+
* key callback.
|
|
433
|
+
* @throws {Error} Propagates errors thrown by
|
|
434
|
+
* `innerParser.validateValue()` (via
|
|
435
|
+
* {@link getConfigOrDefault} / {@link validateFallbackValue})
|
|
436
|
+
* while revalidating a config-sourced value or the
|
|
437
|
+
* configured `default` against the inner CLI parser's
|
|
438
|
+
* constraints (see issue #414).
|
|
439
|
+
*/
|
|
440
|
+
function getConfigSourceValue(state, options, innerState, extractInnerSourceValue, innerParser) {
|
|
441
|
+
const annotations = getAnnotations(state);
|
|
442
|
+
const contextId = options.context.id;
|
|
443
|
+
const annotationValue = annotations?.[contextId];
|
|
444
|
+
const configData = annotationValue?.data;
|
|
445
|
+
const validateFallback = (parsed) => {
|
|
446
|
+
if (!parsed.success) return parsed;
|
|
447
|
+
if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
|
|
448
|
+
return innerParser.validateValue(parsed.value);
|
|
547
449
|
};
|
|
548
|
-
if (
|
|
450
|
+
if (configData !== void 0 && configData !== null) {
|
|
451
|
+
const resolved = getConfigOrDefault(state, options, "sync", void 0);
|
|
452
|
+
if (resolved.success) return validateFallback(resolved);
|
|
453
|
+
}
|
|
454
|
+
if (options.default !== void 0) return validateFallback({
|
|
549
455
|
success: true,
|
|
550
456
|
value: options.default
|
|
551
|
-
};
|
|
552
|
-
return
|
|
553
|
-
success: false,
|
|
554
|
-
error: message`Missing required configuration value.`
|
|
555
|
-
};
|
|
457
|
+
});
|
|
458
|
+
return extractInnerSourceValue(innerState);
|
|
556
459
|
}
|
|
557
460
|
|
|
558
461
|
//#endregion
|
|
559
|
-
export { bindConfig,
|
|
462
|
+
export { bindConfig, createConfigContext };
|