@optique/core 1.0.0-dev.921 → 1.0.1
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/annotation-state.cjs +425 -0
- package/dist/annotation-state.d.cts +24 -0
- package/dist/annotation-state.d.ts +24 -0
- package/dist/annotation-state.js +414 -0
- package/dist/annotations.cjs +2 -248
- package/dist/annotations.d.cts +2 -137
- package/dist/annotations.d.ts +2 -137
- package/dist/annotations.js +2 -238
- package/dist/completion.cjs +611 -100
- package/dist/completion.d.cts +1 -1
- package/dist/completion.d.ts +1 -1
- package/dist/completion.js +611 -100
- package/dist/constructs.cjs +3338 -827
- package/dist/constructs.d.cts +48 -7
- package/dist/constructs.d.ts +48 -7
- package/dist/constructs.js +3338 -827
- package/dist/context.cjs +0 -23
- package/dist/context.d.cts +119 -53
- package/dist/context.d.ts +119 -53
- package/dist/context.js +0 -22
- package/dist/dependency-metadata.cjs +139 -0
- package/dist/dependency-metadata.d.cts +112 -0
- package/dist/dependency-metadata.d.ts +112 -0
- package/dist/dependency-metadata.js +138 -0
- package/dist/dependency-runtime.cjs +698 -0
- package/dist/dependency-runtime.d.cts +149 -0
- package/dist/dependency-runtime.d.ts +149 -0
- package/dist/dependency-runtime.js +687 -0
- package/dist/dependency.cjs +7 -928
- package/dist/dependency.d.cts +2 -794
- package/dist/dependency.d.ts +2 -794
- package/dist/dependency.js +2 -899
- package/dist/displaywidth.cjs +44 -0
- package/dist/displaywidth.js +43 -0
- package/dist/doc.cjs +285 -23
- package/dist/doc.d.cts +57 -2
- package/dist/doc.d.ts +57 -2
- package/dist/doc.js +283 -25
- package/dist/execution-context.cjs +56 -0
- package/dist/execution-context.js +53 -0
- package/dist/extension.cjs +87 -0
- package/dist/extension.d.cts +97 -0
- package/dist/extension.d.ts +97 -0
- package/dist/extension.js +76 -0
- package/dist/facade.cjs +718 -525
- package/dist/facade.d.cts +59 -15
- package/dist/facade.d.ts +59 -15
- package/dist/facade.js +718 -525
- package/dist/index.cjs +14 -29
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +7 -7
- package/dist/input-trace.cjs +56 -0
- package/dist/input-trace.d.cts +77 -0
- package/dist/input-trace.d.ts +77 -0
- package/dist/input-trace.js +55 -0
- package/dist/internal/annotations.cjs +316 -0
- package/dist/internal/annotations.d.cts +140 -0
- package/dist/internal/annotations.d.ts +140 -0
- package/dist/internal/annotations.js +306 -0
- package/dist/internal/dependency.cjs +984 -0
- package/dist/internal/dependency.d.cts +539 -0
- package/dist/internal/dependency.d.ts +539 -0
- package/dist/internal/dependency.js +964 -0
- package/dist/{mode-dispatch.cjs → internal/mode-dispatch.cjs} +1 -3
- package/dist/{mode-dispatch.d.cts → internal/mode-dispatch.d.cts} +3 -7
- package/dist/{mode-dispatch.d.ts → internal/mode-dispatch.d.ts} +3 -7
- package/dist/{mode-dispatch.js → internal/mode-dispatch.js} +1 -3
- package/dist/internal/parser.cjs +728 -0
- package/dist/internal/parser.d.cts +947 -0
- package/dist/internal/parser.d.ts +947 -0
- package/dist/internal/parser.js +711 -0
- package/dist/message.cjs +84 -26
- package/dist/message.d.cts +49 -9
- package/dist/message.d.ts +49 -9
- package/dist/message.js +84 -27
- package/dist/modifiers.cjs +1023 -240
- package/dist/modifiers.d.cts +42 -1
- package/dist/modifiers.d.ts +42 -1
- package/dist/modifiers.js +1023 -240
- package/dist/parser.cjs +11 -463
- package/dist/parser.d.cts +3 -537
- package/dist/parser.d.ts +3 -537
- package/dist/parser.js +2 -433
- package/dist/phase2-seed.cjs +59 -0
- package/dist/phase2-seed.js +56 -0
- package/dist/primitives.cjs +557 -208
- package/dist/primitives.d.cts +10 -14
- package/dist/primitives.d.ts +10 -14
- package/dist/primitives.js +557 -208
- package/dist/program.cjs +5 -1
- package/dist/program.d.cts +5 -3
- package/dist/program.d.ts +5 -3
- package/dist/program.js +6 -1
- package/dist/suggestion.cjs +22 -8
- package/dist/suggestion.js +22 -8
- package/dist/usage-internals.cjs +3 -2
- package/dist/usage-internals.js +4 -2
- package/dist/usage.cjs +195 -40
- package/dist/usage.d.cts +92 -11
- package/dist/usage.d.ts +92 -11
- package/dist/usage.js +194 -41
- package/dist/validate.cjs +170 -0
- package/dist/validate.js +164 -0
- package/dist/valueparser.cjs +1270 -187
- package/dist/valueparser.d.cts +320 -14
- package/dist/valueparser.d.ts +320 -14
- package/dist/valueparser.js +1269 -188
- package/package.json +9 -9
package/dist/valueparser.cjs
CHANGED
|
@@ -6,9 +6,18 @@ const require_nonempty = require('./nonempty.cjs');
|
|
|
6
6
|
* A predicate function that checks if an object is a {@link ValueParser}.
|
|
7
7
|
* @param object The object to check.
|
|
8
8
|
* @return `true` if the object is a {@link ValueParser}, `false` otherwise.
|
|
9
|
+
* @throws {TypeError} If the object looks like a value parser (has `mode`,
|
|
10
|
+
* `metavar`, `parse`, and `format`) but is missing the required
|
|
11
|
+
* `placeholder` property.
|
|
9
12
|
*/
|
|
10
13
|
function isValueParser(object) {
|
|
11
|
-
|
|
14
|
+
if (typeof object !== "object" || object == null || !("mode" in object) || object.mode !== "sync" && object.mode !== "async") return false;
|
|
15
|
+
const hasMetavar = "metavar" in object && typeof object.metavar === "string";
|
|
16
|
+
const hasParse = "parse" in object && typeof object.parse === "function";
|
|
17
|
+
const hasFormat = "format" in object && typeof object.format === "function";
|
|
18
|
+
const hasPlaceholder = "placeholder" in object;
|
|
19
|
+
if (hasMetavar && hasParse && hasFormat && !hasPlaceholder) throw new TypeError("Value parser is missing the required placeholder property. All value parsers must define a placeholder value.");
|
|
20
|
+
return hasMetavar && hasParse && hasFormat && hasPlaceholder;
|
|
12
21
|
}
|
|
13
22
|
/**
|
|
14
23
|
* Implementation of the choice parser for both string and number types.
|
|
@@ -47,8 +56,9 @@ function choice(choices, options = {}) {
|
|
|
47
56
|
const numberStrings = numberChoices.map((v) => Object.is(v, -0) ? "-0" : String(v));
|
|
48
57
|
const frozenNumberChoices = Object.freeze(numberChoices);
|
|
49
58
|
return {
|
|
50
|
-
|
|
59
|
+
mode: "sync",
|
|
51
60
|
metavar,
|
|
61
|
+
placeholder: choices[0],
|
|
52
62
|
choices: frozenNumberChoices,
|
|
53
63
|
parse(input) {
|
|
54
64
|
const index = numberStrings.indexOf(input);
|
|
@@ -113,7 +123,7 @@ function choice(choices, options = {}) {
|
|
|
113
123
|
}
|
|
114
124
|
const stringChoices = Object.freeze([...new Set(choices)]);
|
|
115
125
|
const stringOptions = options;
|
|
116
|
-
|
|
126
|
+
checkBooleanOption(stringOptions, "caseInsensitive");
|
|
117
127
|
const caseInsensitive = stringOptions.caseInsensitive ?? false;
|
|
118
128
|
const normalizedValues = caseInsensitive ? stringChoices.map((v) => v.toLowerCase()) : stringChoices;
|
|
119
129
|
if (caseInsensitive) {
|
|
@@ -128,8 +138,9 @@ function choice(choices, options = {}) {
|
|
|
128
138
|
}
|
|
129
139
|
const stringInvalidChoice = stringOptions.errors?.invalidChoice;
|
|
130
140
|
return {
|
|
131
|
-
|
|
141
|
+
mode: "sync",
|
|
132
142
|
metavar,
|
|
143
|
+
placeholder: choices[0],
|
|
133
144
|
choices: stringChoices,
|
|
134
145
|
parse(input) {
|
|
135
146
|
const normalizedInput = caseInsensitive ? input.toLowerCase() : input;
|
|
@@ -159,6 +170,40 @@ function choice(choices, options = {}) {
|
|
|
159
170
|
};
|
|
160
171
|
}
|
|
161
172
|
/**
|
|
173
|
+
* Validates that an option value, if present, is a boolean.
|
|
174
|
+
* Throws a {@link TypeError} if the value is defined but not a boolean.
|
|
175
|
+
*
|
|
176
|
+
* @template T The type of the options object.
|
|
177
|
+
* @param options The options object to check.
|
|
178
|
+
* @param key The key of the option to validate.
|
|
179
|
+
* @throws {TypeError} If the option value is defined but not a boolean.
|
|
180
|
+
* @since 1.0.0
|
|
181
|
+
*/
|
|
182
|
+
function checkBooleanOption(options, key) {
|
|
183
|
+
const value = options?.[key];
|
|
184
|
+
if (value !== void 0 && typeof value !== "boolean") throw new TypeError(`Expected ${String(key)} to be a boolean, but got ${typeof value}: ${String(value)}.`);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Validates that an option value, if present, is one of the allowed values.
|
|
188
|
+
* Throws a {@link TypeError} if the value is defined but not in the allowed
|
|
189
|
+
* list.
|
|
190
|
+
*
|
|
191
|
+
* @template T The type of the options object.
|
|
192
|
+
* @param options The options object to check.
|
|
193
|
+
* @param key The key of the option to validate.
|
|
194
|
+
* @param allowed The list of allowed values.
|
|
195
|
+
* @throws {TypeError} If the option value is defined but not in the allowed
|
|
196
|
+
* list.
|
|
197
|
+
* @since 1.0.0
|
|
198
|
+
*/
|
|
199
|
+
function checkEnumOption(options, key, allowed) {
|
|
200
|
+
const value = options?.[key];
|
|
201
|
+
if (value !== void 0 && (typeof value !== "string" || !allowed.includes(value))) {
|
|
202
|
+
const rendered = typeof value === "string" ? JSON.stringify(value) : typeof value === "symbol" ? value.toString() : String(value);
|
|
203
|
+
throw new TypeError(`Expected ${String(key)} to be one of ${allowed.map((v) => JSON.stringify(v)).join(", ")}, but got ${typeof value}: ${rendered}.`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
162
207
|
* Expands a numeric string in scientific notation (e.g., `"1e+21"`,
|
|
163
208
|
* `"1.5e-3"`, `".1e-6"`) into plain decimal form for normalization.
|
|
164
209
|
* Used for both canonical `String(number)` output and user input.
|
|
@@ -226,7 +271,10 @@ function formatNumberChoiceError(input, validChoices, allChoices, invalidChoice)
|
|
|
226
271
|
function formatDefaultChoiceError(input, choices) {
|
|
227
272
|
const choiceStrings = choices.filter((c) => typeof c === "string" || !Number.isNaN(c)).map((c) => Object.is(c, -0) ? "-0" : String(c));
|
|
228
273
|
if (choiceStrings.length === 0 && choices.length > 0) return require_message.message`No valid choices are configured, but got ${input}.`;
|
|
229
|
-
return require_message.message`Expected one of ${require_message.valueSet(choiceStrings, {
|
|
274
|
+
return require_message.message`Expected one of ${require_message.valueSet(choiceStrings, {
|
|
275
|
+
fallback: "",
|
|
276
|
+
locale: "en-US"
|
|
277
|
+
})}, but got ${input}.`;
|
|
230
278
|
}
|
|
231
279
|
/**
|
|
232
280
|
* Creates a {@link ValueParser} for strings.
|
|
@@ -252,8 +300,9 @@ function string(options = {}) {
|
|
|
252
300
|
const patternFlags = options.pattern?.flags ?? null;
|
|
253
301
|
const patternMismatch = options.errors?.patternMismatch;
|
|
254
302
|
return {
|
|
255
|
-
|
|
303
|
+
mode: "sync",
|
|
256
304
|
metavar,
|
|
305
|
+
placeholder: options.placeholder ?? "",
|
|
257
306
|
parse(input) {
|
|
258
307
|
if (patternSource != null && patternFlags != null) {
|
|
259
308
|
const pattern = new RegExp(patternSource, patternFlags);
|
|
@@ -308,14 +357,25 @@ function string(options = {}) {
|
|
|
308
357
|
* @param options Configuration options specifying the type and constraints.
|
|
309
358
|
* @returns A {@link ValueParser} that converts string input to the specified
|
|
310
359
|
* integer type.
|
|
360
|
+
* @throws {TypeError} If `options.type` is provided but is neither `"number"`
|
|
361
|
+
* nor `"bigint"`.
|
|
362
|
+
* @throws {RangeError} If the configured min/max range for number mode contains
|
|
363
|
+
* no safe integers.
|
|
311
364
|
*/
|
|
312
365
|
function integer(options) {
|
|
366
|
+
if (options?.type !== void 0 && options.type !== "number" && options.type !== "bigint") throw new TypeError(`Expected type to be "number" or "bigint", but got: ${String(options.type)}.`);
|
|
367
|
+
if (options?.type !== "bigint") {
|
|
368
|
+
if (options?.min != null && !Number.isFinite(options.min)) throw new RangeError(`Expected min to be a finite number, but got: ${options.min}`);
|
|
369
|
+
if (options?.max != null && !Number.isFinite(options.max)) throw new RangeError(`Expected max to be a finite number, but got: ${options.max}`);
|
|
370
|
+
}
|
|
371
|
+
if (options?.min != null && options?.max != null && options.min > options.max) throw new RangeError(`Expected min to be less than or equal to max, but got min: ${options.min} and max: ${options.max}.`);
|
|
313
372
|
if (options?.type === "bigint") {
|
|
314
373
|
const metavar$1 = options.metavar ?? "INTEGER";
|
|
315
374
|
require_nonempty.ensureNonEmptyString(metavar$1);
|
|
316
375
|
return {
|
|
317
|
-
|
|
376
|
+
mode: "sync",
|
|
318
377
|
metavar: metavar$1,
|
|
378
|
+
placeholder: options?.placeholder ?? (options?.min != null && options.min > 0n ? options.min : options?.max != null && options.max < 0n ? options.max : 0n),
|
|
319
379
|
parse(input) {
|
|
320
380
|
if (!input.match(/^-?\d+$/)) return {
|
|
321
381
|
success: false,
|
|
@@ -344,6 +404,11 @@ function integer(options) {
|
|
|
344
404
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
345
405
|
const maxSafe = BigInt(Number.MAX_SAFE_INTEGER);
|
|
346
406
|
const minSafe = BigInt(Number.MIN_SAFE_INTEGER);
|
|
407
|
+
const safeMin = Math.max(options?.min ?? Number.MIN_SAFE_INTEGER, Number.MIN_SAFE_INTEGER);
|
|
408
|
+
const safeMax = Math.min(options?.max ?? Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
|
|
409
|
+
const firstAllowed = Math.ceil(safeMin);
|
|
410
|
+
const lastAllowed = Math.floor(safeMax);
|
|
411
|
+
if (firstAllowed > lastAllowed) throw new RangeError("The configured integer range contains no safe integers. Use type: \"bigint\" instead.");
|
|
347
412
|
const unsafeIntegerError = options?.errors?.unsafeInteger;
|
|
348
413
|
function makeUnsafeIntegerError(input) {
|
|
349
414
|
return {
|
|
@@ -352,8 +417,9 @@ function integer(options) {
|
|
|
352
417
|
};
|
|
353
418
|
}
|
|
354
419
|
return {
|
|
355
|
-
|
|
420
|
+
mode: "sync",
|
|
356
421
|
metavar,
|
|
422
|
+
placeholder: options?.placeholder ?? (firstAllowed > 0 ? firstAllowed : lastAllowed < 0 ? lastAllowed : 0),
|
|
357
423
|
parse(input) {
|
|
358
424
|
if (!input.match(/^-?\d+$/)) return {
|
|
359
425
|
success: false,
|
|
@@ -395,12 +461,16 @@ function integer(options) {
|
|
|
395
461
|
* numbers.
|
|
396
462
|
*/
|
|
397
463
|
function float(options = {}) {
|
|
464
|
+
if (options.min != null && !Number.isFinite(options.min)) throw new RangeError(`Expected min to be a finite number, but got: ${options.min}`);
|
|
465
|
+
if (options.max != null && !Number.isFinite(options.max)) throw new RangeError(`Expected max to be a finite number, but got: ${options.max}`);
|
|
466
|
+
if (options.min != null && options.max != null && options.min > options.max) throw new RangeError(`Expected min to be less than or equal to max, but got min: ${options.min} and max: ${options.max}.`);
|
|
398
467
|
const floatRegex = /^[+-]?(?:(?:\d+\.?\d*)|(?:\d*\.\d+))(?:[eE][+-]?\d+)?$/;
|
|
399
468
|
const metavar = options.metavar ?? "NUMBER";
|
|
400
469
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
401
470
|
return {
|
|
402
|
-
|
|
471
|
+
mode: "sync",
|
|
403
472
|
metavar,
|
|
473
|
+
placeholder: options?.placeholder ?? (options?.min != null && options.min > 0 ? options.min : options?.max != null && options.max < 0 ? options.max : 0),
|
|
404
474
|
parse(input) {
|
|
405
475
|
const invalidNumber = (i) => ({
|
|
406
476
|
success: false,
|
|
@@ -435,6 +505,19 @@ function float(options = {}) {
|
|
|
435
505
|
};
|
|
436
506
|
}
|
|
437
507
|
/**
|
|
508
|
+
* The set of URL schemes that are considered "special" by the WHATWG URL
|
|
509
|
+
* Standard. These schemes always use the `://` authority syntax.
|
|
510
|
+
* Non-special schemes use only `:` (e.g., `mailto:`, `urn:`).
|
|
511
|
+
*/
|
|
512
|
+
const SPECIAL_URL_SCHEMES = new Set([
|
|
513
|
+
"ftp",
|
|
514
|
+
"file",
|
|
515
|
+
"http",
|
|
516
|
+
"https",
|
|
517
|
+
"ws",
|
|
518
|
+
"wss"
|
|
519
|
+
]);
|
|
520
|
+
/**
|
|
438
521
|
* Creates a {@link ValueParser} for URL values.
|
|
439
522
|
*
|
|
440
523
|
* This parser validates that the input is a well-formed URL and optionally
|
|
@@ -442,17 +525,39 @@ function float(options = {}) {
|
|
|
442
525
|
* object.
|
|
443
526
|
* @param options Configuration options for the URL parser.
|
|
444
527
|
* @returns A {@link ValueParser} that converts string input to `URL` objects.
|
|
528
|
+
* @throws {TypeError} If any `allowedProtocols` entry is not a valid protocol
|
|
529
|
+
* string ending with a colon (e.g., `"https:"`).
|
|
445
530
|
*/
|
|
446
531
|
function url(options = {}) {
|
|
447
|
-
const
|
|
448
|
-
const
|
|
532
|
+
const originalProtocolsList = [];
|
|
533
|
+
const normalizedProtocolsList = [];
|
|
534
|
+
if (options.allowedProtocols != null) {
|
|
535
|
+
const seen = /* @__PURE__ */ new Set();
|
|
536
|
+
for (const protocol of options.allowedProtocols) {
|
|
537
|
+
if (typeof protocol !== "string" || !/^[a-z][a-z0-9+\-.]*:$/i.test(protocol)) {
|
|
538
|
+
const rendered = typeof protocol === "string" ? JSON.stringify(protocol) : String(protocol);
|
|
539
|
+
throw new TypeError(`Each allowed protocol must be a valid protocol ending with a colon (e.g., "https:"), got: ${rendered}.`);
|
|
540
|
+
}
|
|
541
|
+
const normalized = protocol.toLowerCase();
|
|
542
|
+
if (seen.has(normalized)) continue;
|
|
543
|
+
seen.add(normalized);
|
|
544
|
+
originalProtocolsList.push(protocol);
|
|
545
|
+
normalizedProtocolsList.push(normalized);
|
|
546
|
+
}
|
|
547
|
+
if (originalProtocolsList.length === 0) throw new TypeError("allowedProtocols must not be empty.");
|
|
548
|
+
}
|
|
549
|
+
const originalProtocols = options.allowedProtocols != null ? Object.freeze(originalProtocolsList) : void 0;
|
|
550
|
+
const allowedProtocols = options.allowedProtocols != null ? Object.freeze(normalizedProtocolsList) : void 0;
|
|
449
551
|
const metavar = options.metavar ?? "URL";
|
|
450
552
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
451
553
|
const invalidUrl = options.errors?.invalidUrl;
|
|
452
554
|
const disallowedProtocol = options.errors?.disallowedProtocol;
|
|
453
555
|
return {
|
|
454
|
-
|
|
556
|
+
mode: "sync",
|
|
455
557
|
metavar,
|
|
558
|
+
get placeholder() {
|
|
559
|
+
return new URL(`${allowedProtocols?.[0] ?? "http:"}//0.invalid`);
|
|
560
|
+
},
|
|
456
561
|
parse(input) {
|
|
457
562
|
if (!URL.canParse(input)) return {
|
|
458
563
|
success: false,
|
|
@@ -461,7 +566,28 @@ function url(options = {}) {
|
|
|
461
566
|
const url$1 = new URL(input);
|
|
462
567
|
if (allowedProtocols != null && !allowedProtocols.includes(url$1.protocol)) return {
|
|
463
568
|
success: false,
|
|
464
|
-
error: disallowedProtocol ? typeof disallowedProtocol === "function" ? disallowedProtocol(url$1.protocol, originalProtocols) : disallowedProtocol :
|
|
569
|
+
error: disallowedProtocol ? typeof disallowedProtocol === "function" ? disallowedProtocol(url$1.protocol, originalProtocols) : disallowedProtocol : [
|
|
570
|
+
{
|
|
571
|
+
type: "text",
|
|
572
|
+
text: "URL protocol "
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
type: "value",
|
|
576
|
+
value: url$1.protocol
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
type: "text",
|
|
580
|
+
text: " is not allowed. Allowed protocols: "
|
|
581
|
+
},
|
|
582
|
+
...require_message.valueSet(originalProtocols, {
|
|
583
|
+
fallback: "",
|
|
584
|
+
locale: "en-US"
|
|
585
|
+
}),
|
|
586
|
+
{
|
|
587
|
+
type: "text",
|
|
588
|
+
text: "."
|
|
589
|
+
}
|
|
590
|
+
]
|
|
465
591
|
};
|
|
466
592
|
return {
|
|
467
593
|
success: true,
|
|
@@ -472,12 +598,15 @@ function url(options = {}) {
|
|
|
472
598
|
return value.href;
|
|
473
599
|
},
|
|
474
600
|
*suggest(prefix) {
|
|
475
|
-
if (allowedProtocols && prefix.length > 0 && !prefix.includes("
|
|
601
|
+
if (allowedProtocols && prefix.length > 0 && !prefix.includes(":")) for (const protocol of allowedProtocols) {
|
|
476
602
|
const cleanProtocol = protocol.replace(/:+$/, "");
|
|
477
|
-
if (cleanProtocol.startsWith(prefix.toLowerCase()))
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
603
|
+
if (cleanProtocol.startsWith(prefix.toLowerCase())) {
|
|
604
|
+
const suffix = SPECIAL_URL_SCHEMES.has(cleanProtocol) ? "://" : ":";
|
|
605
|
+
yield {
|
|
606
|
+
kind: "literal",
|
|
607
|
+
text: `${cleanProtocol}${suffix}`
|
|
608
|
+
};
|
|
609
|
+
}
|
|
481
610
|
}
|
|
482
611
|
}
|
|
483
612
|
};
|
|
@@ -496,8 +625,9 @@ function locale(options = {}) {
|
|
|
496
625
|
const metavar = options.metavar ?? "LOCALE";
|
|
497
626
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
498
627
|
return {
|
|
499
|
-
|
|
628
|
+
mode: "sync",
|
|
500
629
|
metavar,
|
|
630
|
+
placeholder: new Intl.Locale("und"),
|
|
501
631
|
parse(input) {
|
|
502
632
|
let locale$1;
|
|
503
633
|
try {
|
|
@@ -754,31 +884,59 @@ function locale(options = {}) {
|
|
|
754
884
|
*
|
|
755
885
|
* This parser validates that the input is a well-formed UUID string in the
|
|
756
886
|
* standard format: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` where each `x`
|
|
757
|
-
* is a hexadecimal digit.
|
|
758
|
-
* UUID versions.
|
|
887
|
+
* is a hexadecimal digit.
|
|
759
888
|
*
|
|
889
|
+
* By default, the parser enforces strict [RFC 9562] validation: it requires
|
|
890
|
+
* a standardized version digit (1 through 8) and the RFC 9562 variant bits
|
|
891
|
+
* (`10xx`). The nil and max UUIDs are accepted as special standard values.
|
|
892
|
+
* Set `strict: false` to disable the default RFC 9562 version/variant
|
|
893
|
+
* checks. An explicit {@link UuidOptions.allowedVersions} list still
|
|
894
|
+
* constrains the version nibble even in lenient mode.
|
|
895
|
+
*
|
|
896
|
+
* [RFC 9562]: https://www.rfc-editor.org/rfc/rfc9562
|
|
760
897
|
* @param options Configuration options for the UUID parser.
|
|
761
898
|
* @returns A {@link ValueParser} that converts string input to {@link Uuid}
|
|
762
899
|
* strings.
|
|
900
|
+
* @throws {TypeError} If any element of
|
|
901
|
+
* {@link UuidOptions.allowedVersions} is not an integer.
|
|
902
|
+
* @throws {RangeError} If any element of
|
|
903
|
+
* {@link UuidOptions.allowedVersions} is outside the range 1 to 8.
|
|
763
904
|
*/
|
|
764
905
|
function uuid(options = {}) {
|
|
765
906
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
766
907
|
const metavar = options.metavar ?? "UUID";
|
|
767
908
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
768
|
-
|
|
909
|
+
checkBooleanOption(options, "strict");
|
|
910
|
+
const strict = options.strict !== false;
|
|
911
|
+
const allowedVersions = options.allowedVersions != null ? (() => {
|
|
912
|
+
const unique = /* @__PURE__ */ new Set();
|
|
913
|
+
for (const v of options.allowedVersions) {
|
|
914
|
+
if (!Number.isInteger(v)) throw new TypeError(`Expected every element of allowedVersions to be an integer, but got value "${typeof v === "symbol" ? v.toString() : String(v)}" of type "${Array.isArray(v) ? "array" : v === null ? "null" : typeof v}".`);
|
|
915
|
+
if (v < 1 || v > 8) throw new RangeError(`Expected every element of allowedVersions to be between 1 and 8, but got: ${v}.`);
|
|
916
|
+
unique.add(v);
|
|
917
|
+
}
|
|
918
|
+
return Object.freeze([...unique]);
|
|
919
|
+
})() : null;
|
|
769
920
|
const invalidUuid = options.errors?.invalidUuid;
|
|
770
921
|
const disallowedVersion = options.errors?.disallowedVersion;
|
|
922
|
+
const invalidVariant = options.errors?.invalidVariant;
|
|
771
923
|
return {
|
|
772
|
-
|
|
924
|
+
mode: "sync",
|
|
773
925
|
metavar,
|
|
926
|
+
placeholder: "00000000-0000-0000-0000-000000000000",
|
|
774
927
|
parse(input) {
|
|
775
928
|
if (!uuidRegex.test(input)) return {
|
|
776
929
|
success: false,
|
|
777
930
|
error: invalidUuid ? typeof invalidUuid === "function" ? invalidUuid(input) : invalidUuid : require_message.message`Expected a valid UUID in format ${"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}, but got ${input}.`
|
|
778
931
|
};
|
|
932
|
+
const lower = input.toLowerCase();
|
|
933
|
+
if (lower === "00000000-0000-0000-0000-000000000000" || lower === "ffffffff-ffff-ffff-ffff-ffffffffffff") return {
|
|
934
|
+
success: true,
|
|
935
|
+
value: input
|
|
936
|
+
};
|
|
937
|
+
const versionChar = input.charAt(14);
|
|
938
|
+
const version = parseInt(versionChar, 16);
|
|
779
939
|
if (allowedVersions != null && allowedVersions.length > 0) {
|
|
780
|
-
const versionChar = input.charAt(14);
|
|
781
|
-
const version = parseInt(versionChar, 16);
|
|
782
940
|
if (!allowedVersions.includes(version)) return {
|
|
783
941
|
success: false,
|
|
784
942
|
error: disallowedVersion ? typeof disallowedVersion === "function" ? disallowedVersion(version, allowedVersions) : disallowedVersion : (() => {
|
|
@@ -791,6 +949,25 @@ function uuid(options = {}) {
|
|
|
791
949
|
return require_message.message`Expected UUID version ${expectedVersions}, but got version ${version.toLocaleString("en")}.`;
|
|
792
950
|
})()
|
|
793
951
|
};
|
|
952
|
+
} else if (strict && (version < 1 || version > 8)) return {
|
|
953
|
+
success: false,
|
|
954
|
+
error: disallowedVersion ? typeof disallowedVersion === "function" ? disallowedVersion(version, [
|
|
955
|
+
1,
|
|
956
|
+
2,
|
|
957
|
+
3,
|
|
958
|
+
4,
|
|
959
|
+
5,
|
|
960
|
+
6,
|
|
961
|
+
7,
|
|
962
|
+
8
|
|
963
|
+
]) : disallowedVersion : require_message.message`Expected UUID version 1 through 8, but got version ${version.toLocaleString("en")}.`
|
|
964
|
+
};
|
|
965
|
+
if (strict) {
|
|
966
|
+
const variantChar = input.charAt(19).toLowerCase();
|
|
967
|
+
if (variantChar !== "8" && variantChar !== "9" && variantChar !== "a" && variantChar !== "b") return {
|
|
968
|
+
success: false,
|
|
969
|
+
error: invalidVariant ? typeof invalidVariant === "function" ? invalidVariant(input) : invalidVariant : require_message.message`Expected RFC 9562 variant (8, 9, a, or b at position 20), but got ${variantChar} in ${input}.`
|
|
970
|
+
};
|
|
794
971
|
}
|
|
795
972
|
return {
|
|
796
973
|
success: true,
|
|
@@ -837,18 +1014,24 @@ function uuid(options = {}) {
|
|
|
837
1014
|
*
|
|
838
1015
|
* @param options Configuration options specifying the type and constraints.
|
|
839
1016
|
* @returns A {@link ValueParser} that converts string input to port numbers.
|
|
1017
|
+
* @throws {TypeError} If `options.type` is provided but is neither `"number"`
|
|
1018
|
+
* nor `"bigint"`.
|
|
840
1019
|
* @since 0.10.0
|
|
841
1020
|
*/
|
|
842
1021
|
function port(options) {
|
|
843
|
-
|
|
1022
|
+
checkBooleanOption(options, "disallowWellKnown");
|
|
1023
|
+
if (options?.type !== void 0 && options.type !== "number" && options.type !== "bigint") throw new TypeError(`Expected type to be "number" or "bigint", but got: ${String(options.type)}.`);
|
|
844
1024
|
if (options?.type === "bigint") {
|
|
845
1025
|
const metavar$1 = options.metavar ?? "PORT";
|
|
846
1026
|
require_nonempty.ensureNonEmptyString(metavar$1);
|
|
847
1027
|
const min$1 = options.min ?? 1n;
|
|
848
1028
|
const max$1 = options.max ?? 65535n;
|
|
1029
|
+
if (min$1 > max$1) throw new RangeError(`Expected min to be less than or equal to max, but got min: ${min$1} and max: ${max$1}.`);
|
|
1030
|
+
if (options.disallowWellKnown && min$1 < 1024n && max$1 < 1024n) throw new RangeError(`disallowWellKnown is incompatible with the configured port range: all ports ${min$1}..${max$1} are well-known.`);
|
|
849
1031
|
return {
|
|
850
|
-
|
|
1032
|
+
mode: "sync",
|
|
851
1033
|
metavar: metavar$1,
|
|
1034
|
+
placeholder: options.placeholder ?? (options.disallowWellKnown && min$1 < 1024n ? 1024n > min$1 ? 1024n : min$1 : min$1),
|
|
852
1035
|
parse(input) {
|
|
853
1036
|
if (!input.match(/^-?\d+$/)) return {
|
|
854
1037
|
success: false,
|
|
@@ -877,13 +1060,18 @@ function port(options) {
|
|
|
877
1060
|
}
|
|
878
1061
|
};
|
|
879
1062
|
}
|
|
1063
|
+
if (options?.min != null && !Number.isFinite(options.min)) throw new RangeError(`Expected min to be a finite number, but got: ${options.min}`);
|
|
1064
|
+
if (options?.max != null && !Number.isFinite(options.max)) throw new RangeError(`Expected max to be a finite number, but got: ${options.max}`);
|
|
880
1065
|
const metavar = options?.metavar ?? "PORT";
|
|
881
1066
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
882
1067
|
const min = options?.min ?? 1;
|
|
883
1068
|
const max = options?.max ?? 65535;
|
|
1069
|
+
if (min > max) throw new RangeError(`Expected min to be less than or equal to max, but got min: ${min} and max: ${max}.`);
|
|
1070
|
+
if (options?.disallowWellKnown && min < 1024 && max < 1024) throw new RangeError(`disallowWellKnown is incompatible with the configured port range: all ports ${min}..${max} are well-known.`);
|
|
884
1071
|
return {
|
|
885
|
-
|
|
1072
|
+
mode: "sync",
|
|
886
1073
|
metavar,
|
|
1074
|
+
placeholder: options?.placeholder ?? (options?.disallowWellKnown && min < 1024 ? Math.max(1024, min) : min),
|
|
887
1075
|
parse(input) {
|
|
888
1076
|
if (!input.match(/^-?\d+$/)) return {
|
|
889
1077
|
success: false,
|
|
@@ -975,11 +1163,12 @@ function ipv4(options) {
|
|
|
975
1163
|
const allowBroadcast = options?.allowBroadcast ?? true;
|
|
976
1164
|
const allowZero = options?.allowZero ?? true;
|
|
977
1165
|
return {
|
|
978
|
-
|
|
1166
|
+
mode: "sync",
|
|
979
1167
|
metavar,
|
|
1168
|
+
placeholder: allowZero ? "0.0.0.0" : allowLoopback ? "127.0.0.1" : "192.0.2.1",
|
|
980
1169
|
parse(input) {
|
|
981
|
-
const
|
|
982
|
-
if (
|
|
1170
|
+
const octets = parseIpv4Octets(input);
|
|
1171
|
+
if (octets === null) {
|
|
983
1172
|
const errorMsg = options?.errors?.invalidIpv4;
|
|
984
1173
|
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Expected a valid IPv4 address, but got ${input}.`;
|
|
985
1174
|
return {
|
|
@@ -987,43 +1176,6 @@ function ipv4(options) {
|
|
|
987
1176
|
error: msg
|
|
988
1177
|
};
|
|
989
1178
|
}
|
|
990
|
-
const octets = [];
|
|
991
|
-
for (const part of parts) {
|
|
992
|
-
if (part.length === 0) {
|
|
993
|
-
const errorMsg = options?.errors?.invalidIpv4;
|
|
994
|
-
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Expected a valid IPv4 address, but got ${input}.`;
|
|
995
|
-
return {
|
|
996
|
-
success: false,
|
|
997
|
-
error: msg
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
if (part.trim() !== part) {
|
|
1001
|
-
const errorMsg = options?.errors?.invalidIpv4;
|
|
1002
|
-
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Expected a valid IPv4 address, but got ${input}.`;
|
|
1003
|
-
return {
|
|
1004
|
-
success: false,
|
|
1005
|
-
error: msg
|
|
1006
|
-
};
|
|
1007
|
-
}
|
|
1008
|
-
if (part.length > 1 && part[0] === "0") {
|
|
1009
|
-
const errorMsg = options?.errors?.invalidIpv4;
|
|
1010
|
-
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Expected a valid IPv4 address, but got ${input}.`;
|
|
1011
|
-
return {
|
|
1012
|
-
success: false,
|
|
1013
|
-
error: msg
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
|
-
const octet = Number(part);
|
|
1017
|
-
if (!Number.isInteger(octet) || octet < 0 || octet > 255) {
|
|
1018
|
-
const errorMsg = options?.errors?.invalidIpv4;
|
|
1019
|
-
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Expected a valid IPv4 address, but got ${input}.`;
|
|
1020
|
-
return {
|
|
1021
|
-
success: false,
|
|
1022
|
-
error: msg
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
octets.push(octet);
|
|
1026
|
-
}
|
|
1027
1179
|
const ipAddress = octets.join(".");
|
|
1028
1180
|
if (!allowPrivate && isPrivateIp(octets)) {
|
|
1029
1181
|
const errorMsg = options?.errors?.privateNotAllowed;
|
|
@@ -1092,9 +1244,14 @@ function ipv4(options) {
|
|
|
1092
1244
|
* - Labels can contain alphanumeric characters and hyphens
|
|
1093
1245
|
* - Labels cannot start or end with a hyphen
|
|
1094
1246
|
* - Total length ≤ 253 characters (default)
|
|
1247
|
+
* - Dotted all-numeric strings (e.g., `192.168.0.1`) are rejected as they
|
|
1248
|
+
* resemble IPv4 addresses rather than DNS hostnames
|
|
1095
1249
|
*
|
|
1096
1250
|
* @param options - Options for hostname validation.
|
|
1097
1251
|
* @returns A value parser for hostnames.
|
|
1252
|
+
* @throws {TypeError} If `allowWildcard`, `allowUnderscore`, or
|
|
1253
|
+
* `allowLocalhost` is not a boolean.
|
|
1254
|
+
* @throws {RangeError} If `maxLength` is not a positive integer.
|
|
1098
1255
|
* @since 0.10.0
|
|
1099
1256
|
*
|
|
1100
1257
|
* @example
|
|
@@ -1114,13 +1271,18 @@ function ipv4(options) {
|
|
|
1114
1271
|
function hostname(options) {
|
|
1115
1272
|
const metavar = options?.metavar ?? "HOST";
|
|
1116
1273
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
1274
|
+
checkBooleanOption(options, "allowWildcard");
|
|
1275
|
+
checkBooleanOption(options, "allowUnderscore");
|
|
1276
|
+
checkBooleanOption(options, "allowLocalhost");
|
|
1117
1277
|
const allowWildcard = options?.allowWildcard ?? false;
|
|
1118
1278
|
const allowUnderscore = options?.allowUnderscore ?? false;
|
|
1119
1279
|
const allowLocalhost = options?.allowLocalhost ?? true;
|
|
1120
1280
|
const maxLength = options?.maxLength ?? 253;
|
|
1281
|
+
if (!Number.isInteger(maxLength) || maxLength < 1) throw new RangeError("maxLength must be an integer greater than or equal to 1.");
|
|
1121
1282
|
return {
|
|
1122
|
-
|
|
1283
|
+
mode: "sync",
|
|
1123
1284
|
metavar,
|
|
1285
|
+
placeholder: options?.placeholder ?? (allowLocalhost ? maxLength >= 9 ? "localhost" : "a.bc" : maxLength >= 11 ? "example.com" : "a.bc"),
|
|
1124
1286
|
parse(input) {
|
|
1125
1287
|
if (input.length > maxLength) {
|
|
1126
1288
|
const errorMsg = options?.errors?.tooLong;
|
|
@@ -1130,7 +1292,7 @@ function hostname(options) {
|
|
|
1130
1292
|
error: msg
|
|
1131
1293
|
};
|
|
1132
1294
|
}
|
|
1133
|
-
if (!allowLocalhost && input === "localhost") {
|
|
1295
|
+
if (!allowLocalhost && input.toLowerCase() === "localhost") {
|
|
1134
1296
|
const errorMsg = options?.errors?.localhostNotAllowed;
|
|
1135
1297
|
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Hostname 'localhost' is not allowed.`;
|
|
1136
1298
|
return {
|
|
@@ -1156,6 +1318,14 @@ function hostname(options) {
|
|
|
1156
1318
|
error: msg
|
|
1157
1319
|
};
|
|
1158
1320
|
}
|
|
1321
|
+
if (!allowLocalhost && rest.toLowerCase() === "localhost") {
|
|
1322
|
+
const errorMsg = options?.errors?.localhostNotAllowed;
|
|
1323
|
+
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Hostname 'localhost' is not allowed.`;
|
|
1324
|
+
return {
|
|
1325
|
+
success: false,
|
|
1326
|
+
error: msg
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1159
1329
|
}
|
|
1160
1330
|
if (!allowUnderscore && input.includes("_")) {
|
|
1161
1331
|
const errorMsg = options?.errors?.underscoreNotAllowed;
|
|
@@ -1191,7 +1361,7 @@ function hostname(options) {
|
|
|
1191
1361
|
error: msg
|
|
1192
1362
|
};
|
|
1193
1363
|
}
|
|
1194
|
-
if (label === "*") continue;
|
|
1364
|
+
if (label === "*" && allowWildcard && input.startsWith("*.") && label === labels[0]) continue;
|
|
1195
1365
|
if (label.startsWith("-") || label.endsWith("-")) {
|
|
1196
1366
|
const errorMsg = options?.errors?.invalidHostname;
|
|
1197
1367
|
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Expected a valid hostname, but got ${input}.`;
|
|
@@ -1210,6 +1380,14 @@ function hostname(options) {
|
|
|
1210
1380
|
};
|
|
1211
1381
|
}
|
|
1212
1382
|
}
|
|
1383
|
+
if (labels.length >= 2 && labels.every((l) => /^[0-9]+$/.test(l))) {
|
|
1384
|
+
const errorMsg = options?.errors?.invalidHostname;
|
|
1385
|
+
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Expected a valid hostname, but got ${input}.`;
|
|
1386
|
+
return {
|
|
1387
|
+
success: false,
|
|
1388
|
+
error: msg
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1213
1391
|
return {
|
|
1214
1392
|
success: true,
|
|
1215
1393
|
value: input
|
|
@@ -1224,18 +1402,37 @@ function email(options) {
|
|
|
1224
1402
|
const metavar = options?.metavar ?? "EMAIL";
|
|
1225
1403
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
1226
1404
|
const allowMultiple = options?.allowMultiple ?? false;
|
|
1405
|
+
if (options?.placeholder != null) {
|
|
1406
|
+
if (allowMultiple && !Array.isArray(options.placeholder)) throw new TypeError("email() placeholder must be an array when allowMultiple is true.");
|
|
1407
|
+
if (!allowMultiple && typeof options.placeholder !== "string") throw new TypeError("email() placeholder must be a string when allowMultiple is false.");
|
|
1408
|
+
}
|
|
1227
1409
|
const allowDisplayName = options?.allowDisplayName ?? false;
|
|
1228
1410
|
const lowercase = options?.lowercase ?? false;
|
|
1229
1411
|
const allowedDomains = options?.allowedDomains != null ? Object.freeze([...options.allowedDomains]) : void 0;
|
|
1412
|
+
if (allowedDomains != null) {
|
|
1413
|
+
if (allowedDomains.length === 0) throw new TypeError("allowedDomains must not be empty.");
|
|
1414
|
+
for (let i = 0; i < allowedDomains.length; i++) {
|
|
1415
|
+
const entry = allowedDomains[i];
|
|
1416
|
+
if (typeof entry !== "string") throw new TypeError(`allowedDomains[${i}] must be a string, got ${typeof entry}.`);
|
|
1417
|
+
if (entry !== entry.trim()) throw new TypeError(`allowedDomains[${i}] must not have leading or trailing whitespace: ${JSON.stringify(entry)}`);
|
|
1418
|
+
if (entry.startsWith("@")) throw new TypeError(`allowedDomains[${i}] must not start with "@": ${JSON.stringify(entry)}`);
|
|
1419
|
+
if (entry === "" || !entry.includes(".")) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
|
|
1420
|
+
if (entry.startsWith(".") || entry.endsWith(".") || entry.startsWith("-") || entry.endsWith("-")) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
|
|
1421
|
+
const labels = entry.split(".");
|
|
1422
|
+
for (const label of labels) if (label.length === 0 || label.length > 63 || label.startsWith("-") || label.endsWith("-") || !/^[a-zA-Z0-9-]+$/.test(label)) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
|
|
1423
|
+
if (labels.length === 4 && labels.every((label) => /^[0-9]+$/.test(label))) throw new TypeError(`allowedDomains[${i}] is not a valid domain: ${JSON.stringify(entry)}`);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1230
1426
|
const invalidEmail = options?.errors?.invalidEmail;
|
|
1231
1427
|
const domainNotAllowed = options?.errors?.domainNotAllowed;
|
|
1232
1428
|
const atextRegex = /^[a-zA-Z0-9._+-]+$/;
|
|
1429
|
+
const encoder = new TextEncoder();
|
|
1233
1430
|
function validateEmail(input) {
|
|
1234
1431
|
const trimmed = input.trim();
|
|
1235
1432
|
let emailAddr = trimmed;
|
|
1236
|
-
if (allowDisplayName
|
|
1237
|
-
const
|
|
1238
|
-
if (
|
|
1433
|
+
if (allowDisplayName) {
|
|
1434
|
+
const displayNameMatch = trimmed.match(/^((?:"(?:[^"\\]|\\.)*"|[^<>"])+)\s*<([^<>]+)>$/);
|
|
1435
|
+
if (displayNameMatch && /\S/.test(displayNameMatch[1].replace(/"((?:[^"\\]|\\.)*)"/g, (_match, inner) => inner))) emailAddr = displayNameMatch[2].trim();
|
|
1239
1436
|
}
|
|
1240
1437
|
let atIndex = -1;
|
|
1241
1438
|
if (emailAddr.startsWith("\"")) {
|
|
@@ -1257,6 +1454,7 @@ function email(options) {
|
|
|
1257
1454
|
isValidLocal = localParts.length > 0 && localParts.every((part) => part.length > 0 && atextRegex.test(part));
|
|
1258
1455
|
}
|
|
1259
1456
|
if (!isValidLocal) return null;
|
|
1457
|
+
if (encoder.encode(localPart).length > 64) return null;
|
|
1260
1458
|
if (!domain$1 || domain$1.length === 0) return null;
|
|
1261
1459
|
if (!domain$1.includes(".")) return null;
|
|
1262
1460
|
if (domain$1.startsWith(".") || domain$1.endsWith(".") || domain$1.startsWith("-") || domain$1.endsWith("-")) return null;
|
|
@@ -1266,15 +1464,48 @@ function email(options) {
|
|
|
1266
1464
|
if (label.startsWith("-") || label.endsWith("-")) return null;
|
|
1267
1465
|
if (!/^[a-zA-Z0-9-]+$/.test(label)) return null;
|
|
1268
1466
|
}
|
|
1467
|
+
if (domainLabels.length === 4 && domainLabels.every((label) => /^[0-9]+$/.test(label))) return null;
|
|
1468
|
+
if (encoder.encode(emailAddr).length > 254) return null;
|
|
1269
1469
|
const resultEmail = emailAddr;
|
|
1270
|
-
|
|
1470
|
+
if (!lowercase) return resultEmail;
|
|
1471
|
+
const lastAt = resultEmail.lastIndexOf("@");
|
|
1472
|
+
return resultEmail.slice(0, lastAt) + resultEmail.slice(lastAt).toLowerCase();
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Splits an input string on commas, respecting quoted segments and
|
|
1476
|
+
* angle-bracket display-name syntax per RFC 5322.
|
|
1477
|
+
*/
|
|
1478
|
+
function splitEmails(input) {
|
|
1479
|
+
const result = [];
|
|
1480
|
+
let current = "";
|
|
1481
|
+
let inQuotes = false;
|
|
1482
|
+
let inAngleBrackets = false;
|
|
1483
|
+
let escaped = false;
|
|
1484
|
+
for (const char of input) {
|
|
1485
|
+
if (escaped) escaped = false;
|
|
1486
|
+
else if (char === "\\" && inQuotes) escaped = true;
|
|
1487
|
+
else if (char === "\"" && !inAngleBrackets) {
|
|
1488
|
+
if (inQuotes) inQuotes = false;
|
|
1489
|
+
else if (current.trim() === "") inQuotes = true;
|
|
1490
|
+
} else if (char === "<" && !inQuotes) inAngleBrackets = true;
|
|
1491
|
+
else if (char === ">" && !inQuotes) inAngleBrackets = false;
|
|
1492
|
+
else if (char === "," && !inQuotes && !inAngleBrackets) {
|
|
1493
|
+
result.push(current);
|
|
1494
|
+
current = "";
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
current += char;
|
|
1498
|
+
}
|
|
1499
|
+
result.push(current);
|
|
1500
|
+
return result;
|
|
1271
1501
|
}
|
|
1272
1502
|
return {
|
|
1273
|
-
|
|
1503
|
+
mode: "sync",
|
|
1274
1504
|
metavar,
|
|
1505
|
+
placeholder: options?.placeholder ?? (options?.allowMultiple ? [`user@${options?.allowedDomains?.[0] ?? "example.com"}`] : `user@${options?.allowedDomains?.[0] ?? "example.com"}`),
|
|
1275
1506
|
parse(input) {
|
|
1276
1507
|
if (allowMultiple) {
|
|
1277
|
-
const emails = input
|
|
1508
|
+
const emails = splitEmails(input).map((e) => e.trim());
|
|
1278
1509
|
const validatedEmails = [];
|
|
1279
1510
|
for (const email$1 of emails) {
|
|
1280
1511
|
const validated = validateEmail(email$1);
|
|
@@ -1286,7 +1517,7 @@ function email(options) {
|
|
|
1286
1517
|
error: msg
|
|
1287
1518
|
};
|
|
1288
1519
|
}
|
|
1289
|
-
if (allowedDomains
|
|
1520
|
+
if (allowedDomains != null) {
|
|
1290
1521
|
const atIndex = validated.indexOf("@");
|
|
1291
1522
|
const domain$1 = validated.substring(atIndex + 1).toLowerCase();
|
|
1292
1523
|
const isAllowed = allowedDomains.some((allowed) => domain$1 === allowed.toLowerCase());
|
|
@@ -1307,7 +1538,15 @@ function email(options) {
|
|
|
1307
1538
|
},
|
|
1308
1539
|
{
|
|
1309
1540
|
type: "text",
|
|
1310
|
-
text:
|
|
1541
|
+
text: " is not allowed. Allowed domains: "
|
|
1542
|
+
},
|
|
1543
|
+
...require_message.valueSet(allowedDomains, {
|
|
1544
|
+
fallback: "",
|
|
1545
|
+
locale: "en-US"
|
|
1546
|
+
}),
|
|
1547
|
+
{
|
|
1548
|
+
type: "text",
|
|
1549
|
+
text: "."
|
|
1311
1550
|
}
|
|
1312
1551
|
];
|
|
1313
1552
|
return {
|
|
@@ -1332,7 +1571,7 @@ function email(options) {
|
|
|
1332
1571
|
error: msg
|
|
1333
1572
|
};
|
|
1334
1573
|
}
|
|
1335
|
-
if (allowedDomains
|
|
1574
|
+
if (allowedDomains != null) {
|
|
1336
1575
|
const atIndex = validated.indexOf("@");
|
|
1337
1576
|
const domain$1 = validated.substring(atIndex + 1).toLowerCase();
|
|
1338
1577
|
const isAllowed = allowedDomains.some((allowed) => domain$1 === allowed.toLowerCase());
|
|
@@ -1353,7 +1592,15 @@ function email(options) {
|
|
|
1353
1592
|
},
|
|
1354
1593
|
{
|
|
1355
1594
|
type: "text",
|
|
1356
|
-
text:
|
|
1595
|
+
text: " is not allowed. Allowed domains: "
|
|
1596
|
+
},
|
|
1597
|
+
...require_message.valueSet(allowedDomains, {
|
|
1598
|
+
fallback: "",
|
|
1599
|
+
locale: "en-US"
|
|
1600
|
+
}),
|
|
1601
|
+
{
|
|
1602
|
+
type: "text",
|
|
1603
|
+
text: "."
|
|
1357
1604
|
}
|
|
1358
1605
|
];
|
|
1359
1606
|
return {
|
|
@@ -1369,7 +1616,7 @@ function email(options) {
|
|
|
1369
1616
|
}
|
|
1370
1617
|
},
|
|
1371
1618
|
format(value) {
|
|
1372
|
-
if (Array.isArray(value)) return value.join(",");
|
|
1619
|
+
if (Array.isArray(value)) return value.join(", ");
|
|
1373
1620
|
return value;
|
|
1374
1621
|
}
|
|
1375
1622
|
};
|
|
@@ -1386,6 +1633,7 @@ function email(options) {
|
|
|
1386
1633
|
*
|
|
1387
1634
|
* @param options - Options for socket address validation.
|
|
1388
1635
|
* @returns A value parser for socket addresses.
|
|
1636
|
+
* @throws {TypeError} If `separator` is an empty string.
|
|
1389
1637
|
* @throws {TypeError} If `separator` contains digit characters, since digits
|
|
1390
1638
|
* in the separator would cause ambiguous splitting of port input.
|
|
1391
1639
|
* @since 0.10.0
|
|
@@ -1409,7 +1657,9 @@ function email(options) {
|
|
|
1409
1657
|
*/
|
|
1410
1658
|
function socketAddress(options) {
|
|
1411
1659
|
const separator = options?.separator ?? ":";
|
|
1660
|
+
if (separator === "") throw new TypeError("Expected separator to not be empty.");
|
|
1412
1661
|
if (/\p{Nd}/u.test(separator)) throw new TypeError(`Expected separator to not contain digits, but got: ${JSON.stringify(separator)}.`);
|
|
1662
|
+
const formatExample = `host${JSON.stringify(separator).slice(1, -1)}port`;
|
|
1413
1663
|
const metavar = options?.metavar ?? `HOST${separator}PORT`;
|
|
1414
1664
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
1415
1665
|
const defaultPort = options?.defaultPort;
|
|
@@ -1423,88 +1673,274 @@ function socketAddress(options) {
|
|
|
1423
1673
|
...options?.host?.ip,
|
|
1424
1674
|
metavar: "HOST"
|
|
1425
1675
|
});
|
|
1676
|
+
const disambiguationParser = hostType === "ip" ? hostname({
|
|
1677
|
+
metavar: "HOST",
|
|
1678
|
+
allowWildcard: true,
|
|
1679
|
+
allowUnderscore: true,
|
|
1680
|
+
maxLength: Math.max(253, options?.host?.hostname?.maxLength ?? 0)
|
|
1681
|
+
}) : hostnameParser;
|
|
1682
|
+
const separatorIsHostChar = /^[a-zA-Z0-9._-]+$/.test(separator);
|
|
1426
1683
|
const portParser = port({
|
|
1427
1684
|
...options?.port,
|
|
1428
1685
|
metavar: "PORT",
|
|
1429
1686
|
type: "number"
|
|
1430
1687
|
});
|
|
1688
|
+
function looksLikeIpv4(input) {
|
|
1689
|
+
return /^\d+\.\d+\.\d+\.\d+$/.test(input);
|
|
1690
|
+
}
|
|
1691
|
+
function looksLikeAltIpv4Literal(input) {
|
|
1692
|
+
if (/^0[xX][0-9a-fA-F]+$/.test(input)) {
|
|
1693
|
+
const n = parseInt(input.slice(2), 16);
|
|
1694
|
+
return n <= 4294967295;
|
|
1695
|
+
}
|
|
1696
|
+
if (/^0[0-7]+$/.test(input)) {
|
|
1697
|
+
const n = parseInt(input.slice(1), 8);
|
|
1698
|
+
return n <= 4294967295;
|
|
1699
|
+
}
|
|
1700
|
+
const parts = input.split(".");
|
|
1701
|
+
if (parts.length >= 2 && parts.length <= 4) {
|
|
1702
|
+
const numericOrHex = /^(?:[0-9]+|0[xX][0-9a-fA-F]+)$/;
|
|
1703
|
+
if (parts.every((p) => numericOrHex.test(p)) && parts.some((p) => /^0[xX]/i.test(p) || p.length > 1 && p[0] === "0")) {
|
|
1704
|
+
const values = [];
|
|
1705
|
+
for (const p of parts) if (/^0[xX]/i.test(p)) values.push(parseInt(p.slice(2), 16));
|
|
1706
|
+
else if (p.length > 1 && p[0] === "0") {
|
|
1707
|
+
if (/[89]/.test(p)) return false;
|
|
1708
|
+
values.push(parseInt(p, 8));
|
|
1709
|
+
} else values.push(Number(p));
|
|
1710
|
+
const lastMax = 256 ** (5 - parts.length);
|
|
1711
|
+
return values.slice(0, -1).every((v) => v <= 255) && values[values.length - 1] < lastMax;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
return false;
|
|
1715
|
+
}
|
|
1431
1716
|
function parseHost(hostInput) {
|
|
1432
1717
|
if (hostType === "hostname") {
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1718
|
+
if (looksLikeAltIpv4Literal(hostInput)) return {
|
|
1719
|
+
success: false,
|
|
1720
|
+
error: require_message.message`${hostInput} appears to be a non-standard IPv4 address notation.`
|
|
1721
|
+
};
|
|
1722
|
+
if (looksLikeIpv4(hostInput)) return {
|
|
1723
|
+
success: false,
|
|
1724
|
+
error: require_message.message`Expected a valid hostname, but got ${hostInput}.`
|
|
1725
|
+
};
|
|
1726
|
+
return hostnameParser.parse(hostInput);
|
|
1727
|
+
} else if (hostType === "ip") return ipParser.parse(hostInput);
|
|
1728
|
+
else {
|
|
1729
|
+
if (looksLikeAltIpv4Literal(hostInput)) return {
|
|
1730
|
+
success: false,
|
|
1731
|
+
error: require_message.message`${hostInput} appears to be a non-standard IPv4 address notation.`
|
|
1732
|
+
};
|
|
1733
|
+
if (looksLikeIpv4(hostInput)) return ipParser.parse(hostInput);
|
|
1734
|
+
return hostnameParser.parse(hostInput);
|
|
1445
1735
|
}
|
|
1446
1736
|
}
|
|
1447
1737
|
return {
|
|
1448
|
-
|
|
1738
|
+
mode: "sync",
|
|
1449
1739
|
metavar,
|
|
1740
|
+
get placeholder() {
|
|
1741
|
+
return {
|
|
1742
|
+
host: hostType === "ip" ? ipParser.placeholder : hostnameParser.placeholder,
|
|
1743
|
+
port: defaultPort ?? portParser.placeholder
|
|
1744
|
+
};
|
|
1745
|
+
},
|
|
1450
1746
|
parse(input) {
|
|
1451
1747
|
const trimmed = input.trim();
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
let
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1748
|
+
const searchInput = input.trimStart();
|
|
1749
|
+
const canOmitPort = defaultPort !== void 0 && !requirePort;
|
|
1750
|
+
let firstHostError;
|
|
1751
|
+
let validHostInvalidPortError;
|
|
1752
|
+
let trailingSepHost;
|
|
1753
|
+
let trailingSepHostError;
|
|
1754
|
+
let anySeparatorFound = false;
|
|
1755
|
+
let trailingSepInTrimmedRegion = false;
|
|
1756
|
+
let searchFrom = searchInput.length;
|
|
1757
|
+
while (searchFrom > 0) {
|
|
1758
|
+
const separatorIndex = searchInput.lastIndexOf(separator, searchFrom - 1);
|
|
1759
|
+
if (separatorIndex === -1) break;
|
|
1760
|
+
anySeparatorFound = true;
|
|
1761
|
+
const hostPart = searchInput.substring(0, separatorIndex).trim();
|
|
1762
|
+
const portPart = searchInput.substring(separatorIndex + separator.length).trim();
|
|
1763
|
+
if (portPart === "") {
|
|
1764
|
+
if (trailingSepHost === void 0 && trailingSepHostError === void 0) {
|
|
1765
|
+
if (separatorIndex + separator.length > trimmed.length) trailingSepInTrimmedRegion = true;
|
|
1766
|
+
const hostResult = parseHost(hostPart);
|
|
1767
|
+
if (hostResult.success) trailingSepHost = hostResult.value;
|
|
1768
|
+
else trailingSepHostError = {
|
|
1769
|
+
hostPart,
|
|
1770
|
+
error: hostResult.error
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
} else {
|
|
1774
|
+
const portResult = portParser.parse(portPart);
|
|
1775
|
+
if (portResult.success) {
|
|
1776
|
+
const hostResult = parseHost(hostPart);
|
|
1777
|
+
if (hostResult.success) return {
|
|
1778
|
+
success: true,
|
|
1779
|
+
value: {
|
|
1780
|
+
host: hostResult.value,
|
|
1781
|
+
port: portResult.value
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1784
|
+
if (firstHostError === void 0 || (looksLikeIpv4(hostPart) || looksLikeAltIpv4Literal(hostPart)) && !looksLikeIpv4(firstHostError.hostPart) && !looksLikeAltIpv4Literal(firstHostError.hostPart)) firstHostError = {
|
|
1785
|
+
hostPart,
|
|
1786
|
+
error: hostResult.error
|
|
1787
|
+
};
|
|
1788
|
+
} else if (validHostInvalidPortError === void 0 && hostPart !== "" && /^[0-9]+$/.test(portPart)) if (looksLikeIpv4(hostPart) || looksLikeAltIpv4Literal(hostPart)) {
|
|
1789
|
+
const hostResult = parseHost(hostPart);
|
|
1790
|
+
if (!hostResult.success) {
|
|
1791
|
+
if (firstHostError === void 0 || !looksLikeIpv4(firstHostError.hostPart) && !looksLikeAltIpv4Literal(firstHostError.hostPart)) firstHostError = {
|
|
1792
|
+
hostPart,
|
|
1793
|
+
error: hostResult.error
|
|
1794
|
+
};
|
|
1795
|
+
} else validHostInvalidPortError = portResult.error;
|
|
1796
|
+
} else {
|
|
1797
|
+
validHostInvalidPortError = portResult.error;
|
|
1798
|
+
const hostResult = parseHost(hostPart);
|
|
1799
|
+
if (!hostResult.success && firstHostError === void 0) firstHostError = {
|
|
1800
|
+
hostPart,
|
|
1801
|
+
error: hostResult.error
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
else if ((firstHostError === void 0 || !looksLikeIpv4(firstHostError.hostPart) && !looksLikeAltIpv4Literal(firstHostError.hostPart)) && (looksLikeIpv4(hostPart) || looksLikeAltIpv4Literal(hostPart))) {
|
|
1805
|
+
const hostResult = parseHost(hostPart);
|
|
1806
|
+
if (!hostResult.success) firstHostError = {
|
|
1807
|
+
hostPart,
|
|
1808
|
+
error: hostResult.error
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
searchFrom = separatorIndex;
|
|
1461
1813
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1814
|
+
if (validHostInvalidPortError !== void 0) {
|
|
1815
|
+
const errorMsg$1 = options?.errors?.invalidFormat;
|
|
1816
|
+
if (errorMsg$1) {
|
|
1817
|
+
const msg = typeof errorMsg$1 === "function" ? errorMsg$1(input) : errorMsg$1;
|
|
1818
|
+
return {
|
|
1819
|
+
success: false,
|
|
1820
|
+
error: msg
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
if (firstHostError !== void 0 && firstHostError.hostPart !== "") {
|
|
1824
|
+
const portSplitHostIsDegenerate = separatorIsHostChar ? firstHostError.hostPart.replaceAll(separator, "") === "" : firstHostError.hostPart.includes(separator);
|
|
1825
|
+
if (portSplitHostIsDegenerate) return {
|
|
1826
|
+
success: false,
|
|
1827
|
+
error: require_message.message`Expected a socket address in format ${require_message.text(formatExample)}, but got ${input}.`
|
|
1828
|
+
};
|
|
1829
|
+
if (!disambiguationParser.parse(trimmed).success) return {
|
|
1830
|
+
success: false,
|
|
1831
|
+
error: firstHostError.error
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
if (disambiguationParser.parse(trimmed).success) return {
|
|
1835
|
+
success: false,
|
|
1836
|
+
error: require_message.message`Expected a socket address in format ${require_message.text(formatExample)}, but got ${input}.`
|
|
1837
|
+
};
|
|
1838
|
+
return {
|
|
1839
|
+
success: false,
|
|
1840
|
+
error: validHostInvalidPortError
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
if (firstHostError !== void 0) {
|
|
1844
|
+
if (looksLikeIpv4(firstHostError.hostPart) || looksLikeAltIpv4Literal(firstHostError.hostPart)) {
|
|
1845
|
+
const errorMsg$1 = options?.errors?.invalidFormat;
|
|
1846
|
+
if (errorMsg$1) {
|
|
1847
|
+
const msg = typeof errorMsg$1 === "function" ? errorMsg$1(input) : errorMsg$1;
|
|
1848
|
+
return {
|
|
1849
|
+
success: false,
|
|
1850
|
+
error: msg
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
return {
|
|
1854
|
+
success: false,
|
|
1855
|
+
error: firstHostError.error
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
let hostOnlyResult;
|
|
1860
|
+
if (!requirePort || trailingSepHost !== void 0 || trailingSepHostError !== void 0) {
|
|
1861
|
+
hostOnlyResult = parseHost(trimmed);
|
|
1862
|
+
if (canOmitPort && hostOnlyResult.success && !trailingSepInTrimmedRegion) return {
|
|
1863
|
+
success: true,
|
|
1864
|
+
value: {
|
|
1865
|
+
host: hostOnlyResult.value,
|
|
1866
|
+
port: defaultPort
|
|
1867
|
+
}
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
if (trailingSepHost !== void 0 && hostOnlyResult !== void 0 && (!hostOnlyResult.success || trailingSepInTrimmedRegion)) {
|
|
1871
|
+
const errorMsg$1 = options?.errors?.missingPort;
|
|
1872
|
+
const msg = typeof errorMsg$1 === "function" ? errorMsg$1(input) : errorMsg$1 ?? require_message.message`Port number is required but was not specified.`;
|
|
1466
1873
|
return {
|
|
1467
1874
|
success: false,
|
|
1468
1875
|
error: msg
|
|
1469
1876
|
};
|
|
1470
1877
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
if (
|
|
1474
|
-
const
|
|
1475
|
-
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg ?? require_message.message`Port number is required but was not specified.`;
|
|
1878
|
+
if (trailingSepHostError !== void 0 && hostOnlyResult !== void 0 && (!hostOnlyResult.success || trailingSepInTrimmedRegion)) {
|
|
1879
|
+
const errorMsg$1 = options?.errors?.invalidFormat;
|
|
1880
|
+
if (errorMsg$1) {
|
|
1881
|
+
const msg = typeof errorMsg$1 === "function" ? errorMsg$1(input) : errorMsg$1;
|
|
1476
1882
|
return {
|
|
1477
1883
|
success: false,
|
|
1478
1884
|
error: msg
|
|
1479
1885
|
};
|
|
1480
1886
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1887
|
+
const trailingHostIsDegenerate = separatorIsHostChar ? trailingSepHostError.hostPart.replaceAll(separator, "") === "" : trailingSepHostError.hostPart.includes(separator);
|
|
1888
|
+
if (trailingSepHostError.hostPart !== "" && !trailingHostIsDegenerate && !disambiguationParser.parse(trimmed).success) return {
|
|
1889
|
+
success: false,
|
|
1890
|
+
error: trailingSepHostError.error
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
if (!canOmitPort && !requirePort && hostOnlyResult !== void 0 && hostOnlyResult.success) {
|
|
1894
|
+
const errorMsg$1 = options?.errors?.missingPort;
|
|
1895
|
+
const msg = typeof errorMsg$1 === "function" ? errorMsg$1(input) : errorMsg$1 ?? require_message.message`Port number is required but was not specified.`;
|
|
1896
|
+
return {
|
|
1897
|
+
success: false,
|
|
1898
|
+
error: msg
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
if (!canOmitPort && !anySeparatorFound) {
|
|
1902
|
+
hostOnlyResult = hostOnlyResult ?? parseHost(trimmed);
|
|
1903
|
+
if (hostOnlyResult.success) {
|
|
1904
|
+
const errorMsg$1 = options?.errors?.missingPort;
|
|
1905
|
+
const msg = typeof errorMsg$1 === "function" ? errorMsg$1(input) : errorMsg$1 ?? require_message.message`Port number is required but was not specified.`;
|
|
1485
1906
|
return {
|
|
1486
1907
|
success: false,
|
|
1487
1908
|
error: msg
|
|
1488
1909
|
};
|
|
1489
1910
|
}
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg
|
|
1911
|
+
}
|
|
1912
|
+
if (firstHostError !== void 0) {
|
|
1913
|
+
const errorMsg$1 = options?.errors?.invalidFormat;
|
|
1914
|
+
if (errorMsg$1) {
|
|
1915
|
+
const msg = typeof errorMsg$1 === "function" ? errorMsg$1(input) : errorMsg$1;
|
|
1495
1916
|
return {
|
|
1496
1917
|
success: false,
|
|
1497
1918
|
error: msg
|
|
1498
1919
|
};
|
|
1499
1920
|
}
|
|
1500
|
-
|
|
1921
|
+
const hostPartIsDegenerate = separatorIsHostChar ? firstHostError.hostPart.replaceAll(separator, "") === "" : firstHostError.hostPart.includes(separator);
|
|
1922
|
+
if (firstHostError.hostPart !== "" && !hostPartIsDegenerate && !disambiguationParser.parse(trimmed).success) return {
|
|
1923
|
+
success: false,
|
|
1924
|
+
error: firstHostError.error
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
const errorMsg = options?.errors?.invalidFormat;
|
|
1928
|
+
if (errorMsg) {
|
|
1929
|
+
const msg = typeof errorMsg === "function" ? errorMsg(input) : errorMsg;
|
|
1930
|
+
return {
|
|
1931
|
+
success: false,
|
|
1932
|
+
error: msg
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
if (hostOnlyResult !== void 0 && !hostOnlyResult.success) {
|
|
1936
|
+
if (looksLikeIpv4(trimmed) || looksLikeAltIpv4Literal(trimmed)) return {
|
|
1937
|
+
success: false,
|
|
1938
|
+
error: hostOnlyResult.error
|
|
1939
|
+
};
|
|
1501
1940
|
}
|
|
1502
1941
|
return {
|
|
1503
|
-
success:
|
|
1504
|
-
|
|
1505
|
-
host: validatedHost,
|
|
1506
|
-
port: validatedPort
|
|
1507
|
-
}
|
|
1942
|
+
success: false,
|
|
1943
|
+
error: require_message.message`Expected a socket address in format ${require_message.text(formatExample)}, but got ${input}.`
|
|
1508
1944
|
};
|
|
1509
1945
|
},
|
|
1510
1946
|
format(value) {
|
|
@@ -1513,9 +1949,11 @@ function socketAddress(options) {
|
|
|
1513
1949
|
};
|
|
1514
1950
|
}
|
|
1515
1951
|
function portRange(options) {
|
|
1516
|
-
|
|
1517
|
-
|
|
1952
|
+
checkBooleanOption(options, "disallowWellKnown");
|
|
1953
|
+
checkBooleanOption(options, "allowSingle");
|
|
1954
|
+
if (options?.type !== void 0 && options.type !== "number" && options.type !== "bigint") throw new TypeError(`Expected type to be "number" or "bigint", but got: ${String(options.type)}.`);
|
|
1518
1955
|
const separator = options?.separator ?? "-";
|
|
1956
|
+
if (separator === "") throw new TypeError("Expected separator to not be empty.");
|
|
1519
1957
|
if (/\p{Nd}/u.test(separator)) throw new TypeError(`Expected separator to not contain digits, but got: ${JSON.stringify(separator)}.`);
|
|
1520
1958
|
const metavar = options?.metavar ?? `PORT${separator}PORT`;
|
|
1521
1959
|
require_nonempty.ensureNonEmptyString(metavar);
|
|
@@ -1535,8 +1973,17 @@ function portRange(options) {
|
|
|
1535
1973
|
errors: options?.errors
|
|
1536
1974
|
});
|
|
1537
1975
|
return {
|
|
1538
|
-
|
|
1976
|
+
mode: "sync",
|
|
1539
1977
|
metavar,
|
|
1978
|
+
get placeholder() {
|
|
1979
|
+
return isBigInt ? {
|
|
1980
|
+
start: portParser.placeholder,
|
|
1981
|
+
end: portParser.placeholder
|
|
1982
|
+
} : {
|
|
1983
|
+
start: portParser.placeholder,
|
|
1984
|
+
end: portParser.placeholder
|
|
1985
|
+
};
|
|
1986
|
+
},
|
|
1540
1987
|
parse(input) {
|
|
1541
1988
|
const trimmed = input.trim();
|
|
1542
1989
|
const separatorIndex = trimmed.indexOf(separator);
|
|
@@ -1635,10 +2082,14 @@ function portRange(options) {
|
|
|
1635
2082
|
* Creates a value parser for MAC (Media Access Control) addresses.
|
|
1636
2083
|
*
|
|
1637
2084
|
* Validates MAC-48 addresses (6 octets, 12 hex digits) in various formats:
|
|
1638
|
-
* - Colon-separated: `00:1A:2B:3C:4D:5E`
|
|
1639
|
-
* - Hyphen-separated: `00-1A-2B-3C-4D-5E`
|
|
1640
|
-
* - Dot-separated (Cisco): `001A.2B3C.4D5E`
|
|
1641
|
-
* - No separator: `001A2B3C4D5E`
|
|
2085
|
+
* - Colon-separated: `00:1A:2B:3C:4D:5E` (1–2 hex digits per octet)
|
|
2086
|
+
* - Hyphen-separated: `00-1A-2B-3C-4D-5E` (1–2 hex digits per octet)
|
|
2087
|
+
* - Dot-separated (Cisco): `001A.2B3C.4D5E` (exactly 4 hex digits per group)
|
|
2088
|
+
* - No separator: `001A2B3C4D5E` (exactly 12 hex digits)
|
|
2089
|
+
*
|
|
2090
|
+
* Colon-separated and hyphen-separated formats accept single-digit octets
|
|
2091
|
+
* (e.g., `0:1:2:3:4:5`), which are automatically zero-padded to canonical
|
|
2092
|
+
* two-digit form (e.g., `00:01:02:03:04:05`).
|
|
1642
2093
|
*
|
|
1643
2094
|
* Returns the MAC address as a formatted string according to `case` and
|
|
1644
2095
|
* `outputSeparator` options.
|
|
@@ -1662,6 +2113,24 @@ function portRange(options) {
|
|
|
1662
2113
|
* ```
|
|
1663
2114
|
*/
|
|
1664
2115
|
function macAddress(options) {
|
|
2116
|
+
checkEnumOption(options, "separator", [
|
|
2117
|
+
":",
|
|
2118
|
+
"-",
|
|
2119
|
+
".",
|
|
2120
|
+
"none",
|
|
2121
|
+
"any"
|
|
2122
|
+
]);
|
|
2123
|
+
checkEnumOption(options, "outputSeparator", [
|
|
2124
|
+
":",
|
|
2125
|
+
"-",
|
|
2126
|
+
".",
|
|
2127
|
+
"none"
|
|
2128
|
+
]);
|
|
2129
|
+
checkEnumOption(options, "case", [
|
|
2130
|
+
"preserve",
|
|
2131
|
+
"upper",
|
|
2132
|
+
"lower"
|
|
2133
|
+
]);
|
|
1665
2134
|
const separator = options?.separator ?? "any";
|
|
1666
2135
|
const caseOption = options?.case ?? "preserve";
|
|
1667
2136
|
const outputSeparator = options?.outputSeparator;
|
|
@@ -1670,9 +2139,64 @@ function macAddress(options) {
|
|
|
1670
2139
|
const hyphenRegex = /^([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})-([0-9a-fA-F]{1,2})$/;
|
|
1671
2140
|
const dotRegex = /^([0-9a-fA-F]{4})\.([0-9a-fA-F]{4})\.([0-9a-fA-F]{4})$/;
|
|
1672
2141
|
const noneRegex = /^([0-9a-fA-F]{12})$/;
|
|
1673
|
-
|
|
1674
|
-
|
|
2142
|
+
function joinOctets(octets, sep) {
|
|
2143
|
+
let formatted = octets;
|
|
2144
|
+
if (caseOption === "upper") formatted = octets.map((o) => o.toUpperCase());
|
|
2145
|
+
else if (caseOption === "lower") formatted = octets.map((o) => o.toLowerCase());
|
|
2146
|
+
if (sep === ".") return [
|
|
2147
|
+
formatted[0] + formatted[1],
|
|
2148
|
+
formatted[2] + formatted[3],
|
|
2149
|
+
formatted[4] + formatted[5]
|
|
2150
|
+
].join(".");
|
|
2151
|
+
if (sep === "none") return formatted.join("");
|
|
2152
|
+
return formatted.join(sep);
|
|
2153
|
+
}
|
|
2154
|
+
function normalizeMac(value) {
|
|
2155
|
+
if (typeof value !== "string") return metavar;
|
|
2156
|
+
let octets;
|
|
2157
|
+
let detectedSep;
|
|
2158
|
+
if (value.includes(":")) {
|
|
2159
|
+
octets = value.split(":");
|
|
2160
|
+
detectedSep = ":";
|
|
2161
|
+
} else if (value.includes("-")) {
|
|
2162
|
+
octets = value.split("-");
|
|
2163
|
+
detectedSep = "-";
|
|
2164
|
+
} else if (value.includes(".")) {
|
|
2165
|
+
const groups = value.split(".");
|
|
2166
|
+
if (groups.length !== 3 || !groups.every((g) => /^[0-9a-fA-F]{4}$/.test(g))) return value;
|
|
2167
|
+
octets = groups.flatMap((g) => [g.slice(0, 2), g.slice(2)]);
|
|
2168
|
+
detectedSep = ".";
|
|
2169
|
+
} else {
|
|
2170
|
+
if (value.length !== 12) return value;
|
|
2171
|
+
octets = [];
|
|
2172
|
+
for (let i = 0; i < value.length; i += 2) octets.push(value.slice(i, i + 2));
|
|
2173
|
+
detectedSep = "none";
|
|
2174
|
+
}
|
|
2175
|
+
if (octets.length !== 6 || !octets.every((o) => /^[0-9a-fA-F]{1,2}$/.test(o))) return value;
|
|
2176
|
+
octets = octets.map((o) => o.padStart(2, "0"));
|
|
2177
|
+
let sep;
|
|
2178
|
+
if (outputSeparator != null) sep = outputSeparator;
|
|
2179
|
+
else if (separator !== "any") sep = separator;
|
|
2180
|
+
else sep = detectedSep;
|
|
2181
|
+
return joinOctets(octets, sep);
|
|
2182
|
+
}
|
|
2183
|
+
const parserObj = {
|
|
2184
|
+
mode: "sync",
|
|
1675
2185
|
metavar,
|
|
2186
|
+
get placeholder() {
|
|
2187
|
+
const octets = [
|
|
2188
|
+
"00",
|
|
2189
|
+
"00",
|
|
2190
|
+
"00",
|
|
2191
|
+
"00",
|
|
2192
|
+
"00",
|
|
2193
|
+
"00"
|
|
2194
|
+
];
|
|
2195
|
+
const sep = outputSeparator ?? (separator === "any" ? ":" : separator);
|
|
2196
|
+
if (sep === ".") return `${octets[0]}${octets[1]}.${octets[2]}${octets[3]}.${octets[4]}${octets[5]}`;
|
|
2197
|
+
if (sep === "none") return octets.join("");
|
|
2198
|
+
return octets.join(sep);
|
|
2199
|
+
},
|
|
1676
2200
|
parse(input) {
|
|
1677
2201
|
let octets = [];
|
|
1678
2202
|
let inputSeparator;
|
|
@@ -1732,28 +2256,52 @@ function macAddress(options) {
|
|
|
1732
2256
|
error: msg
|
|
1733
2257
|
};
|
|
1734
2258
|
}
|
|
1735
|
-
|
|
1736
|
-
if (caseOption === "upper") formattedOctets = octets.map((octet) => octet.toUpperCase());
|
|
1737
|
-
else if (caseOption === "lower") formattedOctets = octets.map((octet) => octet.toLowerCase());
|
|
2259
|
+
octets = octets.map((o) => o.padStart(2, "0"));
|
|
1738
2260
|
const finalSeparator = outputSeparator ?? inputSeparator ?? ":";
|
|
1739
|
-
let result;
|
|
1740
|
-
if (finalSeparator === ":") result = formattedOctets.join(":");
|
|
1741
|
-
else if (finalSeparator === "-") result = formattedOctets.join("-");
|
|
1742
|
-
else if (finalSeparator === ".") result = [
|
|
1743
|
-
formattedOctets[0] + formattedOctets[1],
|
|
1744
|
-
formattedOctets[2] + formattedOctets[3],
|
|
1745
|
-
formattedOctets[4] + formattedOctets[5]
|
|
1746
|
-
].join(".");
|
|
1747
|
-
else result = formattedOctets.join("");
|
|
1748
2261
|
return {
|
|
1749
2262
|
success: true,
|
|
1750
|
-
value:
|
|
2263
|
+
value: joinOctets(octets, finalSeparator)
|
|
1751
2264
|
};
|
|
1752
2265
|
},
|
|
1753
|
-
format
|
|
1754
|
-
return metavar;
|
|
1755
|
-
}
|
|
2266
|
+
format: normalizeMac
|
|
1756
2267
|
};
|
|
2268
|
+
const macParser = parserObj;
|
|
2269
|
+
let macParsing = false;
|
|
2270
|
+
Object.defineProperty(parserObj, "format", {
|
|
2271
|
+
value(v) {
|
|
2272
|
+
if (typeof v !== "string") return metavar;
|
|
2273
|
+
if (macParsing) return v;
|
|
2274
|
+
macParsing = true;
|
|
2275
|
+
try {
|
|
2276
|
+
const result = macParser.parse(v);
|
|
2277
|
+
return result.success ? result.value : v;
|
|
2278
|
+
} catch {
|
|
2279
|
+
return v;
|
|
2280
|
+
} finally {
|
|
2281
|
+
macParsing = false;
|
|
2282
|
+
}
|
|
2283
|
+
},
|
|
2284
|
+
configurable: true,
|
|
2285
|
+
enumerable: true
|
|
2286
|
+
});
|
|
2287
|
+
Object.defineProperty(parserObj, "normalize", {
|
|
2288
|
+
value(v) {
|
|
2289
|
+
if (typeof v !== "string") return v;
|
|
2290
|
+
if (macParsing) return v;
|
|
2291
|
+
macParsing = true;
|
|
2292
|
+
try {
|
|
2293
|
+
const result = macParser.parse(v);
|
|
2294
|
+
return result.success ? result.value : v;
|
|
2295
|
+
} catch {
|
|
2296
|
+
return v;
|
|
2297
|
+
} finally {
|
|
2298
|
+
macParsing = false;
|
|
2299
|
+
}
|
|
2300
|
+
},
|
|
2301
|
+
configurable: true,
|
|
2302
|
+
enumerable: true
|
|
2303
|
+
});
|
|
2304
|
+
return parserObj;
|
|
1757
2305
|
}
|
|
1758
2306
|
/**
|
|
1759
2307
|
* Creates a value parser for domain names.
|
|
@@ -1764,6 +2312,14 @@ function macAddress(options) {
|
|
|
1764
2312
|
*
|
|
1765
2313
|
* @param options Parser options for domain validation.
|
|
1766
2314
|
* @returns A parser that accepts valid domain names as strings.
|
|
2315
|
+
* @throws {RangeError} If `maxLength` is not a positive integer.
|
|
2316
|
+
* @throws {RangeError} If `minLabels` is not a positive integer.
|
|
2317
|
+
* @throws {TypeError} If `allowSubdomains` or `lowercase` is not a boolean.
|
|
2318
|
+
* @throws {TypeError} If any `allowedTlds` entry is not a string, is empty,
|
|
2319
|
+
* contains dots, has leading/trailing whitespace, or is not a valid DNS
|
|
2320
|
+
* label.
|
|
2321
|
+
* @throws {TypeError} If `allowSubdomains` is `false` and `minLabels` is
|
|
2322
|
+
* greater than 2, since non-subdomain domains have exactly 2 labels.
|
|
1767
2323
|
*
|
|
1768
2324
|
* @example
|
|
1769
2325
|
* ``` typescript
|
|
@@ -1777,7 +2333,7 @@ function macAddress(options) {
|
|
|
1777
2333
|
* option("--root", domain({ allowSubdomains: false }))
|
|
1778
2334
|
*
|
|
1779
2335
|
* // Restrict to specific TLDs
|
|
1780
|
-
* option("--domain", domain({
|
|
2336
|
+
* option("--domain", domain({ allowedTlds: ["com", "org", "net"] }))
|
|
1781
2337
|
*
|
|
1782
2338
|
* // Normalize to lowercase
|
|
1783
2339
|
* option("--domain", domain({ lowercase: true }))
|
|
@@ -1787,19 +2343,49 @@ function macAddress(options) {
|
|
|
1787
2343
|
*/
|
|
1788
2344
|
function domain(options) {
|
|
1789
2345
|
const metavar = options?.metavar ?? "DOMAIN";
|
|
2346
|
+
checkBooleanOption(options, "allowSubdomains");
|
|
2347
|
+
checkBooleanOption(options, "lowercase");
|
|
1790
2348
|
const allowSubdomains = options?.allowSubdomains ?? true;
|
|
1791
|
-
const
|
|
2349
|
+
const allowedTlds = options?.allowedTlds != null ? Object.freeze([...options.allowedTlds]) : void 0;
|
|
2350
|
+
const labelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
|
|
2351
|
+
if (allowedTlds !== void 0) {
|
|
2352
|
+
if (allowedTlds.length === 0) throw new TypeError("allowedTlds must not be empty.");
|
|
2353
|
+
for (const [i, tld] of allowedTlds.entries()) {
|
|
2354
|
+
if (typeof tld !== "string") {
|
|
2355
|
+
const actualType = Array.isArray(tld) ? "array" : typeof tld;
|
|
2356
|
+
throw new TypeError(`allowedTlds[${i}] must be a string, but got ${actualType}.`);
|
|
2357
|
+
}
|
|
2358
|
+
if (tld.length === 0) throw new TypeError(`allowedTlds[${i}] must not be an empty string.`);
|
|
2359
|
+
if (tld.includes(".")) throw new TypeError(`allowedTlds[${i}] must not contain dots: ${JSON.stringify(tld)}.`);
|
|
2360
|
+
if (tld !== tld.trim()) throw new TypeError(`allowedTlds[${i}] must not have leading or trailing whitespace: ${JSON.stringify(tld)}.`);
|
|
2361
|
+
if (!labelRegex.test(tld)) throw new TypeError(`allowedTlds[${i}] is not a valid DNS label: ${JSON.stringify(tld)}.`);
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
const allowedTldsLower = allowedTlds != null ? Object.freeze(allowedTlds.map((t) => t.toLowerCase())) : void 0;
|
|
1792
2365
|
const minLabels = options?.minLabels ?? 2;
|
|
2366
|
+
const maxLength = options?.maxLength ?? 253;
|
|
1793
2367
|
const lowercase = options?.lowercase ?? false;
|
|
2368
|
+
if (!Number.isInteger(maxLength) || maxLength < 1) throw new RangeError("maxLength must be an integer greater than or equal to 1.");
|
|
2369
|
+
if (!Number.isInteger(minLabels) || minLabels < 1) throw new RangeError("minLabels must be an integer greater than or equal to 1.");
|
|
2370
|
+
if (!allowSubdomains && minLabels > 2) throw new TypeError("allowSubdomains: false is incompatible with minLabels > 2, as non-subdomain domains have exactly 2 labels.");
|
|
1794
2371
|
const invalidDomain = options?.errors?.invalidDomain;
|
|
2372
|
+
const tooLong = options?.errors?.tooLong;
|
|
1795
2373
|
const tooFewLabels = options?.errors?.tooFewLabels;
|
|
1796
2374
|
const subdomainsNotAllowed = options?.errors?.subdomainsNotAllowed;
|
|
1797
2375
|
const tldNotAllowed = options?.errors?.tldNotAllowed;
|
|
1798
|
-
const
|
|
1799
|
-
|
|
1800
|
-
$mode: "sync",
|
|
2376
|
+
const domainParserObj = {
|
|
2377
|
+
mode: "sync",
|
|
1801
2378
|
metavar,
|
|
2379
|
+
placeholder: options?.placeholder ?? `example.${allowedTldsLower?.[0] ?? "com"}`,
|
|
1802
2380
|
parse(input) {
|
|
2381
|
+
if (input.length > maxLength) {
|
|
2382
|
+
const errorMsg = tooLong;
|
|
2383
|
+
const msg = typeof errorMsg === "function" ? errorMsg(input, maxLength) : errorMsg ?? require_message.message`Domain ${input} is too long (maximum ${require_message.text(maxLength.toString())} characters).`;
|
|
2384
|
+
return {
|
|
2385
|
+
success: false,
|
|
2386
|
+
error: msg
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
1803
2389
|
if (input.length === 0 || input.startsWith(".") || input.endsWith(".")) {
|
|
1804
2390
|
const errorMsg = invalidDomain;
|
|
1805
2391
|
if (typeof errorMsg === "function") return {
|
|
@@ -1876,6 +2462,31 @@ function domain(options) {
|
|
|
1876
2462
|
error: msg
|
|
1877
2463
|
};
|
|
1878
2464
|
}
|
|
2465
|
+
if (labels.length >= 2 && labels.every((l) => /^[0-9]+$/.test(l))) {
|
|
2466
|
+
const errorMsg = invalidDomain;
|
|
2467
|
+
if (typeof errorMsg === "function") return {
|
|
2468
|
+
success: false,
|
|
2469
|
+
error: errorMsg(input)
|
|
2470
|
+
};
|
|
2471
|
+
const msg = errorMsg ?? [
|
|
2472
|
+
{
|
|
2473
|
+
type: "text",
|
|
2474
|
+
text: "Expected a valid domain name, but got "
|
|
2475
|
+
},
|
|
2476
|
+
{
|
|
2477
|
+
type: "value",
|
|
2478
|
+
value: input
|
|
2479
|
+
},
|
|
2480
|
+
{
|
|
2481
|
+
type: "text",
|
|
2482
|
+
text: "."
|
|
2483
|
+
}
|
|
2484
|
+
];
|
|
2485
|
+
return {
|
|
2486
|
+
success: false,
|
|
2487
|
+
error: msg
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
1879
2490
|
if (labels.length < minLabels) {
|
|
1880
2491
|
const errorMsg = tooFewLabels;
|
|
1881
2492
|
if (typeof errorMsg === "function") return {
|
|
@@ -1926,15 +2537,14 @@ function domain(options) {
|
|
|
1926
2537
|
error: msg
|
|
1927
2538
|
};
|
|
1928
2539
|
}
|
|
1929
|
-
if (
|
|
2540
|
+
if (allowedTlds !== void 0 && allowedTldsLower !== void 0) {
|
|
1930
2541
|
const tld = labels[labels.length - 1];
|
|
1931
2542
|
const tldLower = tld.toLowerCase();
|
|
1932
|
-
|
|
1933
|
-
if (!allowedTLDsLower.includes(tldLower)) {
|
|
2543
|
+
if (!allowedTldsLower.includes(tldLower)) {
|
|
1934
2544
|
const errorMsg = tldNotAllowed;
|
|
1935
2545
|
if (typeof errorMsg === "function") return {
|
|
1936
2546
|
success: false,
|
|
1937
|
-
error: errorMsg(tld,
|
|
2547
|
+
error: errorMsg(tld, allowedTlds)
|
|
1938
2548
|
};
|
|
1939
2549
|
const msg = errorMsg ?? [
|
|
1940
2550
|
{
|
|
@@ -1947,7 +2557,15 @@ function domain(options) {
|
|
|
1947
2557
|
},
|
|
1948
2558
|
{
|
|
1949
2559
|
type: "text",
|
|
1950
|
-
text:
|
|
2560
|
+
text: " is not allowed. Allowed TLDs: "
|
|
2561
|
+
},
|
|
2562
|
+
...require_message.valueSet(allowedTlds, {
|
|
2563
|
+
fallback: "",
|
|
2564
|
+
locale: "en-US"
|
|
2565
|
+
}),
|
|
2566
|
+
{
|
|
2567
|
+
type: "text",
|
|
2568
|
+
text: "."
|
|
1951
2569
|
}
|
|
1952
2570
|
];
|
|
1953
2571
|
return {
|
|
@@ -1962,10 +2580,51 @@ function domain(options) {
|
|
|
1962
2580
|
value: result
|
|
1963
2581
|
};
|
|
1964
2582
|
},
|
|
1965
|
-
format() {
|
|
1966
|
-
return metavar;
|
|
2583
|
+
format(value) {
|
|
2584
|
+
if (typeof value !== "string") return metavar;
|
|
2585
|
+
if (!lowercase) return value;
|
|
2586
|
+
return value.split(".").length >= minLabels ? value.toLowerCase() : value;
|
|
1967
2587
|
}
|
|
1968
2588
|
};
|
|
2589
|
+
if (lowercase) {
|
|
2590
|
+
const domParser = domainParserObj;
|
|
2591
|
+
let domParsing = false;
|
|
2592
|
+
Object.defineProperty(domainParserObj, "format", {
|
|
2593
|
+
value(v) {
|
|
2594
|
+
if (typeof v !== "string") return metavar;
|
|
2595
|
+
if (domParsing) return v;
|
|
2596
|
+
domParsing = true;
|
|
2597
|
+
try {
|
|
2598
|
+
const result = domParser.parse(v);
|
|
2599
|
+
return result.success ? result.value : v;
|
|
2600
|
+
} catch {
|
|
2601
|
+
return v;
|
|
2602
|
+
} finally {
|
|
2603
|
+
domParsing = false;
|
|
2604
|
+
}
|
|
2605
|
+
},
|
|
2606
|
+
configurable: true,
|
|
2607
|
+
enumerable: true
|
|
2608
|
+
});
|
|
2609
|
+
Object.defineProperty(domainParserObj, "normalize", {
|
|
2610
|
+
value(v) {
|
|
2611
|
+
if (typeof v !== "string") return v;
|
|
2612
|
+
if (domParsing) return v;
|
|
2613
|
+
domParsing = true;
|
|
2614
|
+
try {
|
|
2615
|
+
const result = domParser.parse(v);
|
|
2616
|
+
return result.success ? result.value : v;
|
|
2617
|
+
} catch {
|
|
2618
|
+
return v;
|
|
2619
|
+
} finally {
|
|
2620
|
+
domParsing = false;
|
|
2621
|
+
}
|
|
2622
|
+
},
|
|
2623
|
+
configurable: true,
|
|
2624
|
+
enumerable: true
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
return domainParserObj;
|
|
1969
2628
|
}
|
|
1970
2629
|
/**
|
|
1971
2630
|
* Creates a value parser for IPv6 addresses.
|
|
@@ -1998,9 +2657,10 @@ function ipv6(options) {
|
|
|
1998
2657
|
const allowZero = options?.allowZero ?? true;
|
|
1999
2658
|
const errors = options?.errors;
|
|
2000
2659
|
const metavar = options?.metavar ?? "IPV6";
|
|
2001
|
-
|
|
2002
|
-
|
|
2660
|
+
const ipv6ParserObj = {
|
|
2661
|
+
mode: "sync",
|
|
2003
2662
|
metavar,
|
|
2663
|
+
placeholder: allowZero ? "::" : allowLoopback ? "::1" : "2001:db8::1",
|
|
2004
2664
|
parse(input) {
|
|
2005
2665
|
const normalized = parseAndNormalizeIpv6(input);
|
|
2006
2666
|
if (normalized === null) {
|
|
@@ -2150,10 +2810,74 @@ function ipv6(options) {
|
|
|
2150
2810
|
value: normalized
|
|
2151
2811
|
};
|
|
2152
2812
|
},
|
|
2153
|
-
format() {
|
|
2154
|
-
return metavar;
|
|
2813
|
+
format(value) {
|
|
2814
|
+
if (typeof value !== "string") return metavar;
|
|
2815
|
+
return parseAndNormalizeIpv6(value) ?? value;
|
|
2155
2816
|
}
|
|
2156
2817
|
};
|
|
2818
|
+
let ipv6Parsing = false;
|
|
2819
|
+
Object.defineProperty(ipv6ParserObj, "format", {
|
|
2820
|
+
value(v) {
|
|
2821
|
+
if (typeof v !== "string") return metavar;
|
|
2822
|
+
if (ipv6Parsing) return v;
|
|
2823
|
+
ipv6Parsing = true;
|
|
2824
|
+
try {
|
|
2825
|
+
const result = ipv6ParserObj.parse(v);
|
|
2826
|
+
return result.success ? result.value : v;
|
|
2827
|
+
} catch {
|
|
2828
|
+
return v;
|
|
2829
|
+
} finally {
|
|
2830
|
+
ipv6Parsing = false;
|
|
2831
|
+
}
|
|
2832
|
+
},
|
|
2833
|
+
configurable: true,
|
|
2834
|
+
enumerable: true
|
|
2835
|
+
});
|
|
2836
|
+
Object.defineProperty(ipv6ParserObj, "normalize", {
|
|
2837
|
+
value(v) {
|
|
2838
|
+
if (typeof v !== "string") return v;
|
|
2839
|
+
if (ipv6Parsing) return v;
|
|
2840
|
+
ipv6Parsing = true;
|
|
2841
|
+
try {
|
|
2842
|
+
const result = ipv6ParserObj.parse(v);
|
|
2843
|
+
return result.success ? result.value : v;
|
|
2844
|
+
} catch {
|
|
2845
|
+
return v;
|
|
2846
|
+
} finally {
|
|
2847
|
+
ipv6Parsing = false;
|
|
2848
|
+
}
|
|
2849
|
+
},
|
|
2850
|
+
configurable: true,
|
|
2851
|
+
enumerable: true
|
|
2852
|
+
});
|
|
2853
|
+
return ipv6ParserObj;
|
|
2854
|
+
}
|
|
2855
|
+
/**
|
|
2856
|
+
* Parses a dotted-decimal IPv4 string into four validated octets.
|
|
2857
|
+
* Returns null if the input is not a valid strict IPv4 address
|
|
2858
|
+
* (exactly four decimal octets 0–255, no leading zeros, no whitespace,
|
|
2859
|
+
* no non-decimal characters).
|
|
2860
|
+
*/
|
|
2861
|
+
function parseIpv4Octets(input) {
|
|
2862
|
+
const parts = input.split(".");
|
|
2863
|
+
if (parts.length !== 4) return null;
|
|
2864
|
+
const octets = [
|
|
2865
|
+
0,
|
|
2866
|
+
0,
|
|
2867
|
+
0,
|
|
2868
|
+
0
|
|
2869
|
+
];
|
|
2870
|
+
for (let i = 0; i < 4; i++) {
|
|
2871
|
+
const part = parts[i];
|
|
2872
|
+
if (part.length === 0) return null;
|
|
2873
|
+
if (part.trim() !== part) return null;
|
|
2874
|
+
if (part.length > 1 && part[0] === "0") return null;
|
|
2875
|
+
if (!/^[0-9]+$/.test(part)) return null;
|
|
2876
|
+
const octet = Number(part);
|
|
2877
|
+
if (!Number.isInteger(octet) || octet < 0 || octet > 255) return null;
|
|
2878
|
+
octets[i] = octet;
|
|
2879
|
+
}
|
|
2880
|
+
return octets;
|
|
2157
2881
|
}
|
|
2158
2882
|
/**
|
|
2159
2883
|
* Parses and normalizes an IPv6 address to canonical form.
|
|
@@ -2165,10 +2889,8 @@ function parseAndNormalizeIpv6(input) {
|
|
|
2165
2889
|
if (ipv4MappedMatch) {
|
|
2166
2890
|
const ipv6Part = ipv4MappedMatch[1];
|
|
2167
2891
|
const ipv4Part = ipv4MappedMatch[2];
|
|
2168
|
-
const
|
|
2169
|
-
if (
|
|
2170
|
-
const octets = ipv4Octets.map((o) => parseInt(o, 10));
|
|
2171
|
-
if (octets.some((o) => isNaN(o) || o < 0 || o > 255)) return null;
|
|
2892
|
+
const octets = parseIpv4Octets(ipv4Part);
|
|
2893
|
+
if (octets === null) return null;
|
|
2172
2894
|
const group1 = octets[0] << 8 | octets[1];
|
|
2173
2895
|
const group2 = octets[2] << 8 | octets[3];
|
|
2174
2896
|
const fullAddress = `${ipv6Part}:${group1.toString(16)}:${group2.toString(16)}`;
|
|
@@ -2262,6 +2984,90 @@ function compressIpv6(groups) {
|
|
|
2262
2984
|
else return before.join(":") + "::" + after.join(":");
|
|
2263
2985
|
}
|
|
2264
2986
|
/**
|
|
2987
|
+
* Extracts IPv4 octets from an IPv4-mapped IPv6 address.
|
|
2988
|
+
* Returns null if the address is not an IPv4-mapped address
|
|
2989
|
+
* (i.e., not in the `::ffff:x.x.x.x` range).
|
|
2990
|
+
*/
|
|
2991
|
+
function extractIpv4FromMapped(normalizedIpv6) {
|
|
2992
|
+
const groups = expandIpv6(normalizedIpv6);
|
|
2993
|
+
if (groups === null) return null;
|
|
2994
|
+
for (let i = 0; i < 5; i++) if (parseInt(groups[i], 16) !== 0) return null;
|
|
2995
|
+
if (parseInt(groups[5], 16) !== 65535) return null;
|
|
2996
|
+
const high = parseInt(groups[6], 16);
|
|
2997
|
+
const low = parseInt(groups[7], 16);
|
|
2998
|
+
return [
|
|
2999
|
+
high >> 8 & 255,
|
|
3000
|
+
high & 255,
|
|
3001
|
+
low >> 8 & 255,
|
|
3002
|
+
low & 255
|
|
3003
|
+
];
|
|
3004
|
+
}
|
|
3005
|
+
/**
|
|
3006
|
+
* Checks IPv4 restrictions against octets extracted from an IPv4-mapped
|
|
3007
|
+
* IPv6 address. The check uses the base address, consistent with how
|
|
3008
|
+
* the `ipv4()` parser validates the address part of a regular IPv4 CIDR.
|
|
3009
|
+
*
|
|
3010
|
+
* Returns an error result if a restriction is violated, or null if all
|
|
3011
|
+
* checks pass.
|
|
3012
|
+
*/
|
|
3013
|
+
function checkIpv4MappedRestrictions(octets, normalizedIpv6, ipv4Opts, errors) {
|
|
3014
|
+
const allowPrivate = ipv4Opts?.allowPrivate ?? true;
|
|
3015
|
+
const allowLoopback = ipv4Opts?.allowLoopback ?? true;
|
|
3016
|
+
const allowLinkLocal = ipv4Opts?.allowLinkLocal ?? true;
|
|
3017
|
+
const allowMulticast = ipv4Opts?.allowMulticast ?? true;
|
|
3018
|
+
const allowBroadcast = ipv4Opts?.allowBroadcast ?? true;
|
|
3019
|
+
const allowZero = ipv4Opts?.allowZero ?? true;
|
|
3020
|
+
if (!allowPrivate && isPrivateIp(octets)) {
|
|
3021
|
+
const errorMsg = errors?.privateNotAllowed;
|
|
3022
|
+
const msg = typeof errorMsg === "function" ? errorMsg(normalizedIpv6) : errorMsg ?? require_message.message`${normalizedIpv6} is a private IP address.`;
|
|
3023
|
+
return {
|
|
3024
|
+
success: false,
|
|
3025
|
+
error: msg
|
|
3026
|
+
};
|
|
3027
|
+
}
|
|
3028
|
+
if (!allowLoopback && isLoopbackIp(octets)) {
|
|
3029
|
+
const errorMsg = errors?.loopbackNotAllowed;
|
|
3030
|
+
const msg = typeof errorMsg === "function" ? errorMsg(normalizedIpv6) : errorMsg ?? require_message.message`${normalizedIpv6} is a loopback address.`;
|
|
3031
|
+
return {
|
|
3032
|
+
success: false,
|
|
3033
|
+
error: msg
|
|
3034
|
+
};
|
|
3035
|
+
}
|
|
3036
|
+
if (!allowLinkLocal && isLinkLocalIp(octets)) {
|
|
3037
|
+
const errorMsg = errors?.linkLocalNotAllowed;
|
|
3038
|
+
const msg = typeof errorMsg === "function" ? errorMsg(normalizedIpv6) : errorMsg ?? require_message.message`${normalizedIpv6} is a link-local address.`;
|
|
3039
|
+
return {
|
|
3040
|
+
success: false,
|
|
3041
|
+
error: msg
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
if (!allowMulticast && isMulticastIp(octets)) {
|
|
3045
|
+
const errorMsg = errors?.multicastNotAllowed;
|
|
3046
|
+
const msg = typeof errorMsg === "function" ? errorMsg(normalizedIpv6) : errorMsg ?? require_message.message`${normalizedIpv6} is a multicast address.`;
|
|
3047
|
+
return {
|
|
3048
|
+
success: false,
|
|
3049
|
+
error: msg
|
|
3050
|
+
};
|
|
3051
|
+
}
|
|
3052
|
+
if (!allowBroadcast && isBroadcastIp(octets)) {
|
|
3053
|
+
const errorMsg = errors?.broadcastNotAllowed;
|
|
3054
|
+
const msg = typeof errorMsg === "function" ? errorMsg(normalizedIpv6) : errorMsg ?? require_message.message`${normalizedIpv6} is the broadcast address.`;
|
|
3055
|
+
return {
|
|
3056
|
+
success: false,
|
|
3057
|
+
error: msg
|
|
3058
|
+
};
|
|
3059
|
+
}
|
|
3060
|
+
if (!allowZero && isZeroIp(octets)) {
|
|
3061
|
+
const errorMsg = errors?.zeroNotAllowed;
|
|
3062
|
+
const msg = typeof errorMsg === "function" ? errorMsg(normalizedIpv6) : errorMsg ?? require_message.message`${normalizedIpv6} is the zero address.`;
|
|
3063
|
+
return {
|
|
3064
|
+
success: false,
|
|
3065
|
+
error: msg
|
|
3066
|
+
};
|
|
3067
|
+
}
|
|
3068
|
+
return null;
|
|
3069
|
+
}
|
|
3070
|
+
/**
|
|
2265
3071
|
* Creates a value parser that accepts both IPv4 and IPv6 addresses.
|
|
2266
3072
|
*
|
|
2267
3073
|
* By default, accepts both IPv4 and IPv6 addresses. Use the `version` option
|
|
@@ -2314,9 +3120,26 @@ function ip(options) {
|
|
|
2314
3120
|
zeroNotAllowed: errors?.zeroNotAllowed
|
|
2315
3121
|
}
|
|
2316
3122
|
}) : null;
|
|
2317
|
-
|
|
2318
|
-
|
|
3123
|
+
const mappedIpv4Opts = version === "both" ? {
|
|
3124
|
+
allowPrivate: options?.ipv4?.allowPrivate,
|
|
3125
|
+
allowLoopback: options?.ipv4?.allowLoopback,
|
|
3126
|
+
allowLinkLocal: options?.ipv4?.allowLinkLocal,
|
|
3127
|
+
allowMulticast: options?.ipv4?.allowMulticast,
|
|
3128
|
+
allowBroadcast: options?.ipv4?.allowBroadcast,
|
|
3129
|
+
allowZero: options?.ipv4?.allowZero
|
|
3130
|
+
} : void 0;
|
|
3131
|
+
const mappedIpv4Errors = version === "both" ? {
|
|
3132
|
+
privateNotAllowed: errors?.privateNotAllowed,
|
|
3133
|
+
loopbackNotAllowed: errors?.loopbackNotAllowed,
|
|
3134
|
+
linkLocalNotAllowed: errors?.linkLocalNotAllowed,
|
|
3135
|
+
multicastNotAllowed: errors?.multicastNotAllowed,
|
|
3136
|
+
broadcastNotAllowed: errors?.broadcastNotAllowed,
|
|
3137
|
+
zeroNotAllowed: errors?.zeroNotAllowed
|
|
3138
|
+
} : void 0;
|
|
3139
|
+
const ipParserObj = {
|
|
3140
|
+
mode: "sync",
|
|
2319
3141
|
metavar,
|
|
3142
|
+
placeholder: version === 6 ? ipv6Parser.placeholder : ipv4Parser.placeholder,
|
|
2320
3143
|
parse(input) {
|
|
2321
3144
|
let ipv4Error = null;
|
|
2322
3145
|
let ipv6Error = null;
|
|
@@ -2328,7 +3151,16 @@ function ip(options) {
|
|
|
2328
3151
|
}
|
|
2329
3152
|
if (ipv6Parser !== null) {
|
|
2330
3153
|
const result = ipv6Parser.parse(input);
|
|
2331
|
-
if (result.success)
|
|
3154
|
+
if (result.success) {
|
|
3155
|
+
if (version === "both") {
|
|
3156
|
+
const mappedOctets = extractIpv4FromMapped(result.value);
|
|
3157
|
+
if (mappedOctets !== null) {
|
|
3158
|
+
const restrictionError = checkIpv4MappedRestrictions(mappedOctets, result.value, mappedIpv4Opts, mappedIpv4Errors);
|
|
3159
|
+
if (restrictionError !== null) return restrictionError;
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
return result;
|
|
3163
|
+
}
|
|
2332
3164
|
ipv6Error = result;
|
|
2333
3165
|
if (version === 6) return result;
|
|
2334
3166
|
}
|
|
@@ -2364,10 +3196,47 @@ function ip(options) {
|
|
|
2364
3196
|
error: msg
|
|
2365
3197
|
};
|
|
2366
3198
|
},
|
|
2367
|
-
format() {
|
|
2368
|
-
return metavar;
|
|
3199
|
+
format(value) {
|
|
3200
|
+
if (typeof value !== "string") return metavar;
|
|
3201
|
+
return parseAndNormalizeIpv6(value) ?? value;
|
|
2369
3202
|
}
|
|
2370
3203
|
};
|
|
3204
|
+
let ipParsing = false;
|
|
3205
|
+
Object.defineProperty(ipParserObj, "format", {
|
|
3206
|
+
value(v) {
|
|
3207
|
+
if (typeof v !== "string") return metavar;
|
|
3208
|
+
if (ipParsing) return v;
|
|
3209
|
+
ipParsing = true;
|
|
3210
|
+
try {
|
|
3211
|
+
const result = ipParserObj.parse(v);
|
|
3212
|
+
return result.success ? result.value : v;
|
|
3213
|
+
} catch {
|
|
3214
|
+
return v;
|
|
3215
|
+
} finally {
|
|
3216
|
+
ipParsing = false;
|
|
3217
|
+
}
|
|
3218
|
+
},
|
|
3219
|
+
configurable: true,
|
|
3220
|
+
enumerable: true
|
|
3221
|
+
});
|
|
3222
|
+
Object.defineProperty(ipParserObj, "normalize", {
|
|
3223
|
+
value(v) {
|
|
3224
|
+
if (typeof v !== "string") return v;
|
|
3225
|
+
if (ipParsing) return v;
|
|
3226
|
+
ipParsing = true;
|
|
3227
|
+
try {
|
|
3228
|
+
const result = ipParserObj.parse(v);
|
|
3229
|
+
return result.success ? result.value : v;
|
|
3230
|
+
} catch {
|
|
3231
|
+
return v;
|
|
3232
|
+
} finally {
|
|
3233
|
+
ipParsing = false;
|
|
3234
|
+
}
|
|
3235
|
+
},
|
|
3236
|
+
configurable: true,
|
|
3237
|
+
enumerable: true
|
|
3238
|
+
});
|
|
3239
|
+
return ipParserObj;
|
|
2371
3240
|
}
|
|
2372
3241
|
/**
|
|
2373
3242
|
* Creates a value parser for CIDR notation (IP address with prefix length).
|
|
@@ -2395,16 +3264,71 @@ function ip(options) {
|
|
|
2395
3264
|
* @since 0.10.0
|
|
2396
3265
|
*/
|
|
2397
3266
|
function cidr(options) {
|
|
3267
|
+
if (options?.minPrefix != null && !Number.isFinite(options.minPrefix)) throw new RangeError(`Expected minPrefix to be a finite number, but got: ${options.minPrefix}`);
|
|
3268
|
+
if (options?.maxPrefix != null && !Number.isFinite(options.maxPrefix)) throw new RangeError(`Expected maxPrefix to be a finite number, but got: ${options.maxPrefix}`);
|
|
3269
|
+
if (options?.minPrefix != null && options?.maxPrefix != null && options.minPrefix > options.maxPrefix) throw new RangeError(`Expected minPrefix to be less than or equal to maxPrefix, but got minPrefix: ${options.minPrefix} and maxPrefix: ${options.maxPrefix}.`);
|
|
2398
3270
|
const version = options?.version ?? "both";
|
|
3271
|
+
const maxPrefixForVersion = version === 4 ? 32 : version === 6 ? 128 : 128;
|
|
3272
|
+
if (options?.minPrefix != null && (options.minPrefix < 0 || options.minPrefix > maxPrefixForVersion)) throw new RangeError(`Expected minPrefix to be between 0 and ${maxPrefixForVersion} for IPv${version === "both" ? "4/6" : version}, but got minPrefix: ${options.minPrefix}.`);
|
|
3273
|
+
if (options?.maxPrefix != null && (options.maxPrefix < 0 || options.maxPrefix > maxPrefixForVersion)) throw new RangeError(`Expected maxPrefix to be between 0 and ${maxPrefixForVersion} for IPv${version === "both" ? "4/6" : version}, but got maxPrefix: ${options.maxPrefix}.`);
|
|
2399
3274
|
const minPrefix = options?.minPrefix;
|
|
2400
3275
|
const maxPrefix = options?.maxPrefix;
|
|
2401
3276
|
const errors = options?.errors;
|
|
2402
3277
|
const metavar = options?.metavar ?? "CIDR";
|
|
2403
|
-
const
|
|
2404
|
-
const
|
|
2405
|
-
|
|
2406
|
-
|
|
3278
|
+
const genericIpSentinel = [];
|
|
3279
|
+
const ipv4Parser = version === 4 || version === "both" ? ipv4({
|
|
3280
|
+
...options?.ipv4,
|
|
3281
|
+
errors: {
|
|
3282
|
+
invalidIpv4: genericIpSentinel,
|
|
3283
|
+
privateNotAllowed: errors?.privateNotAllowed,
|
|
3284
|
+
loopbackNotAllowed: errors?.loopbackNotAllowed,
|
|
3285
|
+
linkLocalNotAllowed: errors?.linkLocalNotAllowed,
|
|
3286
|
+
multicastNotAllowed: errors?.multicastNotAllowed,
|
|
3287
|
+
broadcastNotAllowed: errors?.broadcastNotAllowed,
|
|
3288
|
+
zeroNotAllowed: errors?.zeroNotAllowed
|
|
3289
|
+
}
|
|
3290
|
+
}) : null;
|
|
3291
|
+
const ipv6Parser = version === 6 || version === "both" ? ipv6({
|
|
3292
|
+
...options?.ipv6,
|
|
3293
|
+
errors: {
|
|
3294
|
+
invalidIpv6: genericIpSentinel,
|
|
3295
|
+
loopbackNotAllowed: errors?.loopbackNotAllowed,
|
|
3296
|
+
linkLocalNotAllowed: errors?.linkLocalNotAllowed,
|
|
3297
|
+
multicastNotAllowed: errors?.multicastNotAllowed,
|
|
3298
|
+
zeroNotAllowed: errors?.zeroNotAllowed,
|
|
3299
|
+
uniqueLocalNotAllowed: errors?.uniqueLocalNotAllowed
|
|
3300
|
+
}
|
|
3301
|
+
}) : null;
|
|
3302
|
+
const mappedIpv4Opts = version === "both" ? {
|
|
3303
|
+
allowPrivate: options?.ipv4?.allowPrivate,
|
|
3304
|
+
allowLoopback: options?.ipv4?.allowLoopback,
|
|
3305
|
+
allowLinkLocal: options?.ipv4?.allowLinkLocal,
|
|
3306
|
+
allowMulticast: options?.ipv4?.allowMulticast,
|
|
3307
|
+
allowBroadcast: options?.ipv4?.allowBroadcast,
|
|
3308
|
+
allowZero: options?.ipv4?.allowZero
|
|
3309
|
+
} : void 0;
|
|
3310
|
+
const mappedIpv4Errors = version === "both" ? {
|
|
3311
|
+
privateNotAllowed: errors?.privateNotAllowed,
|
|
3312
|
+
loopbackNotAllowed: errors?.loopbackNotAllowed,
|
|
3313
|
+
linkLocalNotAllowed: errors?.linkLocalNotAllowed,
|
|
3314
|
+
multicastNotAllowed: errors?.multicastNotAllowed,
|
|
3315
|
+
broadcastNotAllowed: errors?.broadcastNotAllowed,
|
|
3316
|
+
zeroNotAllowed: errors?.zeroNotAllowed
|
|
3317
|
+
} : void 0;
|
|
3318
|
+
const cidrParserObj = {
|
|
3319
|
+
mode: "sync",
|
|
2407
3320
|
metavar,
|
|
3321
|
+
get placeholder() {
|
|
3322
|
+
return version === 6 || version === "both" && (minPrefix ?? 0) > 32 ? {
|
|
3323
|
+
address: ipv6Parser.placeholder,
|
|
3324
|
+
prefix: minPrefix ?? 0,
|
|
3325
|
+
version: 6
|
|
3326
|
+
} : {
|
|
3327
|
+
address: ipv4Parser.placeholder,
|
|
3328
|
+
prefix: minPrefix ?? 0,
|
|
3329
|
+
version: 4
|
|
3330
|
+
};
|
|
3331
|
+
},
|
|
2408
3332
|
parse(input) {
|
|
2409
3333
|
const slashIndex = input.lastIndexOf("/");
|
|
2410
3334
|
if (slashIndex === -1) {
|
|
@@ -2462,6 +3386,8 @@ function cidr(options) {
|
|
|
2462
3386
|
}
|
|
2463
3387
|
let ipVersion = null;
|
|
2464
3388
|
let normalizedIp = null;
|
|
3389
|
+
let ipv4Error = null;
|
|
3390
|
+
let ipv6Error = null;
|
|
2465
3391
|
if (ipv4Parser !== null) {
|
|
2466
3392
|
const result = ipv4Parser.parse(ipPart);
|
|
2467
3393
|
if (result.success) {
|
|
@@ -2500,7 +3426,7 @@ function cidr(options) {
|
|
|
2500
3426
|
error: msg
|
|
2501
3427
|
};
|
|
2502
3428
|
}
|
|
2503
|
-
}
|
|
3429
|
+
} else ipv4Error = result;
|
|
2504
3430
|
}
|
|
2505
3431
|
if (ipVersion === null && ipv6Parser !== null) {
|
|
2506
3432
|
const result = ipv6Parser.parse(ipPart);
|
|
@@ -2540,9 +3466,120 @@ function cidr(options) {
|
|
|
2540
3466
|
error: msg
|
|
2541
3467
|
};
|
|
2542
3468
|
}
|
|
2543
|
-
}
|
|
3469
|
+
} else ipv6Error = result;
|
|
2544
3470
|
}
|
|
2545
3471
|
if (ipVersion === null || normalizedIp === null) {
|
|
3472
|
+
const candidates = [[
|
|
3473
|
+
ipv4Error,
|
|
3474
|
+
4,
|
|
3475
|
+
32
|
|
3476
|
+
], [
|
|
3477
|
+
ipv6Error,
|
|
3478
|
+
6,
|
|
3479
|
+
128
|
|
3480
|
+
]];
|
|
3481
|
+
for (const [err, ver, maxPfx] of candidates) if (err !== null && !err.success && err.error !== genericIpSentinel) {
|
|
3482
|
+
if (prefix > maxPfx) {
|
|
3483
|
+
const errorMsg$1 = errors?.invalidPrefix;
|
|
3484
|
+
if (typeof errorMsg$1 === "function") return {
|
|
3485
|
+
success: false,
|
|
3486
|
+
error: errorMsg$1(prefix, ver)
|
|
3487
|
+
};
|
|
3488
|
+
const msg$1 = errorMsg$1 ?? [
|
|
3489
|
+
{
|
|
3490
|
+
type: "text",
|
|
3491
|
+
text: "Expected a prefix length between 0 and "
|
|
3492
|
+
},
|
|
3493
|
+
{
|
|
3494
|
+
type: "text",
|
|
3495
|
+
text: maxPfx.toString()
|
|
3496
|
+
},
|
|
3497
|
+
{
|
|
3498
|
+
type: "text",
|
|
3499
|
+
text: ` for IPv${ver}, but got `
|
|
3500
|
+
},
|
|
3501
|
+
{
|
|
3502
|
+
type: "text",
|
|
3503
|
+
text: prefix.toString()
|
|
3504
|
+
},
|
|
3505
|
+
{
|
|
3506
|
+
type: "text",
|
|
3507
|
+
text: "."
|
|
3508
|
+
}
|
|
3509
|
+
];
|
|
3510
|
+
return {
|
|
3511
|
+
success: false,
|
|
3512
|
+
error: msg$1
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
3515
|
+
if (minPrefix !== void 0 && prefix < minPrefix) {
|
|
3516
|
+
const errorMsg$1 = errors?.prefixBelowMinimum;
|
|
3517
|
+
if (typeof errorMsg$1 === "function") return {
|
|
3518
|
+
success: false,
|
|
3519
|
+
error: errorMsg$1(prefix, minPrefix)
|
|
3520
|
+
};
|
|
3521
|
+
const msg$1 = errorMsg$1 ?? [
|
|
3522
|
+
{
|
|
3523
|
+
type: "text",
|
|
3524
|
+
text: "Expected a prefix length greater than or equal to "
|
|
3525
|
+
},
|
|
3526
|
+
{
|
|
3527
|
+
type: "text",
|
|
3528
|
+
text: minPrefix.toString()
|
|
3529
|
+
},
|
|
3530
|
+
{
|
|
3531
|
+
type: "text",
|
|
3532
|
+
text: ", but got "
|
|
3533
|
+
},
|
|
3534
|
+
{
|
|
3535
|
+
type: "text",
|
|
3536
|
+
text: prefix.toString()
|
|
3537
|
+
},
|
|
3538
|
+
{
|
|
3539
|
+
type: "text",
|
|
3540
|
+
text: "."
|
|
3541
|
+
}
|
|
3542
|
+
];
|
|
3543
|
+
return {
|
|
3544
|
+
success: false,
|
|
3545
|
+
error: msg$1
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
if (maxPrefix !== void 0 && prefix > maxPrefix) {
|
|
3549
|
+
const errorMsg$1 = errors?.prefixAboveMaximum;
|
|
3550
|
+
if (typeof errorMsg$1 === "function") return {
|
|
3551
|
+
success: false,
|
|
3552
|
+
error: errorMsg$1(prefix, maxPrefix)
|
|
3553
|
+
};
|
|
3554
|
+
const msg$1 = errorMsg$1 ?? [
|
|
3555
|
+
{
|
|
3556
|
+
type: "text",
|
|
3557
|
+
text: "Expected a prefix length less than or equal to "
|
|
3558
|
+
},
|
|
3559
|
+
{
|
|
3560
|
+
type: "text",
|
|
3561
|
+
text: maxPrefix.toString()
|
|
3562
|
+
},
|
|
3563
|
+
{
|
|
3564
|
+
type: "text",
|
|
3565
|
+
text: ", but got "
|
|
3566
|
+
},
|
|
3567
|
+
{
|
|
3568
|
+
type: "text",
|
|
3569
|
+
text: prefix.toString()
|
|
3570
|
+
},
|
|
3571
|
+
{
|
|
3572
|
+
type: "text",
|
|
3573
|
+
text: "."
|
|
3574
|
+
}
|
|
3575
|
+
];
|
|
3576
|
+
return {
|
|
3577
|
+
success: false,
|
|
3578
|
+
error: msg$1
|
|
3579
|
+
};
|
|
3580
|
+
}
|
|
3581
|
+
return err;
|
|
3582
|
+
}
|
|
2546
3583
|
const errorMsg = errors?.invalidCidr;
|
|
2547
3584
|
if (typeof errorMsg === "function") return {
|
|
2548
3585
|
success: false,
|
|
@@ -2633,6 +3670,13 @@ function cidr(options) {
|
|
|
2633
3670
|
error: msg
|
|
2634
3671
|
};
|
|
2635
3672
|
}
|
|
3673
|
+
if (version === "both" && ipVersion === 6 && normalizedIp !== null) {
|
|
3674
|
+
const mappedOctets = extractIpv4FromMapped(normalizedIp);
|
|
3675
|
+
if (mappedOctets !== null) {
|
|
3676
|
+
const restrictionError = checkIpv4MappedRestrictions(mappedOctets, normalizedIp, mappedIpv4Opts, mappedIpv4Errors);
|
|
3677
|
+
if (restrictionError !== null) return restrictionError;
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
2636
3680
|
return {
|
|
2637
3681
|
success: true,
|
|
2638
3682
|
value: {
|
|
@@ -2642,13 +3686,52 @@ function cidr(options) {
|
|
|
2642
3686
|
}
|
|
2643
3687
|
};
|
|
2644
3688
|
},
|
|
2645
|
-
format()
|
|
2646
|
-
return metavar;
|
|
2647
|
-
}
|
|
3689
|
+
format: ((_) => metavar)
|
|
2648
3690
|
};
|
|
3691
|
+
let cidrParsing = false;
|
|
3692
|
+
Object.defineProperty(cidrParserObj, "format", {
|
|
3693
|
+
value(value) {
|
|
3694
|
+
if (typeof value !== "object" || value == null || !("address" in value) || !("prefix" in value) || !("version" in value)) return metavar;
|
|
3695
|
+
if (cidrParsing) return `${value.address}/${value.prefix}`;
|
|
3696
|
+
cidrParsing = true;
|
|
3697
|
+
try {
|
|
3698
|
+
const raw = `${value.address}/${value.prefix}`;
|
|
3699
|
+
const result = cidrParserObj.parse(raw);
|
|
3700
|
+
return result.success && result.value.version === value.version ? `${result.value.address}/${result.value.prefix}` : raw;
|
|
3701
|
+
} catch {
|
|
3702
|
+
return `${value.address}/${value.prefix}`;
|
|
3703
|
+
} finally {
|
|
3704
|
+
cidrParsing = false;
|
|
3705
|
+
}
|
|
3706
|
+
},
|
|
3707
|
+
configurable: true,
|
|
3708
|
+
enumerable: true
|
|
3709
|
+
});
|
|
3710
|
+
Object.defineProperty(cidrParserObj, "normalize", {
|
|
3711
|
+
value(v) {
|
|
3712
|
+
if (typeof v !== "object" || v == null || !("address" in v) || !("prefix" in v) || !("version" in v)) return v;
|
|
3713
|
+
if (cidrParsing) return v;
|
|
3714
|
+
cidrParsing = true;
|
|
3715
|
+
const formatted = `${v.address}/${v.prefix}`;
|
|
3716
|
+
try {
|
|
3717
|
+
const result = cidrParserObj.parse(formatted);
|
|
3718
|
+
if (result.success && result.value.version === v.version) return result.value;
|
|
3719
|
+
return v;
|
|
3720
|
+
} catch {
|
|
3721
|
+
return v;
|
|
3722
|
+
} finally {
|
|
3723
|
+
cidrParsing = false;
|
|
3724
|
+
}
|
|
3725
|
+
},
|
|
3726
|
+
configurable: true,
|
|
3727
|
+
enumerable: true
|
|
3728
|
+
});
|
|
3729
|
+
return cidrParserObj;
|
|
2649
3730
|
}
|
|
2650
3731
|
|
|
2651
3732
|
//#endregion
|
|
3733
|
+
exports.checkBooleanOption = checkBooleanOption;
|
|
3734
|
+
exports.checkEnumOption = checkEnumOption;
|
|
2652
3735
|
exports.choice = choice;
|
|
2653
3736
|
exports.cidr = cidr;
|
|
2654
3737
|
exports.domain = domain;
|