@optique/env 1.0.0-dev.921 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +226 -67
- package/dist/index.d.cts +17 -25
- package/dist/index.d.ts +17 -25
- package/dist/index.js +227 -65
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -22,37 +22,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
|
|
23
23
|
//#endregion
|
|
24
24
|
const __optique_core_annotations = __toESM(require("@optique/core/annotations"));
|
|
25
|
+
const __optique_core_extension = __toESM(require("@optique/core/extension"));
|
|
25
26
|
const __optique_core_message = __toESM(require("@optique/core/message"));
|
|
26
|
-
const __optique_core_mode_dispatch = __toESM(require("@optique/core/mode-dispatch"));
|
|
27
27
|
const __optique_core_valueparser = __toESM(require("@optique/core/valueparser"));
|
|
28
28
|
|
|
29
29
|
//#region src/index.ts
|
|
30
|
-
const deferPromptUntilConfigResolvesKey = Symbol.for("@optique/config/deferPromptUntilResolved");
|
|
31
|
-
const activeEnvSourceRegistry = /* @__PURE__ */ new Map();
|
|
32
|
-
/**
|
|
33
|
-
* Sets active environment source data for a context.
|
|
34
|
-
*
|
|
35
|
-
* @internal
|
|
36
|
-
*/
|
|
37
|
-
function setActiveEnvSource(contextId, sourceData) {
|
|
38
|
-
activeEnvSourceRegistry.set(contextId, sourceData);
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Gets active environment source data for a context.
|
|
42
|
-
*
|
|
43
|
-
* @internal
|
|
44
|
-
*/
|
|
45
|
-
function getActiveEnvSource(contextId) {
|
|
46
|
-
return activeEnvSourceRegistry.get(contextId);
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Clears active environment source data for a context.
|
|
50
|
-
*
|
|
51
|
-
* @internal
|
|
52
|
-
*/
|
|
53
|
-
function clearActiveEnvSource(contextId) {
|
|
54
|
-
activeEnvSourceRegistry.delete(contextId);
|
|
55
|
-
}
|
|
56
30
|
function defaultEnvSource(key) {
|
|
57
31
|
const denoGlobal = globalThis.Deno;
|
|
58
32
|
if (typeof denoGlobal?.env?.get === "function") return denoGlobal.env.get(key);
|
|
@@ -62,30 +36,39 @@ function defaultEnvSource(key) {
|
|
|
62
36
|
/**
|
|
63
37
|
* Creates an environment context for use with Optique runners.
|
|
64
38
|
*
|
|
39
|
+
* When calling `context.getAnnotations()` manually, pass the returned
|
|
40
|
+
* annotations to low-level APIs such as `parse()`, `parseAsync()`,
|
|
41
|
+
* `parser.complete()`, `suggest()`, or `getDocPage()`. Since environment
|
|
42
|
+
* contexts are single-pass, `getAnnotations()` can still be called without
|
|
43
|
+
* a phase request. Calling it by itself does not affect later parses.
|
|
44
|
+
*
|
|
65
45
|
* @param options Environment context options.
|
|
66
46
|
* @returns A context that provides environment source annotations.
|
|
47
|
+
* @throws {TypeError} If `prefix` is not a string.
|
|
48
|
+
* @throws {TypeError} If `source` is not a function.
|
|
67
49
|
* @since 1.0.0
|
|
68
50
|
*/
|
|
69
51
|
function createEnvContext(options = {}) {
|
|
70
52
|
const contextId = Symbol(`@optique/env context:${Math.random()}`);
|
|
71
|
-
const
|
|
72
|
-
|
|
53
|
+
const rawSource = options.source;
|
|
54
|
+
if (rawSource !== void 0 && typeof rawSource !== "function") throw new TypeError(`Expected source to be a function, but got: ${rawSource === null ? "null" : Array.isArray(rawSource) ? "array" : typeof rawSource}.`);
|
|
55
|
+
const source = rawSource ?? defaultEnvSource;
|
|
56
|
+
const rawPrefix = options.prefix;
|
|
57
|
+
if (rawPrefix !== void 0 && typeof rawPrefix !== "string") throw new TypeError(`Expected prefix to be a string, but got: ${rawPrefix === null ? "null" : Array.isArray(rawPrefix) ? "array" : typeof rawPrefix}.`);
|
|
58
|
+
const prefix = rawPrefix ?? "";
|
|
73
59
|
return {
|
|
74
60
|
id: contextId,
|
|
75
61
|
prefix,
|
|
76
62
|
source,
|
|
77
|
-
|
|
63
|
+
phase: "single-pass",
|
|
78
64
|
getAnnotations() {
|
|
79
65
|
const sourceData = {
|
|
80
66
|
prefix,
|
|
81
67
|
source
|
|
82
68
|
};
|
|
83
|
-
setActiveEnvSource(contextId, sourceData);
|
|
84
69
|
return { [contextId]: sourceData };
|
|
85
70
|
},
|
|
86
|
-
[Symbol.dispose]() {
|
|
87
|
-
clearActiveEnvSource(contextId);
|
|
88
|
-
}
|
|
71
|
+
[Symbol.dispose]() {}
|
|
89
72
|
};
|
|
90
73
|
}
|
|
91
74
|
/**
|
|
@@ -101,20 +84,28 @@ function createEnvContext(options = {}) {
|
|
|
101
84
|
* @param parser Parser that reads CLI values.
|
|
102
85
|
* @param options Environment binding options.
|
|
103
86
|
* @returns A parser with environment fallback behavior.
|
|
87
|
+
* @throws {TypeError} If `key` is not a string or `parser` is not a valid
|
|
88
|
+
* {@link ValueParser}.
|
|
104
89
|
* @throws {Error} If the inner parser throws while parsing or completing a
|
|
105
90
|
* value, if the environment source throws while reading a
|
|
106
|
-
* variable,
|
|
107
|
-
* parsing the environment variable value
|
|
91
|
+
* variable, if the environment value parser throws while
|
|
92
|
+
* parsing the environment variable value, or if the inner
|
|
93
|
+
* parser's {@link Parser.validateValue} hook throws while
|
|
94
|
+
* re-validating a fallback value (environment variable value
|
|
95
|
+
* or configured `default`) — the hook can run even when no
|
|
96
|
+
* CLI tokens are parsed (see issue #414).
|
|
108
97
|
* @since 1.0.0
|
|
109
98
|
*/
|
|
110
99
|
function bindEnv(parser, options) {
|
|
100
|
+
if (typeof options.key !== "string") throw new TypeError(`Expected key to be a string, but got: ${options.key === null ? "null" : Array.isArray(options.key) ? "array" : typeof options.key}.`);
|
|
101
|
+
if (!(0, __optique_core_valueparser.isValueParser)(options.parser)) throw new TypeError(`Expected parser to be a ValueParser, but got: ${options.parser === null ? "null" : Array.isArray(options.parser) ? "array" : typeof options.parser}.`);
|
|
111
102
|
const envBindStateKey = Symbol("@optique/env/bindState");
|
|
112
103
|
function isEnvBindState(value) {
|
|
113
104
|
return value != null && typeof value === "object" && envBindStateKey in value;
|
|
114
105
|
}
|
|
115
|
-
const deferPromptUntilConfigResolves =
|
|
116
|
-
|
|
117
|
-
|
|
106
|
+
const deferPromptUntilConfigResolves = parser.shouldDeferCompletion;
|
|
107
|
+
const boundParser = {
|
|
108
|
+
mode: parser.mode,
|
|
118
109
|
$valueType: parser.$valueType,
|
|
119
110
|
$stateType: parser.$stateType,
|
|
120
111
|
priority: parser.priority,
|
|
@@ -122,7 +113,13 @@ function bindEnv(parser, options) {
|
|
|
122
113
|
type: "optional",
|
|
123
114
|
terms: parser.usage
|
|
124
115
|
}] : parser.usage,
|
|
116
|
+
leadingNames: parser.leadingNames,
|
|
117
|
+
acceptingAnyToken: parser.acceptingAnyToken,
|
|
125
118
|
initialState: parser.initialState,
|
|
119
|
+
getSuggestRuntimeNodes(state, path) {
|
|
120
|
+
const innerState = isEnvBindState(state) ? state.cliState === void 0 ? parser.initialState : state.cliState : state;
|
|
121
|
+
return (0, __optique_core_extension.delegateSuggestNodes)(parser, boundParser, state, path, innerState);
|
|
122
|
+
},
|
|
126
123
|
parse: (context) => {
|
|
127
124
|
const annotations = (0, __optique_core_annotations.getAnnotations)(context.state);
|
|
128
125
|
const innerState = isEnvBindState(context.state) ? context.state.hasCliValue ? context.state.cliState : parser.initialState : context.state;
|
|
@@ -133,14 +130,14 @@ function bindEnv(parser, options) {
|
|
|
133
130
|
const processResult = (result) => {
|
|
134
131
|
if (result.success) {
|
|
135
132
|
const cliConsumed = result.consumed.length > 0;
|
|
136
|
-
const nextState$1 = {
|
|
133
|
+
const nextState$1 = (0, __optique_core_extension.injectAnnotations)({
|
|
137
134
|
[envBindStateKey]: true,
|
|
138
135
|
hasCliValue: cliConsumed,
|
|
139
|
-
cliState: result.next.state
|
|
140
|
-
|
|
141
|
-
};
|
|
136
|
+
cliState: result.next.state
|
|
137
|
+
}, annotations);
|
|
142
138
|
return {
|
|
143
139
|
success: true,
|
|
140
|
+
...result.provisional ? { provisional: true } : {},
|
|
144
141
|
next: {
|
|
145
142
|
...result.next,
|
|
146
143
|
state: nextState$1
|
|
@@ -149,11 +146,10 @@ function bindEnv(parser, options) {
|
|
|
149
146
|
};
|
|
150
147
|
}
|
|
151
148
|
if (result.consumed > 0) return result;
|
|
152
|
-
const nextState = {
|
|
149
|
+
const nextState = (0, __optique_core_extension.injectAnnotations)({
|
|
153
150
|
[envBindStateKey]: true,
|
|
154
|
-
hasCliValue: false
|
|
155
|
-
|
|
156
|
-
};
|
|
151
|
+
hasCliValue: false
|
|
152
|
+
}, annotations);
|
|
157
153
|
return {
|
|
158
154
|
success: true,
|
|
159
155
|
next: {
|
|
@@ -163,42 +159,204 @@ function bindEnv(parser, options) {
|
|
|
163
159
|
consumed: []
|
|
164
160
|
};
|
|
165
161
|
};
|
|
166
|
-
return (0,
|
|
162
|
+
return (0, __optique_core_extension.mapModeValue)(parser.mode, parser.parse(innerContext), processResult);
|
|
167
163
|
},
|
|
168
|
-
complete: (state) => {
|
|
169
|
-
if (isEnvBindState(state) && state.hasCliValue) return parser.complete(state.cliState);
|
|
170
|
-
return getEnvOrDefault(state, options, parser
|
|
164
|
+
complete: (state, exec) => {
|
|
165
|
+
if (isEnvBindState(state) && state.hasCliValue) return parser.complete(state.cliState, exec);
|
|
166
|
+
return getEnvOrDefault(state, options, parser.mode, parser, isEnvBindState(state) ? state.cliState : (0, __optique_core_extension.isInjectedAnnotationState)(state) ? void 0 : state, exec);
|
|
171
167
|
},
|
|
172
168
|
suggest: parser.suggest,
|
|
173
|
-
...typeof deferPromptUntilConfigResolves === "function" ? {
|
|
169
|
+
...typeof deferPromptUntilConfigResolves === "function" ? { shouldDeferCompletion: (state, exec) => deferPromptUntilConfigResolves.call(parser, state, exec) } : {},
|
|
174
170
|
getDocFragments(state, upperDefaultValue) {
|
|
175
171
|
const defaultValue = upperDefaultValue ?? options.default;
|
|
176
172
|
return parser.getDocFragments(state, defaultValue);
|
|
177
173
|
}
|
|
178
174
|
};
|
|
175
|
+
(0, __optique_core_extension.defineTraits)(boundParser, {
|
|
176
|
+
inheritsAnnotations: true,
|
|
177
|
+
completesFromSource: true
|
|
178
|
+
});
|
|
179
|
+
if ("placeholder" in parser) Object.defineProperty(boundParser, "placeholder", {
|
|
180
|
+
get() {
|
|
181
|
+
return parser.placeholder;
|
|
182
|
+
},
|
|
183
|
+
configurable: true,
|
|
184
|
+
enumerable: false
|
|
185
|
+
});
|
|
186
|
+
if (typeof parser.normalizeValue === "function") Object.defineProperty(boundParser, "normalizeValue", {
|
|
187
|
+
value: parser.normalizeValue.bind(parser),
|
|
188
|
+
configurable: true,
|
|
189
|
+
enumerable: false
|
|
190
|
+
});
|
|
191
|
+
if (typeof parser.validateValue === "function") Object.defineProperty(boundParser, "validateValue", {
|
|
192
|
+
value: parser.validateValue.bind(parser),
|
|
193
|
+
configurable: true,
|
|
194
|
+
enumerable: false
|
|
195
|
+
});
|
|
196
|
+
const dependencyMetadata = (0, __optique_core_extension.mapSourceMetadata)(parser, (sourceMetadata) => ({
|
|
197
|
+
...sourceMetadata,
|
|
198
|
+
getMissingSourceValue: sourceMetadata.preservesSourceValue !== false && options.default !== void 0 ? () => {
|
|
199
|
+
if (typeof parser.validateValue === "function") return parser.validateValue(options.default);
|
|
200
|
+
return {
|
|
201
|
+
success: true,
|
|
202
|
+
value: options.default
|
|
203
|
+
};
|
|
204
|
+
} : void 0,
|
|
205
|
+
extractSourceValue: (state) => {
|
|
206
|
+
if (!isEnvBindState(state)) {
|
|
207
|
+
if (sourceMetadata.preservesSourceValue) return getEnvSourceValue(state, options, state, sourceMetadata.extractSourceValue, parser);
|
|
208
|
+
return sourceMetadata.extractSourceValue(state);
|
|
209
|
+
}
|
|
210
|
+
if (state.hasCliValue) return sourceMetadata.extractSourceValue(state.cliState);
|
|
211
|
+
const innerState = state.cliState ?? state;
|
|
212
|
+
if (!sourceMetadata.preservesSourceValue) return sourceMetadata.extractSourceValue(innerState);
|
|
213
|
+
return getEnvSourceValue(state, options, innerState, sourceMetadata.extractSourceValue, parser);
|
|
214
|
+
}
|
|
215
|
+
}));
|
|
216
|
+
if (dependencyMetadata != null) Object.defineProperty(boundParser, "dependencyMetadata", {
|
|
217
|
+
value: dependencyMetadata,
|
|
218
|
+
configurable: true,
|
|
219
|
+
enumerable: false
|
|
220
|
+
});
|
|
221
|
+
return boundParser;
|
|
179
222
|
}
|
|
180
|
-
|
|
223
|
+
/**
|
|
224
|
+
* Resolves a `bindEnv()` fallback value with env > default > inner
|
|
225
|
+
* `complete()` priority, running each candidate through the inner
|
|
226
|
+
* parser's `validateValue()` hook when available so the inner CLI
|
|
227
|
+
* parser's constraints are enforced on fallback values (see issue
|
|
228
|
+
* #414).
|
|
229
|
+
*
|
|
230
|
+
* @param state The wrapper state, which may carry env annotations.
|
|
231
|
+
* @param options The binding options with lookup and default settings.
|
|
232
|
+
* @param mode The parser mode (`"sync"` or `"async"`), used to
|
|
233
|
+
* dispatch env parsing and fallback validation.
|
|
234
|
+
* @param innerParser Optional wrapped parser. When present, its
|
|
235
|
+
* `validateValue()` hook is used to re-validate
|
|
236
|
+
* fallback values and its `complete()` is
|
|
237
|
+
* delegated to as the last fallback.
|
|
238
|
+
* @param innerState Optional unwrapped inner state to pass through to
|
|
239
|
+
* `innerParser.complete()`.
|
|
240
|
+
* @param exec Optional execution context forwarded to
|
|
241
|
+
* `innerParser.complete()`.
|
|
242
|
+
* @returns The resolved value as a mode-dependent result.
|
|
243
|
+
* @throws {Error} Propagates errors thrown by the env source callback
|
|
244
|
+
* (`sourceData.source(fullKey)`) while reading the
|
|
245
|
+
* environment variable.
|
|
246
|
+
* @throws {Error} Propagates errors thrown by
|
|
247
|
+
* `options.parser.parse(rawValue)` (sync or async)
|
|
248
|
+
* while parsing the raw env string into `TValue`.
|
|
249
|
+
* @throws {Error} Propagates errors thrown by
|
|
250
|
+
* `innerParser.validateValue()` while re-validating
|
|
251
|
+
* a successful env-sourced value or the configured
|
|
252
|
+
* `default` against the inner CLI parser's
|
|
253
|
+
* constraints.
|
|
254
|
+
* @throws {Error} Propagates errors thrown by `innerParser.complete()`
|
|
255
|
+
* when falling through to the inner parser (e.g.,
|
|
256
|
+
* `bindEnv(bindConfig(...))` with neither env nor
|
|
257
|
+
* default set).
|
|
258
|
+
*/
|
|
259
|
+
function getEnvOrDefault(state, options, mode, innerParser, innerState, exec) {
|
|
181
260
|
const annotations = (0, __optique_core_annotations.getAnnotations)(state);
|
|
182
|
-
const sourceData = annotations?.[options.context.id]
|
|
261
|
+
const sourceData = annotations?.[options.context.id];
|
|
183
262
|
const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
|
|
184
263
|
const rawValue = sourceData?.source(fullKey);
|
|
264
|
+
const validateSync = (parsed) => {
|
|
265
|
+
if (!parsed.success) return parsed;
|
|
266
|
+
if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
|
|
267
|
+
return innerParser.validateValue(parsed.value);
|
|
268
|
+
};
|
|
269
|
+
const validateAsync = async (parsed) => {
|
|
270
|
+
if (!parsed.success) return parsed;
|
|
271
|
+
if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
|
|
272
|
+
return await innerParser.validateValue(parsed.value);
|
|
273
|
+
};
|
|
185
274
|
if (rawValue !== void 0) {
|
|
186
|
-
|
|
187
|
-
|
|
275
|
+
if (typeof rawValue !== "string") {
|
|
276
|
+
const type = rawValue === null ? "null" : Array.isArray(rawValue) ? "array" : typeof rawValue;
|
|
277
|
+
return (0, __optique_core_extension.wrapForMode)(mode, {
|
|
278
|
+
success: false,
|
|
279
|
+
error: __optique_core_message.message`Environment variable ${(0, __optique_core_message.envVar)(fullKey)} must be a string, but got: ${type}.`
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return (0, __optique_core_extension.dispatchByMode)(mode, () => {
|
|
283
|
+
const parsed = options.parser.parse(rawValue);
|
|
284
|
+
return validateSync(parsed);
|
|
285
|
+
}, async () => {
|
|
286
|
+
const parsed = await options.parser.parse(rawValue);
|
|
287
|
+
return await validateAsync(parsed);
|
|
288
|
+
});
|
|
188
289
|
}
|
|
189
|
-
if (options.default !== void 0) return (0,
|
|
290
|
+
if (options.default !== void 0) return (0, __optique_core_extension.dispatchByMode)(mode, () => validateSync({
|
|
190
291
|
success: true,
|
|
191
292
|
value: options.default
|
|
192
|
-
})
|
|
293
|
+
}), () => validateAsync({
|
|
294
|
+
success: true,
|
|
295
|
+
value: options.default
|
|
296
|
+
}));
|
|
193
297
|
if (innerParser != null) {
|
|
194
|
-
const completeState = innerState ?? innerParser.initialState;
|
|
195
|
-
return (0,
|
|
298
|
+
const completeState = innerState ?? (annotations != null && innerParser.initialState == null && (0, __optique_core_extension.getTraits)(innerParser).inheritsAnnotations === true ? (0, __optique_core_extension.injectAnnotations)(innerParser.initialState, annotations) : innerParser.initialState);
|
|
299
|
+
return (0, __optique_core_extension.wrapForMode)(mode, innerParser.complete(completeState, exec));
|
|
196
300
|
}
|
|
197
|
-
return (0,
|
|
301
|
+
return (0, __optique_core_extension.wrapForMode)(mode, {
|
|
198
302
|
success: false,
|
|
199
303
|
error: __optique_core_message.message`Missing required environment variable: ${(0, __optique_core_message.envVar)(fullKey)}`
|
|
200
304
|
});
|
|
201
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Resolves an env-backed dependency source with env and default fallbacks.
|
|
308
|
+
*
|
|
309
|
+
* This first checks annotations for the bound variable. If no env-backed value
|
|
310
|
+
* is available, it falls back to `options.default` and finally delegates to
|
|
311
|
+
* the wrapped parser's source extractor.
|
|
312
|
+
*
|
|
313
|
+
* When `innerParser` exposes a `validateValue` hook, env-sourced values
|
|
314
|
+
* and the configured default are re-validated against the inner parser's
|
|
315
|
+
* CLI constraints (see issue #414). This is only called from the
|
|
316
|
+
* `preservesSourceValue: true` branch in {@link bindEnv}, so the source
|
|
317
|
+
* value type is guaranteed to equal `TValue`.
|
|
318
|
+
*
|
|
319
|
+
* @param state The wrapper state, which may carry env annotations.
|
|
320
|
+
* @param options The binding options with lookup and default settings.
|
|
321
|
+
* @param innerState The unwrapped inner state for delegated extraction.
|
|
322
|
+
* @param extractInnerSourceValue The wrapped parser's source extractor.
|
|
323
|
+
* @param innerParser The wrapped parser, used to revalidate fallback values.
|
|
324
|
+
* @returns The resolved source value, an async source value, or `undefined`.
|
|
325
|
+
* @throws {Error} Propagates errors thrown by the env source callback
|
|
326
|
+
* (`sourceData.source(fullKey)`).
|
|
327
|
+
* @throws {Error} Propagates errors thrown by `options.parser.parse(rawValue)`.
|
|
328
|
+
* @throws {Error} Propagates errors thrown by
|
|
329
|
+
* `innerParser.validateValue()` while revalidating a
|
|
330
|
+
* successful env-sourced value or the configured
|
|
331
|
+
* `default` against the inner CLI parser's constraints
|
|
332
|
+
* (see issue #414).
|
|
333
|
+
*/
|
|
334
|
+
function getEnvSourceValue(state, options, innerState, extractInnerSourceValue, innerParser) {
|
|
335
|
+
const annotations = (0, __optique_core_annotations.getAnnotations)(state);
|
|
336
|
+
const sourceData = annotations?.[options.context.id];
|
|
337
|
+
const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
|
|
338
|
+
const rawValue = sourceData?.source(fullKey);
|
|
339
|
+
const validateFallback = (parsed) => {
|
|
340
|
+
if (!parsed.success) return parsed;
|
|
341
|
+
if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
|
|
342
|
+
return innerParser.validateValue(parsed.value);
|
|
343
|
+
};
|
|
344
|
+
if (rawValue !== void 0) {
|
|
345
|
+
if (typeof rawValue !== "string") {
|
|
346
|
+
const type = rawValue === null ? "null" : Array.isArray(rawValue) ? "array" : typeof rawValue;
|
|
347
|
+
return {
|
|
348
|
+
success: false,
|
|
349
|
+
error: __optique_core_message.message`Environment variable ${(0, __optique_core_message.envVar)(fullKey)} must be a string, but got: ${type}.`
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
return (0, __optique_core_extension.mapModeValue)(options.parser.mode, options.parser.parse(rawValue), (p) => validateFallback(p));
|
|
353
|
+
}
|
|
354
|
+
if (options.default !== void 0) return validateFallback({
|
|
355
|
+
success: true,
|
|
356
|
+
value: options.default
|
|
357
|
+
});
|
|
358
|
+
return extractInnerSourceValue(innerState);
|
|
359
|
+
}
|
|
202
360
|
const TRUE_LITERALS = [
|
|
203
361
|
"true",
|
|
204
362
|
"1",
|
|
@@ -228,8 +386,9 @@ function bool(options = {}) {
|
|
|
228
386
|
const metavar = options.metavar ?? "BOOLEAN";
|
|
229
387
|
(0, __optique_core_valueparser.ensureNonEmptyString)(metavar);
|
|
230
388
|
return {
|
|
231
|
-
|
|
389
|
+
mode: "sync",
|
|
232
390
|
metavar,
|
|
391
|
+
placeholder: false,
|
|
233
392
|
choices: [true, false],
|
|
234
393
|
parse(input) {
|
|
235
394
|
const normalized = input.trim().toLowerCase();
|
|
@@ -243,7 +402,10 @@ function bool(options = {}) {
|
|
|
243
402
|
};
|
|
244
403
|
return {
|
|
245
404
|
success: false,
|
|
246
|
-
error: options.errors?.invalidFormat ? typeof options.errors.invalidFormat === "function" ? options.errors.invalidFormat(input) : options.errors.invalidFormat : __optique_core_message.message`Invalid Boolean value: ${input}. Expected one of ${(0, __optique_core_message.valueSet)([...TRUE_LITERALS, ...FALSE_LITERALS], {
|
|
405
|
+
error: options.errors?.invalidFormat ? typeof options.errors.invalidFormat === "function" ? options.errors.invalidFormat(input) : options.errors.invalidFormat : __optique_core_message.message`Invalid Boolean value: ${input}. Expected one of ${(0, __optique_core_message.valueSet)([...TRUE_LITERALS, ...FALSE_LITERALS], {
|
|
406
|
+
fallback: "",
|
|
407
|
+
locale: "en-US"
|
|
408
|
+
})}`
|
|
247
409
|
};
|
|
248
410
|
},
|
|
249
411
|
format(value) {
|
|
@@ -263,7 +425,4 @@ function bool(options = {}) {
|
|
|
263
425
|
//#endregion
|
|
264
426
|
exports.bindEnv = bindEnv;
|
|
265
427
|
exports.bool = bool;
|
|
266
|
-
exports.
|
|
267
|
-
exports.createEnvContext = createEnvContext;
|
|
268
|
-
exports.getActiveEnvSource = getActiveEnvSource;
|
|
269
|
-
exports.setActiveEnvSource = setActiveEnvSource;
|
|
428
|
+
exports.createEnvContext = createEnvContext;
|
package/dist/index.d.cts
CHANGED
|
@@ -11,10 +11,6 @@ import { NonEmptyString, ValueParser } from "@optique/core/valueparser";
|
|
|
11
11
|
* @since 1.0.0
|
|
12
12
|
*/
|
|
13
13
|
type EnvSource = (key: string) => string | undefined;
|
|
14
|
-
interface EnvSourceData {
|
|
15
|
-
readonly prefix: string;
|
|
16
|
-
readonly source: EnvSource;
|
|
17
|
-
}
|
|
18
14
|
/**
|
|
19
15
|
* Context for environment-variable-based fallback values.
|
|
20
16
|
*
|
|
@@ -49,29 +45,19 @@ interface EnvContextOptions {
|
|
|
49
45
|
*/
|
|
50
46
|
readonly source?: EnvSource;
|
|
51
47
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Sets active environment source data for a context.
|
|
54
|
-
*
|
|
55
|
-
* @internal
|
|
56
|
-
*/
|
|
57
|
-
declare function setActiveEnvSource(contextId: symbol, sourceData: EnvSourceData): void;
|
|
58
|
-
/**
|
|
59
|
-
* Gets active environment source data for a context.
|
|
60
|
-
*
|
|
61
|
-
* @internal
|
|
62
|
-
*/
|
|
63
|
-
declare function getActiveEnvSource(contextId: symbol): EnvSourceData | undefined;
|
|
64
|
-
/**
|
|
65
|
-
* Clears active environment source data for a context.
|
|
66
|
-
*
|
|
67
|
-
* @internal
|
|
68
|
-
*/
|
|
69
|
-
declare function clearActiveEnvSource(contextId: symbol): void;
|
|
70
48
|
/**
|
|
71
49
|
* Creates an environment context for use with Optique runners.
|
|
72
50
|
*
|
|
51
|
+
* When calling `context.getAnnotations()` manually, pass the returned
|
|
52
|
+
* annotations to low-level APIs such as `parse()`, `parseAsync()`,
|
|
53
|
+
* `parser.complete()`, `suggest()`, or `getDocPage()`. Since environment
|
|
54
|
+
* contexts are single-pass, `getAnnotations()` can still be called without
|
|
55
|
+
* a phase request. Calling it by itself does not affect later parses.
|
|
56
|
+
*
|
|
73
57
|
* @param options Environment context options.
|
|
74
58
|
* @returns A context that provides environment source annotations.
|
|
59
|
+
* @throws {TypeError} If `prefix` is not a string.
|
|
60
|
+
* @throws {TypeError} If `source` is not a function.
|
|
75
61
|
* @since 1.0.0
|
|
76
62
|
*/
|
|
77
63
|
declare function createEnvContext(options?: EnvContextOptions): EnvContext;
|
|
@@ -116,10 +102,16 @@ interface BindEnvOptions<M extends Mode, TValue> {
|
|
|
116
102
|
* @param parser Parser that reads CLI values.
|
|
117
103
|
* @param options Environment binding options.
|
|
118
104
|
* @returns A parser with environment fallback behavior.
|
|
105
|
+
* @throws {TypeError} If `key` is not a string or `parser` is not a valid
|
|
106
|
+
* {@link ValueParser}.
|
|
119
107
|
* @throws {Error} If the inner parser throws while parsing or completing a
|
|
120
108
|
* value, if the environment source throws while reading a
|
|
121
|
-
* variable,
|
|
122
|
-
* parsing the environment variable value
|
|
109
|
+
* variable, if the environment value parser throws while
|
|
110
|
+
* parsing the environment variable value, or if the inner
|
|
111
|
+
* parser's {@link Parser.validateValue} hook throws while
|
|
112
|
+
* re-validating a fallback value (environment variable value
|
|
113
|
+
* or configured `default`) — the hook can run even when no
|
|
114
|
+
* CLI tokens are parsed (see issue #414).
|
|
123
115
|
* @since 1.0.0
|
|
124
116
|
*/
|
|
125
117
|
declare function bindEnv<M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, options: BindEnvOptions<M, TValue>): Parser<M, TValue, TState>;
|
|
@@ -160,4 +152,4 @@ interface BoolOptions {
|
|
|
160
152
|
*/
|
|
161
153
|
declare function bool(options?: BoolOptions): ValueParser<"sync", boolean>;
|
|
162
154
|
//#endregion
|
|
163
|
-
export { BindEnvOptions, BoolOptions, EnvContext, EnvContextOptions, EnvSource, bindEnv, bool,
|
|
155
|
+
export { BindEnvOptions, BoolOptions, EnvContext, EnvContextOptions, EnvSource, bindEnv, bool, createEnvContext };
|
package/dist/index.d.ts
CHANGED
|
@@ -11,10 +11,6 @@ import { Mode, Parser } from "@optique/core/parser";
|
|
|
11
11
|
* @since 1.0.0
|
|
12
12
|
*/
|
|
13
13
|
type EnvSource = (key: string) => string | undefined;
|
|
14
|
-
interface EnvSourceData {
|
|
15
|
-
readonly prefix: string;
|
|
16
|
-
readonly source: EnvSource;
|
|
17
|
-
}
|
|
18
14
|
/**
|
|
19
15
|
* Context for environment-variable-based fallback values.
|
|
20
16
|
*
|
|
@@ -49,29 +45,19 @@ interface EnvContextOptions {
|
|
|
49
45
|
*/
|
|
50
46
|
readonly source?: EnvSource;
|
|
51
47
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Sets active environment source data for a context.
|
|
54
|
-
*
|
|
55
|
-
* @internal
|
|
56
|
-
*/
|
|
57
|
-
declare function setActiveEnvSource(contextId: symbol, sourceData: EnvSourceData): void;
|
|
58
|
-
/**
|
|
59
|
-
* Gets active environment source data for a context.
|
|
60
|
-
*
|
|
61
|
-
* @internal
|
|
62
|
-
*/
|
|
63
|
-
declare function getActiveEnvSource(contextId: symbol): EnvSourceData | undefined;
|
|
64
|
-
/**
|
|
65
|
-
* Clears active environment source data for a context.
|
|
66
|
-
*
|
|
67
|
-
* @internal
|
|
68
|
-
*/
|
|
69
|
-
declare function clearActiveEnvSource(contextId: symbol): void;
|
|
70
48
|
/**
|
|
71
49
|
* Creates an environment context for use with Optique runners.
|
|
72
50
|
*
|
|
51
|
+
* When calling `context.getAnnotations()` manually, pass the returned
|
|
52
|
+
* annotations to low-level APIs such as `parse()`, `parseAsync()`,
|
|
53
|
+
* `parser.complete()`, `suggest()`, or `getDocPage()`. Since environment
|
|
54
|
+
* contexts are single-pass, `getAnnotations()` can still be called without
|
|
55
|
+
* a phase request. Calling it by itself does not affect later parses.
|
|
56
|
+
*
|
|
73
57
|
* @param options Environment context options.
|
|
74
58
|
* @returns A context that provides environment source annotations.
|
|
59
|
+
* @throws {TypeError} If `prefix` is not a string.
|
|
60
|
+
* @throws {TypeError} If `source` is not a function.
|
|
75
61
|
* @since 1.0.0
|
|
76
62
|
*/
|
|
77
63
|
declare function createEnvContext(options?: EnvContextOptions): EnvContext;
|
|
@@ -116,10 +102,16 @@ interface BindEnvOptions<M extends Mode, TValue> {
|
|
|
116
102
|
* @param parser Parser that reads CLI values.
|
|
117
103
|
* @param options Environment binding options.
|
|
118
104
|
* @returns A parser with environment fallback behavior.
|
|
105
|
+
* @throws {TypeError} If `key` is not a string or `parser` is not a valid
|
|
106
|
+
* {@link ValueParser}.
|
|
119
107
|
* @throws {Error} If the inner parser throws while parsing or completing a
|
|
120
108
|
* value, if the environment source throws while reading a
|
|
121
|
-
* variable,
|
|
122
|
-
* parsing the environment variable value
|
|
109
|
+
* variable, if the environment value parser throws while
|
|
110
|
+
* parsing the environment variable value, or if the inner
|
|
111
|
+
* parser's {@link Parser.validateValue} hook throws while
|
|
112
|
+
* re-validating a fallback value (environment variable value
|
|
113
|
+
* or configured `default`) — the hook can run even when no
|
|
114
|
+
* CLI tokens are parsed (see issue #414).
|
|
123
115
|
* @since 1.0.0
|
|
124
116
|
*/
|
|
125
117
|
declare function bindEnv<M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, options: BindEnvOptions<M, TValue>): Parser<M, TValue, TState>;
|
|
@@ -160,4 +152,4 @@ interface BoolOptions {
|
|
|
160
152
|
*/
|
|
161
153
|
declare function bool(options?: BoolOptions): ValueParser<"sync", boolean>;
|
|
162
154
|
//#endregion
|
|
163
|
-
export { BindEnvOptions, BoolOptions, EnvContext, EnvContextOptions, EnvSource, bindEnv, bool,
|
|
155
|
+
export { BindEnvOptions, BoolOptions, EnvContext, EnvContextOptions, EnvSource, bindEnv, bool, createEnvContext };
|
package/dist/index.js
CHANGED
|
@@ -1,35 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getAnnotations } from "@optique/core/annotations";
|
|
2
|
+
import { defineTraits, delegateSuggestNodes, dispatchByMode, getTraits, injectAnnotations, isInjectedAnnotationState, mapModeValue, mapSourceMetadata, wrapForMode } from "@optique/core/extension";
|
|
2
3
|
import { envVar, message, valueSet } from "@optique/core/message";
|
|
3
|
-
import {
|
|
4
|
-
import { ensureNonEmptyString } from "@optique/core/valueparser";
|
|
4
|
+
import { ensureNonEmptyString, isValueParser } from "@optique/core/valueparser";
|
|
5
5
|
|
|
6
6
|
//#region src/index.ts
|
|
7
|
-
const deferPromptUntilConfigResolvesKey = Symbol.for("@optique/config/deferPromptUntilResolved");
|
|
8
|
-
const activeEnvSourceRegistry = /* @__PURE__ */ new Map();
|
|
9
|
-
/**
|
|
10
|
-
* Sets active environment source data for a context.
|
|
11
|
-
*
|
|
12
|
-
* @internal
|
|
13
|
-
*/
|
|
14
|
-
function setActiveEnvSource(contextId, sourceData) {
|
|
15
|
-
activeEnvSourceRegistry.set(contextId, sourceData);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Gets active environment source data for a context.
|
|
19
|
-
*
|
|
20
|
-
* @internal
|
|
21
|
-
*/
|
|
22
|
-
function getActiveEnvSource(contextId) {
|
|
23
|
-
return activeEnvSourceRegistry.get(contextId);
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Clears active environment source data for a context.
|
|
27
|
-
*
|
|
28
|
-
* @internal
|
|
29
|
-
*/
|
|
30
|
-
function clearActiveEnvSource(contextId) {
|
|
31
|
-
activeEnvSourceRegistry.delete(contextId);
|
|
32
|
-
}
|
|
33
7
|
function defaultEnvSource(key) {
|
|
34
8
|
const denoGlobal = globalThis.Deno;
|
|
35
9
|
if (typeof denoGlobal?.env?.get === "function") return denoGlobal.env.get(key);
|
|
@@ -39,30 +13,39 @@ function defaultEnvSource(key) {
|
|
|
39
13
|
/**
|
|
40
14
|
* Creates an environment context for use with Optique runners.
|
|
41
15
|
*
|
|
16
|
+
* When calling `context.getAnnotations()` manually, pass the returned
|
|
17
|
+
* annotations to low-level APIs such as `parse()`, `parseAsync()`,
|
|
18
|
+
* `parser.complete()`, `suggest()`, or `getDocPage()`. Since environment
|
|
19
|
+
* contexts are single-pass, `getAnnotations()` can still be called without
|
|
20
|
+
* a phase request. Calling it by itself does not affect later parses.
|
|
21
|
+
*
|
|
42
22
|
* @param options Environment context options.
|
|
43
23
|
* @returns A context that provides environment source annotations.
|
|
24
|
+
* @throws {TypeError} If `prefix` is not a string.
|
|
25
|
+
* @throws {TypeError} If `source` is not a function.
|
|
44
26
|
* @since 1.0.0
|
|
45
27
|
*/
|
|
46
28
|
function createEnvContext(options = {}) {
|
|
47
29
|
const contextId = Symbol(`@optique/env context:${Math.random()}`);
|
|
48
|
-
const
|
|
49
|
-
|
|
30
|
+
const rawSource = options.source;
|
|
31
|
+
if (rawSource !== void 0 && typeof rawSource !== "function") throw new TypeError(`Expected source to be a function, but got: ${rawSource === null ? "null" : Array.isArray(rawSource) ? "array" : typeof rawSource}.`);
|
|
32
|
+
const source = rawSource ?? defaultEnvSource;
|
|
33
|
+
const rawPrefix = options.prefix;
|
|
34
|
+
if (rawPrefix !== void 0 && typeof rawPrefix !== "string") throw new TypeError(`Expected prefix to be a string, but got: ${rawPrefix === null ? "null" : Array.isArray(rawPrefix) ? "array" : typeof rawPrefix}.`);
|
|
35
|
+
const prefix = rawPrefix ?? "";
|
|
50
36
|
return {
|
|
51
37
|
id: contextId,
|
|
52
38
|
prefix,
|
|
53
39
|
source,
|
|
54
|
-
|
|
40
|
+
phase: "single-pass",
|
|
55
41
|
getAnnotations() {
|
|
56
42
|
const sourceData = {
|
|
57
43
|
prefix,
|
|
58
44
|
source
|
|
59
45
|
};
|
|
60
|
-
setActiveEnvSource(contextId, sourceData);
|
|
61
46
|
return { [contextId]: sourceData };
|
|
62
47
|
},
|
|
63
|
-
[Symbol.dispose]() {
|
|
64
|
-
clearActiveEnvSource(contextId);
|
|
65
|
-
}
|
|
48
|
+
[Symbol.dispose]() {}
|
|
66
49
|
};
|
|
67
50
|
}
|
|
68
51
|
/**
|
|
@@ -78,20 +61,28 @@ function createEnvContext(options = {}) {
|
|
|
78
61
|
* @param parser Parser that reads CLI values.
|
|
79
62
|
* @param options Environment binding options.
|
|
80
63
|
* @returns A parser with environment fallback behavior.
|
|
64
|
+
* @throws {TypeError} If `key` is not a string or `parser` is not a valid
|
|
65
|
+
* {@link ValueParser}.
|
|
81
66
|
* @throws {Error} If the inner parser throws while parsing or completing a
|
|
82
67
|
* value, if the environment source throws while reading a
|
|
83
|
-
* variable,
|
|
84
|
-
* parsing the environment variable value
|
|
68
|
+
* variable, if the environment value parser throws while
|
|
69
|
+
* parsing the environment variable value, or if the inner
|
|
70
|
+
* parser's {@link Parser.validateValue} hook throws while
|
|
71
|
+
* re-validating a fallback value (environment variable value
|
|
72
|
+
* or configured `default`) — the hook can run even when no
|
|
73
|
+
* CLI tokens are parsed (see issue #414).
|
|
85
74
|
* @since 1.0.0
|
|
86
75
|
*/
|
|
87
76
|
function bindEnv(parser, options) {
|
|
77
|
+
if (typeof options.key !== "string") throw new TypeError(`Expected key to be a string, but got: ${options.key === null ? "null" : Array.isArray(options.key) ? "array" : typeof options.key}.`);
|
|
78
|
+
if (!isValueParser(options.parser)) throw new TypeError(`Expected parser to be a ValueParser, but got: ${options.parser === null ? "null" : Array.isArray(options.parser) ? "array" : typeof options.parser}.`);
|
|
88
79
|
const envBindStateKey = Symbol("@optique/env/bindState");
|
|
89
80
|
function isEnvBindState(value) {
|
|
90
81
|
return value != null && typeof value === "object" && envBindStateKey in value;
|
|
91
82
|
}
|
|
92
|
-
const deferPromptUntilConfigResolves =
|
|
93
|
-
|
|
94
|
-
|
|
83
|
+
const deferPromptUntilConfigResolves = parser.shouldDeferCompletion;
|
|
84
|
+
const boundParser = {
|
|
85
|
+
mode: parser.mode,
|
|
95
86
|
$valueType: parser.$valueType,
|
|
96
87
|
$stateType: parser.$stateType,
|
|
97
88
|
priority: parser.priority,
|
|
@@ -99,7 +90,13 @@ function bindEnv(parser, options) {
|
|
|
99
90
|
type: "optional",
|
|
100
91
|
terms: parser.usage
|
|
101
92
|
}] : parser.usage,
|
|
93
|
+
leadingNames: parser.leadingNames,
|
|
94
|
+
acceptingAnyToken: parser.acceptingAnyToken,
|
|
102
95
|
initialState: parser.initialState,
|
|
96
|
+
getSuggestRuntimeNodes(state, path) {
|
|
97
|
+
const innerState = isEnvBindState(state) ? state.cliState === void 0 ? parser.initialState : state.cliState : state;
|
|
98
|
+
return delegateSuggestNodes(parser, boundParser, state, path, innerState);
|
|
99
|
+
},
|
|
103
100
|
parse: (context) => {
|
|
104
101
|
const annotations = getAnnotations(context.state);
|
|
105
102
|
const innerState = isEnvBindState(context.state) ? context.state.hasCliValue ? context.state.cliState : parser.initialState : context.state;
|
|
@@ -110,14 +107,14 @@ function bindEnv(parser, options) {
|
|
|
110
107
|
const processResult = (result) => {
|
|
111
108
|
if (result.success) {
|
|
112
109
|
const cliConsumed = result.consumed.length > 0;
|
|
113
|
-
const nextState$1 = {
|
|
110
|
+
const nextState$1 = injectAnnotations({
|
|
114
111
|
[envBindStateKey]: true,
|
|
115
112
|
hasCliValue: cliConsumed,
|
|
116
|
-
cliState: result.next.state
|
|
117
|
-
|
|
118
|
-
};
|
|
113
|
+
cliState: result.next.state
|
|
114
|
+
}, annotations);
|
|
119
115
|
return {
|
|
120
116
|
success: true,
|
|
117
|
+
...result.provisional ? { provisional: true } : {},
|
|
121
118
|
next: {
|
|
122
119
|
...result.next,
|
|
123
120
|
state: nextState$1
|
|
@@ -126,11 +123,10 @@ function bindEnv(parser, options) {
|
|
|
126
123
|
};
|
|
127
124
|
}
|
|
128
125
|
if (result.consumed > 0) return result;
|
|
129
|
-
const nextState = {
|
|
126
|
+
const nextState = injectAnnotations({
|
|
130
127
|
[envBindStateKey]: true,
|
|
131
|
-
hasCliValue: false
|
|
132
|
-
|
|
133
|
-
};
|
|
128
|
+
hasCliValue: false
|
|
129
|
+
}, annotations);
|
|
134
130
|
return {
|
|
135
131
|
success: true,
|
|
136
132
|
next: {
|
|
@@ -140,42 +136,204 @@ function bindEnv(parser, options) {
|
|
|
140
136
|
consumed: []
|
|
141
137
|
};
|
|
142
138
|
};
|
|
143
|
-
return mapModeValue(parser
|
|
139
|
+
return mapModeValue(parser.mode, parser.parse(innerContext), processResult);
|
|
144
140
|
},
|
|
145
|
-
complete: (state) => {
|
|
146
|
-
if (isEnvBindState(state) && state.hasCliValue) return parser.complete(state.cliState);
|
|
147
|
-
return getEnvOrDefault(state, options, parser
|
|
141
|
+
complete: (state, exec) => {
|
|
142
|
+
if (isEnvBindState(state) && state.hasCliValue) return parser.complete(state.cliState, exec);
|
|
143
|
+
return getEnvOrDefault(state, options, parser.mode, parser, isEnvBindState(state) ? state.cliState : isInjectedAnnotationState(state) ? void 0 : state, exec);
|
|
148
144
|
},
|
|
149
145
|
suggest: parser.suggest,
|
|
150
|
-
...typeof deferPromptUntilConfigResolves === "function" ? {
|
|
146
|
+
...typeof deferPromptUntilConfigResolves === "function" ? { shouldDeferCompletion: (state, exec) => deferPromptUntilConfigResolves.call(parser, state, exec) } : {},
|
|
151
147
|
getDocFragments(state, upperDefaultValue) {
|
|
152
148
|
const defaultValue = upperDefaultValue ?? options.default;
|
|
153
149
|
return parser.getDocFragments(state, defaultValue);
|
|
154
150
|
}
|
|
155
151
|
};
|
|
152
|
+
defineTraits(boundParser, {
|
|
153
|
+
inheritsAnnotations: true,
|
|
154
|
+
completesFromSource: true
|
|
155
|
+
});
|
|
156
|
+
if ("placeholder" in parser) Object.defineProperty(boundParser, "placeholder", {
|
|
157
|
+
get() {
|
|
158
|
+
return parser.placeholder;
|
|
159
|
+
},
|
|
160
|
+
configurable: true,
|
|
161
|
+
enumerable: false
|
|
162
|
+
});
|
|
163
|
+
if (typeof parser.normalizeValue === "function") Object.defineProperty(boundParser, "normalizeValue", {
|
|
164
|
+
value: parser.normalizeValue.bind(parser),
|
|
165
|
+
configurable: true,
|
|
166
|
+
enumerable: false
|
|
167
|
+
});
|
|
168
|
+
if (typeof parser.validateValue === "function") Object.defineProperty(boundParser, "validateValue", {
|
|
169
|
+
value: parser.validateValue.bind(parser),
|
|
170
|
+
configurable: true,
|
|
171
|
+
enumerable: false
|
|
172
|
+
});
|
|
173
|
+
const dependencyMetadata = mapSourceMetadata(parser, (sourceMetadata) => ({
|
|
174
|
+
...sourceMetadata,
|
|
175
|
+
getMissingSourceValue: sourceMetadata.preservesSourceValue !== false && options.default !== void 0 ? () => {
|
|
176
|
+
if (typeof parser.validateValue === "function") return parser.validateValue(options.default);
|
|
177
|
+
return {
|
|
178
|
+
success: true,
|
|
179
|
+
value: options.default
|
|
180
|
+
};
|
|
181
|
+
} : void 0,
|
|
182
|
+
extractSourceValue: (state) => {
|
|
183
|
+
if (!isEnvBindState(state)) {
|
|
184
|
+
if (sourceMetadata.preservesSourceValue) return getEnvSourceValue(state, options, state, sourceMetadata.extractSourceValue, parser);
|
|
185
|
+
return sourceMetadata.extractSourceValue(state);
|
|
186
|
+
}
|
|
187
|
+
if (state.hasCliValue) return sourceMetadata.extractSourceValue(state.cliState);
|
|
188
|
+
const innerState = state.cliState ?? state;
|
|
189
|
+
if (!sourceMetadata.preservesSourceValue) return sourceMetadata.extractSourceValue(innerState);
|
|
190
|
+
return getEnvSourceValue(state, options, innerState, sourceMetadata.extractSourceValue, parser);
|
|
191
|
+
}
|
|
192
|
+
}));
|
|
193
|
+
if (dependencyMetadata != null) Object.defineProperty(boundParser, "dependencyMetadata", {
|
|
194
|
+
value: dependencyMetadata,
|
|
195
|
+
configurable: true,
|
|
196
|
+
enumerable: false
|
|
197
|
+
});
|
|
198
|
+
return boundParser;
|
|
156
199
|
}
|
|
157
|
-
|
|
200
|
+
/**
|
|
201
|
+
* Resolves a `bindEnv()` fallback value with env > default > inner
|
|
202
|
+
* `complete()` priority, running each candidate through the inner
|
|
203
|
+
* parser's `validateValue()` hook when available so the inner CLI
|
|
204
|
+
* parser's constraints are enforced on fallback values (see issue
|
|
205
|
+
* #414).
|
|
206
|
+
*
|
|
207
|
+
* @param state The wrapper state, which may carry env annotations.
|
|
208
|
+
* @param options The binding options with lookup and default settings.
|
|
209
|
+
* @param mode The parser mode (`"sync"` or `"async"`), used to
|
|
210
|
+
* dispatch env parsing and fallback validation.
|
|
211
|
+
* @param innerParser Optional wrapped parser. When present, its
|
|
212
|
+
* `validateValue()` hook is used to re-validate
|
|
213
|
+
* fallback values and its `complete()` is
|
|
214
|
+
* delegated to as the last fallback.
|
|
215
|
+
* @param innerState Optional unwrapped inner state to pass through to
|
|
216
|
+
* `innerParser.complete()`.
|
|
217
|
+
* @param exec Optional execution context forwarded to
|
|
218
|
+
* `innerParser.complete()`.
|
|
219
|
+
* @returns The resolved value as a mode-dependent result.
|
|
220
|
+
* @throws {Error} Propagates errors thrown by the env source callback
|
|
221
|
+
* (`sourceData.source(fullKey)`) while reading the
|
|
222
|
+
* environment variable.
|
|
223
|
+
* @throws {Error} Propagates errors thrown by
|
|
224
|
+
* `options.parser.parse(rawValue)` (sync or async)
|
|
225
|
+
* while parsing the raw env string into `TValue`.
|
|
226
|
+
* @throws {Error} Propagates errors thrown by
|
|
227
|
+
* `innerParser.validateValue()` while re-validating
|
|
228
|
+
* a successful env-sourced value or the configured
|
|
229
|
+
* `default` against the inner CLI parser's
|
|
230
|
+
* constraints.
|
|
231
|
+
* @throws {Error} Propagates errors thrown by `innerParser.complete()`
|
|
232
|
+
* when falling through to the inner parser (e.g.,
|
|
233
|
+
* `bindEnv(bindConfig(...))` with neither env nor
|
|
234
|
+
* default set).
|
|
235
|
+
*/
|
|
236
|
+
function getEnvOrDefault(state, options, mode, innerParser, innerState, exec) {
|
|
158
237
|
const annotations = getAnnotations(state);
|
|
159
|
-
const sourceData = annotations?.[options.context.id]
|
|
238
|
+
const sourceData = annotations?.[options.context.id];
|
|
160
239
|
const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
|
|
161
240
|
const rawValue = sourceData?.source(fullKey);
|
|
241
|
+
const validateSync = (parsed) => {
|
|
242
|
+
if (!parsed.success) return parsed;
|
|
243
|
+
if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
|
|
244
|
+
return innerParser.validateValue(parsed.value);
|
|
245
|
+
};
|
|
246
|
+
const validateAsync = async (parsed) => {
|
|
247
|
+
if (!parsed.success) return parsed;
|
|
248
|
+
if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
|
|
249
|
+
return await innerParser.validateValue(parsed.value);
|
|
250
|
+
};
|
|
162
251
|
if (rawValue !== void 0) {
|
|
163
|
-
|
|
164
|
-
|
|
252
|
+
if (typeof rawValue !== "string") {
|
|
253
|
+
const type = rawValue === null ? "null" : Array.isArray(rawValue) ? "array" : typeof rawValue;
|
|
254
|
+
return wrapForMode(mode, {
|
|
255
|
+
success: false,
|
|
256
|
+
error: message`Environment variable ${envVar(fullKey)} must be a string, but got: ${type}.`
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return dispatchByMode(mode, () => {
|
|
260
|
+
const parsed = options.parser.parse(rawValue);
|
|
261
|
+
return validateSync(parsed);
|
|
262
|
+
}, async () => {
|
|
263
|
+
const parsed = await options.parser.parse(rawValue);
|
|
264
|
+
return await validateAsync(parsed);
|
|
265
|
+
});
|
|
165
266
|
}
|
|
166
|
-
if (options.default !== void 0) return
|
|
267
|
+
if (options.default !== void 0) return dispatchByMode(mode, () => validateSync({
|
|
167
268
|
success: true,
|
|
168
269
|
value: options.default
|
|
169
|
-
})
|
|
270
|
+
}), () => validateAsync({
|
|
271
|
+
success: true,
|
|
272
|
+
value: options.default
|
|
273
|
+
}));
|
|
170
274
|
if (innerParser != null) {
|
|
171
|
-
const completeState = innerState ?? innerParser.initialState;
|
|
172
|
-
return wrapForMode(mode, innerParser.complete(completeState));
|
|
275
|
+
const completeState = innerState ?? (annotations != null && innerParser.initialState == null && getTraits(innerParser).inheritsAnnotations === true ? injectAnnotations(innerParser.initialState, annotations) : innerParser.initialState);
|
|
276
|
+
return wrapForMode(mode, innerParser.complete(completeState, exec));
|
|
173
277
|
}
|
|
174
278
|
return wrapForMode(mode, {
|
|
175
279
|
success: false,
|
|
176
280
|
error: message`Missing required environment variable: ${envVar(fullKey)}`
|
|
177
281
|
});
|
|
178
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Resolves an env-backed dependency source with env and default fallbacks.
|
|
285
|
+
*
|
|
286
|
+
* This first checks annotations for the bound variable. If no env-backed value
|
|
287
|
+
* is available, it falls back to `options.default` and finally delegates to
|
|
288
|
+
* the wrapped parser's source extractor.
|
|
289
|
+
*
|
|
290
|
+
* When `innerParser` exposes a `validateValue` hook, env-sourced values
|
|
291
|
+
* and the configured default are re-validated against the inner parser's
|
|
292
|
+
* CLI constraints (see issue #414). This is only called from the
|
|
293
|
+
* `preservesSourceValue: true` branch in {@link bindEnv}, so the source
|
|
294
|
+
* value type is guaranteed to equal `TValue`.
|
|
295
|
+
*
|
|
296
|
+
* @param state The wrapper state, which may carry env annotations.
|
|
297
|
+
* @param options The binding options with lookup and default settings.
|
|
298
|
+
* @param innerState The unwrapped inner state for delegated extraction.
|
|
299
|
+
* @param extractInnerSourceValue The wrapped parser's source extractor.
|
|
300
|
+
* @param innerParser The wrapped parser, used to revalidate fallback values.
|
|
301
|
+
* @returns The resolved source value, an async source value, or `undefined`.
|
|
302
|
+
* @throws {Error} Propagates errors thrown by the env source callback
|
|
303
|
+
* (`sourceData.source(fullKey)`).
|
|
304
|
+
* @throws {Error} Propagates errors thrown by `options.parser.parse(rawValue)`.
|
|
305
|
+
* @throws {Error} Propagates errors thrown by
|
|
306
|
+
* `innerParser.validateValue()` while revalidating a
|
|
307
|
+
* successful env-sourced value or the configured
|
|
308
|
+
* `default` against the inner CLI parser's constraints
|
|
309
|
+
* (see issue #414).
|
|
310
|
+
*/
|
|
311
|
+
function getEnvSourceValue(state, options, innerState, extractInnerSourceValue, innerParser) {
|
|
312
|
+
const annotations = getAnnotations(state);
|
|
313
|
+
const sourceData = annotations?.[options.context.id];
|
|
314
|
+
const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
|
|
315
|
+
const rawValue = sourceData?.source(fullKey);
|
|
316
|
+
const validateFallback = (parsed) => {
|
|
317
|
+
if (!parsed.success) return parsed;
|
|
318
|
+
if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
|
|
319
|
+
return innerParser.validateValue(parsed.value);
|
|
320
|
+
};
|
|
321
|
+
if (rawValue !== void 0) {
|
|
322
|
+
if (typeof rawValue !== "string") {
|
|
323
|
+
const type = rawValue === null ? "null" : Array.isArray(rawValue) ? "array" : typeof rawValue;
|
|
324
|
+
return {
|
|
325
|
+
success: false,
|
|
326
|
+
error: message`Environment variable ${envVar(fullKey)} must be a string, but got: ${type}.`
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
return mapModeValue(options.parser.mode, options.parser.parse(rawValue), (p) => validateFallback(p));
|
|
330
|
+
}
|
|
331
|
+
if (options.default !== void 0) return validateFallback({
|
|
332
|
+
success: true,
|
|
333
|
+
value: options.default
|
|
334
|
+
});
|
|
335
|
+
return extractInnerSourceValue(innerState);
|
|
336
|
+
}
|
|
179
337
|
const TRUE_LITERALS = [
|
|
180
338
|
"true",
|
|
181
339
|
"1",
|
|
@@ -205,8 +363,9 @@ function bool(options = {}) {
|
|
|
205
363
|
const metavar = options.metavar ?? "BOOLEAN";
|
|
206
364
|
ensureNonEmptyString(metavar);
|
|
207
365
|
return {
|
|
208
|
-
|
|
366
|
+
mode: "sync",
|
|
209
367
|
metavar,
|
|
368
|
+
placeholder: false,
|
|
210
369
|
choices: [true, false],
|
|
211
370
|
parse(input) {
|
|
212
371
|
const normalized = input.trim().toLowerCase();
|
|
@@ -220,7 +379,10 @@ function bool(options = {}) {
|
|
|
220
379
|
};
|
|
221
380
|
return {
|
|
222
381
|
success: false,
|
|
223
|
-
error: options.errors?.invalidFormat ? typeof options.errors.invalidFormat === "function" ? options.errors.invalidFormat(input) : options.errors.invalidFormat : message`Invalid Boolean value: ${input}. Expected one of ${valueSet([...TRUE_LITERALS, ...FALSE_LITERALS], {
|
|
382
|
+
error: options.errors?.invalidFormat ? typeof options.errors.invalidFormat === "function" ? options.errors.invalidFormat(input) : options.errors.invalidFormat : message`Invalid Boolean value: ${input}. Expected one of ${valueSet([...TRUE_LITERALS, ...FALSE_LITERALS], {
|
|
383
|
+
fallback: "",
|
|
384
|
+
locale: "en-US"
|
|
385
|
+
})}`
|
|
224
386
|
};
|
|
225
387
|
},
|
|
226
388
|
format(value) {
|
|
@@ -238,4 +400,4 @@ function bool(options = {}) {
|
|
|
238
400
|
}
|
|
239
401
|
|
|
240
402
|
//#endregion
|
|
241
|
-
export { bindEnv, bool,
|
|
403
|
+
export { bindEnv, bool, createEnvContext };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optique/env",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Environment variable support for Optique",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"CLI",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"sideEffects": false,
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@optique/core": "1.0.
|
|
57
|
+
"@optique/core": "1.0.1"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^20.19.9",
|