@optique/core 1.0.0-dev.1572 → 1.0.0-dev.1580

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/message.cjs CHANGED
@@ -143,8 +143,10 @@ function value(value$1) {
143
143
  * string representations of consecutive values.
144
144
  * For example, `["42", "hello"]`.
145
145
  * @returns A {@link MessageTerm} representing the list of values.
146
+ * @throws {TypeError} If the values array is empty.
146
147
  */
147
148
  function values(values$1) {
149
+ if (values$1.length === 0) throw new TypeError("values must not be empty.");
148
150
  return {
149
151
  type: "values",
150
152
  values: [...values$1]
@@ -236,29 +238,44 @@ function link(href) {
236
238
  * to be styled independently while respecting the locale's list formatting
237
239
  * conventions.
238
240
  *
241
+ * A fallback must be provided for when the values array is empty. This can
242
+ * be either a simple string (shorthand) or an options object with a
243
+ * `fallback` field. When the values array is empty, the fallback text
244
+ * is returned as a single text term. An empty fallback string produces
245
+ * an empty {@link Message}.
246
+ *
239
247
  * @example
240
248
  * ```typescript
241
249
  * // English conjunction (default): "error", "warn", and "info"
242
- * const msg1 = message`Expected one of ${valueSet(["error", "warn", "info"])}.`;
250
+ * const msg1 = message`Expected one of ${
251
+ * valueSet(["error", "warn", "info"], "")
252
+ * }.`;
243
253
  *
244
254
  * // English disjunction: "error", "warn", or "info"
245
255
  * const msg2 = message`Expected ${
246
- * valueSet(["error", "warn", "info"], { type: "disjunction" })
256
+ * valueSet(["error", "warn", "info"], { fallback: "", type: "disjunction" })
247
257
  * }.`;
248
258
  *
249
- * // Korean disjunction: "error", "warn" 또는 "info"
250
- * const msg3 = message`${
251
- * valueSet(["error", "warn", "info"], { locale: "ko", type: "disjunction" })
252
- * } 중 하나여야 합니다.`;
259
+ * // Fallback for empty list:
260
+ * const msg3 = message`Expected one of ${valueSet([], "(none)")}.`;
261
+ * // "Expected one of (none)."
253
262
  * ```
254
263
  *
255
264
  * @param values The list of values to format.
256
- * @param options Optional formatting options including locale and list type.
265
+ * @param fallbackOrOptions A fallback string for empty lists, or an options
266
+ * object including the fallback and formatting
267
+ * options such as locale and list type.
257
268
  * @returns A {@link Message} with alternating value and text terms.
269
+ * @throws {TypeError} If the fallback parameter is missing or invalid.
258
270
  * @since 0.9.0
259
271
  */
260
- function valueSet(values$1, options) {
261
- if (values$1.length === 0) return [];
272
+ function valueSet(values$1, fallbackOrOptions) {
273
+ if (fallbackOrOptions == null || typeof fallbackOrOptions !== "string" && typeof fallbackOrOptions.fallback !== "string") throw new TypeError("valueSet() requires a fallback string or an options object with a fallback field.");
274
+ const options = typeof fallbackOrOptions === "string" ? { fallback: fallbackOrOptions } : fallbackOrOptions;
275
+ if (values$1.length === 0) return options.fallback === "" ? [] : [{
276
+ type: "text",
277
+ text: options.fallback
278
+ }];
262
279
  const formatter = new Intl.ListFormat(options?.locale, {
263
280
  type: options?.type,
264
281
  style: options?.style
@@ -233,6 +233,7 @@ declare function value(value: string): MessageTerm;
233
233
  * string representations of consecutive values.
234
234
  * For example, `["42", "hello"]`.
235
235
  * @returns A {@link MessageTerm} representing the list of values.
236
+ * @throws {TypeError} If the values array is empty.
236
237
  */
237
238
  declare function values(values: readonly string[]): MessageTerm;
238
239
  /**
@@ -319,6 +320,15 @@ interface ValueSetOptions {
319
320
  * @default `"long"`
320
321
  */
321
322
  readonly style?: "long" | "short" | "narrow";
323
+ /**
324
+ * A fallback text to return when the values array is empty. When provided
325
+ * and the values array is empty, a {@link Message} containing a single text
326
+ * term with this string is returned instead of an empty {@link Message}.
327
+ * An empty string produces an empty {@link Message}.
328
+ *
329
+ * @since 1.0.0
330
+ */
331
+ readonly fallback: string;
322
332
  }
323
333
  /**
324
334
  * Creates a {@link Message} for a formatted list of values using the
@@ -330,28 +340,38 @@ interface ValueSetOptions {
330
340
  * to be styled independently while respecting the locale's list formatting
331
341
  * conventions.
332
342
  *
343
+ * A fallback must be provided for when the values array is empty. This can
344
+ * be either a simple string (shorthand) or an options object with a
345
+ * `fallback` field. When the values array is empty, the fallback text
346
+ * is returned as a single text term. An empty fallback string produces
347
+ * an empty {@link Message}.
348
+ *
333
349
  * @example
334
350
  * ```typescript
335
351
  * // English conjunction (default): "error", "warn", and "info"
336
- * const msg1 = message`Expected one of ${valueSet(["error", "warn", "info"])}.`;
352
+ * const msg1 = message`Expected one of ${
353
+ * valueSet(["error", "warn", "info"], "")
354
+ * }.`;
337
355
  *
338
356
  * // English disjunction: "error", "warn", or "info"
339
357
  * const msg2 = message`Expected ${
340
- * valueSet(["error", "warn", "info"], { type: "disjunction" })
358
+ * valueSet(["error", "warn", "info"], { fallback: "", type: "disjunction" })
341
359
  * }.`;
342
360
  *
343
- * // Korean disjunction: "error", "warn" 또는 "info"
344
- * const msg3 = message`${
345
- * valueSet(["error", "warn", "info"], { locale: "ko", type: "disjunction" })
346
- * } 중 하나여야 합니다.`;
361
+ * // Fallback for empty list:
362
+ * const msg3 = message`Expected one of ${valueSet([], "(none)")}.`;
363
+ * // "Expected one of (none)."
347
364
  * ```
348
365
  *
349
366
  * @param values The list of values to format.
350
- * @param options Optional formatting options including locale and list type.
367
+ * @param fallbackOrOptions A fallback string for empty lists, or an options
368
+ * object including the fallback and formatting
369
+ * options such as locale and list type.
351
370
  * @returns A {@link Message} with alternating value and text terms.
371
+ * @throws {TypeError} If the fallback parameter is missing or invalid.
352
372
  * @since 0.9.0
353
373
  */
354
- declare function valueSet(values: readonly string[], options?: ValueSetOptions): Message;
374
+ declare function valueSet(values: readonly string[], fallbackOrOptions: string | ValueSetOptions): Message;
355
375
  /**
356
376
  * Options for the {@link formatMessage} function.
357
377
  */
package/dist/message.d.ts CHANGED
@@ -233,6 +233,7 @@ declare function value(value: string): MessageTerm;
233
233
  * string representations of consecutive values.
234
234
  * For example, `["42", "hello"]`.
235
235
  * @returns A {@link MessageTerm} representing the list of values.
236
+ * @throws {TypeError} If the values array is empty.
236
237
  */
237
238
  declare function values(values: readonly string[]): MessageTerm;
238
239
  /**
@@ -319,6 +320,15 @@ interface ValueSetOptions {
319
320
  * @default `"long"`
320
321
  */
321
322
  readonly style?: "long" | "short" | "narrow";
323
+ /**
324
+ * A fallback text to return when the values array is empty. When provided
325
+ * and the values array is empty, a {@link Message} containing a single text
326
+ * term with this string is returned instead of an empty {@link Message}.
327
+ * An empty string produces an empty {@link Message}.
328
+ *
329
+ * @since 1.0.0
330
+ */
331
+ readonly fallback: string;
322
332
  }
323
333
  /**
324
334
  * Creates a {@link Message} for a formatted list of values using the
@@ -330,28 +340,38 @@ interface ValueSetOptions {
330
340
  * to be styled independently while respecting the locale's list formatting
331
341
  * conventions.
332
342
  *
343
+ * A fallback must be provided for when the values array is empty. This can
344
+ * be either a simple string (shorthand) or an options object with a
345
+ * `fallback` field. When the values array is empty, the fallback text
346
+ * is returned as a single text term. An empty fallback string produces
347
+ * an empty {@link Message}.
348
+ *
333
349
  * @example
334
350
  * ```typescript
335
351
  * // English conjunction (default): "error", "warn", and "info"
336
- * const msg1 = message`Expected one of ${valueSet(["error", "warn", "info"])}.`;
352
+ * const msg1 = message`Expected one of ${
353
+ * valueSet(["error", "warn", "info"], "")
354
+ * }.`;
337
355
  *
338
356
  * // English disjunction: "error", "warn", or "info"
339
357
  * const msg2 = message`Expected ${
340
- * valueSet(["error", "warn", "info"], { type: "disjunction" })
358
+ * valueSet(["error", "warn", "info"], { fallback: "", type: "disjunction" })
341
359
  * }.`;
342
360
  *
343
- * // Korean disjunction: "error", "warn" 또는 "info"
344
- * const msg3 = message`${
345
- * valueSet(["error", "warn", "info"], { locale: "ko", type: "disjunction" })
346
- * } 중 하나여야 합니다.`;
361
+ * // Fallback for empty list:
362
+ * const msg3 = message`Expected one of ${valueSet([], "(none)")}.`;
363
+ * // "Expected one of (none)."
347
364
  * ```
348
365
  *
349
366
  * @param values The list of values to format.
350
- * @param options Optional formatting options including locale and list type.
367
+ * @param fallbackOrOptions A fallback string for empty lists, or an options
368
+ * object including the fallback and formatting
369
+ * options such as locale and list type.
351
370
  * @returns A {@link Message} with alternating value and text terms.
371
+ * @throws {TypeError} If the fallback parameter is missing or invalid.
352
372
  * @since 0.9.0
353
373
  */
354
- declare function valueSet(values: readonly string[], options?: ValueSetOptions): Message;
374
+ declare function valueSet(values: readonly string[], fallbackOrOptions: string | ValueSetOptions): Message;
355
375
  /**
356
376
  * Options for the {@link formatMessage} function.
357
377
  */
package/dist/message.js CHANGED
@@ -142,8 +142,10 @@ function value(value$1) {
142
142
  * string representations of consecutive values.
143
143
  * For example, `["42", "hello"]`.
144
144
  * @returns A {@link MessageTerm} representing the list of values.
145
+ * @throws {TypeError} If the values array is empty.
145
146
  */
146
147
  function values(values$1) {
148
+ if (values$1.length === 0) throw new TypeError("values must not be empty.");
147
149
  return {
148
150
  type: "values",
149
151
  values: [...values$1]
@@ -235,29 +237,44 @@ function link(href) {
235
237
  * to be styled independently while respecting the locale's list formatting
236
238
  * conventions.
237
239
  *
240
+ * A fallback must be provided for when the values array is empty. This can
241
+ * be either a simple string (shorthand) or an options object with a
242
+ * `fallback` field. When the values array is empty, the fallback text
243
+ * is returned as a single text term. An empty fallback string produces
244
+ * an empty {@link Message}.
245
+ *
238
246
  * @example
239
247
  * ```typescript
240
248
  * // English conjunction (default): "error", "warn", and "info"
241
- * const msg1 = message`Expected one of ${valueSet(["error", "warn", "info"])}.`;
249
+ * const msg1 = message`Expected one of ${
250
+ * valueSet(["error", "warn", "info"], "")
251
+ * }.`;
242
252
  *
243
253
  * // English disjunction: "error", "warn", or "info"
244
254
  * const msg2 = message`Expected ${
245
- * valueSet(["error", "warn", "info"], { type: "disjunction" })
255
+ * valueSet(["error", "warn", "info"], { fallback: "", type: "disjunction" })
246
256
  * }.`;
247
257
  *
248
- * // Korean disjunction: "error", "warn" 또는 "info"
249
- * const msg3 = message`${
250
- * valueSet(["error", "warn", "info"], { locale: "ko", type: "disjunction" })
251
- * } 중 하나여야 합니다.`;
258
+ * // Fallback for empty list:
259
+ * const msg3 = message`Expected one of ${valueSet([], "(none)")}.`;
260
+ * // "Expected one of (none)."
252
261
  * ```
253
262
  *
254
263
  * @param values The list of values to format.
255
- * @param options Optional formatting options including locale and list type.
264
+ * @param fallbackOrOptions A fallback string for empty lists, or an options
265
+ * object including the fallback and formatting
266
+ * options such as locale and list type.
256
267
  * @returns A {@link Message} with alternating value and text terms.
268
+ * @throws {TypeError} If the fallback parameter is missing or invalid.
257
269
  * @since 0.9.0
258
270
  */
259
- function valueSet(values$1, options) {
260
- if (values$1.length === 0) return [];
271
+ function valueSet(values$1, fallbackOrOptions) {
272
+ if (fallbackOrOptions == null || typeof fallbackOrOptions !== "string" && typeof fallbackOrOptions.fallback !== "string") throw new TypeError("valueSet() requires a fallback string or an options object with a fallback field.");
273
+ const options = typeof fallbackOrOptions === "string" ? { fallback: fallbackOrOptions } : fallbackOrOptions;
274
+ if (values$1.length === 0) return options.fallback === "" ? [] : [{
275
+ type: "text",
276
+ text: options.fallback
277
+ }];
261
278
  const formatter = new Intl.ListFormat(options?.locale, {
262
279
  type: options?.type,
263
280
  style: options?.style
@@ -543,7 +543,10 @@ function option(...args) {
543
543
  fragments: [],
544
544
  description: options.description
545
545
  };
546
- const choicesMessage = valueParser?.choices != null && valueParser.choices.length > 0 ? require_message.valueSet(valueParser.choices.map((c) => valueParser.format(c)), { type: "unit" }) : void 0;
546
+ const choicesMessage = valueParser?.choices != null && valueParser.choices.length > 0 ? require_message.valueSet(valueParser.choices.map((c) => valueParser.format(c)), {
547
+ fallback: "",
548
+ type: "unit"
549
+ }) : void 0;
547
550
  const fragments = [{
548
551
  type: "entry",
549
552
  term: {
@@ -915,7 +918,10 @@ function argument(valueParser, options = {}) {
915
918
  fragments: [],
916
919
  description: options.description
917
920
  };
918
- const choicesMessage = valueParser.choices != null && valueParser.choices.length > 0 ? require_message.valueSet(valueParser.choices.map((c) => valueParser.format(c)), { type: "unit" }) : void 0;
921
+ const choicesMessage = valueParser.choices != null && valueParser.choices.length > 0 ? require_message.valueSet(valueParser.choices.map((c) => valueParser.format(c)), {
922
+ fallback: "",
923
+ type: "unit"
924
+ }) : void 0;
919
925
  const fragments = [{
920
926
  type: "entry",
921
927
  term,
@@ -543,7 +543,10 @@ function option(...args) {
543
543
  fragments: [],
544
544
  description: options.description
545
545
  };
546
- const choicesMessage = valueParser?.choices != null && valueParser.choices.length > 0 ? valueSet(valueParser.choices.map((c) => valueParser.format(c)), { type: "unit" }) : void 0;
546
+ const choicesMessage = valueParser?.choices != null && valueParser.choices.length > 0 ? valueSet(valueParser.choices.map((c) => valueParser.format(c)), {
547
+ fallback: "",
548
+ type: "unit"
549
+ }) : void 0;
547
550
  const fragments = [{
548
551
  type: "entry",
549
552
  term: {
@@ -915,7 +918,10 @@ function argument(valueParser, options = {}) {
915
918
  fragments: [],
916
919
  description: options.description
917
920
  };
918
- const choicesMessage = valueParser.choices != null && valueParser.choices.length > 0 ? valueSet(valueParser.choices.map((c) => valueParser.format(c)), { type: "unit" }) : void 0;
921
+ const choicesMessage = valueParser.choices != null && valueParser.choices.length > 0 ? valueSet(valueParser.choices.map((c) => valueParser.format(c)), {
922
+ fallback: "",
923
+ type: "unit"
924
+ }) : void 0;
919
925
  const fragments = [{
920
926
  type: "entry",
921
927
  term,
@@ -271,7 +271,10 @@ function formatNumberChoiceError(input, validChoices, allChoices, invalidChoice)
271
271
  function formatDefaultChoiceError(input, choices) {
272
272
  const choiceStrings = choices.filter((c) => typeof c === "string" || !Number.isNaN(c)).map((c) => Object.is(c, -0) ? "-0" : String(c));
273
273
  if (choiceStrings.length === 0 && choices.length > 0) return require_message.message`No valid choices are configured, but got ${input}.`;
274
- return require_message.message`Expected one of ${require_message.valueSet(choiceStrings, { locale: "en-US" })}, but got ${input}.`;
274
+ return require_message.message`Expected one of ${require_message.valueSet(choiceStrings, {
275
+ fallback: "",
276
+ locale: "en-US"
277
+ })}, but got ${input}.`;
275
278
  }
276
279
  /**
277
280
  * Creates a {@link ValueParser} for strings.
@@ -576,7 +579,10 @@ function url(options = {}) {
576
579
  type: "text",
577
580
  text: " is not allowed. Allowed protocols: "
578
581
  },
579
- ...require_message.valueSet(originalProtocols, { locale: "en-US" }),
582
+ ...require_message.valueSet(originalProtocols, {
583
+ fallback: "",
584
+ locale: "en-US"
585
+ }),
580
586
  {
581
587
  type: "text",
582
588
  text: "."
@@ -1534,7 +1540,10 @@ function email(options) {
1534
1540
  type: "text",
1535
1541
  text: " is not allowed. Allowed domains: "
1536
1542
  },
1537
- ...require_message.valueSet(allowedDomains, { locale: "en-US" }),
1543
+ ...require_message.valueSet(allowedDomains, {
1544
+ fallback: "",
1545
+ locale: "en-US"
1546
+ }),
1538
1547
  {
1539
1548
  type: "text",
1540
1549
  text: "."
@@ -1585,7 +1594,10 @@ function email(options) {
1585
1594
  type: "text",
1586
1595
  text: " is not allowed. Allowed domains: "
1587
1596
  },
1588
- ...require_message.valueSet(allowedDomains, { locale: "en-US" }),
1597
+ ...require_message.valueSet(allowedDomains, {
1598
+ fallback: "",
1599
+ locale: "en-US"
1600
+ }),
1589
1601
  {
1590
1602
  type: "text",
1591
1603
  text: "."
@@ -1660,6 +1672,13 @@ function socketAddress(options) {
1660
1672
  ...options?.host?.ip,
1661
1673
  metavar: "HOST"
1662
1674
  });
1675
+ const disambiguationParser = hostType === "ip" ? hostname({
1676
+ metavar: "HOST",
1677
+ allowWildcard: true,
1678
+ allowUnderscore: true,
1679
+ maxLength: Math.max(253, options?.host?.hostname?.maxLength ?? 0)
1680
+ }) : hostnameParser;
1681
+ const separatorIsHostChar = /^[a-zA-Z0-9._-]+$/.test(separator);
1663
1682
  const portParser = port({
1664
1683
  ...options?.port,
1665
1684
  metavar: "PORT",
@@ -1727,7 +1746,7 @@ function socketAddress(options) {
1727
1746
  const trimmed = input.trim();
1728
1747
  const canOmitPort = defaultPort !== void 0 && !requirePort;
1729
1748
  let firstHostError;
1730
- let validHostNumericPortInvalid = false;
1749
+ let validHostInvalidPortError;
1731
1750
  let trailingSepHost;
1732
1751
  let trailingSepHostError;
1733
1752
  let anySeparatorFound = false;
@@ -1762,15 +1781,22 @@ function socketAddress(options) {
1762
1781
  hostPart,
1763
1782
  error: hostResult.error
1764
1783
  };
1765
- } else if (!validHostNumericPortInvalid && hostPart !== "" && /^[0-9]+$/.test(portPart)) if (looksLikeIpv4(hostPart) || looksLikeAltIpv4Literal(hostPart)) {
1784
+ } else if (validHostInvalidPortError === void 0 && hostPart !== "" && /^[0-9]+$/.test(portPart)) if (looksLikeIpv4(hostPart) || looksLikeAltIpv4Literal(hostPart)) {
1766
1785
  const hostResult = parseHost(hostPart);
1767
1786
  if (!hostResult.success) {
1768
1787
  if (firstHostError === void 0 || !looksLikeIpv4(firstHostError.hostPart) && !looksLikeAltIpv4Literal(firstHostError.hostPart)) firstHostError = {
1769
1788
  hostPart,
1770
1789
  error: hostResult.error
1771
1790
  };
1772
- } else validHostNumericPortInvalid = true;
1773
- } else validHostNumericPortInvalid = true;
1791
+ } else validHostInvalidPortError = portResult.error;
1792
+ } else {
1793
+ validHostInvalidPortError = portResult.error;
1794
+ const hostResult = parseHost(hostPart);
1795
+ if (!hostResult.success && firstHostError === void 0) firstHostError = {
1796
+ hostPart,
1797
+ error: hostResult.error
1798
+ };
1799
+ }
1774
1800
  else if ((firstHostError === void 0 || !looksLikeIpv4(firstHostError.hostPart) && !looksLikeAltIpv4Literal(firstHostError.hostPart)) && (looksLikeIpv4(hostPart) || looksLikeAltIpv4Literal(hostPart))) {
1775
1801
  const hostResult = parseHost(hostPart);
1776
1802
  if (!hostResult.success) firstHostError = {
@@ -1781,7 +1807,7 @@ function socketAddress(options) {
1781
1807
  }
1782
1808
  searchFrom = separatorIndex;
1783
1809
  }
1784
- if (validHostNumericPortInvalid) {
1810
+ if (validHostInvalidPortError !== void 0) {
1785
1811
  const errorMsg$1 = options?.errors?.invalidFormat;
1786
1812
  if (errorMsg$1) {
1787
1813
  const msg = typeof errorMsg$1 === "function" ? errorMsg$1(input) : errorMsg$1;
@@ -1790,10 +1816,25 @@ function socketAddress(options) {
1790
1816
  error: msg
1791
1817
  };
1792
1818
  }
1793
- return {
1819
+ if (firstHostError !== void 0 && firstHostError.hostPart !== "") {
1820
+ const portSplitHostIsDegenerate = separatorIsHostChar ? firstHostError.hostPart.replaceAll(separator, "") === "" : firstHostError.hostPart.includes(separator);
1821
+ if (portSplitHostIsDegenerate) return {
1822
+ success: false,
1823
+ error: require_message.message`Expected a socket address in format host${separator}port, but got ${input}.`
1824
+ };
1825
+ if (!disambiguationParser.parse(trimmed).success) return {
1826
+ success: false,
1827
+ error: firstHostError.error
1828
+ };
1829
+ }
1830
+ if (disambiguationParser.parse(trimmed).success) return {
1794
1831
  success: false,
1795
1832
  error: require_message.message`Expected a socket address in format host${separator}port, but got ${input}.`
1796
1833
  };
1834
+ return {
1835
+ success: false,
1836
+ error: validHostInvalidPortError
1837
+ };
1797
1838
  }
1798
1839
  if (firstHostError !== void 0) {
1799
1840
  if (looksLikeIpv4(firstHostError.hostPart) || looksLikeAltIpv4Literal(firstHostError.hostPart)) {
@@ -1846,14 +1887,11 @@ function socketAddress(options) {
1846
1887
  error: msg
1847
1888
  };
1848
1889
  }
1849
- if (looksLikeIpv4(trailingSepHostError.hostPart) || looksLikeAltIpv4Literal(trailingSepHostError.hostPart)) return {
1890
+ const trailingHostIsDegenerate = separatorIsHostChar ? trailingSepHostError.hostPart.replaceAll(separator, "") === "" : trailingSepHostError.hostPart.includes(separator);
1891
+ if (trailingSepHostError.hostPart !== "" && !trailingHostIsDegenerate && !disambiguationParser.parse(trimmed).success) return {
1850
1892
  success: false,
1851
1893
  error: trailingSepHostError.error
1852
1894
  };
1853
- return {
1854
- success: false,
1855
- error: require_message.message`Expected a socket address in format host${separator}port, but got ${input}.`
1856
- };
1857
1895
  }
1858
1896
  if (!canOmitPort && !requirePort && hostOnlyResult !== void 0 && hostOnlyResult.success) {
1859
1897
  const errorMsg$1 = options?.errors?.missingPort;
@@ -1883,6 +1921,11 @@ function socketAddress(options) {
1883
1921
  error: msg
1884
1922
  };
1885
1923
  }
1924
+ const hostPartIsDegenerate = separatorIsHostChar ? firstHostError.hostPart.replaceAll(separator, "") === "" : firstHostError.hostPart.includes(separator);
1925
+ if (firstHostError.hostPart !== "" && !hostPartIsDegenerate && !disambiguationParser.parse(trimmed).success) return {
1926
+ success: false,
1927
+ error: firstHostError.error
1928
+ };
1886
1929
  }
1887
1930
  const errorMsg = options?.errors?.invalidFormat;
1888
1931
  if (errorMsg) {
@@ -2519,7 +2562,10 @@ function domain(options) {
2519
2562
  type: "text",
2520
2563
  text: " is not allowed. Allowed TLDs: "
2521
2564
  },
2522
- ...require_message.valueSet(allowedTlds, { locale: "en-US" }),
2565
+ ...require_message.valueSet(allowedTlds, {
2566
+ fallback: "",
2567
+ locale: "en-US"
2568
+ }),
2523
2569
  {
2524
2570
  type: "text",
2525
2571
  text: "."
@@ -271,7 +271,10 @@ function formatNumberChoiceError(input, validChoices, allChoices, invalidChoice)
271
271
  function formatDefaultChoiceError(input, choices) {
272
272
  const choiceStrings = choices.filter((c) => typeof c === "string" || !Number.isNaN(c)).map((c) => Object.is(c, -0) ? "-0" : String(c));
273
273
  if (choiceStrings.length === 0 && choices.length > 0) return message`No valid choices are configured, but got ${input}.`;
274
- return message`Expected one of ${valueSet(choiceStrings, { locale: "en-US" })}, but got ${input}.`;
274
+ return message`Expected one of ${valueSet(choiceStrings, {
275
+ fallback: "",
276
+ locale: "en-US"
277
+ })}, but got ${input}.`;
275
278
  }
276
279
  /**
277
280
  * Creates a {@link ValueParser} for strings.
@@ -576,7 +579,10 @@ function url(options = {}) {
576
579
  type: "text",
577
580
  text: " is not allowed. Allowed protocols: "
578
581
  },
579
- ...valueSet(originalProtocols, { locale: "en-US" }),
582
+ ...valueSet(originalProtocols, {
583
+ fallback: "",
584
+ locale: "en-US"
585
+ }),
580
586
  {
581
587
  type: "text",
582
588
  text: "."
@@ -1534,7 +1540,10 @@ function email(options) {
1534
1540
  type: "text",
1535
1541
  text: " is not allowed. Allowed domains: "
1536
1542
  },
1537
- ...valueSet(allowedDomains, { locale: "en-US" }),
1543
+ ...valueSet(allowedDomains, {
1544
+ fallback: "",
1545
+ locale: "en-US"
1546
+ }),
1538
1547
  {
1539
1548
  type: "text",
1540
1549
  text: "."
@@ -1585,7 +1594,10 @@ function email(options) {
1585
1594
  type: "text",
1586
1595
  text: " is not allowed. Allowed domains: "
1587
1596
  },
1588
- ...valueSet(allowedDomains, { locale: "en-US" }),
1597
+ ...valueSet(allowedDomains, {
1598
+ fallback: "",
1599
+ locale: "en-US"
1600
+ }),
1589
1601
  {
1590
1602
  type: "text",
1591
1603
  text: "."
@@ -1660,6 +1672,13 @@ function socketAddress(options) {
1660
1672
  ...options?.host?.ip,
1661
1673
  metavar: "HOST"
1662
1674
  });
1675
+ const disambiguationParser = hostType === "ip" ? hostname({
1676
+ metavar: "HOST",
1677
+ allowWildcard: true,
1678
+ allowUnderscore: true,
1679
+ maxLength: Math.max(253, options?.host?.hostname?.maxLength ?? 0)
1680
+ }) : hostnameParser;
1681
+ const separatorIsHostChar = /^[a-zA-Z0-9._-]+$/.test(separator);
1663
1682
  const portParser = port({
1664
1683
  ...options?.port,
1665
1684
  metavar: "PORT",
@@ -1727,7 +1746,7 @@ function socketAddress(options) {
1727
1746
  const trimmed = input.trim();
1728
1747
  const canOmitPort = defaultPort !== void 0 && !requirePort;
1729
1748
  let firstHostError;
1730
- let validHostNumericPortInvalid = false;
1749
+ let validHostInvalidPortError;
1731
1750
  let trailingSepHost;
1732
1751
  let trailingSepHostError;
1733
1752
  let anySeparatorFound = false;
@@ -1762,15 +1781,22 @@ function socketAddress(options) {
1762
1781
  hostPart,
1763
1782
  error: hostResult.error
1764
1783
  };
1765
- } else if (!validHostNumericPortInvalid && hostPart !== "" && /^[0-9]+$/.test(portPart)) if (looksLikeIpv4(hostPart) || looksLikeAltIpv4Literal(hostPart)) {
1784
+ } else if (validHostInvalidPortError === void 0 && hostPart !== "" && /^[0-9]+$/.test(portPart)) if (looksLikeIpv4(hostPart) || looksLikeAltIpv4Literal(hostPart)) {
1766
1785
  const hostResult = parseHost(hostPart);
1767
1786
  if (!hostResult.success) {
1768
1787
  if (firstHostError === void 0 || !looksLikeIpv4(firstHostError.hostPart) && !looksLikeAltIpv4Literal(firstHostError.hostPart)) firstHostError = {
1769
1788
  hostPart,
1770
1789
  error: hostResult.error
1771
1790
  };
1772
- } else validHostNumericPortInvalid = true;
1773
- } else validHostNumericPortInvalid = true;
1791
+ } else validHostInvalidPortError = portResult.error;
1792
+ } else {
1793
+ validHostInvalidPortError = portResult.error;
1794
+ const hostResult = parseHost(hostPart);
1795
+ if (!hostResult.success && firstHostError === void 0) firstHostError = {
1796
+ hostPart,
1797
+ error: hostResult.error
1798
+ };
1799
+ }
1774
1800
  else if ((firstHostError === void 0 || !looksLikeIpv4(firstHostError.hostPart) && !looksLikeAltIpv4Literal(firstHostError.hostPart)) && (looksLikeIpv4(hostPart) || looksLikeAltIpv4Literal(hostPart))) {
1775
1801
  const hostResult = parseHost(hostPart);
1776
1802
  if (!hostResult.success) firstHostError = {
@@ -1781,7 +1807,7 @@ function socketAddress(options) {
1781
1807
  }
1782
1808
  searchFrom = separatorIndex;
1783
1809
  }
1784
- if (validHostNumericPortInvalid) {
1810
+ if (validHostInvalidPortError !== void 0) {
1785
1811
  const errorMsg$1 = options?.errors?.invalidFormat;
1786
1812
  if (errorMsg$1) {
1787
1813
  const msg = typeof errorMsg$1 === "function" ? errorMsg$1(input) : errorMsg$1;
@@ -1790,10 +1816,25 @@ function socketAddress(options) {
1790
1816
  error: msg
1791
1817
  };
1792
1818
  }
1793
- return {
1819
+ if (firstHostError !== void 0 && firstHostError.hostPart !== "") {
1820
+ const portSplitHostIsDegenerate = separatorIsHostChar ? firstHostError.hostPart.replaceAll(separator, "") === "" : firstHostError.hostPart.includes(separator);
1821
+ if (portSplitHostIsDegenerate) return {
1822
+ success: false,
1823
+ error: message`Expected a socket address in format host${separator}port, but got ${input}.`
1824
+ };
1825
+ if (!disambiguationParser.parse(trimmed).success) return {
1826
+ success: false,
1827
+ error: firstHostError.error
1828
+ };
1829
+ }
1830
+ if (disambiguationParser.parse(trimmed).success) return {
1794
1831
  success: false,
1795
1832
  error: message`Expected a socket address in format host${separator}port, but got ${input}.`
1796
1833
  };
1834
+ return {
1835
+ success: false,
1836
+ error: validHostInvalidPortError
1837
+ };
1797
1838
  }
1798
1839
  if (firstHostError !== void 0) {
1799
1840
  if (looksLikeIpv4(firstHostError.hostPart) || looksLikeAltIpv4Literal(firstHostError.hostPart)) {
@@ -1846,14 +1887,11 @@ function socketAddress(options) {
1846
1887
  error: msg
1847
1888
  };
1848
1889
  }
1849
- if (looksLikeIpv4(trailingSepHostError.hostPart) || looksLikeAltIpv4Literal(trailingSepHostError.hostPart)) return {
1890
+ const trailingHostIsDegenerate = separatorIsHostChar ? trailingSepHostError.hostPart.replaceAll(separator, "") === "" : trailingSepHostError.hostPart.includes(separator);
1891
+ if (trailingSepHostError.hostPart !== "" && !trailingHostIsDegenerate && !disambiguationParser.parse(trimmed).success) return {
1850
1892
  success: false,
1851
1893
  error: trailingSepHostError.error
1852
1894
  };
1853
- return {
1854
- success: false,
1855
- error: message`Expected a socket address in format host${separator}port, but got ${input}.`
1856
- };
1857
1895
  }
1858
1896
  if (!canOmitPort && !requirePort && hostOnlyResult !== void 0 && hostOnlyResult.success) {
1859
1897
  const errorMsg$1 = options?.errors?.missingPort;
@@ -1883,6 +1921,11 @@ function socketAddress(options) {
1883
1921
  error: msg
1884
1922
  };
1885
1923
  }
1924
+ const hostPartIsDegenerate = separatorIsHostChar ? firstHostError.hostPart.replaceAll(separator, "") === "" : firstHostError.hostPart.includes(separator);
1925
+ if (firstHostError.hostPart !== "" && !hostPartIsDegenerate && !disambiguationParser.parse(trimmed).success) return {
1926
+ success: false,
1927
+ error: firstHostError.error
1928
+ };
1886
1929
  }
1887
1930
  const errorMsg = options?.errors?.invalidFormat;
1888
1931
  if (errorMsg) {
@@ -2519,7 +2562,10 @@ function domain(options) {
2519
2562
  type: "text",
2520
2563
  text: " is not allowed. Allowed TLDs: "
2521
2564
  },
2522
- ...valueSet(allowedTlds, { locale: "en-US" }),
2565
+ ...valueSet(allowedTlds, {
2566
+ fallback: "",
2567
+ locale: "en-US"
2568
+ }),
2523
2569
  {
2524
2570
  type: "text",
2525
2571
  text: "."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.1572+6945a5ef",
3
+ "version": "1.0.0-dev.1580+2d932679",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",