@optique/env 1.0.0-dev.1758 → 1.0.0-dev.1766

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
@@ -111,8 +111,12 @@ function createEnvContext(options = {}) {
111
111
  * {@link ValueParser}.
112
112
  * @throws {Error} If the inner parser throws while parsing or completing a
113
113
  * value, if the environment source throws while reading a
114
- * variable, or if the environment value parser throws while
115
- * parsing the environment variable value.
114
+ * variable, if the environment value parser throws while
115
+ * parsing the environment variable value, or if the inner
116
+ * parser's {@link Parser.validateValue} hook throws while
117
+ * re-validating a fallback value (environment variable value
118
+ * or configured `default`) — the hook can run even when no
119
+ * CLI tokens are parsed (see issue #414).
116
120
  * @since 1.0.0
117
121
  */
118
122
  function bindEnv(parser, options) {
@@ -207,17 +211,22 @@ function bindEnv(parser, options) {
207
211
  configurable: true,
208
212
  enumerable: false
209
213
  });
214
+ if (typeof parser.validateValue === "function") Object.defineProperty(boundParser, "validateValue", {
215
+ value: parser.validateValue.bind(parser),
216
+ configurable: true,
217
+ enumerable: false
218
+ });
210
219
  const dependencyMetadata = (0, __optique_core_parser.composeWrappedSourceMetadata)(parser.dependencyMetadata, (sourceMetadata) => ({
211
220
  ...sourceMetadata,
212
221
  extractSourceValue: (state) => {
213
222
  if (!isEnvBindState(state)) {
214
- if (sourceMetadata.preservesSourceValue) return getEnvSourceValue(state, options, state, sourceMetadata.extractSourceValue);
223
+ if (sourceMetadata.preservesSourceValue) return getEnvSourceValue(state, options, state, sourceMetadata.extractSourceValue, parser);
215
224
  return sourceMetadata.extractSourceValue(state);
216
225
  }
217
226
  if (state.hasCliValue) return sourceMetadata.extractSourceValue(state.cliState);
218
227
  const innerState = state.cliState ?? state;
219
228
  if (!sourceMetadata.preservesSourceValue) return sourceMetadata.extractSourceValue(innerState);
220
- return getEnvSourceValue(state, options, innerState, sourceMetadata.extractSourceValue);
229
+ return getEnvSourceValue(state, options, innerState, sourceMetadata.extractSourceValue, parser);
221
230
  }
222
231
  }));
223
232
  if (dependencyMetadata != null) Object.defineProperty(boundParser, "dependencyMetadata", {
@@ -227,11 +236,57 @@ function bindEnv(parser, options) {
227
236
  });
228
237
  return boundParser;
229
238
  }
239
+ /**
240
+ * Resolves a `bindEnv()` fallback value with env > default > inner
241
+ * `complete()` priority, running each candidate through the inner
242
+ * parser's `validateValue()` hook when available so the inner CLI
243
+ * parser's constraints are enforced on fallback values (see issue
244
+ * #414).
245
+ *
246
+ * @param state The wrapper state, which may carry env annotations.
247
+ * @param options The binding options with lookup and default settings.
248
+ * @param mode The parser mode (`"sync"` or `"async"`), used to
249
+ * dispatch env parsing and fallback validation.
250
+ * @param innerParser Optional wrapped parser. When present, its
251
+ * `validateValue()` hook is used to re-validate
252
+ * fallback values and its `complete()` is
253
+ * delegated to as the last fallback.
254
+ * @param innerState Optional unwrapped inner state to pass through to
255
+ * `innerParser.complete()`.
256
+ * @param exec Optional execution context forwarded to
257
+ * `innerParser.complete()`.
258
+ * @returns The resolved value as a mode-dependent result.
259
+ * @throws {Error} Propagates errors thrown by the env source callback
260
+ * (`sourceData.source(fullKey)`) while reading the
261
+ * environment variable.
262
+ * @throws {Error} Propagates errors thrown by
263
+ * `options.parser.parse(rawValue)` (sync or async)
264
+ * while parsing the raw env string into `TValue`.
265
+ * @throws {Error} Propagates errors thrown by
266
+ * `innerParser.validateValue()` while re-validating
267
+ * a successful env-sourced value or the configured
268
+ * `default` against the inner CLI parser's
269
+ * constraints.
270
+ * @throws {Error} Propagates errors thrown by `innerParser.complete()`
271
+ * when falling through to the inner parser (e.g.,
272
+ * `bindEnv(bindConfig(...))` with neither env nor
273
+ * default set).
274
+ */
230
275
  function getEnvOrDefault(state, options, mode, innerParser, innerState, exec) {
231
276
  const annotations = (0, __optique_core_annotations.getAnnotations)(state);
232
277
  const sourceData = annotations?.[options.context.id] ?? getActiveEnvSource(options.context.id);
233
278
  const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
234
279
  const rawValue = sourceData?.source(fullKey);
280
+ const validateSync = (parsed) => {
281
+ if (!parsed.success) return parsed;
282
+ if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
283
+ return innerParser.validateValue(parsed.value);
284
+ };
285
+ const validateAsync = async (parsed) => {
286
+ if (!parsed.success) return parsed;
287
+ if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
288
+ return await innerParser.validateValue(parsed.value);
289
+ };
235
290
  if (rawValue !== void 0) {
236
291
  if (typeof rawValue !== "string") {
237
292
  const type = rawValue === null ? "null" : Array.isArray(rawValue) ? "array" : typeof rawValue;
@@ -240,13 +295,21 @@ function getEnvOrDefault(state, options, mode, innerParser, innerState, exec) {
240
295
  error: __optique_core_message.message`Environment variable ${(0, __optique_core_message.envVar)(fullKey)} must be a string, but got: ${type}.`
241
296
  });
242
297
  }
243
- const parsed = options.parser.parse(rawValue);
244
- return (0, __optique_core_mode_dispatch.wrapForMode)(mode, parsed);
298
+ return (0, __optique_core_mode_dispatch.dispatchByMode)(mode, () => {
299
+ const parsed = options.parser.parse(rawValue);
300
+ return validateSync(parsed);
301
+ }, async () => {
302
+ const parsed = await options.parser.parse(rawValue);
303
+ return await validateAsync(parsed);
304
+ });
245
305
  }
246
- if (options.default !== void 0) return (0, __optique_core_mode_dispatch.wrapForMode)(mode, {
306
+ if (options.default !== void 0) return (0, __optique_core_mode_dispatch.dispatchByMode)(mode, () => validateSync({
247
307
  success: true,
248
308
  value: options.default
249
- });
309
+ }), () => validateAsync({
310
+ success: true,
311
+ value: options.default
312
+ }));
250
313
  if (innerParser != null) {
251
314
  const completeState = innerState ?? (annotations != null && innerParser.initialState == null && Reflect.get(innerParser, __optique_core_parser.inheritParentAnnotationsKey) === true ? (0, __optique_core_annotations.injectAnnotations)(innerParser.initialState, annotations) : innerParser.initialState);
252
315
  return (0, __optique_core_mode_dispatch.wrapForMode)(mode, innerParser.complete(completeState, exec));
@@ -264,20 +327,37 @@ function getEnvOrDefault(state, options, mode, innerParser, innerState, exec) {
264
327
  * `options.default` and finally delegates to the wrapped parser's source
265
328
  * extractor.
266
329
  *
330
+ * When `innerParser` exposes a `validateValue` hook, env-sourced values
331
+ * and the configured default are re-validated against the inner parser's
332
+ * CLI constraints (see issue #414). This is only called from the
333
+ * `preservesSourceValue: true` branch in {@link bindEnv}, so the source
334
+ * value type is guaranteed to equal `TValue`.
335
+ *
267
336
  * @param state The wrapper state, which may carry env annotations.
268
337
  * @param options The binding options with lookup and default settings.
269
338
  * @param innerState The unwrapped inner state for delegated extraction.
270
339
  * @param extractInnerSourceValue The wrapped parser's source extractor.
340
+ * @param innerParser The wrapped parser, used to revalidate fallback values.
271
341
  * @returns The resolved source value, an async source value, or `undefined`.
272
342
  * @throws {Error} Propagates errors thrown by the env source callback
273
343
  * (`sourceData.source(fullKey)`).
274
344
  * @throws {Error} Propagates errors thrown by `options.parser.parse(rawValue)`.
345
+ * @throws {Error} Propagates errors thrown by
346
+ * `innerParser.validateValue()` while revalidating a
347
+ * successful env-sourced value or the configured
348
+ * `default` against the inner CLI parser's constraints
349
+ * (see issue #414).
275
350
  */
276
- function getEnvSourceValue(state, options, innerState, extractInnerSourceValue) {
351
+ function getEnvSourceValue(state, options, innerState, extractInnerSourceValue, innerParser) {
277
352
  const annotations = (0, __optique_core_annotations.getAnnotations)(state);
278
353
  const sourceData = annotations?.[options.context.id] ?? getActiveEnvSource(options.context.id);
279
354
  const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
280
355
  const rawValue = sourceData?.source(fullKey);
356
+ const validateFallback = (parsed) => {
357
+ if (!parsed.success) return parsed;
358
+ if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
359
+ return innerParser.validateValue(parsed.value);
360
+ };
281
361
  if (rawValue !== void 0) {
282
362
  if (typeof rawValue !== "string") {
283
363
  const type = rawValue === null ? "null" : Array.isArray(rawValue) ? "array" : typeof rawValue;
@@ -286,13 +366,12 @@ function getEnvSourceValue(state, options, innerState, extractInnerSourceValue)
286
366
  error: __optique_core_message.message`Environment variable ${(0, __optique_core_message.envVar)(fullKey)} must be a string, but got: ${type}.`
287
367
  };
288
368
  }
289
- const parsed = options.parser.parse(rawValue);
290
- return parsed;
369
+ return (0, __optique_core_mode_dispatch.mapModeValue)(options.parser.$mode, options.parser.parse(rawValue), (p) => validateFallback(p));
291
370
  }
292
- if (options.default !== void 0) return {
371
+ if (options.default !== void 0) return validateFallback({
293
372
  success: true,
294
373
  value: options.default
295
- };
374
+ });
296
375
  return extractInnerSourceValue(innerState);
297
376
  }
298
377
  const TRUE_LITERALS = [
package/dist/index.d.cts CHANGED
@@ -122,8 +122,12 @@ interface BindEnvOptions<M extends Mode, TValue> {
122
122
  * {@link ValueParser}.
123
123
  * @throws {Error} If the inner parser throws while parsing or completing a
124
124
  * value, if the environment source throws while reading a
125
- * variable, or if the environment value parser throws while
126
- * parsing the environment variable value.
125
+ * variable, if the environment value parser throws while
126
+ * parsing the environment variable value, or if the inner
127
+ * parser's {@link Parser.validateValue} hook throws while
128
+ * re-validating a fallback value (environment variable value
129
+ * or configured `default`) — the hook can run even when no
130
+ * CLI tokens are parsed (see issue #414).
127
131
  * @since 1.0.0
128
132
  */
129
133
  declare function bindEnv<M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, options: BindEnvOptions<M, TValue>): Parser<M, TValue, TState>;
package/dist/index.d.ts CHANGED
@@ -122,8 +122,12 @@ interface BindEnvOptions<M extends Mode, TValue> {
122
122
  * {@link ValueParser}.
123
123
  * @throws {Error} If the inner parser throws while parsing or completing a
124
124
  * value, if the environment source throws while reading a
125
- * variable, or if the environment value parser throws while
126
- * parsing the environment variable value.
125
+ * variable, if the environment value parser throws while
126
+ * parsing the environment variable value, or if the inner
127
+ * parser's {@link Parser.validateValue} hook throws while
128
+ * re-validating a fallback value (environment variable value
129
+ * or configured `default`) — the hook can run even when no
130
+ * CLI tokens are parsed (see issue #414).
127
131
  * @since 1.0.0
128
132
  */
129
133
  declare function bindEnv<M extends Mode, TValue, TState>(parser: Parser<M, TValue, TState>, options: BindEnvOptions<M, TValue>): Parser<M, TValue, TState>;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { annotationKey, getAnnotations, injectAnnotations, isInjectedAnnotationWrapper } from "@optique/core/annotations";
2
2
  import { envVar, message, valueSet } from "@optique/core/message";
3
- import { mapModeValue, wrapForMode } from "@optique/core/mode-dispatch";
3
+ import { dispatchByMode, mapModeValue, wrapForMode } from "@optique/core/mode-dispatch";
4
4
  import { composeWrappedSourceMetadata, defineInheritedAnnotationParser, getDelegatingSuggestRuntimeNodes, inheritParentAnnotationsKey, unmatchedNonCliDependencySourceStateMarker } from "@optique/core/parser";
5
5
  import { ensureNonEmptyString, isValueParser } from "@optique/core/valueparser";
6
6
 
@@ -88,8 +88,12 @@ function createEnvContext(options = {}) {
88
88
  * {@link ValueParser}.
89
89
  * @throws {Error} If the inner parser throws while parsing or completing a
90
90
  * value, if the environment source throws while reading a
91
- * variable, or if the environment value parser throws while
92
- * 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).
93
97
  * @since 1.0.0
94
98
  */
95
99
  function bindEnv(parser, options) {
@@ -184,17 +188,22 @@ function bindEnv(parser, options) {
184
188
  configurable: true,
185
189
  enumerable: false
186
190
  });
191
+ if (typeof parser.validateValue === "function") Object.defineProperty(boundParser, "validateValue", {
192
+ value: parser.validateValue.bind(parser),
193
+ configurable: true,
194
+ enumerable: false
195
+ });
187
196
  const dependencyMetadata = composeWrappedSourceMetadata(parser.dependencyMetadata, (sourceMetadata) => ({
188
197
  ...sourceMetadata,
189
198
  extractSourceValue: (state) => {
190
199
  if (!isEnvBindState(state)) {
191
- if (sourceMetadata.preservesSourceValue) return getEnvSourceValue(state, options, state, sourceMetadata.extractSourceValue);
200
+ if (sourceMetadata.preservesSourceValue) return getEnvSourceValue(state, options, state, sourceMetadata.extractSourceValue, parser);
192
201
  return sourceMetadata.extractSourceValue(state);
193
202
  }
194
203
  if (state.hasCliValue) return sourceMetadata.extractSourceValue(state.cliState);
195
204
  const innerState = state.cliState ?? state;
196
205
  if (!sourceMetadata.preservesSourceValue) return sourceMetadata.extractSourceValue(innerState);
197
- return getEnvSourceValue(state, options, innerState, sourceMetadata.extractSourceValue);
206
+ return getEnvSourceValue(state, options, innerState, sourceMetadata.extractSourceValue, parser);
198
207
  }
199
208
  }));
200
209
  if (dependencyMetadata != null) Object.defineProperty(boundParser, "dependencyMetadata", {
@@ -204,11 +213,57 @@ function bindEnv(parser, options) {
204
213
  });
205
214
  return boundParser;
206
215
  }
216
+ /**
217
+ * Resolves a `bindEnv()` fallback value with env > default > inner
218
+ * `complete()` priority, running each candidate through the inner
219
+ * parser's `validateValue()` hook when available so the inner CLI
220
+ * parser's constraints are enforced on fallback values (see issue
221
+ * #414).
222
+ *
223
+ * @param state The wrapper state, which may carry env annotations.
224
+ * @param options The binding options with lookup and default settings.
225
+ * @param mode The parser mode (`"sync"` or `"async"`), used to
226
+ * dispatch env parsing and fallback validation.
227
+ * @param innerParser Optional wrapped parser. When present, its
228
+ * `validateValue()` hook is used to re-validate
229
+ * fallback values and its `complete()` is
230
+ * delegated to as the last fallback.
231
+ * @param innerState Optional unwrapped inner state to pass through to
232
+ * `innerParser.complete()`.
233
+ * @param exec Optional execution context forwarded to
234
+ * `innerParser.complete()`.
235
+ * @returns The resolved value as a mode-dependent result.
236
+ * @throws {Error} Propagates errors thrown by the env source callback
237
+ * (`sourceData.source(fullKey)`) while reading the
238
+ * environment variable.
239
+ * @throws {Error} Propagates errors thrown by
240
+ * `options.parser.parse(rawValue)` (sync or async)
241
+ * while parsing the raw env string into `TValue`.
242
+ * @throws {Error} Propagates errors thrown by
243
+ * `innerParser.validateValue()` while re-validating
244
+ * a successful env-sourced value or the configured
245
+ * `default` against the inner CLI parser's
246
+ * constraints.
247
+ * @throws {Error} Propagates errors thrown by `innerParser.complete()`
248
+ * when falling through to the inner parser (e.g.,
249
+ * `bindEnv(bindConfig(...))` with neither env nor
250
+ * default set).
251
+ */
207
252
  function getEnvOrDefault(state, options, mode, innerParser, innerState, exec) {
208
253
  const annotations = getAnnotations(state);
209
254
  const sourceData = annotations?.[options.context.id] ?? getActiveEnvSource(options.context.id);
210
255
  const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
211
256
  const rawValue = sourceData?.source(fullKey);
257
+ const validateSync = (parsed) => {
258
+ if (!parsed.success) return parsed;
259
+ if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
260
+ return innerParser.validateValue(parsed.value);
261
+ };
262
+ const validateAsync = async (parsed) => {
263
+ if (!parsed.success) return parsed;
264
+ if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
265
+ return await innerParser.validateValue(parsed.value);
266
+ };
212
267
  if (rawValue !== void 0) {
213
268
  if (typeof rawValue !== "string") {
214
269
  const type = rawValue === null ? "null" : Array.isArray(rawValue) ? "array" : typeof rawValue;
@@ -217,13 +272,21 @@ function getEnvOrDefault(state, options, mode, innerParser, innerState, exec) {
217
272
  error: message`Environment variable ${envVar(fullKey)} must be a string, but got: ${type}.`
218
273
  });
219
274
  }
220
- const parsed = options.parser.parse(rawValue);
221
- return wrapForMode(mode, parsed);
275
+ return dispatchByMode(mode, () => {
276
+ const parsed = options.parser.parse(rawValue);
277
+ return validateSync(parsed);
278
+ }, async () => {
279
+ const parsed = await options.parser.parse(rawValue);
280
+ return await validateAsync(parsed);
281
+ });
222
282
  }
223
- if (options.default !== void 0) return wrapForMode(mode, {
283
+ if (options.default !== void 0) return dispatchByMode(mode, () => validateSync({
224
284
  success: true,
225
285
  value: options.default
226
- });
286
+ }), () => validateAsync({
287
+ success: true,
288
+ value: options.default
289
+ }));
227
290
  if (innerParser != null) {
228
291
  const completeState = innerState ?? (annotations != null && innerParser.initialState == null && Reflect.get(innerParser, inheritParentAnnotationsKey) === true ? injectAnnotations(innerParser.initialState, annotations) : innerParser.initialState);
229
292
  return wrapForMode(mode, innerParser.complete(completeState, exec));
@@ -241,20 +304,37 @@ function getEnvOrDefault(state, options, mode, innerParser, innerState, exec) {
241
304
  * `options.default` and finally delegates to the wrapped parser's source
242
305
  * extractor.
243
306
  *
307
+ * When `innerParser` exposes a `validateValue` hook, env-sourced values
308
+ * and the configured default are re-validated against the inner parser's
309
+ * CLI constraints (see issue #414). This is only called from the
310
+ * `preservesSourceValue: true` branch in {@link bindEnv}, so the source
311
+ * value type is guaranteed to equal `TValue`.
312
+ *
244
313
  * @param state The wrapper state, which may carry env annotations.
245
314
  * @param options The binding options with lookup and default settings.
246
315
  * @param innerState The unwrapped inner state for delegated extraction.
247
316
  * @param extractInnerSourceValue The wrapped parser's source extractor.
317
+ * @param innerParser The wrapped parser, used to revalidate fallback values.
248
318
  * @returns The resolved source value, an async source value, or `undefined`.
249
319
  * @throws {Error} Propagates errors thrown by the env source callback
250
320
  * (`sourceData.source(fullKey)`).
251
321
  * @throws {Error} Propagates errors thrown by `options.parser.parse(rawValue)`.
322
+ * @throws {Error} Propagates errors thrown by
323
+ * `innerParser.validateValue()` while revalidating a
324
+ * successful env-sourced value or the configured
325
+ * `default` against the inner CLI parser's constraints
326
+ * (see issue #414).
252
327
  */
253
- function getEnvSourceValue(state, options, innerState, extractInnerSourceValue) {
328
+ function getEnvSourceValue(state, options, innerState, extractInnerSourceValue, innerParser) {
254
329
  const annotations = getAnnotations(state);
255
330
  const sourceData = annotations?.[options.context.id] ?? getActiveEnvSource(options.context.id);
256
331
  const fullKey = `${sourceData?.prefix ?? options.context.prefix}${options.key}`;
257
332
  const rawValue = sourceData?.source(fullKey);
333
+ const validateFallback = (parsed) => {
334
+ if (!parsed.success) return parsed;
335
+ if (innerParser == null || typeof innerParser.validateValue !== "function") return parsed;
336
+ return innerParser.validateValue(parsed.value);
337
+ };
258
338
  if (rawValue !== void 0) {
259
339
  if (typeof rawValue !== "string") {
260
340
  const type = rawValue === null ? "null" : Array.isArray(rawValue) ? "array" : typeof rawValue;
@@ -263,13 +343,12 @@ function getEnvSourceValue(state, options, innerState, extractInnerSourceValue)
263
343
  error: message`Environment variable ${envVar(fullKey)} must be a string, but got: ${type}.`
264
344
  };
265
345
  }
266
- const parsed = options.parser.parse(rawValue);
267
- return parsed;
346
+ return mapModeValue(options.parser.$mode, options.parser.parse(rawValue), (p) => validateFallback(p));
268
347
  }
269
- if (options.default !== void 0) return {
348
+ if (options.default !== void 0) return validateFallback({
270
349
  success: true,
271
350
  value: options.default
272
- };
351
+ });
273
352
  return extractInnerSourceValue(innerState);
274
353
  }
275
354
  const TRUE_LITERALS = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/env",
3
- "version": "1.0.0-dev.1758+0d05b449",
3
+ "version": "1.0.0-dev.1766+87276239",
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.1758+0d05b449"
57
+ "@optique/core": "1.0.0-dev.1766+87276239"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/node": "^20.19.9",