@optique/core 1.0.0-dev.1251 → 1.0.0-dev.1258

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.
@@ -8,6 +8,21 @@ const require_usage_internals = require('./usage-internals.cjs');
8
8
 
9
9
  //#region src/constructs.ts
10
10
  const inheritParentAnnotationsKey = Symbol.for("@optique/core/inheritParentAnnotations");
11
+ /**
12
+ * Returns the field state with parent annotations inherited, respecting
13
+ * the parser's {@link inheritParentAnnotationsKey} flag. This is the
14
+ * same logic as {@link createFieldStateGetter} inside `object()` but
15
+ * without the per-state cache, for use in module-level helpers like
16
+ * {@link pendingDependencyDefaults}.
17
+ * @internal
18
+ */
19
+ function getAnnotatedFieldState(parentState, field, parser) {
20
+ const sourceState = parentState != null && typeof parentState === "object" && field in parentState ? parentState[field] : parser.initialState;
21
+ if (sourceState == null || typeof sourceState !== "object") return sourceState;
22
+ const annotations = require_annotations.getAnnotations(parentState);
23
+ if (annotations === void 0 || require_annotations.getAnnotations(sourceState) === annotations) return sourceState;
24
+ return Reflect.get(parser, inheritParentAnnotationsKey) === true ? require_annotations.injectAnnotations(sourceState, annotations) : require_annotations.inheritAnnotations(parentState, sourceState);
25
+ }
11
26
  function createUnexpectedInputErrorWithScopedSuggestions(baseError, invalidInput, parsers, customFormatter) {
12
27
  const options = /* @__PURE__ */ new Set();
13
28
  const commands = /* @__PURE__ */ new Set();
@@ -693,7 +708,13 @@ function registerCompletedDependency(completed, registry) {
693
708
  function* pendingDependencyDefaults(context, parserPairs) {
694
709
  for (const [field, fieldParser] of parserPairs) {
695
710
  const fieldState = context.state != null && typeof context.state === "object" && field in context.state ? context.state[field] : void 0;
696
- if (fieldState != null) continue;
711
+ if (fieldState != null) {
712
+ if (!Array.isArray(fieldState) && !require_dependency.isDependencySourceState(fieldState) && (require_dependency.isWrappedDependencySource(fieldParser) || require_dependency.isPendingDependencySourceState(fieldParser.initialState))) yield {
713
+ parser: fieldParser,
714
+ state: getAnnotatedFieldState(context.state, field, fieldParser)
715
+ };
716
+ continue;
717
+ }
697
718
  if (require_dependency.isPendingDependencySourceState(fieldParser.initialState)) yield {
698
719
  parser: fieldParser,
699
720
  state: fieldParser.initialState
@@ -714,7 +735,31 @@ function* pendingDependencyDefaults(context, parserPairs) {
714
735
  * @internal
715
736
  */
716
737
  function completeDependencySourceDefaults(context, parserPairs, registry) {
717
- for (const { parser, state } of pendingDependencyDefaults(context, parserPairs)) registerCompletedDependency(parser.complete(state), registry);
738
+ for (const { parser, state } of pendingDependencyDefaults(context, parserPairs)) {
739
+ const completed = parser.complete(state);
740
+ const depState = wrapAsDependencySourceState(completed, parser);
741
+ if (depState) registerCompletedDependency(depState, registry);
742
+ }
743
+ }
744
+ /**
745
+ * Wraps a completed result as a {@link DependencySourceState} if the parser
746
+ * contains a dependency source and the result is a successful plain Result
747
+ * with a defined value. Returns the existing state if the result is already
748
+ * a DependencySourceState, or `undefined` if no wrapping applies.
749
+ *
750
+ * This helper is shared by both `object()` Phase 1 and the suggest-time
751
+ * pre-completion paths to keep the dep-ID selection and `value !== undefined`
752
+ * rule in one place.
753
+ * @internal
754
+ */
755
+ function wrapAsDependencySourceState(completed, parser) {
756
+ if (require_dependency.isDependencySourceState(completed)) return completed;
757
+ const hasDep = require_dependency.isWrappedDependencySource(parser) || require_dependency.isPendingDependencySourceState(parser.initialState);
758
+ if (hasDep && typeof completed === "object" && completed !== null && "success" in completed && completed.success && "value" in completed && completed.value !== void 0) {
759
+ const depId = require_dependency.isWrappedDependencySource(parser) ? parser[require_dependency.wrappedDependencySourceMarker][require_dependency.dependencyId] : parser.initialState[require_dependency.dependencyId];
760
+ return require_dependency.createDependencySourceState(completed, depId);
761
+ }
762
+ return void 0;
718
763
  }
719
764
  /**
720
765
  * Async version of {@link completeDependencySourceDefaults} that awaits
@@ -725,7 +770,11 @@ function completeDependencySourceDefaults(context, parserPairs, registry) {
725
770
  * @internal
726
771
  */
727
772
  async function completeDependencySourceDefaultsAsync(context, parserPairs, registry) {
728
- for (const { parser, state } of pendingDependencyDefaults(context, parserPairs)) registerCompletedDependency(await parser.complete(state), registry);
773
+ for (const { parser, state } of pendingDependencyDefaults(context, parserPairs)) {
774
+ const completed = await parser.complete(state);
775
+ const depState = wrapAsDependencySourceState(completed, parser);
776
+ if (depState) registerCompletedDependency(depState, registry);
777
+ }
729
778
  }
730
779
  /**
731
780
  * Recursively collects dependency values from DependencySourceState objects
@@ -1085,6 +1134,14 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1085
1134
  preCompletedState[fieldKey] = completed;
1086
1135
  preCompletedKeys.add(fieldKey);
1087
1136
  } else preCompletedState[fieldKey] = fieldState;
1137
+ } else if (fieldState != null && !Array.isArray(fieldState) && !require_dependency.isDependencySourceState(fieldState) && (require_dependency.isWrappedDependencySource(fieldParser) || require_dependency.isPendingDependencySourceState(fieldParser.initialState))) {
1138
+ const annotatedFieldState = getFieldState(field, fieldParser);
1139
+ const completed = fieldParser.complete(annotatedFieldState);
1140
+ const depState = wrapAsDependencySourceState(completed, fieldParser);
1141
+ if (depState) {
1142
+ preCompletedState[fieldKey] = depState;
1143
+ preCompletedKeys.add(fieldKey);
1144
+ } else preCompletedState[fieldKey] = annotatedFieldState;
1088
1145
  } else preCompletedState[fieldKey] = getFieldState(field, fieldParser);
1089
1146
  }
1090
1147
  const resolvedState = resolveDeferredParseStates(preCompletedState);
@@ -1138,6 +1195,14 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1138
1195
  preCompletedState[fieldKey] = completed;
1139
1196
  preCompletedKeys.add(fieldKey);
1140
1197
  } else preCompletedState[fieldKey] = fieldState;
1198
+ } else if (fieldState != null && !Array.isArray(fieldState) && !require_dependency.isDependencySourceState(fieldState) && (require_dependency.isWrappedDependencySource(fieldParser) || require_dependency.isPendingDependencySourceState(fieldParser.initialState))) {
1199
+ const annotatedFieldState = getFieldState(field, fieldParser);
1200
+ const completed = await fieldParser.complete(annotatedFieldState);
1201
+ const depState = wrapAsDependencySourceState(completed, fieldParser);
1202
+ if (depState) {
1203
+ preCompletedState[fieldKey] = depState;
1204
+ preCompletedKeys.add(fieldKey);
1205
+ } else preCompletedState[fieldKey] = annotatedFieldState;
1141
1206
  } else preCompletedState[fieldKey] = getFieldState(field, fieldParser);
1142
1207
  }
1143
1208
  const resolvedState = await resolveDeferredParseStatesAsync(preCompletedState);
@@ -1,6 +1,6 @@
1
1
  import { getAnnotations, inheritAnnotations, injectAnnotations } from "./annotations.js";
2
2
  import { message, optionName, text, values } from "./message.js";
3
- import { DependencyRegistry, dependencyId, isDeferredParseState, isDependencySourceState, isPendingDependencySourceState, isWrappedDependencySource, parseWithDependency, wrappedDependencySourceMarker } from "./dependency.js";
3
+ import { DependencyRegistry, createDependencySourceState, dependencyId, isDeferredParseState, isDependencySourceState, isPendingDependencySourceState, isWrappedDependencySource, parseWithDependency, wrappedDependencySourceMarker } from "./dependency.js";
4
4
  import { dispatchByMode, dispatchIterableByMode } from "./mode-dispatch.js";
5
5
  import { extractArgumentMetavars, extractCommandNames, extractOptionNames, isDocHidden, mergeHidden } from "./usage.js";
6
6
  import { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, findSimilar } from "./suggestion.js";
@@ -8,6 +8,21 @@ import { collectLeadingCandidates } from "./usage-internals.js";
8
8
 
9
9
  //#region src/constructs.ts
10
10
  const inheritParentAnnotationsKey = Symbol.for("@optique/core/inheritParentAnnotations");
11
+ /**
12
+ * Returns the field state with parent annotations inherited, respecting
13
+ * the parser's {@link inheritParentAnnotationsKey} flag. This is the
14
+ * same logic as {@link createFieldStateGetter} inside `object()` but
15
+ * without the per-state cache, for use in module-level helpers like
16
+ * {@link pendingDependencyDefaults}.
17
+ * @internal
18
+ */
19
+ function getAnnotatedFieldState(parentState, field, parser) {
20
+ const sourceState = parentState != null && typeof parentState === "object" && field in parentState ? parentState[field] : parser.initialState;
21
+ if (sourceState == null || typeof sourceState !== "object") return sourceState;
22
+ const annotations = getAnnotations(parentState);
23
+ if (annotations === void 0 || getAnnotations(sourceState) === annotations) return sourceState;
24
+ return Reflect.get(parser, inheritParentAnnotationsKey) === true ? injectAnnotations(sourceState, annotations) : inheritAnnotations(parentState, sourceState);
25
+ }
11
26
  function createUnexpectedInputErrorWithScopedSuggestions(baseError, invalidInput, parsers, customFormatter) {
12
27
  const options = /* @__PURE__ */ new Set();
13
28
  const commands = /* @__PURE__ */ new Set();
@@ -693,7 +708,13 @@ function registerCompletedDependency(completed, registry) {
693
708
  function* pendingDependencyDefaults(context, parserPairs) {
694
709
  for (const [field, fieldParser] of parserPairs) {
695
710
  const fieldState = context.state != null && typeof context.state === "object" && field in context.state ? context.state[field] : void 0;
696
- if (fieldState != null) continue;
711
+ if (fieldState != null) {
712
+ if (!Array.isArray(fieldState) && !isDependencySourceState(fieldState) && (isWrappedDependencySource(fieldParser) || isPendingDependencySourceState(fieldParser.initialState))) yield {
713
+ parser: fieldParser,
714
+ state: getAnnotatedFieldState(context.state, field, fieldParser)
715
+ };
716
+ continue;
717
+ }
697
718
  if (isPendingDependencySourceState(fieldParser.initialState)) yield {
698
719
  parser: fieldParser,
699
720
  state: fieldParser.initialState
@@ -714,7 +735,31 @@ function* pendingDependencyDefaults(context, parserPairs) {
714
735
  * @internal
715
736
  */
716
737
  function completeDependencySourceDefaults(context, parserPairs, registry) {
717
- for (const { parser, state } of pendingDependencyDefaults(context, parserPairs)) registerCompletedDependency(parser.complete(state), registry);
738
+ for (const { parser, state } of pendingDependencyDefaults(context, parserPairs)) {
739
+ const completed = parser.complete(state);
740
+ const depState = wrapAsDependencySourceState(completed, parser);
741
+ if (depState) registerCompletedDependency(depState, registry);
742
+ }
743
+ }
744
+ /**
745
+ * Wraps a completed result as a {@link DependencySourceState} if the parser
746
+ * contains a dependency source and the result is a successful plain Result
747
+ * with a defined value. Returns the existing state if the result is already
748
+ * a DependencySourceState, or `undefined` if no wrapping applies.
749
+ *
750
+ * This helper is shared by both `object()` Phase 1 and the suggest-time
751
+ * pre-completion paths to keep the dep-ID selection and `value !== undefined`
752
+ * rule in one place.
753
+ * @internal
754
+ */
755
+ function wrapAsDependencySourceState(completed, parser) {
756
+ if (isDependencySourceState(completed)) return completed;
757
+ const hasDep = isWrappedDependencySource(parser) || isPendingDependencySourceState(parser.initialState);
758
+ if (hasDep && typeof completed === "object" && completed !== null && "success" in completed && completed.success && "value" in completed && completed.value !== void 0) {
759
+ const depId = isWrappedDependencySource(parser) ? parser[wrappedDependencySourceMarker][dependencyId] : parser.initialState[dependencyId];
760
+ return createDependencySourceState(completed, depId);
761
+ }
762
+ return void 0;
718
763
  }
719
764
  /**
720
765
  * Async version of {@link completeDependencySourceDefaults} that awaits
@@ -725,7 +770,11 @@ function completeDependencySourceDefaults(context, parserPairs, registry) {
725
770
  * @internal
726
771
  */
727
772
  async function completeDependencySourceDefaultsAsync(context, parserPairs, registry) {
728
- for (const { parser, state } of pendingDependencyDefaults(context, parserPairs)) registerCompletedDependency(await parser.complete(state), registry);
773
+ for (const { parser, state } of pendingDependencyDefaults(context, parserPairs)) {
774
+ const completed = await parser.complete(state);
775
+ const depState = wrapAsDependencySourceState(completed, parser);
776
+ if (depState) registerCompletedDependency(depState, registry);
777
+ }
729
778
  }
730
779
  /**
731
780
  * Recursively collects dependency values from DependencySourceState objects
@@ -1085,6 +1134,14 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1085
1134
  preCompletedState[fieldKey] = completed;
1086
1135
  preCompletedKeys.add(fieldKey);
1087
1136
  } else preCompletedState[fieldKey] = fieldState;
1137
+ } else if (fieldState != null && !Array.isArray(fieldState) && !isDependencySourceState(fieldState) && (isWrappedDependencySource(fieldParser) || isPendingDependencySourceState(fieldParser.initialState))) {
1138
+ const annotatedFieldState = getFieldState(field, fieldParser);
1139
+ const completed = fieldParser.complete(annotatedFieldState);
1140
+ const depState = wrapAsDependencySourceState(completed, fieldParser);
1141
+ if (depState) {
1142
+ preCompletedState[fieldKey] = depState;
1143
+ preCompletedKeys.add(fieldKey);
1144
+ } else preCompletedState[fieldKey] = annotatedFieldState;
1088
1145
  } else preCompletedState[fieldKey] = getFieldState(field, fieldParser);
1089
1146
  }
1090
1147
  const resolvedState = resolveDeferredParseStates(preCompletedState);
@@ -1138,6 +1195,14 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1138
1195
  preCompletedState[fieldKey] = completed;
1139
1196
  preCompletedKeys.add(fieldKey);
1140
1197
  } else preCompletedState[fieldKey] = fieldState;
1198
+ } else if (fieldState != null && !Array.isArray(fieldState) && !isDependencySourceState(fieldState) && (isWrappedDependencySource(fieldParser) || isPendingDependencySourceState(fieldParser.initialState))) {
1199
+ const annotatedFieldState = getFieldState(field, fieldParser);
1200
+ const completed = await fieldParser.complete(annotatedFieldState);
1201
+ const depState = wrapAsDependencySourceState(completed, fieldParser);
1202
+ if (depState) {
1203
+ preCompletedState[fieldKey] = depState;
1204
+ preCompletedKeys.add(fieldKey);
1205
+ } else preCompletedState[fieldKey] = annotatedFieldState;
1141
1206
  } else preCompletedState[fieldKey] = getFieldState(field, fieldParser);
1142
1207
  }
1143
1208
  const resolvedState = await resolveDeferredParseStatesAsync(preCompletedState);
@@ -499,6 +499,7 @@ function url(options = {}) {
499
499
  originalProtocolsList.push(protocol);
500
500
  normalizedProtocolsList.push(normalized);
501
501
  }
502
+ if (originalProtocolsList.length === 0) throw new TypeError("allowedProtocols must not be empty.");
502
503
  }
503
504
  const originalProtocols = options.allowedProtocols != null ? Object.freeze(originalProtocolsList) : void 0;
504
505
  const allowedProtocols = options.allowedProtocols != null ? Object.freeze(normalizedProtocolsList) : void 0;
@@ -517,7 +518,25 @@ function url(options = {}) {
517
518
  const url$1 = new URL(input);
518
519
  if (allowedProtocols != null && !allowedProtocols.includes(url$1.protocol)) return {
519
520
  success: false,
520
- error: disallowedProtocol ? typeof disallowedProtocol === "function" ? disallowedProtocol(url$1.protocol, originalProtocols) : disallowedProtocol : require_message.message`URL protocol ${url$1.protocol} is not allowed. Allowed protocols: ${allowedProtocols.join(", ")}.`
521
+ error: disallowedProtocol ? typeof disallowedProtocol === "function" ? disallowedProtocol(url$1.protocol, originalProtocols) : disallowedProtocol : [
522
+ {
523
+ type: "text",
524
+ text: "URL protocol "
525
+ },
526
+ {
527
+ type: "value",
528
+ value: url$1.protocol
529
+ },
530
+ {
531
+ type: "text",
532
+ text: " is not allowed. Allowed protocols: "
533
+ },
534
+ ...require_message.valueSet(originalProtocols, { locale: "en-US" }),
535
+ {
536
+ type: "text",
537
+ text: "."
538
+ }
539
+ ]
521
540
  };
522
541
  return {
523
542
  success: true,
@@ -1364,16 +1383,19 @@ function email(options) {
1364
1383
  const allowDisplayName = options?.allowDisplayName ?? false;
1365
1384
  const lowercase = options?.lowercase ?? false;
1366
1385
  const allowedDomains = options?.allowedDomains != null ? Object.freeze([...options.allowedDomains]) : void 0;
1367
- if (allowedDomains != null) for (let i = 0; i < allowedDomains.length; i++) {
1368
- const entry = allowedDomains[i];
1369
- if (typeof entry !== "string") throw new TypeError(`allowedDomains[${i}] must be a string, got ${typeof entry}.`);
1370
- if (entry !== entry.trim()) throw new TypeError(`allowedDomains[${i}] must not have leading or trailing whitespace: ${JSON.stringify(entry)}`);
1371
- if (entry.startsWith("@")) throw new TypeError(`allowedDomains[${i}] must not start with "@": ${JSON.stringify(entry)}`);
1372
- if (entry === "" || !entry.includes(".")) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1373
- if (entry.startsWith(".") || entry.endsWith(".") || entry.startsWith("-") || entry.endsWith("-")) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1374
- const labels = entry.split(".");
1375
- for (const label of labels) if (label.length === 0 || label.length > 63 || label.startsWith("-") || label.endsWith("-") || !/^[a-zA-Z0-9-]+$/.test(label)) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1376
- if (labels.length === 4 && labels.every((label) => /^[0-9]+$/.test(label))) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1386
+ if (allowedDomains != null) {
1387
+ if (allowedDomains.length === 0) throw new TypeError("allowedDomains must not be empty.");
1388
+ for (let i = 0; i < allowedDomains.length; i++) {
1389
+ const entry = allowedDomains[i];
1390
+ if (typeof entry !== "string") throw new TypeError(`allowedDomains[${i}] must be a string, got ${typeof entry}.`);
1391
+ if (entry !== entry.trim()) throw new TypeError(`allowedDomains[${i}] must not have leading or trailing whitespace: ${JSON.stringify(entry)}`);
1392
+ if (entry.startsWith("@")) throw new TypeError(`allowedDomains[${i}] must not start with "@": ${JSON.stringify(entry)}`);
1393
+ if (entry === "" || !entry.includes(".")) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1394
+ if (entry.startsWith(".") || entry.endsWith(".") || entry.startsWith("-") || entry.endsWith("-")) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1395
+ const labels = entry.split(".");
1396
+ for (const label of labels) if (label.length === 0 || label.length > 63 || label.startsWith("-") || label.endsWith("-") || !/^[a-zA-Z0-9-]+$/.test(label)) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1397
+ if (labels.length === 4 && labels.every((label) => /^[0-9]+$/.test(label))) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1398
+ }
1377
1399
  }
1378
1400
  const invalidEmail = options?.errors?.invalidEmail;
1379
1401
  const domainNotAllowed = options?.errors?.domainNotAllowed;
@@ -1489,7 +1511,12 @@ function email(options) {
1489
1511
  },
1490
1512
  {
1491
1513
  type: "text",
1492
- text: ` is not allowed. Allowed domains: ${allowedDomains.join(", ")}.`
1514
+ text: " is not allowed. Allowed domains: "
1515
+ },
1516
+ ...require_message.valueSet(allowedDomains, { locale: "en-US" }),
1517
+ {
1518
+ type: "text",
1519
+ text: "."
1493
1520
  }
1494
1521
  ];
1495
1522
  return {
@@ -1535,7 +1562,12 @@ function email(options) {
1535
1562
  },
1536
1563
  {
1537
1564
  type: "text",
1538
- text: ` is not allowed. Allowed domains: ${allowedDomains.join(", ")}.`
1565
+ text: " is not allowed. Allowed domains: "
1566
+ },
1567
+ ...require_message.valueSet(allowedDomains, { locale: "en-US" }),
1568
+ {
1569
+ type: "text",
1570
+ text: "."
1539
1571
  }
1540
1572
  ];
1541
1573
  return {
@@ -1849,8 +1881,8 @@ function macAddress(options) {
1849
1881
  const caseOption = options?.case ?? "preserve";
1850
1882
  const outputSeparator = options?.outputSeparator;
1851
1883
  const metavar = options?.metavar ?? "MAC";
1852
- const colonRegex = /^([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2})$/;
1853
- const hyphenRegex = /^([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})$/;
1884
+ const colonRegex = /^([0-9a-fA-F]{2}):([0-9a-fA-F]{2}):([0-9a-fA-F]{2}):([0-9a-fA-F]{2}):([0-9a-fA-F]{2}):([0-9a-fA-F]{2})$/;
1885
+ const hyphenRegex = /^([0-9a-fA-F]{2})-([0-9a-fA-F]{2})-([0-9a-fA-F]{2})-([0-9a-fA-F]{2})-([0-9a-fA-F]{2})-([0-9a-fA-F]{2})$/;
1854
1886
  const dotRegex = /^([0-9a-fA-F]{4})\.([0-9a-fA-F]{4})\.([0-9a-fA-F]{4})$/;
1855
1887
  const noneRegex = /^([0-9a-fA-F]{12})$/;
1856
1888
  return {
@@ -1983,15 +2015,18 @@ function domain(options) {
1983
2015
  const allowSubdomains = options?.allowSubdomains ?? true;
1984
2016
  const allowedTlds = options?.allowedTlds != null ? Object.freeze([...options.allowedTlds]) : void 0;
1985
2017
  const labelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
1986
- if (allowedTlds !== void 0) for (const [i, tld] of allowedTlds.entries()) {
1987
- if (typeof tld !== "string") {
1988
- const actualType = Array.isArray(tld) ? "array" : typeof tld;
1989
- throw new TypeError(`allowedTlds[${i}] must be a string, but got ${actualType}.`);
2018
+ if (allowedTlds !== void 0) {
2019
+ if (allowedTlds.length === 0) throw new TypeError("allowedTlds must not be empty.");
2020
+ for (const [i, tld] of allowedTlds.entries()) {
2021
+ if (typeof tld !== "string") {
2022
+ const actualType = Array.isArray(tld) ? "array" : typeof tld;
2023
+ throw new TypeError(`allowedTlds[${i}] must be a string, but got ${actualType}.`);
2024
+ }
2025
+ if (tld.length === 0) throw new TypeError(`allowedTlds[${i}] must not be an empty string.`);
2026
+ if (tld.includes(".")) throw new TypeError(`allowedTlds[${i}] must not contain dots: ${JSON.stringify(tld)}.`);
2027
+ if (tld !== tld.trim()) throw new TypeError(`allowedTlds[${i}] must not have leading or trailing whitespace: ${JSON.stringify(tld)}.`);
2028
+ if (!labelRegex.test(tld)) throw new TypeError(`allowedTlds[${i}] is not a valid DNS label: ${JSON.stringify(tld)}.`);
1990
2029
  }
1991
- if (tld.length === 0) throw new TypeError(`allowedTlds[${i}] must not be an empty string.`);
1992
- if (tld.includes(".")) throw new TypeError(`allowedTlds[${i}] must not contain dots: ${JSON.stringify(tld)}.`);
1993
- if (tld !== tld.trim()) throw new TypeError(`allowedTlds[${i}] must not have leading or trailing whitespace: ${JSON.stringify(tld)}.`);
1994
- if (!labelRegex.test(tld)) throw new TypeError(`allowedTlds[${i}] is not a valid DNS label: ${JSON.stringify(tld)}.`);
1995
2030
  }
1996
2031
  const allowedTldsLower = allowedTlds != null ? Object.freeze(allowedTlds.map((t) => t.toLowerCase())) : void 0;
1997
2032
  const minLabels = options?.minLabels ?? 2;
@@ -2188,7 +2223,12 @@ function domain(options) {
2188
2223
  },
2189
2224
  {
2190
2225
  type: "text",
2191
- text: ` is not allowed. Allowed TLDs: ${allowedTlds.join(", ")}.`
2226
+ text: " is not allowed. Allowed TLDs: "
2227
+ },
2228
+ ...require_message.valueSet(allowedTlds, { locale: "en-US" }),
2229
+ {
2230
+ type: "text",
2231
+ text: "."
2192
2232
  }
2193
2233
  ];
2194
2234
  return {
@@ -499,6 +499,7 @@ function url(options = {}) {
499
499
  originalProtocolsList.push(protocol);
500
500
  normalizedProtocolsList.push(normalized);
501
501
  }
502
+ if (originalProtocolsList.length === 0) throw new TypeError("allowedProtocols must not be empty.");
502
503
  }
503
504
  const originalProtocols = options.allowedProtocols != null ? Object.freeze(originalProtocolsList) : void 0;
504
505
  const allowedProtocols = options.allowedProtocols != null ? Object.freeze(normalizedProtocolsList) : void 0;
@@ -517,7 +518,25 @@ function url(options = {}) {
517
518
  const url$1 = new URL(input);
518
519
  if (allowedProtocols != null && !allowedProtocols.includes(url$1.protocol)) return {
519
520
  success: false,
520
- error: disallowedProtocol ? typeof disallowedProtocol === "function" ? disallowedProtocol(url$1.protocol, originalProtocols) : disallowedProtocol : message`URL protocol ${url$1.protocol} is not allowed. Allowed protocols: ${allowedProtocols.join(", ")}.`
521
+ error: disallowedProtocol ? typeof disallowedProtocol === "function" ? disallowedProtocol(url$1.protocol, originalProtocols) : disallowedProtocol : [
522
+ {
523
+ type: "text",
524
+ text: "URL protocol "
525
+ },
526
+ {
527
+ type: "value",
528
+ value: url$1.protocol
529
+ },
530
+ {
531
+ type: "text",
532
+ text: " is not allowed. Allowed protocols: "
533
+ },
534
+ ...valueSet(originalProtocols, { locale: "en-US" }),
535
+ {
536
+ type: "text",
537
+ text: "."
538
+ }
539
+ ]
521
540
  };
522
541
  return {
523
542
  success: true,
@@ -1364,16 +1383,19 @@ function email(options) {
1364
1383
  const allowDisplayName = options?.allowDisplayName ?? false;
1365
1384
  const lowercase = options?.lowercase ?? false;
1366
1385
  const allowedDomains = options?.allowedDomains != null ? Object.freeze([...options.allowedDomains]) : void 0;
1367
- if (allowedDomains != null) for (let i = 0; i < allowedDomains.length; i++) {
1368
- const entry = allowedDomains[i];
1369
- if (typeof entry !== "string") throw new TypeError(`allowedDomains[${i}] must be a string, got ${typeof entry}.`);
1370
- if (entry !== entry.trim()) throw new TypeError(`allowedDomains[${i}] must not have leading or trailing whitespace: ${JSON.stringify(entry)}`);
1371
- if (entry.startsWith("@")) throw new TypeError(`allowedDomains[${i}] must not start with "@": ${JSON.stringify(entry)}`);
1372
- if (entry === "" || !entry.includes(".")) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1373
- if (entry.startsWith(".") || entry.endsWith(".") || entry.startsWith("-") || entry.endsWith("-")) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1374
- const labels = entry.split(".");
1375
- for (const label of labels) if (label.length === 0 || label.length > 63 || label.startsWith("-") || label.endsWith("-") || !/^[a-zA-Z0-9-]+$/.test(label)) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1376
- if (labels.length === 4 && labels.every((label) => /^[0-9]+$/.test(label))) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1386
+ if (allowedDomains != null) {
1387
+ if (allowedDomains.length === 0) throw new TypeError("allowedDomains must not be empty.");
1388
+ for (let i = 0; i < allowedDomains.length; i++) {
1389
+ const entry = allowedDomains[i];
1390
+ if (typeof entry !== "string") throw new TypeError(`allowedDomains[${i}] must be a string, got ${typeof entry}.`);
1391
+ if (entry !== entry.trim()) throw new TypeError(`allowedDomains[${i}] must not have leading or trailing whitespace: ${JSON.stringify(entry)}`);
1392
+ if (entry.startsWith("@")) throw new TypeError(`allowedDomains[${i}] must not start with "@": ${JSON.stringify(entry)}`);
1393
+ if (entry === "" || !entry.includes(".")) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1394
+ if (entry.startsWith(".") || entry.endsWith(".") || entry.startsWith("-") || entry.endsWith("-")) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1395
+ const labels = entry.split(".");
1396
+ for (const label of labels) if (label.length === 0 || label.length > 63 || label.startsWith("-") || label.endsWith("-") || !/^[a-zA-Z0-9-]+$/.test(label)) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1397
+ if (labels.length === 4 && labels.every((label) => /^[0-9]+$/.test(label))) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
1398
+ }
1377
1399
  }
1378
1400
  const invalidEmail = options?.errors?.invalidEmail;
1379
1401
  const domainNotAllowed = options?.errors?.domainNotAllowed;
@@ -1489,7 +1511,12 @@ function email(options) {
1489
1511
  },
1490
1512
  {
1491
1513
  type: "text",
1492
- text: ` is not allowed. Allowed domains: ${allowedDomains.join(", ")}.`
1514
+ text: " is not allowed. Allowed domains: "
1515
+ },
1516
+ ...valueSet(allowedDomains, { locale: "en-US" }),
1517
+ {
1518
+ type: "text",
1519
+ text: "."
1493
1520
  }
1494
1521
  ];
1495
1522
  return {
@@ -1535,7 +1562,12 @@ function email(options) {
1535
1562
  },
1536
1563
  {
1537
1564
  type: "text",
1538
- text: ` is not allowed. Allowed domains: ${allowedDomains.join(", ")}.`
1565
+ text: " is not allowed. Allowed domains: "
1566
+ },
1567
+ ...valueSet(allowedDomains, { locale: "en-US" }),
1568
+ {
1569
+ type: "text",
1570
+ text: "."
1539
1571
  }
1540
1572
  ];
1541
1573
  return {
@@ -1849,8 +1881,8 @@ function macAddress(options) {
1849
1881
  const caseOption = options?.case ?? "preserve";
1850
1882
  const outputSeparator = options?.outputSeparator;
1851
1883
  const metavar = options?.metavar ?? "MAC";
1852
- const colonRegex = /^([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2})$/;
1853
- const hyphenRegex = /^([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})$/;
1884
+ const colonRegex = /^([0-9a-fA-F]{2}):([0-9a-fA-F]{2}):([0-9a-fA-F]{2}):([0-9a-fA-F]{2}):([0-9a-fA-F]{2}):([0-9a-fA-F]{2})$/;
1885
+ const hyphenRegex = /^([0-9a-fA-F]{2})-([0-9a-fA-F]{2})-([0-9a-fA-F]{2})-([0-9a-fA-F]{2})-([0-9a-fA-F]{2})-([0-9a-fA-F]{2})$/;
1854
1886
  const dotRegex = /^([0-9a-fA-F]{4})\.([0-9a-fA-F]{4})\.([0-9a-fA-F]{4})$/;
1855
1887
  const noneRegex = /^([0-9a-fA-F]{12})$/;
1856
1888
  return {
@@ -1983,15 +2015,18 @@ function domain(options) {
1983
2015
  const allowSubdomains = options?.allowSubdomains ?? true;
1984
2016
  const allowedTlds = options?.allowedTlds != null ? Object.freeze([...options.allowedTlds]) : void 0;
1985
2017
  const labelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
1986
- if (allowedTlds !== void 0) for (const [i, tld] of allowedTlds.entries()) {
1987
- if (typeof tld !== "string") {
1988
- const actualType = Array.isArray(tld) ? "array" : typeof tld;
1989
- throw new TypeError(`allowedTlds[${i}] must be a string, but got ${actualType}.`);
2018
+ if (allowedTlds !== void 0) {
2019
+ if (allowedTlds.length === 0) throw new TypeError("allowedTlds must not be empty.");
2020
+ for (const [i, tld] of allowedTlds.entries()) {
2021
+ if (typeof tld !== "string") {
2022
+ const actualType = Array.isArray(tld) ? "array" : typeof tld;
2023
+ throw new TypeError(`allowedTlds[${i}] must be a string, but got ${actualType}.`);
2024
+ }
2025
+ if (tld.length === 0) throw new TypeError(`allowedTlds[${i}] must not be an empty string.`);
2026
+ if (tld.includes(".")) throw new TypeError(`allowedTlds[${i}] must not contain dots: ${JSON.stringify(tld)}.`);
2027
+ if (tld !== tld.trim()) throw new TypeError(`allowedTlds[${i}] must not have leading or trailing whitespace: ${JSON.stringify(tld)}.`);
2028
+ if (!labelRegex.test(tld)) throw new TypeError(`allowedTlds[${i}] is not a valid DNS label: ${JSON.stringify(tld)}.`);
1990
2029
  }
1991
- if (tld.length === 0) throw new TypeError(`allowedTlds[${i}] must not be an empty string.`);
1992
- if (tld.includes(".")) throw new TypeError(`allowedTlds[${i}] must not contain dots: ${JSON.stringify(tld)}.`);
1993
- if (tld !== tld.trim()) throw new TypeError(`allowedTlds[${i}] must not have leading or trailing whitespace: ${JSON.stringify(tld)}.`);
1994
- if (!labelRegex.test(tld)) throw new TypeError(`allowedTlds[${i}] is not a valid DNS label: ${JSON.stringify(tld)}.`);
1995
2030
  }
1996
2031
  const allowedTldsLower = allowedTlds != null ? Object.freeze(allowedTlds.map((t) => t.toLowerCase())) : void 0;
1997
2032
  const minLabels = options?.minLabels ?? 2;
@@ -2188,7 +2223,12 @@ function domain(options) {
2188
2223
  },
2189
2224
  {
2190
2225
  type: "text",
2191
- text: ` is not allowed. Allowed TLDs: ${allowedTlds.join(", ")}.`
2226
+ text: " is not allowed. Allowed TLDs: "
2227
+ },
2228
+ ...valueSet(allowedTlds, { locale: "en-US" }),
2229
+ {
2230
+ type: "text",
2231
+ text: "."
2192
2232
  }
2193
2233
  ];
2194
2234
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.1251+2501c981",
3
+ "version": "1.0.0-dev.1258+ce84a88d",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",