@optique/config 0.10.7 → 1.0.0-dev.1116
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/index.cjs +616 -7
- package/dist/index.d.cts +237 -2
- package/dist/index.d.ts +237 -2
- package/dist/index.js +586 -2
- package/package.json +4 -11
- package/dist/index-CYn_0yAG.d.ts +0 -155
- package/dist/index-D25zYfjf.d.cts +0 -155
- package/dist/run.cjs +0 -189
- package/dist/run.d.cts +0 -297
- package/dist/run.d.ts +0 -297
- package/dist/run.js +0 -189
- package/dist/src-9MkUoh9z.cjs +0 -301
- package/dist/src-Dm_17c1d.js +0 -237
package/README.md
CHANGED
|
@@ -29,11 +29,11 @@ Quick start
|
|
|
29
29
|
~~~~ typescript
|
|
30
30
|
import { z } from "zod";
|
|
31
31
|
import { createConfigContext, bindConfig } from "@optique/config";
|
|
32
|
-
import { runWithConfig } from "@optique/config/run";
|
|
33
32
|
import { object } from "@optique/core/constructs";
|
|
34
33
|
import { option } from "@optique/core/primitives";
|
|
35
34
|
import { string, integer } from "@optique/core/valueparser";
|
|
36
35
|
import { withDefault } from "@optique/core/modifiers";
|
|
36
|
+
import { runAsync } from "@optique/run";
|
|
37
37
|
|
|
38
38
|
// 1. Define config schema
|
|
39
39
|
const configSchema = z.object({
|
|
@@ -58,8 +58,9 @@ const parser = object({
|
|
|
58
58
|
}),
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
// 3. Run with config support
|
|
62
|
-
const result = await
|
|
61
|
+
// 3. Run with config support via contexts
|
|
62
|
+
const result = await runAsync(parser, {
|
|
63
|
+
contexts: [configContext],
|
|
63
64
|
getConfigPath: (parsed) => parsed.config,
|
|
64
65
|
args: process.argv.slice(2),
|
|
65
66
|
});
|
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,617 @@
|
|
|
1
|
-
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
2
22
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
23
|
+
//#endregion
|
|
24
|
+
const node_fs = __toESM(require("node:fs"));
|
|
25
|
+
const node_path = __toESM(require("node:path"));
|
|
26
|
+
const __optique_core_annotations = __toESM(require("@optique/core/annotations"));
|
|
27
|
+
const __optique_core_context = __toESM(require("@optique/core/context"));
|
|
28
|
+
const __optique_core_message = __toESM(require("@optique/core/message"));
|
|
29
|
+
const __optique_core_mode_dispatch = __toESM(require("@optique/core/mode-dispatch"));
|
|
30
|
+
|
|
31
|
+
//#region src/index.ts
|
|
32
|
+
const phase2UndefinedParsedValueKey = Symbol("@optique/config/phase2UndefinedParsedValue");
|
|
33
|
+
/**
|
|
34
|
+
* Internal registry for active config data during config context execution.
|
|
35
|
+
* This is a workaround for the limitation that object() doesn't propagate
|
|
36
|
+
* annotations to child field parsers.
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
const activeConfigRegistry = /* @__PURE__ */ new Map();
|
|
40
|
+
/**
|
|
41
|
+
* Internal registry for active config metadata during config context execution.
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
const activeConfigMetaRegistry = /* @__PURE__ */ new Map();
|
|
45
|
+
const phase1ConfigAnnotationMarker = Symbol("@optique/config/phase1Annotation");
|
|
46
|
+
function isDeferredPromptValue(value) {
|
|
47
|
+
return (0, __optique_core_context.isPlaceholderValue)(value);
|
|
48
|
+
}
|
|
49
|
+
function isPhase2UndefinedParsedValue(value) {
|
|
50
|
+
return value != null && typeof value === "object" && phase2UndefinedParsedValueKey in value;
|
|
51
|
+
}
|
|
52
|
+
function isPlainObject(value) {
|
|
53
|
+
const proto = Object.getPrototypeOf(value);
|
|
54
|
+
return proto === Object.prototype || proto === null;
|
|
55
|
+
}
|
|
56
|
+
function shouldSkipCollectionOwnKey(value, key) {
|
|
57
|
+
if (Array.isArray(value)) return key === "length" || typeof key === "string" && Number.isInteger(Number(key)) && String(Number(key)) === key;
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
function containsDeferredPromptValuesInOwnProperties(value, seen) {
|
|
61
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
62
|
+
if (shouldSkipCollectionOwnKey(value, key)) continue;
|
|
63
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
64
|
+
if (descriptor != null && "value" in descriptor && containsDeferredPromptValues(descriptor.value, seen)) return true;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
function copySanitizedOwnProperties(source, target, seen) {
|
|
69
|
+
for (const key of Reflect.ownKeys(source)) {
|
|
70
|
+
if (shouldSkipCollectionOwnKey(source, key)) continue;
|
|
71
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
72
|
+
if (descriptor == null) continue;
|
|
73
|
+
if ("value" in descriptor) descriptor.value = stripDeferredPromptValues(descriptor.value, seen);
|
|
74
|
+
Object.defineProperty(target, key, descriptor);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function containsDeferredPromptValues(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
78
|
+
if (isDeferredPromptValue(value)) return true;
|
|
79
|
+
if (value == null || typeof value !== "object") return false;
|
|
80
|
+
if (seen.has(value)) return false;
|
|
81
|
+
seen.add(value);
|
|
82
|
+
if (Array.isArray(value)) {
|
|
83
|
+
if (value.some((item) => containsDeferredPromptValues(item, seen))) return true;
|
|
84
|
+
return containsDeferredPromptValuesInOwnProperties(value, seen);
|
|
85
|
+
}
|
|
86
|
+
if (value instanceof Set) {
|
|
87
|
+
for (const entryValue of value) if (containsDeferredPromptValues(entryValue, seen)) return true;
|
|
88
|
+
return containsDeferredPromptValuesInOwnProperties(value, seen);
|
|
89
|
+
}
|
|
90
|
+
if (value instanceof Map) {
|
|
91
|
+
for (const [key, entryValue] of value) if (containsDeferredPromptValues(key, seen) || containsDeferredPromptValues(entryValue, seen)) return true;
|
|
92
|
+
return containsDeferredPromptValuesInOwnProperties(value, seen);
|
|
93
|
+
}
|
|
94
|
+
return containsDeferredPromptValuesInOwnProperties(value, seen);
|
|
95
|
+
}
|
|
96
|
+
const SANITIZE_FAILED = Symbol("sanitizeFailed");
|
|
97
|
+
const activeSanitizations = /* @__PURE__ */ new WeakMap();
|
|
98
|
+
function callWithSanitizedOwnProperties(target, fn, args, strip, seen) {
|
|
99
|
+
let active = activeSanitizations.get(target);
|
|
100
|
+
if (active != null) active.count++;
|
|
101
|
+
else {
|
|
102
|
+
const saved = /* @__PURE__ */ new Map();
|
|
103
|
+
const sanitizedValues = /* @__PURE__ */ new Map();
|
|
104
|
+
for (const key of Reflect.ownKeys(target)) {
|
|
105
|
+
const desc = Object.getOwnPropertyDescriptor(target, key);
|
|
106
|
+
if (desc != null && "value" in desc) {
|
|
107
|
+
let stripped;
|
|
108
|
+
try {
|
|
109
|
+
stripped = strip(desc.value, seen);
|
|
110
|
+
} catch {
|
|
111
|
+
for (const [k, d] of saved) try {
|
|
112
|
+
Object.defineProperty(target, k, d);
|
|
113
|
+
} catch {}
|
|
114
|
+
return SANITIZE_FAILED;
|
|
115
|
+
}
|
|
116
|
+
if (stripped !== desc.value) try {
|
|
117
|
+
Object.defineProperty(target, key, {
|
|
118
|
+
...desc,
|
|
119
|
+
value: stripped
|
|
120
|
+
});
|
|
121
|
+
saved.set(key, desc);
|
|
122
|
+
sanitizedValues.set(key, stripped);
|
|
123
|
+
} catch {
|
|
124
|
+
for (const [k, d] of saved) try {
|
|
125
|
+
Object.defineProperty(target, k, d);
|
|
126
|
+
} catch {}
|
|
127
|
+
return SANITIZE_FAILED;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
active = {
|
|
132
|
+
saved,
|
|
133
|
+
sanitizedValues,
|
|
134
|
+
count: 1
|
|
135
|
+
};
|
|
136
|
+
activeSanitizations.set(target, active);
|
|
137
|
+
}
|
|
138
|
+
function release() {
|
|
139
|
+
active.count--;
|
|
140
|
+
if (active.count === 0) {
|
|
141
|
+
activeSanitizations.delete(target);
|
|
142
|
+
for (const [key, desc] of active.saved) try {
|
|
143
|
+
const current = Object.getOwnPropertyDescriptor(target, key);
|
|
144
|
+
if (current == null) continue;
|
|
145
|
+
if ("value" in current && current.value !== active.sanitizedValues.get(key)) continue;
|
|
146
|
+
Object.defineProperty(target, key, desc);
|
|
147
|
+
} catch {}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
let result;
|
|
151
|
+
try {
|
|
152
|
+
result = fn.apply(target, args);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
release();
|
|
155
|
+
throw e;
|
|
156
|
+
}
|
|
157
|
+
if (result instanceof Promise) return result.then((v) => {
|
|
158
|
+
release();
|
|
159
|
+
return strip(v, seen);
|
|
160
|
+
}, (e) => {
|
|
161
|
+
release();
|
|
162
|
+
throw e;
|
|
163
|
+
});
|
|
164
|
+
release();
|
|
165
|
+
return strip(result, seen);
|
|
166
|
+
}
|
|
167
|
+
function callMethodOnSanitizedTarget(fn, proxy, target, args, strip, seen) {
|
|
168
|
+
const result = callWithSanitizedOwnProperties(target, fn, args, strip, seen);
|
|
169
|
+
if (result !== SANITIZE_FAILED) return result;
|
|
170
|
+
const fallback = fn.apply(proxy, args);
|
|
171
|
+
if (fallback instanceof Promise) return fallback.then((v) => strip(v, seen));
|
|
172
|
+
return strip(fallback, seen);
|
|
173
|
+
}
|
|
174
|
+
function createSanitizedNonPlainView(value, seen) {
|
|
175
|
+
const methodCache = /* @__PURE__ */ new Map();
|
|
176
|
+
const proxy = new Proxy(value, {
|
|
177
|
+
get(target, key, receiver) {
|
|
178
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
179
|
+
if (descriptor != null && "value" in descriptor) {
|
|
180
|
+
if (!descriptor.configurable && !descriptor.writable) return descriptor.value;
|
|
181
|
+
const val = stripDeferredPromptValues(descriptor.value, seen);
|
|
182
|
+
if (typeof val === "function") {
|
|
183
|
+
if (!descriptor.configurable && !descriptor.writable || /^class[\s{]/.test(Function.prototype.toString.call(val))) return val;
|
|
184
|
+
const cached = methodCache.get(key);
|
|
185
|
+
if (cached != null && cached.fn === val) return cached.wrapper;
|
|
186
|
+
const wrapper = function(...args) {
|
|
187
|
+
if (this !== proxy) return stripDeferredPromptValues(val.apply(this, args), seen);
|
|
188
|
+
return callMethodOnSanitizedTarget(val, proxy, target, args, stripDeferredPromptValues, seen);
|
|
189
|
+
};
|
|
190
|
+
methodCache.set(key, {
|
|
191
|
+
fn: val,
|
|
192
|
+
wrapper
|
|
193
|
+
});
|
|
194
|
+
return wrapper;
|
|
195
|
+
}
|
|
196
|
+
return val;
|
|
197
|
+
}
|
|
198
|
+
let isAccessor = false;
|
|
199
|
+
for (let proto = target; proto != null; proto = Object.getPrototypeOf(proto)) {
|
|
200
|
+
const d = Object.getOwnPropertyDescriptor(proto, key);
|
|
201
|
+
if (d != null) {
|
|
202
|
+
isAccessor = "get" in d;
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const result = Reflect.get(target, key, receiver);
|
|
207
|
+
if (typeof result === "function") {
|
|
208
|
+
if (/^class[\s{]/.test(Function.prototype.toString.call(result))) return result;
|
|
209
|
+
if (!isAccessor) {
|
|
210
|
+
const cached = methodCache.get(key);
|
|
211
|
+
if (cached != null && cached.fn === result) return cached.wrapper;
|
|
212
|
+
const wrapper = function(...args) {
|
|
213
|
+
if (this !== proxy) return stripDeferredPromptValues(result.apply(this, args), seen);
|
|
214
|
+
return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValues, seen);
|
|
215
|
+
};
|
|
216
|
+
methodCache.set(key, {
|
|
217
|
+
fn: result,
|
|
218
|
+
wrapper
|
|
219
|
+
});
|
|
220
|
+
return wrapper;
|
|
221
|
+
}
|
|
222
|
+
return function(...args) {
|
|
223
|
+
if (this !== proxy) return stripDeferredPromptValues(result.apply(this, args), seen);
|
|
224
|
+
return callMethodOnSanitizedTarget(result, proxy, target, args, stripDeferredPromptValues, seen);
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return stripDeferredPromptValues(result, seen);
|
|
228
|
+
},
|
|
229
|
+
getOwnPropertyDescriptor(target, key) {
|
|
230
|
+
const descriptor = Object.getOwnPropertyDescriptor(target, key);
|
|
231
|
+
if (descriptor == null || !("value" in descriptor)) return descriptor;
|
|
232
|
+
if (!descriptor.configurable && !descriptor.writable) return descriptor;
|
|
233
|
+
return {
|
|
234
|
+
...descriptor,
|
|
235
|
+
value: stripDeferredPromptValues(descriptor.value, seen)
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
seen.set(value, proxy);
|
|
240
|
+
return proxy;
|
|
241
|
+
}
|
|
242
|
+
function stripDeferredPromptValues(value, seen = /* @__PURE__ */ new WeakMap()) {
|
|
243
|
+
if (isDeferredPromptValue(value)) return void 0;
|
|
244
|
+
if (value == null || typeof value !== "object") return value;
|
|
245
|
+
const cached = seen.get(value);
|
|
246
|
+
if (cached !== void 0) return cached;
|
|
247
|
+
if (Array.isArray(value)) {
|
|
248
|
+
if (!containsDeferredPromptValues(value)) return value;
|
|
249
|
+
const clone$1 = new Array(value.length);
|
|
250
|
+
seen.set(value, clone$1);
|
|
251
|
+
for (let i = 0; i < value.length; i++) clone$1[i] = stripDeferredPromptValues(value[i], seen);
|
|
252
|
+
copySanitizedOwnProperties(value, clone$1, seen);
|
|
253
|
+
return clone$1;
|
|
254
|
+
}
|
|
255
|
+
if (value instanceof Set) {
|
|
256
|
+
if (!containsDeferredPromptValues(value)) return value;
|
|
257
|
+
const clone$1 = /* @__PURE__ */ new Set();
|
|
258
|
+
seen.set(value, clone$1);
|
|
259
|
+
for (const entryValue of value) clone$1.add(stripDeferredPromptValues(entryValue, seen));
|
|
260
|
+
copySanitizedOwnProperties(value, clone$1, seen);
|
|
261
|
+
return clone$1;
|
|
262
|
+
}
|
|
263
|
+
if (value instanceof Map) {
|
|
264
|
+
if (!containsDeferredPromptValues(value)) return value;
|
|
265
|
+
const clone$1 = /* @__PURE__ */ new Map();
|
|
266
|
+
seen.set(value, clone$1);
|
|
267
|
+
for (const [key, entryValue] of value) clone$1.set(stripDeferredPromptValues(key, seen), stripDeferredPromptValues(entryValue, seen));
|
|
268
|
+
copySanitizedOwnProperties(value, clone$1, seen);
|
|
269
|
+
return clone$1;
|
|
270
|
+
}
|
|
271
|
+
if (!isPlainObject(value)) return containsDeferredPromptValues(value) ? createSanitizedNonPlainView(value, seen) : value;
|
|
272
|
+
if (!containsDeferredPromptValues(value)) return value;
|
|
273
|
+
const clone = Object.create(Object.getPrototypeOf(value));
|
|
274
|
+
seen.set(value, clone);
|
|
275
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
276
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
277
|
+
if (descriptor == null) continue;
|
|
278
|
+
if ("value" in descriptor) descriptor.value = stripDeferredPromptValues(descriptor.value, seen);
|
|
279
|
+
Object.defineProperty(clone, key, descriptor);
|
|
280
|
+
}
|
|
281
|
+
return clone;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Sets active config data for a context.
|
|
285
|
+
* @internal
|
|
286
|
+
*/
|
|
287
|
+
function setActiveConfig(contextId, data) {
|
|
288
|
+
activeConfigRegistry.set(contextId, data);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Gets active config data for a context.
|
|
292
|
+
* @internal
|
|
293
|
+
*/
|
|
294
|
+
function getActiveConfig(contextId) {
|
|
295
|
+
return activeConfigRegistry.get(contextId);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Clears active config data for a context.
|
|
299
|
+
* @internal
|
|
300
|
+
*/
|
|
301
|
+
function clearActiveConfig(contextId) {
|
|
302
|
+
activeConfigRegistry.delete(contextId);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Sets active config metadata for a context.
|
|
306
|
+
* @internal
|
|
307
|
+
*/
|
|
308
|
+
function setActiveConfigMeta(contextId, meta) {
|
|
309
|
+
activeConfigMetaRegistry.set(contextId, meta);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Gets active config metadata for a context.
|
|
313
|
+
* @internal
|
|
314
|
+
*/
|
|
315
|
+
function getActiveConfigMeta(contextId) {
|
|
316
|
+
return activeConfigMetaRegistry.get(contextId);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Clears active config metadata for a context.
|
|
320
|
+
* @internal
|
|
321
|
+
*/
|
|
322
|
+
function clearActiveConfigMeta(contextId) {
|
|
323
|
+
activeConfigMetaRegistry.delete(contextId);
|
|
324
|
+
}
|
|
325
|
+
function getTypeName(value) {
|
|
326
|
+
if (value === null) return "null";
|
|
327
|
+
if (Array.isArray(value)) return "array";
|
|
328
|
+
return typeof value;
|
|
329
|
+
}
|
|
330
|
+
function isStandardSchema(value) {
|
|
331
|
+
if (value == null || typeof value !== "object" && typeof value !== "function") return false;
|
|
332
|
+
if (!("~standard" in value)) return false;
|
|
333
|
+
const standard = value["~standard"];
|
|
334
|
+
if (standard == null || typeof standard !== "object" && typeof standard !== "function") return false;
|
|
335
|
+
return typeof standard.validate === "function";
|
|
336
|
+
}
|
|
337
|
+
function isErrnoException(error) {
|
|
338
|
+
return typeof error === "object" && error !== null && "code" in error;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Validates raw data against a Standard Schema and returns the validated value.
|
|
342
|
+
* @internal
|
|
343
|
+
*/
|
|
344
|
+
function processValidationResult(validationResult) {
|
|
345
|
+
if (validationResult.issues) {
|
|
346
|
+
const firstIssue = validationResult.issues[0];
|
|
347
|
+
throw new Error(`Config validation failed: ${firstIssue?.message ?? "Unknown error"}`);
|
|
348
|
+
}
|
|
349
|
+
return validationResult.value;
|
|
350
|
+
}
|
|
351
|
+
function validateWithSchema(schema, rawData) {
|
|
352
|
+
const validation = schema["~standard"].validate(rawData);
|
|
353
|
+
if (validation instanceof Promise) return validation.then((result) => processValidationResult(result));
|
|
354
|
+
return processValidationResult(validation);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Creates a config context for use with Optique parsers.
|
|
358
|
+
*
|
|
359
|
+
* The config context implements the `SourceContext` interface and can be used
|
|
360
|
+
* with `runWith()` from *@optique/core* or `run()`/`runAsync()` from
|
|
361
|
+
* *@optique/run* to provide configuration file support.
|
|
362
|
+
*
|
|
363
|
+
* @template T The output type of the config schema.
|
|
364
|
+
* @template TConfigMeta The metadata type for config sources.
|
|
365
|
+
* @param options Configuration options including schema and optional file
|
|
366
|
+
* parser.
|
|
367
|
+
* @returns A config context that can be used with `bindConfig()` and runners.
|
|
368
|
+
* @throws {TypeError} If `schema` is not a valid Standard Schema object.
|
|
369
|
+
* @throws {TypeError} If `fileParser` is provided but is not a function.
|
|
370
|
+
* @since 0.10.0
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```typescript
|
|
374
|
+
* import { z } from "zod";
|
|
375
|
+
* import { createConfigContext } from "@optique/config";
|
|
376
|
+
*
|
|
377
|
+
* const schema = z.object({
|
|
378
|
+
* host: z.string(),
|
|
379
|
+
* port: z.number(),
|
|
380
|
+
* });
|
|
381
|
+
*
|
|
382
|
+
* const configContext = createConfigContext({ schema });
|
|
383
|
+
* ```
|
|
384
|
+
*/
|
|
385
|
+
function createConfigContext(options) {
|
|
386
|
+
const rawSchema = options.schema;
|
|
387
|
+
if (!isStandardSchema(rawSchema)) throw new TypeError(`Expected schema to be a Standard Schema object, but got: ${getTypeName(rawSchema)}.`);
|
|
388
|
+
const rawFileParser = options.fileParser;
|
|
389
|
+
if (rawFileParser !== void 0 && typeof rawFileParser !== "function") throw new TypeError(`Expected fileParser to be a function, but got: ${getTypeName(rawFileParser)}.`);
|
|
390
|
+
const contextId = Symbol(`@optique/config:${Math.random()}`);
|
|
391
|
+
const context = {
|
|
392
|
+
id: contextId,
|
|
393
|
+
schema: rawSchema,
|
|
394
|
+
mode: "dynamic",
|
|
395
|
+
getInternalAnnotations(parsed, annotations) {
|
|
396
|
+
if (parsed === void 0) return { [contextId]: phase1ConfigAnnotationMarker };
|
|
397
|
+
return Object.getOwnPropertySymbols(annotations).includes(contextId) ? void 0 : { [contextId]: void 0 };
|
|
398
|
+
},
|
|
399
|
+
finalizeParsed(parsed) {
|
|
400
|
+
return parsed === void 0 ? { [phase2UndefinedParsedValueKey]: true } : parsed;
|
|
401
|
+
},
|
|
402
|
+
getAnnotations(parsed, runtimeOptions) {
|
|
403
|
+
if (parsed === void 0) return {};
|
|
404
|
+
const opts = runtimeOptions;
|
|
405
|
+
if (!opts || !opts.getConfigPath && !opts.load) throw new TypeError("Either getConfigPath or load must be provided in the runner options when using ConfigContext.");
|
|
406
|
+
if (opts.load !== void 0 && typeof opts.load !== "function") throw new TypeError(`Expected load to be a function, but got: ${getTypeName(opts.load)}.`);
|
|
407
|
+
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)}.`);
|
|
408
|
+
const parsedValue = isPhase2UndefinedParsedValue(parsed) ? void 0 : stripDeferredPromptValues(parsed);
|
|
409
|
+
const parsedPlaceholder = parsedValue;
|
|
410
|
+
const buildAnnotations = (configData, configMeta) => {
|
|
411
|
+
if (configData === void 0 || configData === null) return {};
|
|
412
|
+
setActiveConfig(contextId, configData);
|
|
413
|
+
if (configMeta !== void 0) {
|
|
414
|
+
setActiveConfigMeta(contextId, configMeta);
|
|
415
|
+
return { [contextId]: {
|
|
416
|
+
data: configData,
|
|
417
|
+
meta: configMeta
|
|
418
|
+
} };
|
|
419
|
+
}
|
|
420
|
+
return { [contextId]: { data: configData } };
|
|
421
|
+
};
|
|
422
|
+
const validateAndBuildAnnotations = (rawData, configMeta) => {
|
|
423
|
+
const validated = validateWithSchema(rawSchema, rawData);
|
|
424
|
+
if (validated instanceof Promise) return validated.then((configData) => buildAnnotations(configData, configMeta));
|
|
425
|
+
return buildAnnotations(validated, configMeta);
|
|
426
|
+
};
|
|
427
|
+
if (opts.load) {
|
|
428
|
+
const loaded = opts.load(parsedPlaceholder);
|
|
429
|
+
if (loaded instanceof Promise) return loaded.then(({ config, meta }) => validateAndBuildAnnotations(config, meta));
|
|
430
|
+
return validateAndBuildAnnotations(loaded.config, loaded.meta);
|
|
431
|
+
}
|
|
432
|
+
if (opts.getConfigPath) {
|
|
433
|
+
const configPath = opts.getConfigPath(parsedPlaceholder);
|
|
434
|
+
if (configPath !== void 0 && typeof configPath !== "string") throw new TypeError(`Expected getConfigPath() to return a string or undefined, but got: ${getTypeName(configPath)}.`);
|
|
435
|
+
if (!configPath) return {};
|
|
436
|
+
const absoluteConfigPath = (0, node_path.resolve)(configPath);
|
|
437
|
+
const singleFileMeta = {
|
|
438
|
+
configDir: (0, node_path.dirname)(absoluteConfigPath),
|
|
439
|
+
configPath: absoluteConfigPath
|
|
440
|
+
};
|
|
441
|
+
try {
|
|
442
|
+
const contents = (0, node_fs.readFileSync)(absoluteConfigPath);
|
|
443
|
+
let rawData;
|
|
444
|
+
if (rawFileParser) rawData = rawFileParser(contents);
|
|
445
|
+
else {
|
|
446
|
+
const text = new TextDecoder().decode(contents);
|
|
447
|
+
rawData = JSON.parse(text);
|
|
448
|
+
}
|
|
449
|
+
return validateAndBuildAnnotations(rawData, singleFileMeta);
|
|
450
|
+
} catch (error) {
|
|
451
|
+
if (isErrnoException(error) && error.code === "ENOENT") return {};
|
|
452
|
+
if (error instanceof SyntaxError) throw new Error(`Failed to parse config file ${absoluteConfigPath}: ${error.message}`);
|
|
453
|
+
throw error;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return {};
|
|
457
|
+
},
|
|
458
|
+
[Symbol.dispose]() {
|
|
459
|
+
clearActiveConfig(contextId);
|
|
460
|
+
clearActiveConfigMeta(contextId);
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
return context;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Binds a parser to configuration values with fallback priority.
|
|
467
|
+
*
|
|
468
|
+
* The binding implements the following priority order:
|
|
469
|
+
* 1. CLI argument (if provided)
|
|
470
|
+
* 2. Config file value (if available)
|
|
471
|
+
* 3. Default value (if specified)
|
|
472
|
+
* 4. Error (if none of the above)
|
|
473
|
+
*
|
|
474
|
+
* @template M The parser mode (sync or async).
|
|
475
|
+
* @template TValue The parser value type.
|
|
476
|
+
* @template TState The parser state type.
|
|
477
|
+
* @template T The config data type.
|
|
478
|
+
* @param parser The parser to bind to config values.
|
|
479
|
+
* @param options Binding options including context, key, and default.
|
|
480
|
+
* @returns A new parser with config fallback behavior.
|
|
481
|
+
* @throws {TypeError} If `key` is not a property key or function.
|
|
482
|
+
* @since 0.10.0
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```typescript
|
|
486
|
+
* import { bindConfig } from "@optique/config";
|
|
487
|
+
* import { option } from "@optique/core/primitives";
|
|
488
|
+
* import { string } from "@optique/core/valueparser";
|
|
489
|
+
*
|
|
490
|
+
* const hostParser = bindConfig(option("--host", string()), {
|
|
491
|
+
* context: configContext,
|
|
492
|
+
* key: "host",
|
|
493
|
+
* default: "localhost",
|
|
494
|
+
* });
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
497
|
+
function bindConfig(parser, options) {
|
|
498
|
+
const keyType = typeof options.key;
|
|
499
|
+
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)}.`);
|
|
500
|
+
const configBindStateKey = Symbol("@optique/config/bindState");
|
|
501
|
+
function isConfigBindState(value) {
|
|
502
|
+
return value != null && typeof value === "object" && configBindStateKey in value;
|
|
503
|
+
}
|
|
504
|
+
function shouldDeferPromptUntilConfigResolves(state) {
|
|
505
|
+
const annotations = (0, __optique_core_annotations.getAnnotations)(state);
|
|
506
|
+
return annotations?.[options.context.id] === phase1ConfigAnnotationMarker;
|
|
507
|
+
}
|
|
508
|
+
const boundParser = {
|
|
509
|
+
$mode: parser.$mode,
|
|
510
|
+
$valueType: parser.$valueType,
|
|
511
|
+
$stateType: parser.$stateType,
|
|
512
|
+
priority: parser.priority,
|
|
513
|
+
usage: options.default !== void 0 ? [{
|
|
514
|
+
type: "optional",
|
|
515
|
+
terms: parser.usage
|
|
516
|
+
}] : parser.usage,
|
|
517
|
+
initialState: parser.initialState,
|
|
518
|
+
parse: (context) => {
|
|
519
|
+
const annotations = (0, __optique_core_annotations.getAnnotations)(context.state);
|
|
520
|
+
const innerState = isConfigBindState(context.state) ? context.state.hasCliValue ? context.state.cliState : parser.initialState : context.state;
|
|
521
|
+
const innerContext = innerState !== context.state ? {
|
|
522
|
+
...context,
|
|
523
|
+
state: innerState
|
|
524
|
+
} : context;
|
|
525
|
+
const processResult = (result) => {
|
|
526
|
+
if (result.success) {
|
|
527
|
+
const cliConsumed = result.consumed.length > 0;
|
|
528
|
+
const newState$1 = {
|
|
529
|
+
[configBindStateKey]: true,
|
|
530
|
+
hasCliValue: cliConsumed,
|
|
531
|
+
cliState: result.next.state,
|
|
532
|
+
...annotations && { [__optique_core_annotations.annotationKey]: annotations }
|
|
533
|
+
};
|
|
534
|
+
return {
|
|
535
|
+
success: true,
|
|
536
|
+
next: {
|
|
537
|
+
...result.next,
|
|
538
|
+
state: newState$1
|
|
539
|
+
},
|
|
540
|
+
consumed: result.consumed
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
if (result.consumed > 0) return result;
|
|
544
|
+
const newState = {
|
|
545
|
+
[configBindStateKey]: true,
|
|
546
|
+
hasCliValue: false,
|
|
547
|
+
...annotations && { [__optique_core_annotations.annotationKey]: annotations }
|
|
548
|
+
};
|
|
549
|
+
return {
|
|
550
|
+
success: true,
|
|
551
|
+
next: {
|
|
552
|
+
...innerContext,
|
|
553
|
+
state: newState
|
|
554
|
+
},
|
|
555
|
+
consumed: []
|
|
556
|
+
};
|
|
557
|
+
};
|
|
558
|
+
return (0, __optique_core_mode_dispatch.mapModeValue)(parser.$mode, parser.parse(innerContext), processResult);
|
|
559
|
+
},
|
|
560
|
+
complete: (state) => {
|
|
561
|
+
if (isConfigBindState(state) && state.hasCliValue) return parser.complete(state.cliState);
|
|
562
|
+
return (0, __optique_core_mode_dispatch.wrapForMode)(parser.$mode, getConfigOrDefault(state, options));
|
|
563
|
+
},
|
|
564
|
+
suggest: parser.suggest,
|
|
565
|
+
shouldDeferCompletion: shouldDeferPromptUntilConfigResolves,
|
|
566
|
+
getDocFragments(state, upperDefaultValue) {
|
|
567
|
+
const defaultValue = upperDefaultValue ?? options.default;
|
|
568
|
+
return parser.getDocFragments(state, defaultValue);
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
return boundParser;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Helper function to get value from config or default.
|
|
575
|
+
* Checks both annotations (for top-level parsers) and the active config
|
|
576
|
+
* registry (for parsers nested inside object() when used with context-aware
|
|
577
|
+
* runners).
|
|
578
|
+
* @throws {TypeError} If the key callback returns a Promise or thenable.
|
|
579
|
+
*/
|
|
580
|
+
function getConfigOrDefault(state, options) {
|
|
581
|
+
const annotations = (0, __optique_core_annotations.getAnnotations)(state);
|
|
582
|
+
const contextId = options.context.id;
|
|
583
|
+
const annotationValue = annotations?.[contextId];
|
|
584
|
+
let configData = annotationValue?.data;
|
|
585
|
+
let configMeta = annotationValue?.meta;
|
|
586
|
+
if (configData === void 0 || configData === null) {
|
|
587
|
+
configData = getActiveConfig(contextId);
|
|
588
|
+
configMeta = getActiveConfigMeta(contextId);
|
|
589
|
+
}
|
|
590
|
+
let configValue;
|
|
591
|
+
if (configData !== void 0 && configData !== null) if (typeof options.key === "function") {
|
|
592
|
+
configValue = options.key(configData, configMeta);
|
|
593
|
+
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.");
|
|
594
|
+
} else configValue = configData[options.key];
|
|
595
|
+
if (configValue !== void 0) return {
|
|
596
|
+
success: true,
|
|
597
|
+
value: configValue
|
|
598
|
+
};
|
|
599
|
+
if (options.default !== void 0) return {
|
|
600
|
+
success: true,
|
|
601
|
+
value: options.default
|
|
602
|
+
};
|
|
603
|
+
return {
|
|
604
|
+
success: false,
|
|
605
|
+
error: __optique_core_message.message`Missing required configuration value.`
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
//#endregion
|
|
610
|
+
exports.bindConfig = bindConfig;
|
|
611
|
+
exports.clearActiveConfig = clearActiveConfig;
|
|
612
|
+
exports.clearActiveConfigMeta = clearActiveConfigMeta;
|
|
613
|
+
exports.createConfigContext = createConfigContext;
|
|
614
|
+
exports.getActiveConfig = getActiveConfig;
|
|
615
|
+
exports.getActiveConfigMeta = getActiveConfigMeta;
|
|
616
|
+
exports.setActiveConfig = setActiveConfig;
|
|
617
|
+
exports.setActiveConfigMeta = setActiveConfigMeta;
|