@optique/core 1.0.0-dev.1574 → 1.0.0-dev.1583

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/parser.d.ts CHANGED
@@ -164,13 +164,17 @@ interface Parser<M extends Mode = "sync", TValue = unknown, TState = unknown> {
164
164
  * message.
165
165
  * @param state The current state of the parser, which may contain accumulated
166
166
  * data or context needed to produce the final value.
167
+ * @param exec Optional shared execution context. When provided, gives the
168
+ * parser access to cross-cutting runtime data such as the current
169
+ * execution phase and dependency registry.
167
170
  * @returns A result object indicating success or failure of
168
171
  * the transformation. If successful, it should contain
169
172
  * the parsed value of type {@link TValue}. If not applicable,
170
173
  * it should return an error message.
171
174
  * In async mode, returns a Promise that resolves to the result.
175
+ * @since 1.0.0 Added optional `exec` parameter.
172
176
  */
173
- complete(state: TState): ModeValue<M, ValueParserResult<TValue>>;
177
+ complete(state: TState, exec?: ExecutionContext): ModeValue<M, ValueParserResult<TValue>>;
174
178
  /**
175
179
  * Generates next-step suggestions based on the current context
176
180
  * and an optional prefix. This can be used to provide shell completion
@@ -221,10 +225,12 @@ interface Parser<M extends Mode = "sync", TValue = unknown, TState = unknown> {
221
225
  * source) has resolved.
222
226
  *
223
227
  * @param state The current parser state.
228
+ * @param exec Optional shared execution context.
224
229
  * @returns `true` if completion should be deferred.
225
230
  * @since 1.0.0
231
+ * @since 1.0.0 Added optional `exec` parameter.
226
232
  */
227
- shouldDeferCompletion?(state: TState): boolean;
233
+ shouldDeferCompletion?(state: TState, exec?: ExecutionContext): boolean;
228
234
  /**
229
235
  * Normalizes a parsed value according to the underlying value parser's
230
236
  * configuration. When present, {@link withDefault} calls this method
@@ -246,11 +252,82 @@ interface Parser<M extends Mode = "sync", TValue = unknown, TState = unknown> {
246
252
  */
247
253
  normalizeValue?(value: TValue): TValue;
248
254
  }
255
+ /**
256
+ * Parser-local frame data containing the input buffer and parser state.
257
+ * This represents the per-parser progress during parsing, separated from
258
+ * cross-cutting execution context.
259
+ * @template TState The type of the state used during parsing.
260
+ * @since 1.0.0
261
+ */
262
+ interface ParseFrame<TState> {
263
+ /**
264
+ * The array of input strings that the parser is currently processing.
265
+ */
266
+ readonly buffer: readonly string[];
267
+ /**
268
+ * The current state of the parser, which is used to track
269
+ * the progress of parsing and any accumulated data.
270
+ */
271
+ readonly state: TState;
272
+ /**
273
+ * A flag indicating whether no more options should be parsed and instead
274
+ * the remaining input should be treated as positional arguments.
275
+ */
276
+ readonly optionsTerminated: boolean;
277
+ }
278
+ /**
279
+ * The phase of the execution pipeline.
280
+ * @since 1.0.0
281
+ */
282
+ type ExecutionPhase = "parse" | "precomplete" | "resolve" | "complete" | "suggest";
283
+ /**
284
+ * Shared execution context carrying cross-cutting runtime data.
285
+ * This includes information that is shared across all parsers in a parse
286
+ * tree, such as usage information, dependency registries, and the current
287
+ * execution phase.
288
+ * @since 1.0.0
289
+ */
290
+ interface ExecutionContext {
291
+ /**
292
+ * Usage information for the entire parser tree.
293
+ */
294
+ readonly usage: Usage;
295
+ /**
296
+ * The current phase of the execution pipeline.
297
+ */
298
+ readonly phase: ExecutionPhase;
299
+ /**
300
+ * The path from the root to the current parser in the parse tree.
301
+ * Used by constructs to track the current position during dependency
302
+ * resolution and completion.
303
+ */
304
+ readonly path: readonly PropertyKey[];
305
+ /**
306
+ * A registry containing resolved dependency values from DependencySource
307
+ * parsers.
308
+ * @since 0.10.0
309
+ */
310
+ readonly dependencyRegistry?: DependencyRegistryLike;
311
+ }
249
312
  /**
250
313
  * The context of the parser, which includes the input buffer and the state.
314
+ *
315
+ * `ParserContext` provides structured access to shared execution context
316
+ * via {@link exec}, and flat access to all fields for backward
317
+ * compatibility.
318
+ *
251
319
  * @template TState The type of the state used during parsing.
252
320
  */
253
321
  interface ParserContext<TState> {
322
+ /**
323
+ * Shared execution context (usage, phase, path, dependencyRegistry).
324
+ *
325
+ * Present when the context was created via {@link createParserContext}.
326
+ * Later runtime work will make this field required.
327
+ *
328
+ * @since 1.0.0
329
+ */
330
+ readonly exec?: ExecutionContext;
254
331
  /**
255
332
  * The array of input strings that the parser is currently processing.
256
333
  */
@@ -284,6 +361,19 @@ interface ParserContext<TState> {
284
361
  */
285
362
  readonly dependencyRegistry?: DependencyRegistryLike;
286
363
  }
364
+ /**
365
+ * Creates a {@link ParserContext} from a {@link ParseFrame} and an
366
+ * {@link ExecutionContext}. The returned object provides both structured
367
+ * access (`frame`, `exec`) and flat access (`buffer`, `state`, etc.)
368
+ * for backward compatibility.
369
+ *
370
+ * @template TState The type of the state used during parsing.
371
+ * @param frame Parser-local frame data.
372
+ * @param exec Shared execution context.
373
+ * @returns A {@link ParserContext} instance.
374
+ * @since 1.0.0
375
+ */
376
+ declare function createParserContext<TState>(frame: ParseFrame<TState>, exec: ExecutionContext): ParserContext<TState>;
287
377
  /**
288
378
  * Represents a suggestion for command-line completion or guidance.
289
379
  * @since 0.6.0
@@ -648,4 +738,4 @@ declare function getDocPage(parser: Parser<"sync", unknown, unknown>, argsOrOpti
648
738
  declare function getDocPage(parser: Parser<"async", unknown, unknown>, argsOrOptions?: readonly string[] | ParseOptions, options?: ParseOptions): Promise<DocPage | undefined>;
649
739
  declare function getDocPage<M extends Mode>(parser: Parser<M, unknown, unknown>, argsOrOptions?: readonly string[] | ParseOptions, options?: ParseOptions): ModeValue<M, DocPage | undefined>;
650
740
  //#endregion
651
- export { ArgumentErrorOptions, ArgumentOptions, CombineModes, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocState, DuplicateOptionError, FlagErrorOptions, FlagOptions, GroupOptions, InferMode, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, Mode, ModeIterable, ModeValue, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OptionState, OrErrorOptions, OrOptions, type ParseOptions, Parser, ParserContext, ParserResult, PassThroughFormat, PassThroughOptions, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, conditional, constant, fail, flag, getDocPage, getDocPageAsync, getDocPageSync, group, longestMatch, map, merge, multiple, nonEmpty, object, option, optional, or, parse, parseAsync, parseSync, passThrough, suggest, suggestAsync, suggestSync, tuple, withDefault };
741
+ export { ArgumentErrorOptions, ArgumentOptions, CombineModes, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocState, DuplicateOptionError, ExecutionContext, ExecutionPhase, FlagErrorOptions, FlagOptions, GroupOptions, InferMode, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, Mode, ModeIterable, ModeValue, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OptionState, OrErrorOptions, OrOptions, ParseFrame, type ParseOptions, Parser, ParserContext, ParserResult, PassThroughFormat, PassThroughOptions, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, conditional, constant, createParserContext, fail, flag, getDocPage, getDocPageAsync, getDocPageSync, group, longestMatch, map, merge, multiple, nonEmpty, object, option, optional, or, parse, parseAsync, parseSync, passThrough, suggest, suggestAsync, suggestSync, tuple, withDefault };
package/dist/parser.js CHANGED
@@ -8,6 +8,28 @@ import { WithDefaultError, map, multiple, nonEmpty, optional, withDefault } from
8
8
  import { argument, command, constant, fail, flag, option, passThrough } from "./primitives.js";
9
9
 
10
10
  //#region src/parser.ts
11
+ /**
12
+ * Creates a {@link ParserContext} from a {@link ParseFrame} and an
13
+ * {@link ExecutionContext}. The returned object provides both structured
14
+ * access (`frame`, `exec`) and flat access (`buffer`, `state`, etc.)
15
+ * for backward compatibility.
16
+ *
17
+ * @template TState The type of the state used during parsing.
18
+ * @param frame Parser-local frame data.
19
+ * @param exec Shared execution context.
20
+ * @returns A {@link ParserContext} instance.
21
+ * @since 1.0.0
22
+ */
23
+ function createParserContext(frame, exec) {
24
+ return {
25
+ exec,
26
+ buffer: frame.buffer,
27
+ state: frame.state,
28
+ optionsTerminated: frame.optionsTerminated,
29
+ usage: exec.usage,
30
+ dependencyRegistry: exec.dependencyRegistry
31
+ };
32
+ }
11
33
  function injectAnnotationsIntoState(state, options) {
12
34
  const annotations = options?.annotations;
13
35
  if (annotations == null) return state;
@@ -38,12 +60,16 @@ function injectAnnotationsIntoState(state, options) {
38
60
  function parseSync(parser, args, options) {
39
61
  const initialState = injectAnnotationsIntoState(parser.initialState, options);
40
62
  const shouldUnwrapAnnotatedValue = options?.annotations != null || isInjectedAnnotationWrapper(parser.initialState);
41
- let context = {
63
+ const exec = {
64
+ usage: parser.usage,
65
+ phase: "parse",
66
+ path: []
67
+ };
68
+ let context = createParserContext({
42
69
  buffer: args,
43
- optionsTerminated: false,
44
70
  state: initialState,
45
- usage: parser.usage
46
- };
71
+ optionsTerminated: false
72
+ }, exec);
47
73
  do {
48
74
  const result = parser.parse(context);
49
75
  if (!result.success) return {
@@ -57,7 +83,11 @@ function parseSync(parser, args, options) {
57
83
  error: message`Unexpected option or argument: ${context.buffer[0]}.`
58
84
  };
59
85
  } while (context.buffer.length > 0);
60
- const endResult = parser.complete(context.state);
86
+ const completeExec = {
87
+ ...exec,
88
+ phase: "complete"
89
+ };
90
+ const endResult = parser.complete(context.state, completeExec);
61
91
  return endResult.success ? {
62
92
  success: true,
63
93
  value: shouldUnwrapAnnotatedValue ? unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value,
@@ -97,12 +127,16 @@ function isBufferUnchanged(previous, current) {
97
127
  async function parseAsync(parser, args, options) {
98
128
  const initialState = injectAnnotationsIntoState(parser.initialState, options);
99
129
  const shouldUnwrapAnnotatedValue = options?.annotations != null || isInjectedAnnotationWrapper(parser.initialState);
100
- let context = {
130
+ const exec = {
131
+ usage: parser.usage,
132
+ phase: "parse",
133
+ path: []
134
+ };
135
+ let context = createParserContext({
101
136
  buffer: args,
102
- optionsTerminated: false,
103
137
  state: initialState,
104
- usage: parser.usage
105
- };
138
+ optionsTerminated: false
139
+ }, exec);
106
140
  do {
107
141
  const result = await parser.parse(context);
108
142
  if (!result.success) return {
@@ -116,7 +150,11 @@ async function parseAsync(parser, args, options) {
116
150
  error: message`Unexpected option or argument: ${context.buffer[0]}.`
117
151
  };
118
152
  } while (context.buffer.length > 0);
119
- const endResult = await parser.complete(context.state);
153
+ const completeExec = {
154
+ ...exec,
155
+ phase: "complete"
156
+ };
157
+ const endResult = await parser.complete(context.state, completeExec);
120
158
  return endResult.success ? {
121
159
  success: true,
122
160
  value: shouldUnwrapAnnotatedValue ? unwrapInjectedAnnotationWrapper(endResult.value) : endResult.value,
@@ -192,12 +230,15 @@ function suggestSync(parser, args, options) {
192
230
  const allButLast = args.slice(0, -1);
193
231
  const prefix = args[args.length - 1];
194
232
  const initialState = injectAnnotationsIntoState(parser.initialState, options);
195
- let context = {
233
+ let context = createParserContext({
196
234
  buffer: allButLast,
197
- optionsTerminated: false,
198
235
  state: initialState,
199
- usage: parser.usage
200
- };
236
+ optionsTerminated: false
237
+ }, {
238
+ usage: parser.usage,
239
+ phase: "suggest",
240
+ path: []
241
+ });
201
242
  while (context.buffer.length > 0) {
202
243
  const result = parser.parse(context);
203
244
  if (!result.success) return Array.from(parser.suggest(context, prefix));
@@ -231,12 +272,15 @@ async function suggestAsync(parser, args, options) {
231
272
  const allButLast = args.slice(0, -1);
232
273
  const prefix = args[args.length - 1];
233
274
  const initialState = injectAnnotationsIntoState(parser.initialState, options);
234
- let context = {
275
+ let context = createParserContext({
235
276
  buffer: allButLast,
236
- optionsTerminated: false,
237
277
  state: initialState,
238
- usage: parser.usage
239
- };
278
+ optionsTerminated: false
279
+ }, {
280
+ usage: parser.usage,
281
+ phase: "suggest",
282
+ path: []
283
+ });
240
284
  while (context.buffer.length > 0) {
241
285
  const result = await parser.parse(context);
242
286
  if (!result.success) {
@@ -486,4 +530,4 @@ function buildDocPage(parser, context, args) {
486
530
  }
487
531
 
488
532
  //#endregion
489
- export { DuplicateOptionError, WithDefaultError, argument, command, concat, conditional, constant, fail, flag, getDocPage, getDocPageAsync, getDocPageSync, group, longestMatch, map, merge, multiple, nonEmpty, object, option, optional, or, parse, parseAsync, parseSync, passThrough, suggest, suggestAsync, suggestSync, tuple, withDefault };
533
+ export { DuplicateOptionError, WithDefaultError, argument, command, concat, conditional, constant, createParserContext, fail, flag, getDocPage, getDocPageAsync, getDocPageSync, group, longestMatch, map, merge, multiple, nonEmpty, object, option, optional, or, parse, parseAsync, parseSync, passThrough, suggest, suggestAsync, suggestSync, tuple, withDefault };
@@ -53,7 +53,7 @@ function constant(value) {
53
53
  consumed: []
54
54
  };
55
55
  },
56
- complete(state) {
56
+ complete(state, _exec) {
57
57
  return {
58
58
  success: true,
59
59
  value: state
@@ -110,7 +110,7 @@ function fail() {
110
110
  error: require_message.message`No value provided.`
111
111
  };
112
112
  },
113
- complete(_state) {
113
+ complete(_state, _exec) {
114
114
  return {
115
115
  success: false,
116
116
  error: require_message.message`No value provided.`
@@ -500,7 +500,7 @@ function option(...args) {
500
500
  error: require_suggestion.createErrorWithSuggestions(baseError, invalidOption, context.usage, "option")
501
501
  };
502
502
  },
503
- complete(state) {
503
+ complete(state, _exec) {
504
504
  if (state == null) return valueParser == null ? {
505
505
  success: true,
506
506
  value: false
@@ -742,7 +742,7 @@ function flag(...args) {
742
742
  error: require_suggestion.createErrorWithSuggestions(baseError, invalidOption, context.usage, "option")
743
743
  };
744
744
  },
745
- complete(state) {
745
+ complete(state, _exec) {
746
746
  if (state == null) return {
747
747
  success: false,
748
748
  error: options.errors?.missing ? typeof options.errors.missing === "function" ? options.errors.missing(optionNames$1) : options.errors.missing : require_message.message`Required flag ${require_message.optionNames(optionNames$1)} is missing.`
@@ -881,7 +881,7 @@ function argument(valueParser, options = {}) {
881
881
  consumed: context.buffer.slice(0, i + 1)
882
882
  };
883
883
  },
884
- complete(state) {
884
+ complete(state, _exec) {
885
885
  if (state == null) return {
886
886
  success: false,
887
887
  error: options.errors?.endOfInput ?? require_message.message`Expected a ${require_message.metavar(valueParser.metavar)}, but too few arguments.`
@@ -1104,7 +1104,7 @@ function command(name, parser, options = {}) {
1104
1104
  error: options.errors?.invalidState ?? require_message.message`Invalid command state.`
1105
1105
  };
1106
1106
  },
1107
- complete(state) {
1107
+ complete(state, exec) {
1108
1108
  if (typeof state === "undefined") return {
1109
1109
  success: false,
1110
1110
  error: options.errors?.notFound ?? require_message.message`Command ${require_message.optionName(name)} was not matched.`
@@ -1117,13 +1117,13 @@ function command(name, parser, options = {}) {
1117
1117
  state: parser.initialState
1118
1118
  });
1119
1119
  if (isAsync) return parseResultOrPromise.then((parseResult$1) => {
1120
- if (parseResult$1.success) return parser.complete(parseResult$1.next.state);
1121
- return parser.complete(parser.initialState);
1120
+ if (parseResult$1.success) return parser.complete(parseResult$1.next.state, exec);
1121
+ return parser.complete(parser.initialState, exec);
1122
1122
  });
1123
1123
  const parseResult = parseResultOrPromise;
1124
- if (parseResult.success) return parser.complete(parseResult.next.state);
1125
- return parser.complete(parser.initialState);
1126
- } else if (state[0] === "parsing") return parser.complete(state[1]);
1124
+ if (parseResult.success) return parser.complete(parseResult.next.state, exec);
1125
+ return parser.complete(parser.initialState, exec);
1126
+ } else if (state[0] === "parsing") return parser.complete(state[1], exec);
1127
1127
  return {
1128
1128
  success: false,
1129
1129
  error: options.errors?.invalidState ?? require_message.message`Invalid command state during completion.`
@@ -1324,7 +1324,7 @@ function passThrough(options = {}) {
1324
1324
  error: require_message.message`Unknown passThrough format: ${format}.`
1325
1325
  };
1326
1326
  },
1327
- complete(state) {
1327
+ complete(state, _exec) {
1328
1328
  if (require_annotations.getAnnotations(state) == null) return {
1329
1329
  success: true,
1330
1330
  value: state
@@ -53,7 +53,7 @@ function constant(value) {
53
53
  consumed: []
54
54
  };
55
55
  },
56
- complete(state) {
56
+ complete(state, _exec) {
57
57
  return {
58
58
  success: true,
59
59
  value: state
@@ -110,7 +110,7 @@ function fail() {
110
110
  error: message`No value provided.`
111
111
  };
112
112
  },
113
- complete(_state) {
113
+ complete(_state, _exec) {
114
114
  return {
115
115
  success: false,
116
116
  error: message`No value provided.`
@@ -500,7 +500,7 @@ function option(...args) {
500
500
  error: createErrorWithSuggestions(baseError, invalidOption, context.usage, "option")
501
501
  };
502
502
  },
503
- complete(state) {
503
+ complete(state, _exec) {
504
504
  if (state == null) return valueParser == null ? {
505
505
  success: true,
506
506
  value: false
@@ -742,7 +742,7 @@ function flag(...args) {
742
742
  error: createErrorWithSuggestions(baseError, invalidOption, context.usage, "option")
743
743
  };
744
744
  },
745
- complete(state) {
745
+ complete(state, _exec) {
746
746
  if (state == null) return {
747
747
  success: false,
748
748
  error: options.errors?.missing ? typeof options.errors.missing === "function" ? options.errors.missing(optionNames$1) : options.errors.missing : message`Required flag ${optionNames(optionNames$1)} is missing.`
@@ -881,7 +881,7 @@ function argument(valueParser, options = {}) {
881
881
  consumed: context.buffer.slice(0, i + 1)
882
882
  };
883
883
  },
884
- complete(state) {
884
+ complete(state, _exec) {
885
885
  if (state == null) return {
886
886
  success: false,
887
887
  error: options.errors?.endOfInput ?? message`Expected a ${metavar(valueParser.metavar)}, but too few arguments.`
@@ -1104,7 +1104,7 @@ function command(name, parser, options = {}) {
1104
1104
  error: options.errors?.invalidState ?? message`Invalid command state.`
1105
1105
  };
1106
1106
  },
1107
- complete(state) {
1107
+ complete(state, exec) {
1108
1108
  if (typeof state === "undefined") return {
1109
1109
  success: false,
1110
1110
  error: options.errors?.notFound ?? message`Command ${optionName(name)} was not matched.`
@@ -1117,13 +1117,13 @@ function command(name, parser, options = {}) {
1117
1117
  state: parser.initialState
1118
1118
  });
1119
1119
  if (isAsync) return parseResultOrPromise.then((parseResult$1) => {
1120
- if (parseResult$1.success) return parser.complete(parseResult$1.next.state);
1121
- return parser.complete(parser.initialState);
1120
+ if (parseResult$1.success) return parser.complete(parseResult$1.next.state, exec);
1121
+ return parser.complete(parser.initialState, exec);
1122
1122
  });
1123
1123
  const parseResult = parseResultOrPromise;
1124
- if (parseResult.success) return parser.complete(parseResult.next.state);
1125
- return parser.complete(parser.initialState);
1126
- } else if (state[0] === "parsing") return parser.complete(state[1]);
1124
+ if (parseResult.success) return parser.complete(parseResult.next.state, exec);
1125
+ return parser.complete(parser.initialState, exec);
1126
+ } else if (state[0] === "parsing") return parser.complete(state[1], exec);
1127
1127
  return {
1128
1128
  success: false,
1129
1129
  error: options.errors?.invalidState ?? message`Invalid command state during completion.`
@@ -1324,7 +1324,7 @@ function passThrough(options = {}) {
1324
1324
  error: message`Unknown passThrough format: ${format}.`
1325
1325
  };
1326
1326
  },
1327
- complete(state) {
1327
+ complete(state, _exec) {
1328
1328
  if (getAnnotations(state) == null) return {
1329
1329
  success: true,
1330
1330
  value: state
@@ -1672,6 +1672,13 @@ function socketAddress(options) {
1672
1672
  ...options?.host?.ip,
1673
1673
  metavar: "HOST"
1674
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);
1675
1682
  const portParser = port({
1676
1683
  ...options?.port,
1677
1684
  metavar: "PORT",
@@ -1739,7 +1746,7 @@ function socketAddress(options) {
1739
1746
  const trimmed = input.trim();
1740
1747
  const canOmitPort = defaultPort !== void 0 && !requirePort;
1741
1748
  let firstHostError;
1742
- let validHostNumericPortInvalid = false;
1749
+ let validHostInvalidPortError;
1743
1750
  let trailingSepHost;
1744
1751
  let trailingSepHostError;
1745
1752
  let anySeparatorFound = false;
@@ -1774,15 +1781,22 @@ function socketAddress(options) {
1774
1781
  hostPart,
1775
1782
  error: hostResult.error
1776
1783
  };
1777
- } 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)) {
1778
1785
  const hostResult = parseHost(hostPart);
1779
1786
  if (!hostResult.success) {
1780
1787
  if (firstHostError === void 0 || !looksLikeIpv4(firstHostError.hostPart) && !looksLikeAltIpv4Literal(firstHostError.hostPart)) firstHostError = {
1781
1788
  hostPart,
1782
1789
  error: hostResult.error
1783
1790
  };
1784
- } else validHostNumericPortInvalid = true;
1785
- } 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
+ }
1786
1800
  else if ((firstHostError === void 0 || !looksLikeIpv4(firstHostError.hostPart) && !looksLikeAltIpv4Literal(firstHostError.hostPart)) && (looksLikeIpv4(hostPart) || looksLikeAltIpv4Literal(hostPart))) {
1787
1801
  const hostResult = parseHost(hostPart);
1788
1802
  if (!hostResult.success) firstHostError = {
@@ -1793,7 +1807,7 @@ function socketAddress(options) {
1793
1807
  }
1794
1808
  searchFrom = separatorIndex;
1795
1809
  }
1796
- if (validHostNumericPortInvalid) {
1810
+ if (validHostInvalidPortError !== void 0) {
1797
1811
  const errorMsg$1 = options?.errors?.invalidFormat;
1798
1812
  if (errorMsg$1) {
1799
1813
  const msg = typeof errorMsg$1 === "function" ? errorMsg$1(input) : errorMsg$1;
@@ -1802,10 +1816,25 @@ function socketAddress(options) {
1802
1816
  error: msg
1803
1817
  };
1804
1818
  }
1805
- 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 {
1806
1831
  success: false,
1807
1832
  error: require_message.message`Expected a socket address in format host${separator}port, but got ${input}.`
1808
1833
  };
1834
+ return {
1835
+ success: false,
1836
+ error: validHostInvalidPortError
1837
+ };
1809
1838
  }
1810
1839
  if (firstHostError !== void 0) {
1811
1840
  if (looksLikeIpv4(firstHostError.hostPart) || looksLikeAltIpv4Literal(firstHostError.hostPart)) {
@@ -1858,14 +1887,11 @@ function socketAddress(options) {
1858
1887
  error: msg
1859
1888
  };
1860
1889
  }
1861
- 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 {
1862
1892
  success: false,
1863
1893
  error: trailingSepHostError.error
1864
1894
  };
1865
- return {
1866
- success: false,
1867
- error: require_message.message`Expected a socket address in format host${separator}port, but got ${input}.`
1868
- };
1869
1895
  }
1870
1896
  if (!canOmitPort && !requirePort && hostOnlyResult !== void 0 && hostOnlyResult.success) {
1871
1897
  const errorMsg$1 = options?.errors?.missingPort;
@@ -1895,6 +1921,11 @@ function socketAddress(options) {
1895
1921
  error: msg
1896
1922
  };
1897
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
+ };
1898
1929
  }
1899
1930
  const errorMsg = options?.errors?.invalidFormat;
1900
1931
  if (errorMsg) {
@@ -1672,6 +1672,13 @@ function socketAddress(options) {
1672
1672
  ...options?.host?.ip,
1673
1673
  metavar: "HOST"
1674
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);
1675
1682
  const portParser = port({
1676
1683
  ...options?.port,
1677
1684
  metavar: "PORT",
@@ -1739,7 +1746,7 @@ function socketAddress(options) {
1739
1746
  const trimmed = input.trim();
1740
1747
  const canOmitPort = defaultPort !== void 0 && !requirePort;
1741
1748
  let firstHostError;
1742
- let validHostNumericPortInvalid = false;
1749
+ let validHostInvalidPortError;
1743
1750
  let trailingSepHost;
1744
1751
  let trailingSepHostError;
1745
1752
  let anySeparatorFound = false;
@@ -1774,15 +1781,22 @@ function socketAddress(options) {
1774
1781
  hostPart,
1775
1782
  error: hostResult.error
1776
1783
  };
1777
- } 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)) {
1778
1785
  const hostResult = parseHost(hostPart);
1779
1786
  if (!hostResult.success) {
1780
1787
  if (firstHostError === void 0 || !looksLikeIpv4(firstHostError.hostPart) && !looksLikeAltIpv4Literal(firstHostError.hostPart)) firstHostError = {
1781
1788
  hostPart,
1782
1789
  error: hostResult.error
1783
1790
  };
1784
- } else validHostNumericPortInvalid = true;
1785
- } 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
+ }
1786
1800
  else if ((firstHostError === void 0 || !looksLikeIpv4(firstHostError.hostPart) && !looksLikeAltIpv4Literal(firstHostError.hostPart)) && (looksLikeIpv4(hostPart) || looksLikeAltIpv4Literal(hostPart))) {
1787
1801
  const hostResult = parseHost(hostPart);
1788
1802
  if (!hostResult.success) firstHostError = {
@@ -1793,7 +1807,7 @@ function socketAddress(options) {
1793
1807
  }
1794
1808
  searchFrom = separatorIndex;
1795
1809
  }
1796
- if (validHostNumericPortInvalid) {
1810
+ if (validHostInvalidPortError !== void 0) {
1797
1811
  const errorMsg$1 = options?.errors?.invalidFormat;
1798
1812
  if (errorMsg$1) {
1799
1813
  const msg = typeof errorMsg$1 === "function" ? errorMsg$1(input) : errorMsg$1;
@@ -1802,10 +1816,25 @@ function socketAddress(options) {
1802
1816
  error: msg
1803
1817
  };
1804
1818
  }
1805
- 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 {
1806
1831
  success: false,
1807
1832
  error: message`Expected a socket address in format host${separator}port, but got ${input}.`
1808
1833
  };
1834
+ return {
1835
+ success: false,
1836
+ error: validHostInvalidPortError
1837
+ };
1809
1838
  }
1810
1839
  if (firstHostError !== void 0) {
1811
1840
  if (looksLikeIpv4(firstHostError.hostPart) || looksLikeAltIpv4Literal(firstHostError.hostPart)) {
@@ -1858,14 +1887,11 @@ function socketAddress(options) {
1858
1887
  error: msg
1859
1888
  };
1860
1889
  }
1861
- 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 {
1862
1892
  success: false,
1863
1893
  error: trailingSepHostError.error
1864
1894
  };
1865
- return {
1866
- success: false,
1867
- error: message`Expected a socket address in format host${separator}port, but got ${input}.`
1868
- };
1869
1895
  }
1870
1896
  if (!canOmitPort && !requirePort && hostOnlyResult !== void 0 && hostOnlyResult.success) {
1871
1897
  const errorMsg$1 = options?.errors?.missingPort;
@@ -1895,6 +1921,11 @@ function socketAddress(options) {
1895
1921
  error: msg
1896
1922
  };
1897
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
+ };
1898
1929
  }
1899
1930
  const errorMsg = options?.errors?.invalidFormat;
1900
1931
  if (errorMsg) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.1574+d9b3596d",
3
+ "version": "1.0.0-dev.1583+82b74b4b",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",