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