@optique/core 1.1.0-dev.2148 → 1.1.0-dev.2152
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/valueparser.cjs +165 -9
- package/dist/valueparser.d.cts +29 -3
- package/dist/valueparser.d.ts +29 -3
- package/dist/valueparser.js +165 -9
- package/package.json +2 -2
package/dist/valueparser.cjs
CHANGED
|
@@ -3088,7 +3088,7 @@ function email(options) {
|
|
|
3088
3088
|
* Creates a value parser for socket addresses in "host:port" format.
|
|
3089
3089
|
*
|
|
3090
3090
|
* Validates socket addresses with support for:
|
|
3091
|
-
* - Hostnames
|
|
3091
|
+
* - Hostnames, IPv4 addresses, and IPv6 addresses
|
|
3092
3092
|
* - Configurable host:port separator
|
|
3093
3093
|
* - Optional default port
|
|
3094
3094
|
* - Host type filtering (hostname only, IP only, or both)
|
|
@@ -3099,6 +3099,8 @@ function email(options) {
|
|
|
3099
3099
|
* @throws {TypeError} If `separator` is an empty string.
|
|
3100
3100
|
* @throws {TypeError} If `separator` contains digit characters, since digits
|
|
3101
3101
|
* in the separator would cause ambiguous splitting of port input.
|
|
3102
|
+
* @throws {TypeError} If `host.version` is provided but is not `4`, `6`, or
|
|
3103
|
+
* `"both"`.
|
|
3102
3104
|
* @since 0.10.0
|
|
3103
3105
|
*
|
|
3104
3106
|
* @example
|
|
@@ -3128,12 +3130,25 @@ function socketAddress(options) {
|
|
|
3128
3130
|
const defaultPort = options?.defaultPort;
|
|
3129
3131
|
const requirePort = options?.requirePort ?? false;
|
|
3130
3132
|
const hostType = options?.host?.type ?? "both";
|
|
3133
|
+
const rawHostVersion = options?.host?.version;
|
|
3134
|
+
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)}.`);
|
|
3135
|
+
const hasLegacyIpOptions = options?.host?.ip !== void 0;
|
|
3136
|
+
const hasNewIpOptions = rawHostVersion !== void 0 || options?.host?.ipv4 !== void 0 || options?.host?.ipv6 !== void 0;
|
|
3137
|
+
const hostVersion = rawHostVersion ?? (hasLegacyIpOptions && !hasNewIpOptions ? 4 : "both");
|
|
3131
3138
|
const hostnameParser = hostname({
|
|
3132
3139
|
...options?.host?.hostname,
|
|
3133
3140
|
metavar: "HOST"
|
|
3134
3141
|
});
|
|
3135
|
-
const
|
|
3142
|
+
const ipv4Options = {
|
|
3136
3143
|
...options?.host?.ip,
|
|
3144
|
+
...options?.host?.ipv4
|
|
3145
|
+
};
|
|
3146
|
+
const ipv4Parser = ipv4({
|
|
3147
|
+
...ipv4Options,
|
|
3148
|
+
metavar: "HOST"
|
|
3149
|
+
});
|
|
3150
|
+
const ipv6Parser = ipv6({
|
|
3151
|
+
...options?.host?.ipv6,
|
|
3137
3152
|
metavar: "HOST"
|
|
3138
3153
|
});
|
|
3139
3154
|
const disambiguationParser = hostType === "ip" ? hostname({
|
|
@@ -3151,6 +3166,15 @@ function socketAddress(options) {
|
|
|
3151
3166
|
function looksLikeIpv4(input) {
|
|
3152
3167
|
return /^\d+\.\d+\.\d+\.\d+$/.test(input);
|
|
3153
3168
|
}
|
|
3169
|
+
function looksLikeIpv6(input) {
|
|
3170
|
+
const colonCount = input.match(/:/g)?.length ?? 0;
|
|
3171
|
+
return colonCount >= 2 && (input.includes("::") || /^[0-9a-fA-F:.]+$/.test(input));
|
|
3172
|
+
}
|
|
3173
|
+
function looksLikeBareIpv6(input) {
|
|
3174
|
+
if (!looksLikeIpv6(input)) return false;
|
|
3175
|
+
if (parseAndNormalizeIpv6(input) !== null) return true;
|
|
3176
|
+
return input.includes("::") || !input.includes(".");
|
|
3177
|
+
}
|
|
3154
3178
|
function looksLikeAltIpv4Literal(input) {
|
|
3155
3179
|
if (/^0[xX][0-9a-fA-F]+$/.test(input)) {
|
|
3156
3180
|
const n = parseInt(input.slice(2), 16);
|
|
@@ -3176,6 +3200,22 @@ function socketAddress(options) {
|
|
|
3176
3200
|
}
|
|
3177
3201
|
return false;
|
|
3178
3202
|
}
|
|
3203
|
+
function parseIpHost(hostInput) {
|
|
3204
|
+
if (hostVersion === 4) return ipv4Parser.parse(hostInput);
|
|
3205
|
+
if (hostVersion === 6) return ipv6Parser.parse(hostInput);
|
|
3206
|
+
if (looksLikeIpv6(hostInput)) {
|
|
3207
|
+
const result = ipv6Parser.parse(hostInput);
|
|
3208
|
+
if (result.success) {
|
|
3209
|
+
const mappedOctets = extractIpv4FromMapped(result.value);
|
|
3210
|
+
if (mappedOctets !== null) {
|
|
3211
|
+
const restrictionError = checkIpv4MappedRestrictions(mappedOctets, result.value, ipv4Options, void 0);
|
|
3212
|
+
if (restrictionError !== null) return restrictionError;
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
return result;
|
|
3216
|
+
}
|
|
3217
|
+
return ipv4Parser.parse(hostInput);
|
|
3218
|
+
}
|
|
3179
3219
|
function parseHost(hostInput) {
|
|
3180
3220
|
if (hostType === "hostname") {
|
|
3181
3221
|
if (looksLikeAltIpv4Literal(hostInput)) return {
|
|
@@ -3187,22 +3227,106 @@ function socketAddress(options) {
|
|
|
3187
3227
|
error: require_message.message`Expected a valid hostname, but got ${hostInput}.`
|
|
3188
3228
|
};
|
|
3189
3229
|
return hostnameParser.parse(hostInput);
|
|
3190
|
-
} else if (hostType === "ip") return
|
|
3230
|
+
} else if (hostType === "ip") return parseIpHost(hostInput);
|
|
3191
3231
|
else {
|
|
3192
3232
|
if (looksLikeAltIpv4Literal(hostInput)) return {
|
|
3193
3233
|
success: false,
|
|
3194
3234
|
error: require_message.message`${hostInput} appears to be a non-standard IPv4 address notation.`
|
|
3195
3235
|
};
|
|
3196
|
-
if (looksLikeIpv4(hostInput)) return
|
|
3236
|
+
if (looksLikeIpv4(hostInput)) return parseIpHost(hostInput);
|
|
3237
|
+
if (looksLikeIpv6(hostInput)) return parseIpHost(hostInput);
|
|
3197
3238
|
return hostnameParser.parse(hostInput);
|
|
3198
3239
|
}
|
|
3199
3240
|
}
|
|
3200
|
-
|
|
3241
|
+
function makeInvalidFormatError(input) {
|
|
3242
|
+
const errorMsg = options?.errors?.invalidFormat;
|
|
3243
|
+
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Expected a socket address in format ${require_message.text(formatExample)}, but got ${input}.`;
|
|
3244
|
+
return {
|
|
3245
|
+
success: false,
|
|
3246
|
+
error: msg
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
function makeMissingPortError(input) {
|
|
3250
|
+
const errorMsg = options?.errors?.missingPort;
|
|
3251
|
+
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Port number is required but was not specified.`;
|
|
3252
|
+
return {
|
|
3253
|
+
success: false,
|
|
3254
|
+
error: msg
|
|
3255
|
+
};
|
|
3256
|
+
}
|
|
3257
|
+
function parseBracketedHost(input, trimmed, canOmitPort) {
|
|
3258
|
+
if (!trimmed.startsWith("[")) return void 0;
|
|
3259
|
+
const closingBracket = trimmed.indexOf("]");
|
|
3260
|
+
if (closingBracket === -1) return makeInvalidFormatError(input);
|
|
3261
|
+
const hostInput = trimmed.slice(1, closingBracket);
|
|
3262
|
+
if (!looksLikeBareIpv6(hostInput)) return makeInvalidFormatError(input);
|
|
3263
|
+
const rest = trimmed.slice(closingBracket + 1).trimStart();
|
|
3264
|
+
const hostResult = parseHost(hostInput);
|
|
3265
|
+
if (rest === "") {
|
|
3266
|
+
if (!hostResult.success) {
|
|
3267
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3268
|
+
return {
|
|
3269
|
+
success: false,
|
|
3270
|
+
error: hostResult.error
|
|
3271
|
+
};
|
|
3272
|
+
}
|
|
3273
|
+
if (canOmitPort) return {
|
|
3274
|
+
success: true,
|
|
3275
|
+
value: {
|
|
3276
|
+
host: hostResult.value,
|
|
3277
|
+
port: defaultPort
|
|
3278
|
+
}
|
|
3279
|
+
};
|
|
3280
|
+
return makeMissingPortError(input);
|
|
3281
|
+
}
|
|
3282
|
+
if (!rest.startsWith(separator)) return makeInvalidFormatError(input);
|
|
3283
|
+
const portPart = rest.slice(separator.length).trim();
|
|
3284
|
+
if (portPart === "") {
|
|
3285
|
+
if (!hostResult.success) {
|
|
3286
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3287
|
+
return {
|
|
3288
|
+
success: false,
|
|
3289
|
+
error: hostResult.error
|
|
3290
|
+
};
|
|
3291
|
+
}
|
|
3292
|
+
return makeMissingPortError(input);
|
|
3293
|
+
}
|
|
3294
|
+
const portResult = portParser.parse(portPart);
|
|
3295
|
+
if (!hostResult.success) {
|
|
3296
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3297
|
+
return {
|
|
3298
|
+
success: false,
|
|
3299
|
+
error: hostResult.error
|
|
3300
|
+
};
|
|
3301
|
+
}
|
|
3302
|
+
if (portResult.success) return {
|
|
3303
|
+
success: true,
|
|
3304
|
+
value: {
|
|
3305
|
+
host: hostResult.value,
|
|
3306
|
+
port: portResult.value
|
|
3307
|
+
}
|
|
3308
|
+
};
|
|
3309
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3310
|
+
if (/^[0-9]+$/.test(portPart)) return {
|
|
3311
|
+
success: false,
|
|
3312
|
+
error: portResult.error
|
|
3313
|
+
};
|
|
3314
|
+
return makeInvalidFormatError(input);
|
|
3315
|
+
}
|
|
3316
|
+
function isSocketAddressValue(value) {
|
|
3317
|
+
return typeof value === "object" && value !== null && "host" in value && typeof value.host === "string" && "port" in value && typeof value.port === "number";
|
|
3318
|
+
}
|
|
3319
|
+
function formatSocketAddressValue(value) {
|
|
3320
|
+
const ipv6Host = parseAndNormalizeIpv6(value.host);
|
|
3321
|
+
if (separator === ":" && ipv6Host !== null) return `[${ipv6Host}]${separator}${value.port}`;
|
|
3322
|
+
return `${value.host}${separator}${value.port}`;
|
|
3323
|
+
}
|
|
3324
|
+
const parser = {
|
|
3201
3325
|
mode: "sync",
|
|
3202
3326
|
metavar: metavar$1,
|
|
3203
3327
|
get placeholder() {
|
|
3204
3328
|
return {
|
|
3205
|
-
host: hostType === "ip" ?
|
|
3329
|
+
host: hostType === "ip" ? hostVersion === 6 ? ipv6Parser.placeholder : ipv4Parser.placeholder : hostnameParser.placeholder,
|
|
3206
3330
|
port: defaultPort ?? portParser.placeholder
|
|
3207
3331
|
};
|
|
3208
3332
|
},
|
|
@@ -3210,6 +3334,28 @@ function socketAddress(options) {
|
|
|
3210
3334
|
const trimmed = input.trim();
|
|
3211
3335
|
const searchInput = input.trimStart();
|
|
3212
3336
|
const canOmitPort = defaultPort !== void 0 && !requirePort;
|
|
3337
|
+
if (separator === ":") {
|
|
3338
|
+
const bracketedResult = parseBracketedHost(input, trimmed, canOmitPort);
|
|
3339
|
+
if (bracketedResult !== void 0) return bracketedResult;
|
|
3340
|
+
if (looksLikeBareIpv6(trimmed)) {
|
|
3341
|
+
const hostResult = parseHost(trimmed);
|
|
3342
|
+
if (hostResult.success) {
|
|
3343
|
+
if (canOmitPort) return {
|
|
3344
|
+
success: true,
|
|
3345
|
+
value: {
|
|
3346
|
+
host: hostResult.value,
|
|
3347
|
+
port: defaultPort
|
|
3348
|
+
}
|
|
3349
|
+
};
|
|
3350
|
+
return makeMissingPortError(input);
|
|
3351
|
+
}
|
|
3352
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3353
|
+
return {
|
|
3354
|
+
success: false,
|
|
3355
|
+
error: hostResult.error
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3213
3359
|
let firstHostError;
|
|
3214
3360
|
let validHostInvalidPortError;
|
|
3215
3361
|
let trailingSepHost;
|
|
@@ -3407,9 +3553,19 @@ function socketAddress(options) {
|
|
|
3407
3553
|
};
|
|
3408
3554
|
},
|
|
3409
3555
|
format(value) {
|
|
3410
|
-
return
|
|
3556
|
+
return formatSocketAddressValue(value);
|
|
3557
|
+
},
|
|
3558
|
+
normalize(value) {
|
|
3559
|
+
if (!isSocketAddressValue(value)) return value;
|
|
3560
|
+
try {
|
|
3561
|
+
const result = parser.parse(formatSocketAddressValue(value));
|
|
3562
|
+
return result.success ? result.value : value;
|
|
3563
|
+
} catch {
|
|
3564
|
+
return value;
|
|
3565
|
+
}
|
|
3411
3566
|
}
|
|
3412
3567
|
};
|
|
3568
|
+
return parser;
|
|
3413
3569
|
}
|
|
3414
3570
|
function portRange(options) {
|
|
3415
3571
|
checkBooleanOption(options, "disallowWellKnown");
|
|
@@ -4323,7 +4479,7 @@ function parseAndNormalizeIpv6(input) {
|
|
|
4323
4479
|
const leftGroups = parts[0] ? parts[0].split(":") : [];
|
|
4324
4480
|
const rightGroups = parts[1] ? parts[1].split(":") : [];
|
|
4325
4481
|
const totalGroups = leftGroups.length + rightGroups.length;
|
|
4326
|
-
if (totalGroups
|
|
4482
|
+
if (totalGroups >= 8) return null;
|
|
4327
4483
|
const zeroCount = 8 - totalGroups;
|
|
4328
4484
|
const zeros = Array(zeroCount).fill("0");
|
|
4329
4485
|
groups = [
|
|
@@ -4354,7 +4510,7 @@ function expandIpv6(input) {
|
|
|
4354
4510
|
const leftGroups = parts[0] ? parts[0].split(":").filter((g) => g) : [];
|
|
4355
4511
|
const rightGroups = parts[1] ? parts[1].split(":").filter((g) => g) : [];
|
|
4356
4512
|
const totalGroups = leftGroups.length + rightGroups.length;
|
|
4357
|
-
if (totalGroups
|
|
4513
|
+
if (totalGroups >= 8) return null;
|
|
4358
4514
|
const zeroCount = 8 - totalGroups;
|
|
4359
4515
|
const zeros = Array(zeroCount).fill("0");
|
|
4360
4516
|
const groups = [
|
package/dist/valueparser.d.cts
CHANGED
|
@@ -1572,10 +1572,34 @@ interface SocketAddressOptions {
|
|
|
1572
1572
|
*/
|
|
1573
1573
|
readonly hostname?: Omit<HostnameOptions, "metavar" | "errors">;
|
|
1574
1574
|
/**
|
|
1575
|
-
*
|
|
1576
|
-
*
|
|
1575
|
+
* IP version to accept when `type` is `"ip"` or `"both"`.
|
|
1576
|
+
* - `4`: IPv4 only
|
|
1577
|
+
* - `6`: IPv6 only
|
|
1578
|
+
* - `"both"`: Accept both IPv4 and IPv6
|
|
1579
|
+
*
|
|
1580
|
+
* @default `"both"` unless only the legacy {@link ip} field is set, in
|
|
1581
|
+
* which case the default is `4` to preserve IPv4-only compatibility.
|
|
1582
|
+
* @since 1.1.0
|
|
1583
|
+
*/
|
|
1584
|
+
readonly version?: 4 | 6 | "both";
|
|
1585
|
+
/**
|
|
1586
|
+
* Options for IPv4 validation (when type is "ip" or "both").
|
|
1587
|
+
* This is kept for backwards compatibility; prefer {@link ipv4} for
|
|
1588
|
+
* new code.
|
|
1577
1589
|
*/
|
|
1578
1590
|
readonly ip?: Omit<Ipv4Options, "metavar" | "errors">;
|
|
1591
|
+
/**
|
|
1592
|
+
* Options for IPv4 validation (when type is "ip" or "both").
|
|
1593
|
+
*
|
|
1594
|
+
* @since 1.1.0
|
|
1595
|
+
*/
|
|
1596
|
+
readonly ipv4?: Omit<Ipv4Options, "metavar" | "errors">;
|
|
1597
|
+
/**
|
|
1598
|
+
* Options for IPv6 validation (when type is "ip" or "both").
|
|
1599
|
+
*
|
|
1600
|
+
* @since 1.1.0
|
|
1601
|
+
*/
|
|
1602
|
+
readonly ipv6?: Omit<Ipv6Options, "metavar" | "errors">;
|
|
1579
1603
|
};
|
|
1580
1604
|
/**
|
|
1581
1605
|
* Options for port validation.
|
|
@@ -1601,7 +1625,7 @@ interface SocketAddressOptions {
|
|
|
1601
1625
|
* Creates a value parser for socket addresses in "host:port" format.
|
|
1602
1626
|
*
|
|
1603
1627
|
* Validates socket addresses with support for:
|
|
1604
|
-
* - Hostnames
|
|
1628
|
+
* - Hostnames, IPv4 addresses, and IPv6 addresses
|
|
1605
1629
|
* - Configurable host:port separator
|
|
1606
1630
|
* - Optional default port
|
|
1607
1631
|
* - Host type filtering (hostname only, IP only, or both)
|
|
@@ -1612,6 +1636,8 @@ interface SocketAddressOptions {
|
|
|
1612
1636
|
* @throws {TypeError} If `separator` is an empty string.
|
|
1613
1637
|
* @throws {TypeError} If `separator` contains digit characters, since digits
|
|
1614
1638
|
* in the separator would cause ambiguous splitting of port input.
|
|
1639
|
+
* @throws {TypeError} If `host.version` is provided but is not `4`, `6`, or
|
|
1640
|
+
* `"both"`.
|
|
1615
1641
|
* @since 0.10.0
|
|
1616
1642
|
*
|
|
1617
1643
|
* @example
|
package/dist/valueparser.d.ts
CHANGED
|
@@ -1572,10 +1572,34 @@ interface SocketAddressOptions {
|
|
|
1572
1572
|
*/
|
|
1573
1573
|
readonly hostname?: Omit<HostnameOptions, "metavar" | "errors">;
|
|
1574
1574
|
/**
|
|
1575
|
-
*
|
|
1576
|
-
*
|
|
1575
|
+
* IP version to accept when `type` is `"ip"` or `"both"`.
|
|
1576
|
+
* - `4`: IPv4 only
|
|
1577
|
+
* - `6`: IPv6 only
|
|
1578
|
+
* - `"both"`: Accept both IPv4 and IPv6
|
|
1579
|
+
*
|
|
1580
|
+
* @default `"both"` unless only the legacy {@link ip} field is set, in
|
|
1581
|
+
* which case the default is `4` to preserve IPv4-only compatibility.
|
|
1582
|
+
* @since 1.1.0
|
|
1583
|
+
*/
|
|
1584
|
+
readonly version?: 4 | 6 | "both";
|
|
1585
|
+
/**
|
|
1586
|
+
* Options for IPv4 validation (when type is "ip" or "both").
|
|
1587
|
+
* This is kept for backwards compatibility; prefer {@link ipv4} for
|
|
1588
|
+
* new code.
|
|
1577
1589
|
*/
|
|
1578
1590
|
readonly ip?: Omit<Ipv4Options, "metavar" | "errors">;
|
|
1591
|
+
/**
|
|
1592
|
+
* Options for IPv4 validation (when type is "ip" or "both").
|
|
1593
|
+
*
|
|
1594
|
+
* @since 1.1.0
|
|
1595
|
+
*/
|
|
1596
|
+
readonly ipv4?: Omit<Ipv4Options, "metavar" | "errors">;
|
|
1597
|
+
/**
|
|
1598
|
+
* Options for IPv6 validation (when type is "ip" or "both").
|
|
1599
|
+
*
|
|
1600
|
+
* @since 1.1.0
|
|
1601
|
+
*/
|
|
1602
|
+
readonly ipv6?: Omit<Ipv6Options, "metavar" | "errors">;
|
|
1579
1603
|
};
|
|
1580
1604
|
/**
|
|
1581
1605
|
* Options for port validation.
|
|
@@ -1601,7 +1625,7 @@ interface SocketAddressOptions {
|
|
|
1601
1625
|
* Creates a value parser for socket addresses in "host:port" format.
|
|
1602
1626
|
*
|
|
1603
1627
|
* Validates socket addresses with support for:
|
|
1604
|
-
* - Hostnames
|
|
1628
|
+
* - Hostnames, IPv4 addresses, and IPv6 addresses
|
|
1605
1629
|
* - Configurable host:port separator
|
|
1606
1630
|
* - Optional default port
|
|
1607
1631
|
* - Host type filtering (hostname only, IP only, or both)
|
|
@@ -1612,6 +1636,8 @@ interface SocketAddressOptions {
|
|
|
1612
1636
|
* @throws {TypeError} If `separator` is an empty string.
|
|
1613
1637
|
* @throws {TypeError} If `separator` contains digit characters, since digits
|
|
1614
1638
|
* in the separator would cause ambiguous splitting of port input.
|
|
1639
|
+
* @throws {TypeError} If `host.version` is provided but is not `4`, `6`, or
|
|
1640
|
+
* `"both"`.
|
|
1615
1641
|
* @since 0.10.0
|
|
1616
1642
|
*
|
|
1617
1643
|
* @example
|
package/dist/valueparser.js
CHANGED
|
@@ -3088,7 +3088,7 @@ function email(options) {
|
|
|
3088
3088
|
* Creates a value parser for socket addresses in "host:port" format.
|
|
3089
3089
|
*
|
|
3090
3090
|
* Validates socket addresses with support for:
|
|
3091
|
-
* - Hostnames
|
|
3091
|
+
* - Hostnames, IPv4 addresses, and IPv6 addresses
|
|
3092
3092
|
* - Configurable host:port separator
|
|
3093
3093
|
* - Optional default port
|
|
3094
3094
|
* - Host type filtering (hostname only, IP only, or both)
|
|
@@ -3099,6 +3099,8 @@ function email(options) {
|
|
|
3099
3099
|
* @throws {TypeError} If `separator` is an empty string.
|
|
3100
3100
|
* @throws {TypeError} If `separator` contains digit characters, since digits
|
|
3101
3101
|
* in the separator would cause ambiguous splitting of port input.
|
|
3102
|
+
* @throws {TypeError} If `host.version` is provided but is not `4`, `6`, or
|
|
3103
|
+
* `"both"`.
|
|
3102
3104
|
* @since 0.10.0
|
|
3103
3105
|
*
|
|
3104
3106
|
* @example
|
|
@@ -3128,12 +3130,25 @@ function socketAddress(options) {
|
|
|
3128
3130
|
const defaultPort = options?.defaultPort;
|
|
3129
3131
|
const requirePort = options?.requirePort ?? false;
|
|
3130
3132
|
const hostType = options?.host?.type ?? "both";
|
|
3133
|
+
const rawHostVersion = options?.host?.version;
|
|
3134
|
+
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)}.`);
|
|
3135
|
+
const hasLegacyIpOptions = options?.host?.ip !== void 0;
|
|
3136
|
+
const hasNewIpOptions = rawHostVersion !== void 0 || options?.host?.ipv4 !== void 0 || options?.host?.ipv6 !== void 0;
|
|
3137
|
+
const hostVersion = rawHostVersion ?? (hasLegacyIpOptions && !hasNewIpOptions ? 4 : "both");
|
|
3131
3138
|
const hostnameParser = hostname({
|
|
3132
3139
|
...options?.host?.hostname,
|
|
3133
3140
|
metavar: "HOST"
|
|
3134
3141
|
});
|
|
3135
|
-
const
|
|
3142
|
+
const ipv4Options = {
|
|
3136
3143
|
...options?.host?.ip,
|
|
3144
|
+
...options?.host?.ipv4
|
|
3145
|
+
};
|
|
3146
|
+
const ipv4Parser = ipv4({
|
|
3147
|
+
...ipv4Options,
|
|
3148
|
+
metavar: "HOST"
|
|
3149
|
+
});
|
|
3150
|
+
const ipv6Parser = ipv6({
|
|
3151
|
+
...options?.host?.ipv6,
|
|
3137
3152
|
metavar: "HOST"
|
|
3138
3153
|
});
|
|
3139
3154
|
const disambiguationParser = hostType === "ip" ? hostname({
|
|
@@ -3151,6 +3166,15 @@ function socketAddress(options) {
|
|
|
3151
3166
|
function looksLikeIpv4(input) {
|
|
3152
3167
|
return /^\d+\.\d+\.\d+\.\d+$/.test(input);
|
|
3153
3168
|
}
|
|
3169
|
+
function looksLikeIpv6(input) {
|
|
3170
|
+
const colonCount = input.match(/:/g)?.length ?? 0;
|
|
3171
|
+
return colonCount >= 2 && (input.includes("::") || /^[0-9a-fA-F:.]+$/.test(input));
|
|
3172
|
+
}
|
|
3173
|
+
function looksLikeBareIpv6(input) {
|
|
3174
|
+
if (!looksLikeIpv6(input)) return false;
|
|
3175
|
+
if (parseAndNormalizeIpv6(input) !== null) return true;
|
|
3176
|
+
return input.includes("::") || !input.includes(".");
|
|
3177
|
+
}
|
|
3154
3178
|
function looksLikeAltIpv4Literal(input) {
|
|
3155
3179
|
if (/^0[xX][0-9a-fA-F]+$/.test(input)) {
|
|
3156
3180
|
const n = parseInt(input.slice(2), 16);
|
|
@@ -3176,6 +3200,22 @@ function socketAddress(options) {
|
|
|
3176
3200
|
}
|
|
3177
3201
|
return false;
|
|
3178
3202
|
}
|
|
3203
|
+
function parseIpHost(hostInput) {
|
|
3204
|
+
if (hostVersion === 4) return ipv4Parser.parse(hostInput);
|
|
3205
|
+
if (hostVersion === 6) return ipv6Parser.parse(hostInput);
|
|
3206
|
+
if (looksLikeIpv6(hostInput)) {
|
|
3207
|
+
const result = ipv6Parser.parse(hostInput);
|
|
3208
|
+
if (result.success) {
|
|
3209
|
+
const mappedOctets = extractIpv4FromMapped(result.value);
|
|
3210
|
+
if (mappedOctets !== null) {
|
|
3211
|
+
const restrictionError = checkIpv4MappedRestrictions(mappedOctets, result.value, ipv4Options, void 0);
|
|
3212
|
+
if (restrictionError !== null) return restrictionError;
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
return result;
|
|
3216
|
+
}
|
|
3217
|
+
return ipv4Parser.parse(hostInput);
|
|
3218
|
+
}
|
|
3179
3219
|
function parseHost(hostInput) {
|
|
3180
3220
|
if (hostType === "hostname") {
|
|
3181
3221
|
if (looksLikeAltIpv4Literal(hostInput)) return {
|
|
@@ -3187,22 +3227,106 @@ function socketAddress(options) {
|
|
|
3187
3227
|
error: message`Expected a valid hostname, but got ${hostInput}.`
|
|
3188
3228
|
};
|
|
3189
3229
|
return hostnameParser.parse(hostInput);
|
|
3190
|
-
} else if (hostType === "ip") return
|
|
3230
|
+
} else if (hostType === "ip") return parseIpHost(hostInput);
|
|
3191
3231
|
else {
|
|
3192
3232
|
if (looksLikeAltIpv4Literal(hostInput)) return {
|
|
3193
3233
|
success: false,
|
|
3194
3234
|
error: message`${hostInput} appears to be a non-standard IPv4 address notation.`
|
|
3195
3235
|
};
|
|
3196
|
-
if (looksLikeIpv4(hostInput)) return
|
|
3236
|
+
if (looksLikeIpv4(hostInput)) return parseIpHost(hostInput);
|
|
3237
|
+
if (looksLikeIpv6(hostInput)) return parseIpHost(hostInput);
|
|
3197
3238
|
return hostnameParser.parse(hostInput);
|
|
3198
3239
|
}
|
|
3199
3240
|
}
|
|
3200
|
-
|
|
3241
|
+
function makeInvalidFormatError(input) {
|
|
3242
|
+
const errorMsg = options?.errors?.invalidFormat;
|
|
3243
|
+
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? message`Expected a socket address in format ${text(formatExample)}, but got ${input}.`;
|
|
3244
|
+
return {
|
|
3245
|
+
success: false,
|
|
3246
|
+
error: msg
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
function makeMissingPortError(input) {
|
|
3250
|
+
const errorMsg = options?.errors?.missingPort;
|
|
3251
|
+
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? message`Port number is required but was not specified.`;
|
|
3252
|
+
return {
|
|
3253
|
+
success: false,
|
|
3254
|
+
error: msg
|
|
3255
|
+
};
|
|
3256
|
+
}
|
|
3257
|
+
function parseBracketedHost(input, trimmed, canOmitPort) {
|
|
3258
|
+
if (!trimmed.startsWith("[")) return void 0;
|
|
3259
|
+
const closingBracket = trimmed.indexOf("]");
|
|
3260
|
+
if (closingBracket === -1) return makeInvalidFormatError(input);
|
|
3261
|
+
const hostInput = trimmed.slice(1, closingBracket);
|
|
3262
|
+
if (!looksLikeBareIpv6(hostInput)) return makeInvalidFormatError(input);
|
|
3263
|
+
const rest = trimmed.slice(closingBracket + 1).trimStart();
|
|
3264
|
+
const hostResult = parseHost(hostInput);
|
|
3265
|
+
if (rest === "") {
|
|
3266
|
+
if (!hostResult.success) {
|
|
3267
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3268
|
+
return {
|
|
3269
|
+
success: false,
|
|
3270
|
+
error: hostResult.error
|
|
3271
|
+
};
|
|
3272
|
+
}
|
|
3273
|
+
if (canOmitPort) return {
|
|
3274
|
+
success: true,
|
|
3275
|
+
value: {
|
|
3276
|
+
host: hostResult.value,
|
|
3277
|
+
port: defaultPort
|
|
3278
|
+
}
|
|
3279
|
+
};
|
|
3280
|
+
return makeMissingPortError(input);
|
|
3281
|
+
}
|
|
3282
|
+
if (!rest.startsWith(separator)) return makeInvalidFormatError(input);
|
|
3283
|
+
const portPart = rest.slice(separator.length).trim();
|
|
3284
|
+
if (portPart === "") {
|
|
3285
|
+
if (!hostResult.success) {
|
|
3286
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3287
|
+
return {
|
|
3288
|
+
success: false,
|
|
3289
|
+
error: hostResult.error
|
|
3290
|
+
};
|
|
3291
|
+
}
|
|
3292
|
+
return makeMissingPortError(input);
|
|
3293
|
+
}
|
|
3294
|
+
const portResult = portParser.parse(portPart);
|
|
3295
|
+
if (!hostResult.success) {
|
|
3296
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3297
|
+
return {
|
|
3298
|
+
success: false,
|
|
3299
|
+
error: hostResult.error
|
|
3300
|
+
};
|
|
3301
|
+
}
|
|
3302
|
+
if (portResult.success) return {
|
|
3303
|
+
success: true,
|
|
3304
|
+
value: {
|
|
3305
|
+
host: hostResult.value,
|
|
3306
|
+
port: portResult.value
|
|
3307
|
+
}
|
|
3308
|
+
};
|
|
3309
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3310
|
+
if (/^[0-9]+$/.test(portPart)) return {
|
|
3311
|
+
success: false,
|
|
3312
|
+
error: portResult.error
|
|
3313
|
+
};
|
|
3314
|
+
return makeInvalidFormatError(input);
|
|
3315
|
+
}
|
|
3316
|
+
function isSocketAddressValue(value) {
|
|
3317
|
+
return typeof value === "object" && value !== null && "host" in value && typeof value.host === "string" && "port" in value && typeof value.port === "number";
|
|
3318
|
+
}
|
|
3319
|
+
function formatSocketAddressValue(value) {
|
|
3320
|
+
const ipv6Host = parseAndNormalizeIpv6(value.host);
|
|
3321
|
+
if (separator === ":" && ipv6Host !== null) return `[${ipv6Host}]${separator}${value.port}`;
|
|
3322
|
+
return `${value.host}${separator}${value.port}`;
|
|
3323
|
+
}
|
|
3324
|
+
const parser = {
|
|
3201
3325
|
mode: "sync",
|
|
3202
3326
|
metavar: metavar$1,
|
|
3203
3327
|
get placeholder() {
|
|
3204
3328
|
return {
|
|
3205
|
-
host: hostType === "ip" ?
|
|
3329
|
+
host: hostType === "ip" ? hostVersion === 6 ? ipv6Parser.placeholder : ipv4Parser.placeholder : hostnameParser.placeholder,
|
|
3206
3330
|
port: defaultPort ?? portParser.placeholder
|
|
3207
3331
|
};
|
|
3208
3332
|
},
|
|
@@ -3210,6 +3334,28 @@ function socketAddress(options) {
|
|
|
3210
3334
|
const trimmed = input.trim();
|
|
3211
3335
|
const searchInput = input.trimStart();
|
|
3212
3336
|
const canOmitPort = defaultPort !== void 0 && !requirePort;
|
|
3337
|
+
if (separator === ":") {
|
|
3338
|
+
const bracketedResult = parseBracketedHost(input, trimmed, canOmitPort);
|
|
3339
|
+
if (bracketedResult !== void 0) return bracketedResult;
|
|
3340
|
+
if (looksLikeBareIpv6(trimmed)) {
|
|
3341
|
+
const hostResult = parseHost(trimmed);
|
|
3342
|
+
if (hostResult.success) {
|
|
3343
|
+
if (canOmitPort) return {
|
|
3344
|
+
success: true,
|
|
3345
|
+
value: {
|
|
3346
|
+
host: hostResult.value,
|
|
3347
|
+
port: defaultPort
|
|
3348
|
+
}
|
|
3349
|
+
};
|
|
3350
|
+
return makeMissingPortError(input);
|
|
3351
|
+
}
|
|
3352
|
+
if (options?.errors?.invalidFormat) return makeInvalidFormatError(input);
|
|
3353
|
+
return {
|
|
3354
|
+
success: false,
|
|
3355
|
+
error: hostResult.error
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3213
3359
|
let firstHostError;
|
|
3214
3360
|
let validHostInvalidPortError;
|
|
3215
3361
|
let trailingSepHost;
|
|
@@ -3407,9 +3553,19 @@ function socketAddress(options) {
|
|
|
3407
3553
|
};
|
|
3408
3554
|
},
|
|
3409
3555
|
format(value) {
|
|
3410
|
-
return
|
|
3556
|
+
return formatSocketAddressValue(value);
|
|
3557
|
+
},
|
|
3558
|
+
normalize(value) {
|
|
3559
|
+
if (!isSocketAddressValue(value)) return value;
|
|
3560
|
+
try {
|
|
3561
|
+
const result = parser.parse(formatSocketAddressValue(value));
|
|
3562
|
+
return result.success ? result.value : value;
|
|
3563
|
+
} catch {
|
|
3564
|
+
return value;
|
|
3565
|
+
}
|
|
3411
3566
|
}
|
|
3412
3567
|
};
|
|
3568
|
+
return parser;
|
|
3413
3569
|
}
|
|
3414
3570
|
function portRange(options) {
|
|
3415
3571
|
checkBooleanOption(options, "disallowWellKnown");
|
|
@@ -4323,7 +4479,7 @@ function parseAndNormalizeIpv6(input) {
|
|
|
4323
4479
|
const leftGroups = parts[0] ? parts[0].split(":") : [];
|
|
4324
4480
|
const rightGroups = parts[1] ? parts[1].split(":") : [];
|
|
4325
4481
|
const totalGroups = leftGroups.length + rightGroups.length;
|
|
4326
|
-
if (totalGroups
|
|
4482
|
+
if (totalGroups >= 8) return null;
|
|
4327
4483
|
const zeroCount = 8 - totalGroups;
|
|
4328
4484
|
const zeros = Array(zeroCount).fill("0");
|
|
4329
4485
|
groups = [
|
|
@@ -4354,7 +4510,7 @@ function expandIpv6(input) {
|
|
|
4354
4510
|
const leftGroups = parts[0] ? parts[0].split(":").filter((g) => g) : [];
|
|
4355
4511
|
const rightGroups = parts[1] ? parts[1].split(":").filter((g) => g) : [];
|
|
4356
4512
|
const totalGroups = leftGroups.length + rightGroups.length;
|
|
4357
|
-
if (totalGroups
|
|
4513
|
+
if (totalGroups >= 8) return null;
|
|
4358
4514
|
const zeroCount = 8 - totalGroups;
|
|
4359
4515
|
const zeros = Array(zeroCount).fill("0");
|
|
4360
4516
|
const groups = [
|
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.2152",
|
|
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.2152+cc0a0741"
|
|
204
204
|
},
|
|
205
205
|
"scripts": {
|
|
206
206
|
"build": "tsdown",
|