@optique/core 1.1.0-dev.2148 → 1.1.0-dev.2160
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/valueparser.cjs +483 -9
- package/dist/valueparser.d.cts +154 -4
- package/dist/valueparser.d.ts +154 -4
- package/dist/valueparser.js +483 -10
- package/package.json +2 -2
package/dist/valueparser.js
CHANGED
|
@@ -327,6 +327,323 @@ function string(options = {}) {
|
|
|
327
327
|
}
|
|
328
328
|
};
|
|
329
329
|
}
|
|
330
|
+
function keyValue(options = {}) {
|
|
331
|
+
const separator = options.separator ?? "=";
|
|
332
|
+
if (typeof separator !== "string") throw new TypeError(`Expected separator to be a string, but got ${typeof separator}: ${String(separator)}.`);
|
|
333
|
+
ensureNonEmptyString(separator);
|
|
334
|
+
checkBooleanOption(options, "allowEmptyKey");
|
|
335
|
+
checkBooleanOption(options, "allowEmptyValue");
|
|
336
|
+
checkEnumOption(options, "split", ["first", "last"]);
|
|
337
|
+
const split = options.split ?? "first";
|
|
338
|
+
const allowEmptyKey = options.allowEmptyKey ?? false;
|
|
339
|
+
const allowEmptyValue = options.allowEmptyValue ?? true;
|
|
340
|
+
const metavar$1 = options.metavar ?? `KEY${separator}VALUE`;
|
|
341
|
+
ensureNonEmptyString(metavar$1);
|
|
342
|
+
const rawKeyParser = options.key ?? string({ placeholder: "KEY" });
|
|
343
|
+
const rawValueParser = options.value ?? string({ placeholder: allowEmptyValue ? "" : "VALUE" });
|
|
344
|
+
checkKeyValueChildParser("key", rawKeyParser);
|
|
345
|
+
checkKeyValueChildParser("value", rawValueParser);
|
|
346
|
+
const keyParser = rawKeyParser;
|
|
347
|
+
const valueParser = rawValueParser;
|
|
348
|
+
const hasNormalize = typeof keyParser.normalize === "function" || typeof valueParser.normalize === "function";
|
|
349
|
+
const hasSuggest = typeof keyParser.suggest === "function" || typeof valueParser.suggest === "function";
|
|
350
|
+
function findSeparator(input) {
|
|
351
|
+
return split === "last" ? input.lastIndexOf(separator) : input.indexOf(separator);
|
|
352
|
+
}
|
|
353
|
+
function emptyKeyError(input) {
|
|
354
|
+
const custom = options.errors?.emptyKey;
|
|
355
|
+
if (custom != null) return typeof custom === "function" ? custom(input) : custom;
|
|
356
|
+
return message`Expected a non-empty key in ${input}.`;
|
|
357
|
+
}
|
|
358
|
+
function emptyValueError(input) {
|
|
359
|
+
const custom = options.errors?.emptyValue;
|
|
360
|
+
if (custom != null) return typeof custom === "function" ? custom(input) : custom;
|
|
361
|
+
return message`Expected a non-empty value in ${input}.`;
|
|
362
|
+
}
|
|
363
|
+
function missingSeparatorError(input) {
|
|
364
|
+
const custom = options.errors?.missingSeparator;
|
|
365
|
+
if (custom != null) return typeof custom === "function" ? custom(input, separator) : custom;
|
|
366
|
+
return message`Expected ${separator} in ${input}.`;
|
|
367
|
+
}
|
|
368
|
+
function invalidKeyError(error) {
|
|
369
|
+
const custom = options.errors?.invalidKey;
|
|
370
|
+
if (custom != null) return typeof custom === "function" ? custom(error) : custom;
|
|
371
|
+
return [text("Invalid key: "), ...cloneMessage(error)];
|
|
372
|
+
}
|
|
373
|
+
function invalidValueError(error) {
|
|
374
|
+
const custom = options.errors?.invalidValue;
|
|
375
|
+
if (custom != null) return typeof custom === "function" ? custom(error) : custom;
|
|
376
|
+
return [text("Invalid value: "), ...cloneMessage(error)];
|
|
377
|
+
}
|
|
378
|
+
function validateParts(input, key, value) {
|
|
379
|
+
if (!allowEmptyKey && key === "") return {
|
|
380
|
+
success: false,
|
|
381
|
+
error: emptyKeyError(input)
|
|
382
|
+
};
|
|
383
|
+
if (!allowEmptyValue && value === "") return {
|
|
384
|
+
success: false,
|
|
385
|
+
error: emptyValueError(input)
|
|
386
|
+
};
|
|
387
|
+
const keyResult = keyParser.parse(key);
|
|
388
|
+
if (!keyResult.success) return {
|
|
389
|
+
success: false,
|
|
390
|
+
error: invalidKeyError(keyResult.error)
|
|
391
|
+
};
|
|
392
|
+
const valueResult = valueParser.parse(value);
|
|
393
|
+
if (!valueResult.success) return {
|
|
394
|
+
success: false,
|
|
395
|
+
error: invalidValueError(valueResult.error)
|
|
396
|
+
};
|
|
397
|
+
return validateResultParts(input, keyResult, valueResult);
|
|
398
|
+
}
|
|
399
|
+
function validateKeyForSuggestion(key) {
|
|
400
|
+
if (!allowEmptyKey && key === "") return false;
|
|
401
|
+
if (!partsRoundTrip(key, "")) return false;
|
|
402
|
+
const keyResult = keyParser.parse(key);
|
|
403
|
+
if (!keyResult.success) return false;
|
|
404
|
+
if (!allowEmptyKey && keyResult.value === "") return false;
|
|
405
|
+
const formattedKeyResult = formatValueParserValue(keyParser, keyResult.value);
|
|
406
|
+
if (!formattedKeyResult.success) return false;
|
|
407
|
+
const formattedKey = formattedKeyResult.value;
|
|
408
|
+
return formattedKey == null || partsRoundTrip(formattedKey, "");
|
|
409
|
+
}
|
|
410
|
+
function validateKeyPatternForSuggestion(key) {
|
|
411
|
+
if (!allowEmptyKey && key === "") return false;
|
|
412
|
+
return partsRoundTrip(key, "");
|
|
413
|
+
}
|
|
414
|
+
function fallbackInput(key, value) {
|
|
415
|
+
return `${typeof key === "string" ? key : ""}${separator}${typeof value === "string" ? value : ""}`;
|
|
416
|
+
}
|
|
417
|
+
function partsRoundTrip(key, value) {
|
|
418
|
+
const input = `${key}${separator}${value}`;
|
|
419
|
+
const index = findSeparator(input);
|
|
420
|
+
if (index < 0) return false;
|
|
421
|
+
return input.slice(0, index) === key && input.slice(index + separator.length) === value;
|
|
422
|
+
}
|
|
423
|
+
function validateResultParts(input, keyResult, valueResult) {
|
|
424
|
+
if (!allowEmptyKey && keyResult.value === "") return {
|
|
425
|
+
success: false,
|
|
426
|
+
error: emptyKeyError(input)
|
|
427
|
+
};
|
|
428
|
+
if (!allowEmptyValue && valueResult.value === "") return {
|
|
429
|
+
success: false,
|
|
430
|
+
error: emptyValueError(input)
|
|
431
|
+
};
|
|
432
|
+
const formattedKeyResult = formatValueParserValue(keyParser, keyResult.value);
|
|
433
|
+
if (!formattedKeyResult.success) return {
|
|
434
|
+
success: false,
|
|
435
|
+
error: invalidKeyError(formattedKeyResult.error)
|
|
436
|
+
};
|
|
437
|
+
const formattedValueResult = formatValueParserValue(valueParser, valueResult.value);
|
|
438
|
+
if (!formattedValueResult.success) return {
|
|
439
|
+
success: false,
|
|
440
|
+
error: invalidValueError(formattedValueResult.error)
|
|
441
|
+
};
|
|
442
|
+
const formattedKey = formattedKeyResult.value;
|
|
443
|
+
const formattedValue = formattedValueResult.value;
|
|
444
|
+
if (!allowEmptyKey && formattedKey === "") return {
|
|
445
|
+
success: false,
|
|
446
|
+
error: emptyKeyError(input)
|
|
447
|
+
};
|
|
448
|
+
if (!allowEmptyValue && formattedValue === "") return {
|
|
449
|
+
success: false,
|
|
450
|
+
error: emptyValueError(input)
|
|
451
|
+
};
|
|
452
|
+
if (split === "first" && formattedKey != null && formattedKey.includes(separator)) return {
|
|
453
|
+
success: false,
|
|
454
|
+
error: invalidKeyError(message`Expected a key without ${separator}, but got ${formattedKey}.`)
|
|
455
|
+
};
|
|
456
|
+
if (split === "last" && formattedValue != null && formattedValue.includes(separator)) return {
|
|
457
|
+
success: false,
|
|
458
|
+
error: invalidValueError(message`Expected a value without ${separator}, but got ${formattedValue}.`)
|
|
459
|
+
};
|
|
460
|
+
if (formattedKey != null && formattedValue != null) {
|
|
461
|
+
if (!partsRoundTrip(formattedKey, formattedValue)) return split === "first" ? {
|
|
462
|
+
success: false,
|
|
463
|
+
error: invalidKeyError(message`Expected a key that round-trips with ${separator}, but got ${formattedKey}.`)
|
|
464
|
+
} : {
|
|
465
|
+
success: false,
|
|
466
|
+
error: invalidValueError(message`Expected a value that round-trips with ${separator}, but got ${formattedValue}.`)
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
return makeKeyValueSuccess(keyResult, valueResult);
|
|
470
|
+
}
|
|
471
|
+
function validateTuple(value) {
|
|
472
|
+
if (!allowEmptyKey && value[0] === "") return {
|
|
473
|
+
success: false,
|
|
474
|
+
error: emptyKeyError(fallbackInput(value[0], value[1]))
|
|
475
|
+
};
|
|
476
|
+
if (!allowEmptyValue && value[1] === "") return {
|
|
477
|
+
success: false,
|
|
478
|
+
error: emptyValueError(fallbackInput(value[0], value[1]))
|
|
479
|
+
};
|
|
480
|
+
const keyResult = validateValueParserValue(keyParser, value[0]);
|
|
481
|
+
if (!keyResult.success) return {
|
|
482
|
+
success: false,
|
|
483
|
+
error: invalidKeyError(keyResult.error)
|
|
484
|
+
};
|
|
485
|
+
const valueResult = validateValueParserValue(valueParser, value[1]);
|
|
486
|
+
if (!valueResult.success) return {
|
|
487
|
+
success: false,
|
|
488
|
+
error: invalidValueError(valueResult.error)
|
|
489
|
+
};
|
|
490
|
+
return validateResultParts(fallbackInput(value[0], value[1]), keyResult, valueResult);
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
mode: "sync",
|
|
494
|
+
metavar: metavar$1,
|
|
495
|
+
placeholder: [keyParser.placeholder, valueParser.placeholder],
|
|
496
|
+
parse(input) {
|
|
497
|
+
const index = findSeparator(input);
|
|
498
|
+
if (index < 0) return {
|
|
499
|
+
success: false,
|
|
500
|
+
error: missingSeparatorError(input)
|
|
501
|
+
};
|
|
502
|
+
const key = input.slice(0, index);
|
|
503
|
+
const value = input.slice(index + separator.length);
|
|
504
|
+
return validateParts(input, key, value);
|
|
505
|
+
},
|
|
506
|
+
format(value) {
|
|
507
|
+
return `${keyParser.format(value[0])}${separator}${valueParser.format(value[1])}`;
|
|
508
|
+
},
|
|
509
|
+
validate(value) {
|
|
510
|
+
if (!isKeyValueTuple(value)) return {
|
|
511
|
+
success: false,
|
|
512
|
+
error: message`Expected a key-value tuple.`
|
|
513
|
+
};
|
|
514
|
+
return validateTuple(value);
|
|
515
|
+
},
|
|
516
|
+
...hasNormalize ? { normalize(value) {
|
|
517
|
+
if (!isKeyValueTuple(value)) return value;
|
|
518
|
+
const normalized = [typeof keyParser.normalize === "function" ? keyParser.normalize(value[0]) : value[0], typeof valueParser.normalize === "function" ? valueParser.normalize(value[1]) : value[1]];
|
|
519
|
+
return validateTuple(normalized).success ? normalized : value;
|
|
520
|
+
} } : {},
|
|
521
|
+
...hasSuggest ? { *suggest(prefix) {
|
|
522
|
+
const index = findSeparator(prefix);
|
|
523
|
+
const suggestions = [];
|
|
524
|
+
if (index < 0 || split === "last") {
|
|
525
|
+
if (typeof keyParser.suggest === "function") for (const suggestion of keyParser.suggest(prefix)) {
|
|
526
|
+
const key = suggestionTextValue(suggestion);
|
|
527
|
+
if (suggestion.kind === "literal" ? !validateKeyForSuggestion(key) : !validateKeyPatternForSuggestion(key)) continue;
|
|
528
|
+
suggestions.push(prefixKeyValueSuggestion(suggestion, "", separator));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (index < 0) {
|
|
532
|
+
yield* deduplicateSuggestions(suggestions);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (typeof valueParser.suggest === "function") {
|
|
536
|
+
const key = prefix.slice(0, index);
|
|
537
|
+
if (!validateKeyForSuggestion(key)) {
|
|
538
|
+
yield* deduplicateSuggestions(suggestions);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const valuePrefix = prefix.slice(index + separator.length);
|
|
542
|
+
for (const suggestion of valueParser.suggest(valuePrefix)) {
|
|
543
|
+
const value = suggestionTextValue(suggestion);
|
|
544
|
+
if (!partsRoundTrip(key, value)) continue;
|
|
545
|
+
if (suggestion.kind === "literal" && !validateParts(`${key}${separator}${value}`, key, value).success) continue;
|
|
546
|
+
suggestions.push(prefixKeyValueSuggestion(suggestion, `${key}${separator}`));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
yield* deduplicateSuggestions(suggestions);
|
|
550
|
+
} } : {}
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function checkKeyValueChildParser(role, parser) {
|
|
554
|
+
if (!isValueParser(parser)) throw new TypeError(`The ${role} option for keyValue() must be a value parser.`);
|
|
555
|
+
if (parser.mode !== "sync") throw new TypeError(`keyValue() only supports sync ${role} parsers, but an async one was given.`);
|
|
556
|
+
if (isDerivedValueParser(parser)) throw new TypeError(`keyValue() does not support dependency-derived ${role} parsers (created via deriveFrom() or dependency().derive()); pass the derived parser directly to option() or argument() instead.`);
|
|
557
|
+
}
|
|
558
|
+
function validateValueParserValue(parser, value) {
|
|
559
|
+
if (typeof parser.validate === "function") return parser.validate(value);
|
|
560
|
+
let formatted;
|
|
561
|
+
try {
|
|
562
|
+
formatted = parser.format(value);
|
|
563
|
+
} catch {
|
|
564
|
+
return {
|
|
565
|
+
success: true,
|
|
566
|
+
value
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
if (typeof formatted !== "string") return {
|
|
570
|
+
success: false,
|
|
571
|
+
error: stringFormatError()
|
|
572
|
+
};
|
|
573
|
+
return parser.parse(formatted);
|
|
574
|
+
}
|
|
575
|
+
function formatValueParserValue(parser, value) {
|
|
576
|
+
let formatted;
|
|
577
|
+
try {
|
|
578
|
+
formatted = parser.format(value);
|
|
579
|
+
} catch {
|
|
580
|
+
return {
|
|
581
|
+
success: true,
|
|
582
|
+
value: void 0
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
if (typeof formatted !== "string") return {
|
|
586
|
+
success: false,
|
|
587
|
+
error: stringFormatError()
|
|
588
|
+
};
|
|
589
|
+
return {
|
|
590
|
+
success: true,
|
|
591
|
+
value: formatted
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function stringFormatError() {
|
|
595
|
+
return message`Expected a value formatted as a string.`;
|
|
596
|
+
}
|
|
597
|
+
function suggestionTextValue(suggestion) {
|
|
598
|
+
return suggestion.kind === "literal" ? suggestion.text : suggestion.pattern ?? "";
|
|
599
|
+
}
|
|
600
|
+
function prefixKeyValueSuggestion(suggestion, prefix, suffix = "") {
|
|
601
|
+
const textValue = suggestionTextValue(suggestion);
|
|
602
|
+
const text$1 = `${prefix}${textValue}${suffix}`;
|
|
603
|
+
if (suggestion.kind === "file" && suffix === "") return {
|
|
604
|
+
...suggestion,
|
|
605
|
+
pattern: text$1
|
|
606
|
+
};
|
|
607
|
+
return suggestion.description == null ? {
|
|
608
|
+
kind: "literal",
|
|
609
|
+
text: text$1
|
|
610
|
+
} : {
|
|
611
|
+
kind: "literal",
|
|
612
|
+
text: text$1,
|
|
613
|
+
description: suggestion.description
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
function isKeyValueTuple(value) {
|
|
617
|
+
return Array.isArray(value) && value.length === 2 && 0 in value && 1 in value;
|
|
618
|
+
}
|
|
619
|
+
function makeKeyValueSuccess(keyResult, valueResult) {
|
|
620
|
+
const deferredKeys = /* @__PURE__ */ new Map();
|
|
621
|
+
let hasDeferred = false;
|
|
622
|
+
collectKeyValueDeferredKeys(deferredKeys, 0, keyResult, () => {
|
|
623
|
+
hasDeferred = true;
|
|
624
|
+
});
|
|
625
|
+
collectKeyValueDeferredKeys(deferredKeys, 1, valueResult, () => {
|
|
626
|
+
hasDeferred = true;
|
|
627
|
+
});
|
|
628
|
+
return {
|
|
629
|
+
success: true,
|
|
630
|
+
value: [keyResult.value, valueResult.value],
|
|
631
|
+
...hasDeferred ? {
|
|
632
|
+
deferred: true,
|
|
633
|
+
...deferredKeys.size > 0 ? { deferredKeys } : {}
|
|
634
|
+
} : {}
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function collectKeyValueDeferredKeys(deferredKeys, index, result, markDeferred) {
|
|
638
|
+
if (result.deferredKeys != null) {
|
|
639
|
+
markDeferred();
|
|
640
|
+
deferredKeys.set(index, result.deferredKeys);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (result.deferred !== true) return;
|
|
644
|
+
markDeferred();
|
|
645
|
+
deferredKeys.set(index, null);
|
|
646
|
+
}
|
|
330
647
|
/**
|
|
331
648
|
* Creates a ValueParser for parsing integer values from strings.
|
|
332
649
|
*
|
|
@@ -3088,7 +3405,7 @@ function email(options) {
|
|
|
3088
3405
|
* Creates a value parser for socket addresses in "host:port" format.
|
|
3089
3406
|
*
|
|
3090
3407
|
* Validates socket addresses with support for:
|
|
3091
|
-
* - Hostnames
|
|
3408
|
+
* - Hostnames, IPv4 addresses, and IPv6 addresses
|
|
3092
3409
|
* - Configurable host:port separator
|
|
3093
3410
|
* - Optional default port
|
|
3094
3411
|
* - Host type filtering (hostname only, IP only, or both)
|
|
@@ -3099,6 +3416,8 @@ function email(options) {
|
|
|
3099
3416
|
* @throws {TypeError} If `separator` is an empty string.
|
|
3100
3417
|
* @throws {TypeError} If `separator` contains digit characters, since digits
|
|
3101
3418
|
* in the separator would cause ambiguous splitting of port input.
|
|
3419
|
+
* @throws {TypeError} If `host.version` is provided but is not `4`, `6`, or
|
|
3420
|
+
* `"both"`.
|
|
3102
3421
|
* @since 0.10.0
|
|
3103
3422
|
*
|
|
3104
3423
|
* @example
|
|
@@ -3128,12 +3447,25 @@ function socketAddress(options) {
|
|
|
3128
3447
|
const defaultPort = options?.defaultPort;
|
|
3129
3448
|
const requirePort = options?.requirePort ?? false;
|
|
3130
3449
|
const hostType = options?.host?.type ?? "both";
|
|
3450
|
+
const rawHostVersion = options?.host?.version;
|
|
3451
|
+
if (rawHostVersion !== void 0 && rawHostVersion !== 4 && rawHostVersion !== 6 && rawHostVersion !== "both") throw new TypeError(`Expected host.version to be 4, 6, or "both", but got ${Array.isArray(rawHostVersion) ? "array" : typeof rawHostVersion}: ${String(rawHostVersion)}.`);
|
|
3452
|
+
const hasLegacyIpOptions = options?.host?.ip !== void 0;
|
|
3453
|
+
const hasNewIpOptions = rawHostVersion !== void 0 || options?.host?.ipv4 !== void 0 || options?.host?.ipv6 !== void 0;
|
|
3454
|
+
const hostVersion = rawHostVersion ?? (hasLegacyIpOptions && !hasNewIpOptions ? 4 : "both");
|
|
3131
3455
|
const hostnameParser = hostname({
|
|
3132
3456
|
...options?.host?.hostname,
|
|
3133
3457
|
metavar: "HOST"
|
|
3134
3458
|
});
|
|
3135
|
-
const
|
|
3459
|
+
const ipv4Options = {
|
|
3136
3460
|
...options?.host?.ip,
|
|
3461
|
+
...options?.host?.ipv4
|
|
3462
|
+
};
|
|
3463
|
+
const ipv4Parser = ipv4({
|
|
3464
|
+
...ipv4Options,
|
|
3465
|
+
metavar: "HOST"
|
|
3466
|
+
});
|
|
3467
|
+
const ipv6Parser = ipv6({
|
|
3468
|
+
...options?.host?.ipv6,
|
|
3137
3469
|
metavar: "HOST"
|
|
3138
3470
|
});
|
|
3139
3471
|
const disambiguationParser = hostType === "ip" ? hostname({
|
|
@@ -3151,6 +3483,15 @@ function socketAddress(options) {
|
|
|
3151
3483
|
function looksLikeIpv4(input) {
|
|
3152
3484
|
return /^\d+\.\d+\.\d+\.\d+$/.test(input);
|
|
3153
3485
|
}
|
|
3486
|
+
function looksLikeIpv6(input) {
|
|
3487
|
+
const colonCount = input.match(/:/g)?.length ?? 0;
|
|
3488
|
+
return colonCount >= 2 && (input.includes("::") || /^[0-9a-fA-F:.]+$/.test(input));
|
|
3489
|
+
}
|
|
3490
|
+
function looksLikeBareIpv6(input) {
|
|
3491
|
+
if (!looksLikeIpv6(input)) return false;
|
|
3492
|
+
if (parseAndNormalizeIpv6(input) !== null) return true;
|
|
3493
|
+
return input.includes("::") || !input.includes(".");
|
|
3494
|
+
}
|
|
3154
3495
|
function looksLikeAltIpv4Literal(input) {
|
|
3155
3496
|
if (/^0[xX][0-9a-fA-F]+$/.test(input)) {
|
|
3156
3497
|
const n = parseInt(input.slice(2), 16);
|
|
@@ -3176,6 +3517,22 @@ function socketAddress(options) {
|
|
|
3176
3517
|
}
|
|
3177
3518
|
return false;
|
|
3178
3519
|
}
|
|
3520
|
+
function parseIpHost(hostInput) {
|
|
3521
|
+
if (hostVersion === 4) return ipv4Parser.parse(hostInput);
|
|
3522
|
+
if (hostVersion === 6) return ipv6Parser.parse(hostInput);
|
|
3523
|
+
if (looksLikeIpv6(hostInput)) {
|
|
3524
|
+
const result = ipv6Parser.parse(hostInput);
|
|
3525
|
+
if (result.success) {
|
|
3526
|
+
const mappedOctets = extractIpv4FromMapped(result.value);
|
|
3527
|
+
if (mappedOctets !== null) {
|
|
3528
|
+
const restrictionError = checkIpv4MappedRestrictions(mappedOctets, result.value, ipv4Options, void 0);
|
|
3529
|
+
if (restrictionError !== null) return restrictionError;
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
return result;
|
|
3533
|
+
}
|
|
3534
|
+
return ipv4Parser.parse(hostInput);
|
|
3535
|
+
}
|
|
3179
3536
|
function parseHost(hostInput) {
|
|
3180
3537
|
if (hostType === "hostname") {
|
|
3181
3538
|
if (looksLikeAltIpv4Literal(hostInput)) return {
|
|
@@ -3187,22 +3544,106 @@ function socketAddress(options) {
|
|
|
3187
3544
|
error: message`Expected a valid hostname, but got ${hostInput}.`
|
|
3188
3545
|
};
|
|
3189
3546
|
return hostnameParser.parse(hostInput);
|
|
3190
|
-
} else if (hostType === "ip") return
|
|
3547
|
+
} else if (hostType === "ip") return parseIpHost(hostInput);
|
|
3191
3548
|
else {
|
|
3192
3549
|
if (looksLikeAltIpv4Literal(hostInput)) return {
|
|
3193
3550
|
success: false,
|
|
3194
3551
|
error: message`${hostInput} appears to be a non-standard IPv4 address notation.`
|
|
3195
3552
|
};
|
|
3196
|
-
if (looksLikeIpv4(hostInput)) return
|
|
3553
|
+
if (looksLikeIpv4(hostInput)) return parseIpHost(hostInput);
|
|
3554
|
+
if (looksLikeIpv6(hostInput)) return parseIpHost(hostInput);
|
|
3197
3555
|
return hostnameParser.parse(hostInput);
|
|
3198
3556
|
}
|
|
3199
3557
|
}
|
|
3200
|
-
|
|
3558
|
+
function makeInvalidFormatError(input) {
|
|
3559
|
+
const errorMsg = options?.errors?.invalidFormat;
|
|
3560
|
+
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? message`Expected a socket address in format ${text(formatExample)}, but got ${input}.`;
|
|
3561
|
+
return {
|
|
3562
|
+
success: false,
|
|
3563
|
+
error: msg
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
function makeMissingPortError(input) {
|
|
3567
|
+
const errorMsg = options?.errors?.missingPort;
|
|
3568
|
+
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? message`Port number is required but was not specified.`;
|
|
3569
|
+
return {
|
|
3570
|
+
success: false,
|
|
3571
|
+
error: msg
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
function parseBracketedHost(input, trimmed, canOmitPort) {
|
|
3575
|
+
if (!trimmed.startsWith("[")) return void 0;
|
|
3576
|
+
const closingBracket = trimmed.indexOf("]");
|
|
3577
|
+
if (closingBracket === -1) return makeInvalidFormatError(input);
|
|
3578
|
+
const hostInput = trimmed.slice(1, closingBracket);
|
|
3579
|
+
if (!looksLikeBareIpv6(hostInput)) return makeInvalidFormatError(input);
|
|
3580
|
+
const rest = trimmed.slice(closingBracket + 1).trimStart();
|
|
3581
|
+
const hostResult = parseHost(hostInput);
|
|
3582
|
+
if (rest === "") {
|
|
3583
|
+
if (!hostResult.success) {
|
|
3584
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3585
|
+
return {
|
|
3586
|
+
success: false,
|
|
3587
|
+
error: hostResult.error
|
|
3588
|
+
};
|
|
3589
|
+
}
|
|
3590
|
+
if (canOmitPort) return {
|
|
3591
|
+
success: true,
|
|
3592
|
+
value: {
|
|
3593
|
+
host: hostResult.value,
|
|
3594
|
+
port: defaultPort
|
|
3595
|
+
}
|
|
3596
|
+
};
|
|
3597
|
+
return makeMissingPortError(input);
|
|
3598
|
+
}
|
|
3599
|
+
if (!rest.startsWith(separator)) return makeInvalidFormatError(input);
|
|
3600
|
+
const portPart = rest.slice(separator.length).trim();
|
|
3601
|
+
if (portPart === "") {
|
|
3602
|
+
if (!hostResult.success) {
|
|
3603
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3604
|
+
return {
|
|
3605
|
+
success: false,
|
|
3606
|
+
error: hostResult.error
|
|
3607
|
+
};
|
|
3608
|
+
}
|
|
3609
|
+
return makeMissingPortError(input);
|
|
3610
|
+
}
|
|
3611
|
+
const portResult = portParser.parse(portPart);
|
|
3612
|
+
if (!hostResult.success) {
|
|
3613
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3614
|
+
return {
|
|
3615
|
+
success: false,
|
|
3616
|
+
error: hostResult.error
|
|
3617
|
+
};
|
|
3618
|
+
}
|
|
3619
|
+
if (portResult.success) return {
|
|
3620
|
+
success: true,
|
|
3621
|
+
value: {
|
|
3622
|
+
host: hostResult.value,
|
|
3623
|
+
port: portResult.value
|
|
3624
|
+
}
|
|
3625
|
+
};
|
|
3626
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3627
|
+
if (/^[0-9]+$/.test(portPart)) return {
|
|
3628
|
+
success: false,
|
|
3629
|
+
error: portResult.error
|
|
3630
|
+
};
|
|
3631
|
+
return makeInvalidFormatError(input);
|
|
3632
|
+
}
|
|
3633
|
+
function isSocketAddressValue(value) {
|
|
3634
|
+
return typeof value === "object" && value !== null && "host" in value && typeof value.host === "string" && "port" in value && typeof value.port === "number";
|
|
3635
|
+
}
|
|
3636
|
+
function formatSocketAddressValue(value) {
|
|
3637
|
+
const ipv6Host = parseAndNormalizeIpv6(value.host);
|
|
3638
|
+
if (separator === ":" && ipv6Host !== null) return `[${ipv6Host}]${separator}${value.port}`;
|
|
3639
|
+
return `${value.host}${separator}${value.port}`;
|
|
3640
|
+
}
|
|
3641
|
+
const parser = {
|
|
3201
3642
|
mode: "sync",
|
|
3202
3643
|
metavar: metavar$1,
|
|
3203
3644
|
get placeholder() {
|
|
3204
3645
|
return {
|
|
3205
|
-
host: hostType === "ip" ?
|
|
3646
|
+
host: hostType === "ip" ? hostVersion === 6 ? ipv6Parser.placeholder : ipv4Parser.placeholder : hostnameParser.placeholder,
|
|
3206
3647
|
port: defaultPort ?? portParser.placeholder
|
|
3207
3648
|
};
|
|
3208
3649
|
},
|
|
@@ -3210,6 +3651,28 @@ function socketAddress(options) {
|
|
|
3210
3651
|
const trimmed = input.trim();
|
|
3211
3652
|
const searchInput = input.trimStart();
|
|
3212
3653
|
const canOmitPort = defaultPort !== void 0 && !requirePort;
|
|
3654
|
+
if (separator === ":") {
|
|
3655
|
+
const bracketedResult = parseBracketedHost(input, trimmed, canOmitPort);
|
|
3656
|
+
if (bracketedResult !== void 0) return bracketedResult;
|
|
3657
|
+
if (looksLikeBareIpv6(trimmed)) {
|
|
3658
|
+
const hostResult = parseHost(trimmed);
|
|
3659
|
+
if (hostResult.success) {
|
|
3660
|
+
if (canOmitPort) return {
|
|
3661
|
+
success: true,
|
|
3662
|
+
value: {
|
|
3663
|
+
host: hostResult.value,
|
|
3664
|
+
port: defaultPort
|
|
3665
|
+
}
|
|
3666
|
+
};
|
|
3667
|
+
return makeMissingPortError(input);
|
|
3668
|
+
}
|
|
3669
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3670
|
+
return {
|
|
3671
|
+
success: false,
|
|
3672
|
+
error: hostResult.error
|
|
3673
|
+
};
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3213
3676
|
let firstHostError;
|
|
3214
3677
|
let validHostInvalidPortError;
|
|
3215
3678
|
let trailingSepHost;
|
|
@@ -3407,9 +3870,19 @@ function socketAddress(options) {
|
|
|
3407
3870
|
};
|
|
3408
3871
|
},
|
|
3409
3872
|
format(value) {
|
|
3410
|
-
return
|
|
3873
|
+
return formatSocketAddressValue(value);
|
|
3874
|
+
},
|
|
3875
|
+
normalize(value) {
|
|
3876
|
+
if (!isSocketAddressValue(value)) return value;
|
|
3877
|
+
try {
|
|
3878
|
+
const result = parser.parse(formatSocketAddressValue(value));
|
|
3879
|
+
return result.success ? result.value : value;
|
|
3880
|
+
} catch {
|
|
3881
|
+
return value;
|
|
3882
|
+
}
|
|
3411
3883
|
}
|
|
3412
3884
|
};
|
|
3885
|
+
return parser;
|
|
3413
3886
|
}
|
|
3414
3887
|
function portRange(options) {
|
|
3415
3888
|
checkBooleanOption(options, "disallowWellKnown");
|
|
@@ -4323,7 +4796,7 @@ function parseAndNormalizeIpv6(input) {
|
|
|
4323
4796
|
const leftGroups = parts[0] ? parts[0].split(":") : [];
|
|
4324
4797
|
const rightGroups = parts[1] ? parts[1].split(":") : [];
|
|
4325
4798
|
const totalGroups = leftGroups.length + rightGroups.length;
|
|
4326
|
-
if (totalGroups
|
|
4799
|
+
if (totalGroups >= 8) return null;
|
|
4327
4800
|
const zeroCount = 8 - totalGroups;
|
|
4328
4801
|
const zeros = Array(zeroCount).fill("0");
|
|
4329
4802
|
groups = [
|
|
@@ -4354,7 +4827,7 @@ function expandIpv6(input) {
|
|
|
4354
4827
|
const leftGroups = parts[0] ? parts[0].split(":").filter((g) => g) : [];
|
|
4355
4828
|
const rightGroups = parts[1] ? parts[1].split(":").filter((g) => g) : [];
|
|
4356
4829
|
const totalGroups = leftGroups.length + rightGroups.length;
|
|
4357
|
-
if (totalGroups
|
|
4830
|
+
if (totalGroups >= 8) return null;
|
|
4358
4831
|
const zeroCount = 8 - totalGroups;
|
|
4359
4832
|
const zeros = Array(zeroCount).fill("0");
|
|
4360
4833
|
const groups = [
|
|
@@ -5624,4 +6097,4 @@ function plainObjectsEqual(a, b) {
|
|
|
5624
6097
|
}
|
|
5625
6098
|
|
|
5626
6099
|
//#endregion
|
|
5627
|
-
export { checkBooleanOption, checkEnumOption, choice, cidr, color, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, locale, macAddress, port, portRange, semVer, socketAddress, string, url, uuid };
|
|
6100
|
+
export { checkBooleanOption, checkEnumOption, choice, cidr, color, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, semVer, socketAddress, string, url, uuid };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optique/core",
|
|
3
|
-
"version": "1.1.0-dev.
|
|
3
|
+
"version": "1.1.0-dev.2160",
|
|
4
4
|
"description": "Type-safe combinatorial command-line interface parser",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"CLI",
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
"fast-check": "^4.7.0",
|
|
201
201
|
"tsdown": "^0.13.0",
|
|
202
202
|
"typescript": "^5.8.3",
|
|
203
|
-
"@optique/env": "1.1.0-dev.
|
|
203
|
+
"@optique/env": "1.1.0-dev.2160+4b3b85cc"
|
|
204
204
|
},
|
|
205
205
|
"scripts": {
|
|
206
206
|
"build": "tsdown",
|