@optique/core 1.0.0-dev.908 → 1.0.0
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 -523
- package/dist/facade.d.cts +87 -18
- package/dist/facade.d.ts +87 -18
- package/dist/facade.js +718 -523
- 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 +1278 -191
- package/dist/valueparser.d.cts +330 -20
- package/dist/valueparser.d.ts +330 -20
- package/dist/valueparser.js +1277 -192
- 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,9 @@ 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.
|
|
1637
|
+
* @throws {TypeError} If `separator` contains digit characters, since digits
|
|
1638
|
+
* in the separator would cause ambiguous splitting of port input.
|
|
1389
1639
|
* @since 0.10.0
|
|
1390
1640
|
*
|
|
1391
1641
|
* @example
|
|
@@ -1406,9 +1656,12 @@ function email(options) {
|
|
|
1406
1656
|
* ```
|
|
1407
1657
|
*/
|
|
1408
1658
|
function socketAddress(options) {
|
|
1409
|
-
const metavar = options?.metavar ?? "HOST:PORT";
|
|
1410
|
-
require_nonempty.ensureNonEmptyString(metavar);
|
|
1411
1659
|
const separator = options?.separator ?? ":";
|
|
1660
|
+
if (separator === "") throw new TypeError("Expected separator to not be empty.");
|
|
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`;
|
|
1663
|
+
const metavar = options?.metavar ?? `HOST${separator}PORT`;
|
|
1664
|
+
require_nonempty.ensureNonEmptyString(metavar);
|
|
1412
1665
|
const defaultPort = options?.defaultPort;
|
|
1413
1666
|
const requirePort = options?.requirePort ?? false;
|
|
1414
1667
|
const hostType = options?.host?.type ?? "both";
|
|
@@ -1420,88 +1673,274 @@ function socketAddress(options) {
|
|
|
1420
1673
|
...options?.host?.ip,
|
|
1421
1674
|
metavar: "HOST"
|
|
1422
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);
|
|
1423
1683
|
const portParser = port({
|
|
1424
1684
|
...options?.port,
|
|
1425
1685
|
metavar: "PORT",
|
|
1426
1686
|
type: "number"
|
|
1427
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
|
+
}
|
|
1428
1716
|
function parseHost(hostInput) {
|
|
1429
1717
|
if (hostType === "hostname") {
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
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);
|
|
1442
1735
|
}
|
|
1443
1736
|
}
|
|
1444
1737
|
return {
|
|
1445
|
-
|
|
1738
|
+
mode: "sync",
|
|
1446
1739
|
metavar,
|
|
1740
|
+
get placeholder() {
|
|
1741
|
+
return {
|
|
1742
|
+
host: hostType === "ip" ? ipParser.placeholder : hostnameParser.placeholder,
|
|
1743
|
+
port: defaultPort ?? portParser.placeholder
|
|
1744
|
+
};
|
|
1745
|
+
},
|
|
1447
1746
|
parse(input) {
|
|
1448
1747
|
const trimmed = input.trim();
|
|
1449
|
-
const
|
|
1450
|
-
|
|
1451
|
-
let
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
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;
|
|
1458
1813
|
}
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
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.`;
|
|
1463
1873
|
return {
|
|
1464
1874
|
success: false,
|
|
1465
1875
|
error: msg
|
|
1466
1876
|
};
|
|
1467
1877
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
if (
|
|
1471
|
-
const
|
|
1472
|
-
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;
|
|
1473
1882
|
return {
|
|
1474
1883
|
success: false,
|
|
1475
1884
|
error: msg
|
|
1476
1885
|
};
|
|
1477
1886
|
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
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.`;
|
|
1482
1906
|
return {
|
|
1483
1907
|
success: false,
|
|
1484
1908
|
error: msg
|
|
1485
1909
|
};
|
|
1486
1910
|
}
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
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;
|
|
1492
1916
|
return {
|
|
1493
1917
|
success: false,
|
|
1494
1918
|
error: msg
|
|
1495
1919
|
};
|
|
1496
1920
|
}
|
|
1497
|
-
|
|
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
|
+
};
|
|
1498
1940
|
}
|
|
1499
1941
|
return {
|
|
1500
|
-
success:
|
|
1501
|
-
|
|
1502
|
-
host: validatedHost,
|
|
1503
|
-
port: validatedPort
|
|
1504
|
-
}
|
|
1942
|
+
success: false,
|
|
1943
|
+
error: require_message.message`Expected a socket address in format ${require_message.text(formatExample)}, but got ${input}.`
|
|
1505
1944
|
};
|
|
1506
1945
|
},
|
|
1507
1946
|
format(value) {
|
|
@@ -1510,11 +1949,14 @@ function socketAddress(options) {
|
|
|
1510
1949
|
};
|
|
1511
1950
|
}
|
|
1512
1951
|
function portRange(options) {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
require_nonempty.ensureNonEmptyString(metavar);
|
|
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)}.`);
|
|
1517
1955
|
const separator = options?.separator ?? "-";
|
|
1956
|
+
if (separator === "") throw new TypeError("Expected separator to not be empty.");
|
|
1957
|
+
if (/\p{Nd}/u.test(separator)) throw new TypeError(`Expected separator to not contain digits, but got: ${JSON.stringify(separator)}.`);
|
|
1958
|
+
const metavar = options?.metavar ?? `PORT${separator}PORT`;
|
|
1959
|
+
require_nonempty.ensureNonEmptyString(metavar);
|
|
1518
1960
|
const allowSingle = options?.allowSingle ?? false;
|
|
1519
1961
|
const isBigInt = options?.type === "bigint";
|
|
1520
1962
|
const portParser = isBigInt ? port({
|
|
@@ -1531,8 +1973,17 @@ function portRange(options) {
|
|
|
1531
1973
|
errors: options?.errors
|
|
1532
1974
|
});
|
|
1533
1975
|
return {
|
|
1534
|
-
|
|
1976
|
+
mode: "sync",
|
|
1535
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
|
+
},
|
|
1536
1987
|
parse(input) {
|
|
1537
1988
|
const trimmed = input.trim();
|
|
1538
1989
|
const separatorIndex = trimmed.indexOf(separator);
|
|
@@ -1631,10 +2082,14 @@ function portRange(options) {
|
|
|
1631
2082
|
* Creates a value parser for MAC (Media Access Control) addresses.
|
|
1632
2083
|
*
|
|
1633
2084
|
* Validates MAC-48 addresses (6 octets, 12 hex digits) in various formats:
|
|
1634
|
-
* - Colon-separated: `00:1A:2B:3C:4D:5E`
|
|
1635
|
-
* - Hyphen-separated: `00-1A-2B-3C-4D-5E`
|
|
1636
|
-
* - Dot-separated (Cisco): `001A.2B3C.4D5E`
|
|
1637
|
-
* - 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`).
|
|
1638
2093
|
*
|
|
1639
2094
|
* Returns the MAC address as a formatted string according to `case` and
|
|
1640
2095
|
* `outputSeparator` options.
|
|
@@ -1658,6 +2113,24 @@ function portRange(options) {
|
|
|
1658
2113
|
* ```
|
|
1659
2114
|
*/
|
|
1660
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
|
+
]);
|
|
1661
2134
|
const separator = options?.separator ?? "any";
|
|
1662
2135
|
const caseOption = options?.case ?? "preserve";
|
|
1663
2136
|
const outputSeparator = options?.outputSeparator;
|
|
@@ -1666,9 +2139,64 @@ function macAddress(options) {
|
|
|
1666
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})$/;
|
|
1667
2140
|
const dotRegex = /^([0-9a-fA-F]{4})\.([0-9a-fA-F]{4})\.([0-9a-fA-F]{4})$/;
|
|
1668
2141
|
const noneRegex = /^([0-9a-fA-F]{12})$/;
|
|
1669
|
-
|
|
1670
|
-
|
|
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",
|
|
1671
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
|
+
},
|
|
1672
2200
|
parse(input) {
|
|
1673
2201
|
let octets = [];
|
|
1674
2202
|
let inputSeparator;
|
|
@@ -1728,28 +2256,52 @@ function macAddress(options) {
|
|
|
1728
2256
|
error: msg
|
|
1729
2257
|
};
|
|
1730
2258
|
}
|
|
1731
|
-
|
|
1732
|
-
if (caseOption === "upper") formattedOctets = octets.map((octet) => octet.toUpperCase());
|
|
1733
|
-
else if (caseOption === "lower") formattedOctets = octets.map((octet) => octet.toLowerCase());
|
|
2259
|
+
octets = octets.map((o) => o.padStart(2, "0"));
|
|
1734
2260
|
const finalSeparator = outputSeparator ?? inputSeparator ?? ":";
|
|
1735
|
-
let result;
|
|
1736
|
-
if (finalSeparator === ":") result = formattedOctets.join(":");
|
|
1737
|
-
else if (finalSeparator === "-") result = formattedOctets.join("-");
|
|
1738
|
-
else if (finalSeparator === ".") result = [
|
|
1739
|
-
formattedOctets[0] + formattedOctets[1],
|
|
1740
|
-
formattedOctets[2] + formattedOctets[3],
|
|
1741
|
-
formattedOctets[4] + formattedOctets[5]
|
|
1742
|
-
].join(".");
|
|
1743
|
-
else result = formattedOctets.join("");
|
|
1744
2261
|
return {
|
|
1745
2262
|
success: true,
|
|
1746
|
-
value:
|
|
2263
|
+
value: joinOctets(octets, finalSeparator)
|
|
1747
2264
|
};
|
|
1748
2265
|
},
|
|
1749
|
-
format
|
|
1750
|
-
return metavar;
|
|
1751
|
-
}
|
|
2266
|
+
format: normalizeMac
|
|
1752
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;
|
|
1753
2305
|
}
|
|
1754
2306
|
/**
|
|
1755
2307
|
* Creates a value parser for domain names.
|
|
@@ -1760,6 +2312,14 @@ function macAddress(options) {
|
|
|
1760
2312
|
*
|
|
1761
2313
|
* @param options Parser options for domain validation.
|
|
1762
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.
|
|
1763
2323
|
*
|
|
1764
2324
|
* @example
|
|
1765
2325
|
* ``` typescript
|
|
@@ -1773,7 +2333,7 @@ function macAddress(options) {
|
|
|
1773
2333
|
* option("--root", domain({ allowSubdomains: false }))
|
|
1774
2334
|
*
|
|
1775
2335
|
* // Restrict to specific TLDs
|
|
1776
|
-
* option("--domain", domain({
|
|
2336
|
+
* option("--domain", domain({ allowedTlds: ["com", "org", "net"] }))
|
|
1777
2337
|
*
|
|
1778
2338
|
* // Normalize to lowercase
|
|
1779
2339
|
* option("--domain", domain({ lowercase: true }))
|
|
@@ -1783,19 +2343,49 @@ function macAddress(options) {
|
|
|
1783
2343
|
*/
|
|
1784
2344
|
function domain(options) {
|
|
1785
2345
|
const metavar = options?.metavar ?? "DOMAIN";
|
|
2346
|
+
checkBooleanOption(options, "allowSubdomains");
|
|
2347
|
+
checkBooleanOption(options, "lowercase");
|
|
1786
2348
|
const allowSubdomains = options?.allowSubdomains ?? true;
|
|
1787
|
-
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;
|
|
1788
2365
|
const minLabels = options?.minLabels ?? 2;
|
|
2366
|
+
const maxLength = options?.maxLength ?? 253;
|
|
1789
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.");
|
|
1790
2371
|
const invalidDomain = options?.errors?.invalidDomain;
|
|
2372
|
+
const tooLong = options?.errors?.tooLong;
|
|
1791
2373
|
const tooFewLabels = options?.errors?.tooFewLabels;
|
|
1792
2374
|
const subdomainsNotAllowed = options?.errors?.subdomainsNotAllowed;
|
|
1793
2375
|
const tldNotAllowed = options?.errors?.tldNotAllowed;
|
|
1794
|
-
const
|
|
1795
|
-
|
|
1796
|
-
$mode: "sync",
|
|
2376
|
+
const domainParserObj = {
|
|
2377
|
+
mode: "sync",
|
|
1797
2378
|
metavar,
|
|
2379
|
+
placeholder: options?.placeholder ?? `example.${allowedTldsLower?.[0] ?? "com"}`,
|
|
1798
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
|
+
}
|
|
1799
2389
|
if (input.length === 0 || input.startsWith(".") || input.endsWith(".")) {
|
|
1800
2390
|
const errorMsg = invalidDomain;
|
|
1801
2391
|
if (typeof errorMsg === "function") return {
|
|
@@ -1872,6 +2462,31 @@ function domain(options) {
|
|
|
1872
2462
|
error: msg
|
|
1873
2463
|
};
|
|
1874
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
|
+
}
|
|
1875
2490
|
if (labels.length < minLabels) {
|
|
1876
2491
|
const errorMsg = tooFewLabels;
|
|
1877
2492
|
if (typeof errorMsg === "function") return {
|
|
@@ -1922,15 +2537,14 @@ function domain(options) {
|
|
|
1922
2537
|
error: msg
|
|
1923
2538
|
};
|
|
1924
2539
|
}
|
|
1925
|
-
if (
|
|
2540
|
+
if (allowedTlds !== void 0 && allowedTldsLower !== void 0) {
|
|
1926
2541
|
const tld = labels[labels.length - 1];
|
|
1927
2542
|
const tldLower = tld.toLowerCase();
|
|
1928
|
-
|
|
1929
|
-
if (!allowedTLDsLower.includes(tldLower)) {
|
|
2543
|
+
if (!allowedTldsLower.includes(tldLower)) {
|
|
1930
2544
|
const errorMsg = tldNotAllowed;
|
|
1931
2545
|
if (typeof errorMsg === "function") return {
|
|
1932
2546
|
success: false,
|
|
1933
|
-
error: errorMsg(tld,
|
|
2547
|
+
error: errorMsg(tld, allowedTlds)
|
|
1934
2548
|
};
|
|
1935
2549
|
const msg = errorMsg ?? [
|
|
1936
2550
|
{
|
|
@@ -1943,7 +2557,15 @@ function domain(options) {
|
|
|
1943
2557
|
},
|
|
1944
2558
|
{
|
|
1945
2559
|
type: "text",
|
|
1946
|
-
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: "."
|
|
1947
2569
|
}
|
|
1948
2570
|
];
|
|
1949
2571
|
return {
|
|
@@ -1958,10 +2580,51 @@ function domain(options) {
|
|
|
1958
2580
|
value: result
|
|
1959
2581
|
};
|
|
1960
2582
|
},
|
|
1961
|
-
format() {
|
|
1962
|
-
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;
|
|
1963
2587
|
}
|
|
1964
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;
|
|
1965
2628
|
}
|
|
1966
2629
|
/**
|
|
1967
2630
|
* Creates a value parser for IPv6 addresses.
|
|
@@ -1994,9 +2657,10 @@ function ipv6(options) {
|
|
|
1994
2657
|
const allowZero = options?.allowZero ?? true;
|
|
1995
2658
|
const errors = options?.errors;
|
|
1996
2659
|
const metavar = options?.metavar ?? "IPV6";
|
|
1997
|
-
|
|
1998
|
-
|
|
2660
|
+
const ipv6ParserObj = {
|
|
2661
|
+
mode: "sync",
|
|
1999
2662
|
metavar,
|
|
2663
|
+
placeholder: allowZero ? "::" : allowLoopback ? "::1" : "2001:db8::1",
|
|
2000
2664
|
parse(input) {
|
|
2001
2665
|
const normalized = parseAndNormalizeIpv6(input);
|
|
2002
2666
|
if (normalized === null) {
|
|
@@ -2146,10 +2810,74 @@ function ipv6(options) {
|
|
|
2146
2810
|
value: normalized
|
|
2147
2811
|
};
|
|
2148
2812
|
},
|
|
2149
|
-
format() {
|
|
2150
|
-
return metavar;
|
|
2813
|
+
format(value) {
|
|
2814
|
+
if (typeof value !== "string") return metavar;
|
|
2815
|
+
return parseAndNormalizeIpv6(value) ?? value;
|
|
2151
2816
|
}
|
|
2152
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;
|
|
2153
2881
|
}
|
|
2154
2882
|
/**
|
|
2155
2883
|
* Parses and normalizes an IPv6 address to canonical form.
|
|
@@ -2161,10 +2889,8 @@ function parseAndNormalizeIpv6(input) {
|
|
|
2161
2889
|
if (ipv4MappedMatch) {
|
|
2162
2890
|
const ipv6Part = ipv4MappedMatch[1];
|
|
2163
2891
|
const ipv4Part = ipv4MappedMatch[2];
|
|
2164
|
-
const
|
|
2165
|
-
if (
|
|
2166
|
-
const octets = ipv4Octets.map((o) => parseInt(o, 10));
|
|
2167
|
-
if (octets.some((o) => isNaN(o) || o < 0 || o > 255)) return null;
|
|
2892
|
+
const octets = parseIpv4Octets(ipv4Part);
|
|
2893
|
+
if (octets === null) return null;
|
|
2168
2894
|
const group1 = octets[0] << 8 | octets[1];
|
|
2169
2895
|
const group2 = octets[2] << 8 | octets[3];
|
|
2170
2896
|
const fullAddress = `${ipv6Part}:${group1.toString(16)}:${group2.toString(16)}`;
|
|
@@ -2258,6 +2984,90 @@ function compressIpv6(groups) {
|
|
|
2258
2984
|
else return before.join(":") + "::" + after.join(":");
|
|
2259
2985
|
}
|
|
2260
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
|
+
/**
|
|
2261
3071
|
* Creates a value parser that accepts both IPv4 and IPv6 addresses.
|
|
2262
3072
|
*
|
|
2263
3073
|
* By default, accepts both IPv4 and IPv6 addresses. Use the `version` option
|
|
@@ -2310,9 +3120,26 @@ function ip(options) {
|
|
|
2310
3120
|
zeroNotAllowed: errors?.zeroNotAllowed
|
|
2311
3121
|
}
|
|
2312
3122
|
}) : null;
|
|
2313
|
-
|
|
2314
|
-
|
|
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",
|
|
2315
3141
|
metavar,
|
|
3142
|
+
placeholder: version === 6 ? ipv6Parser.placeholder : ipv4Parser.placeholder,
|
|
2316
3143
|
parse(input) {
|
|
2317
3144
|
let ipv4Error = null;
|
|
2318
3145
|
let ipv6Error = null;
|
|
@@ -2324,7 +3151,16 @@ function ip(options) {
|
|
|
2324
3151
|
}
|
|
2325
3152
|
if (ipv6Parser !== null) {
|
|
2326
3153
|
const result = ipv6Parser.parse(input);
|
|
2327
|
-
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
|
+
}
|
|
2328
3164
|
ipv6Error = result;
|
|
2329
3165
|
if (version === 6) return result;
|
|
2330
3166
|
}
|
|
@@ -2360,10 +3196,47 @@ function ip(options) {
|
|
|
2360
3196
|
error: msg
|
|
2361
3197
|
};
|
|
2362
3198
|
},
|
|
2363
|
-
format() {
|
|
2364
|
-
return metavar;
|
|
3199
|
+
format(value) {
|
|
3200
|
+
if (typeof value !== "string") return metavar;
|
|
3201
|
+
return parseAndNormalizeIpv6(value) ?? value;
|
|
2365
3202
|
}
|
|
2366
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;
|
|
2367
3240
|
}
|
|
2368
3241
|
/**
|
|
2369
3242
|
* Creates a value parser for CIDR notation (IP address with prefix length).
|
|
@@ -2391,16 +3264,71 @@ function ip(options) {
|
|
|
2391
3264
|
* @since 0.10.0
|
|
2392
3265
|
*/
|
|
2393
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}.`);
|
|
2394
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}.`);
|
|
2395
3274
|
const minPrefix = options?.minPrefix;
|
|
2396
3275
|
const maxPrefix = options?.maxPrefix;
|
|
2397
3276
|
const errors = options?.errors;
|
|
2398
3277
|
const metavar = options?.metavar ?? "CIDR";
|
|
2399
|
-
const
|
|
2400
|
-
const
|
|
2401
|
-
|
|
2402
|
-
|
|
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",
|
|
2403
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
|
+
},
|
|
2404
3332
|
parse(input) {
|
|
2405
3333
|
const slashIndex = input.lastIndexOf("/");
|
|
2406
3334
|
if (slashIndex === -1) {
|
|
@@ -2458,6 +3386,8 @@ function cidr(options) {
|
|
|
2458
3386
|
}
|
|
2459
3387
|
let ipVersion = null;
|
|
2460
3388
|
let normalizedIp = null;
|
|
3389
|
+
let ipv4Error = null;
|
|
3390
|
+
let ipv6Error = null;
|
|
2461
3391
|
if (ipv4Parser !== null) {
|
|
2462
3392
|
const result = ipv4Parser.parse(ipPart);
|
|
2463
3393
|
if (result.success) {
|
|
@@ -2496,7 +3426,7 @@ function cidr(options) {
|
|
|
2496
3426
|
error: msg
|
|
2497
3427
|
};
|
|
2498
3428
|
}
|
|
2499
|
-
}
|
|
3429
|
+
} else ipv4Error = result;
|
|
2500
3430
|
}
|
|
2501
3431
|
if (ipVersion === null && ipv6Parser !== null) {
|
|
2502
3432
|
const result = ipv6Parser.parse(ipPart);
|
|
@@ -2536,9 +3466,120 @@ function cidr(options) {
|
|
|
2536
3466
|
error: msg
|
|
2537
3467
|
};
|
|
2538
3468
|
}
|
|
2539
|
-
}
|
|
3469
|
+
} else ipv6Error = result;
|
|
2540
3470
|
}
|
|
2541
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
|
+
}
|
|
2542
3583
|
const errorMsg = errors?.invalidCidr;
|
|
2543
3584
|
if (typeof errorMsg === "function") return {
|
|
2544
3585
|
success: false,
|
|
@@ -2629,6 +3670,13 @@ function cidr(options) {
|
|
|
2629
3670
|
error: msg
|
|
2630
3671
|
};
|
|
2631
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
|
+
}
|
|
2632
3680
|
return {
|
|
2633
3681
|
success: true,
|
|
2634
3682
|
value: {
|
|
@@ -2638,13 +3686,52 @@ function cidr(options) {
|
|
|
2638
3686
|
}
|
|
2639
3687
|
};
|
|
2640
3688
|
},
|
|
2641
|
-
format()
|
|
2642
|
-
return metavar;
|
|
2643
|
-
}
|
|
3689
|
+
format: ((_) => metavar)
|
|
2644
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;
|
|
2645
3730
|
}
|
|
2646
3731
|
|
|
2647
3732
|
//#endregion
|
|
3733
|
+
exports.checkBooleanOption = checkBooleanOption;
|
|
3734
|
+
exports.checkEnumOption = checkEnumOption;
|
|
2648
3735
|
exports.choice = choice;
|
|
2649
3736
|
exports.cidr = cidr;
|
|
2650
3737
|
exports.domain = domain;
|