@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 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 source = options.source ?? defaultEnvSource;
72
- const prefix = options.prefix ?? "";
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
- mode: "static",
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, or if the environment value parser throws while
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 = Reflect.get(parser, deferPromptUntilConfigResolvesKey);
116
- return {
117
- $mode: parser.$mode,
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
- ...annotations && { [__optique_core_annotations.annotationKey]: annotations }
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
- ...annotations && { [__optique_core_annotations.annotationKey]: annotations }
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, __optique_core_mode_dispatch.mapModeValue)(parser.$mode, parser.parse(innerContext), processResult);
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.$mode, parser, isEnvBindState(state) ? state.cliState : void 0);
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" ? { [deferPromptUntilConfigResolvesKey]: (state) => deferPromptUntilConfigResolves(state) } : {},
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
- function getEnvOrDefault(state, options, mode, innerParser, innerState) {
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] ?? getActiveEnvSource(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
- const parsed = options.parser.parse(rawValue);
187
- return (0, __optique_core_mode_dispatch.wrapForMode)(mode, parsed);
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, __optique_core_mode_dispatch.wrapForMode)(mode, {
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, __optique_core_mode_dispatch.wrapForMode)(mode, innerParser.complete(completeState));
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, __optique_core_mode_dispatch.wrapForMode)(mode, {
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
- $mode: "sync",
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], { locale: "en-US" })}`
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.clearActiveEnvSource = clearActiveEnvSource;
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, or if the environment value parser throws while
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, clearActiveEnvSource, createEnvContext, getActiveEnvSource, setActiveEnvSource };
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, or if the environment value parser throws while
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, clearActiveEnvSource, createEnvContext, getActiveEnvSource, setActiveEnvSource };
155
+ export { BindEnvOptions, BoolOptions, EnvContext, EnvContextOptions, EnvSource, bindEnv, bool, createEnvContext };
package/dist/index.js CHANGED
@@ -1,35 +1,9 @@
1
- import { annotationKey, getAnnotations } from "@optique/core/annotations";
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 { mapModeValue, wrapForMode } from "@optique/core/mode-dispatch";
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 source = options.source ?? defaultEnvSource;
49
- const prefix = options.prefix ?? "";
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
- mode: "static",
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, or if the environment value parser throws while
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 = Reflect.get(parser, deferPromptUntilConfigResolvesKey);
93
- return {
94
- $mode: parser.$mode,
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
- ...annotations && { [annotationKey]: annotations }
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
- ...annotations && { [annotationKey]: annotations }
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.$mode, parser.parse(innerContext), processResult);
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.$mode, parser, isEnvBindState(state) ? state.cliState : void 0);
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" ? { [deferPromptUntilConfigResolvesKey]: (state) => deferPromptUntilConfigResolves(state) } : {},
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
- function getEnvOrDefault(state, options, mode, innerParser, innerState) {
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] ?? getActiveEnvSource(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
- const parsed = options.parser.parse(rawValue);
164
- return wrapForMode(mode, parsed);
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 wrapForMode(mode, {
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
- $mode: "sync",
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], { locale: "en-US" })}`
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, clearActiveEnvSource, createEnvContext, getActiveEnvSource, setActiveEnvSource };
403
+ export { bindEnv, bool, createEnvContext };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/env",
3
- "version": "1.0.0-dev.921+754748bd",
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.0-dev.921+754748bd"
57
+ "@optique/core": "1.0.1"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/node": "^20.19.9",