@optique/core 1.3.0-dev.2581 → 1.3.0-dev.2583
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/constructs.cjs +114 -9
- package/dist/constructs.js +115 -10
- package/dist/dependency-metadata.d.cts +15 -0
- package/dist/dependency-metadata.d.ts +15 -0
- package/dist/dependency-runtime.cjs +21 -0
- package/dist/dependency-runtime.d.cts +48 -3
- package/dist/dependency-runtime.d.ts +48 -3
- package/dist/dependency-runtime.js +20 -1
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/valueparser.cjs +173 -0
- package/dist/valueparser.d.cts +86 -1
- package/dist/valueparser.d.ts +86 -1
- package/dist/valueparser.js +173 -1
- package/package.json +2 -2
- package/skills/optique/SKILL.md +5 -5
package/dist/valueparser.js
CHANGED
|
@@ -2750,6 +2750,178 @@ function url(options = {}) {
|
|
|
2750
2750
|
};
|
|
2751
2751
|
}
|
|
2752
2752
|
/**
|
|
2753
|
+
* A host name that the WHATWG URL parser has canonicalized into a dotted-quad
|
|
2754
|
+
* IPv4 address. Matching the canonical form is enough, because the parser
|
|
2755
|
+
* turns every other spelling (`0x7f.1`, `2130706433`, …) into it.
|
|
2756
|
+
*/
|
|
2757
|
+
const IPV4_PATTERN = /^\d{1,3}(?:\.\d{1,3}){3}$/u;
|
|
2758
|
+
/**
|
|
2759
|
+
* Creates a {@link ValueParser} for web origins.
|
|
2760
|
+
*
|
|
2761
|
+
* An origin is a scheme, a host, and an optional port, and nothing else. The
|
|
2762
|
+
* parser returns a `URL` whose pathname is `/` and which carries no
|
|
2763
|
+
* credentials, query, or fragment.
|
|
2764
|
+
*
|
|
2765
|
+
* Input is canonicalized rather than rejected when it carries more than an
|
|
2766
|
+
* origin: the host is lowercased, internationalized names are converted to
|
|
2767
|
+
* punycode, and a default port is elided, so `HTTPS://Example.COM/`,
|
|
2768
|
+
* `https://example.com:443/path?query#fragment` all parse to
|
|
2769
|
+
* `https://example.com`. Set `extraComponents: "reject"` to fail on a path,
|
|
2770
|
+
* query, or fragment instead. Credentials are always rejected. Schemes whose
|
|
2771
|
+
* origin is opaque (`mailto:`, `data:`, `file:`) are rejected, as is `blob:`,
|
|
2772
|
+
* whose origin belongs to the URL it wraps.
|
|
2773
|
+
*
|
|
2774
|
+
* By default a trailing root-zone dot is stripped, so `example.com.` becomes
|
|
2775
|
+
* `example.com`. This differs from the WHATWG origin, which keeps the dot, so
|
|
2776
|
+
* the two strings are not interchangeable when an exact origin comparison
|
|
2777
|
+
* matters. Use `trailingDot: "preserve"` for the WHATWG behavior or
|
|
2778
|
+
* `trailingDot: "append"` to force a fully-qualified name.
|
|
2779
|
+
*
|
|
2780
|
+
* @param options Configuration options for the origin parser.
|
|
2781
|
+
* @returns A {@link ValueParser} that converts string input to `URL` objects
|
|
2782
|
+
* that name an origin.
|
|
2783
|
+
* @throws {TypeError} If any `allowedProtocols` entry is not a valid protocol
|
|
2784
|
+
* string ending with a colon (e.g., `"https:"`).
|
|
2785
|
+
* @since 1.3.0
|
|
2786
|
+
*/
|
|
2787
|
+
function origin(options = {}) {
|
|
2788
|
+
const metavar$1 = options.metavar ?? "ORIGIN";
|
|
2789
|
+
ensureNonEmptyString(metavar$1);
|
|
2790
|
+
checkEnumOption(options, "extraComponents", ["strip", "reject"]);
|
|
2791
|
+
checkEnumOption(options, "trailingDot", [
|
|
2792
|
+
"strip",
|
|
2793
|
+
"preserve",
|
|
2794
|
+
"append"
|
|
2795
|
+
]);
|
|
2796
|
+
const extraComponents = options.extraComponents ?? "strip";
|
|
2797
|
+
const trailingDot = options.trailingDot ?? "strip";
|
|
2798
|
+
const originalProtocolsList = [];
|
|
2799
|
+
const normalizedProtocolsList = [];
|
|
2800
|
+
if (options.allowedProtocols != null) {
|
|
2801
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2802
|
+
for (const protocol of options.allowedProtocols) {
|
|
2803
|
+
if (typeof protocol !== "string" || !/^[a-z][a-z0-9+\-.]*:$/i.test(protocol)) {
|
|
2804
|
+
const rendered = typeof protocol === "string" ? JSON.stringify(protocol) : String(protocol);
|
|
2805
|
+
throw new TypeError(`Each allowed protocol must be a valid protocol ending with a colon (e.g., "https:"), got: ${rendered}.`);
|
|
2806
|
+
}
|
|
2807
|
+
const normalized = protocol.toLowerCase();
|
|
2808
|
+
if (seen.has(normalized)) continue;
|
|
2809
|
+
seen.add(normalized);
|
|
2810
|
+
originalProtocolsList.push(protocol);
|
|
2811
|
+
normalizedProtocolsList.push(normalized);
|
|
2812
|
+
}
|
|
2813
|
+
if (originalProtocolsList.length === 0) throw new TypeError("allowedProtocols must not be empty.");
|
|
2814
|
+
}
|
|
2815
|
+
const originalProtocols = options.allowedProtocols != null ? Object.freeze(originalProtocolsList) : void 0;
|
|
2816
|
+
const allowedProtocols = options.allowedProtocols != null ? Object.freeze(normalizedProtocolsList) : void 0;
|
|
2817
|
+
const invalidOrigin = options.errors?.invalidOrigin;
|
|
2818
|
+
const disallowedProtocol = options.errors?.disallowedProtocol;
|
|
2819
|
+
function invalidOriginError(input) {
|
|
2820
|
+
return invalidOrigin ? typeof invalidOrigin === "function" ? invalidOrigin(input) : invalidOrigin : message`Invalid origin: ${input}.`;
|
|
2821
|
+
}
|
|
2822
|
+
function disallowedProtocolError(protocol) {
|
|
2823
|
+
return disallowedProtocol ? typeof disallowedProtocol === "function" ? disallowedProtocol(protocol, originalProtocols) : disallowedProtocol : [
|
|
2824
|
+
{
|
|
2825
|
+
type: "text",
|
|
2826
|
+
text: "URL protocol "
|
|
2827
|
+
},
|
|
2828
|
+
{
|
|
2829
|
+
type: "value",
|
|
2830
|
+
value: protocol
|
|
2831
|
+
},
|
|
2832
|
+
{
|
|
2833
|
+
type: "text",
|
|
2834
|
+
text: " is not allowed. Allowed protocols: "
|
|
2835
|
+
},
|
|
2836
|
+
...valueSet(originalProtocols, {
|
|
2837
|
+
fallback: "",
|
|
2838
|
+
locale: "en-US"
|
|
2839
|
+
}),
|
|
2840
|
+
{
|
|
2841
|
+
type: "text",
|
|
2842
|
+
text: "."
|
|
2843
|
+
}
|
|
2844
|
+
];
|
|
2845
|
+
}
|
|
2846
|
+
function withTrailingDot(url$1) {
|
|
2847
|
+
if (trailingDot === "preserve") return url$1;
|
|
2848
|
+
const { hostname: hostname$1 } = url$1;
|
|
2849
|
+
if (hostname$1.startsWith("[") || IPV4_PATTERN.test(hostname$1)) return url$1;
|
|
2850
|
+
if (trailingDot === "strip") {
|
|
2851
|
+
const stripped = hostname$1.replace(/\.+$/u, "");
|
|
2852
|
+
if (stripped !== "") url$1.hostname = stripped;
|
|
2853
|
+
} else if (!hostname$1.endsWith(".")) url$1.hostname = `${hostname$1}.`;
|
|
2854
|
+
return url$1;
|
|
2855
|
+
}
|
|
2856
|
+
function resolve(url$1, label) {
|
|
2857
|
+
if (allowedProtocols != null && !allowedProtocols.includes(url$1.protocol)) return {
|
|
2858
|
+
success: false,
|
|
2859
|
+
error: disallowedProtocolError(url$1.protocol)
|
|
2860
|
+
};
|
|
2861
|
+
if (url$1.protocol === "blob:") return {
|
|
2862
|
+
success: false,
|
|
2863
|
+
error: message`The URL ${label} wraps another origin. Pass that origin instead.`
|
|
2864
|
+
};
|
|
2865
|
+
if (url$1.origin === "null") return {
|
|
2866
|
+
success: false,
|
|
2867
|
+
error: message`The URL ${label} has no origin.`
|
|
2868
|
+
};
|
|
2869
|
+
if (url$1.username !== "" || url$1.password !== "") return {
|
|
2870
|
+
success: false,
|
|
2871
|
+
error: message`An origin must not contain credentials: ${url$1.origin}`
|
|
2872
|
+
};
|
|
2873
|
+
if (extraComponents === "reject" && (url$1.pathname !== "/" || url$1.search !== "" || url$1.hash !== "")) return {
|
|
2874
|
+
success: false,
|
|
2875
|
+
error: message`Expected only an origin, but got: ${label}`
|
|
2876
|
+
};
|
|
2877
|
+
return {
|
|
2878
|
+
success: true,
|
|
2879
|
+
value: withTrailingDot(new URL(url$1.origin))
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
return {
|
|
2883
|
+
mode: "sync",
|
|
2884
|
+
metavar: metavar$1,
|
|
2885
|
+
get placeholder() {
|
|
2886
|
+
return new URL(`${allowedProtocols?.[0] ?? "http:"}//0.invalid`);
|
|
2887
|
+
},
|
|
2888
|
+
parse(input) {
|
|
2889
|
+
if (!URL.canParse(input)) return {
|
|
2890
|
+
success: false,
|
|
2891
|
+
error: invalidOriginError(input)
|
|
2892
|
+
};
|
|
2893
|
+
return resolve(new URL(input), input);
|
|
2894
|
+
},
|
|
2895
|
+
validate(value) {
|
|
2896
|
+
if (!(value instanceof URL)) return {
|
|
2897
|
+
success: false,
|
|
2898
|
+
error: invalidOriginError(String(value))
|
|
2899
|
+
};
|
|
2900
|
+
return resolve(value, value.href);
|
|
2901
|
+
},
|
|
2902
|
+
format(value) {
|
|
2903
|
+
return value.origin;
|
|
2904
|
+
},
|
|
2905
|
+
normalize(value) {
|
|
2906
|
+
if (!(value instanceof URL)) return value;
|
|
2907
|
+
const result = resolve(value, value.href);
|
|
2908
|
+
return result.success ? result.value : value;
|
|
2909
|
+
},
|
|
2910
|
+
*suggest(prefix) {
|
|
2911
|
+
if (allowedProtocols && prefix.length > 0 && !prefix.includes(":")) for (const protocol of allowedProtocols) {
|
|
2912
|
+
const cleanProtocol = protocol.replace(/:+$/, "");
|
|
2913
|
+
if (cleanProtocol.startsWith(prefix.toLowerCase())) {
|
|
2914
|
+
const suffix = SPECIAL_URL_SCHEMES.has(cleanProtocol) ? "://" : ":";
|
|
2915
|
+
yield {
|
|
2916
|
+
kind: "literal",
|
|
2917
|
+
text: `${cleanProtocol}${suffix}`
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
};
|
|
2923
|
+
}
|
|
2924
|
+
/**
|
|
2753
2925
|
* Creates a {@link ValueParser} for locale values.
|
|
2754
2926
|
*
|
|
2755
2927
|
* This parser validates that the input is a well-formed locale identifier
|
|
@@ -6667,4 +6839,4 @@ function plainObjectsEqual(a, b) {
|
|
|
6667
6839
|
}
|
|
6668
6840
|
|
|
6669
6841
|
//#endregion
|
|
6670
|
-
export { biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, port, portRange, regExp, semVer, socketAddress, string, transform, url, uuid };
|
|
6842
|
+
export { biject, checkBooleanOption, checkEnumOption, choice, cidr, color, cron, domain, email, ensureNonEmptyString, fileSize, firstOf, float, hostname, integer, ip, ipv4, ipv6, isNonEmptyString, isValueParser, json, keyValue, locale, macAddress, origin, port, portRange, regExp, semVer, socketAddress, string, transform, url, uuid };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optique/core",
|
|
3
|
-
"version": "1.3.0-dev.
|
|
3
|
+
"version": "1.3.0-dev.2583",
|
|
4
4
|
"description": "Type-safe combinatorial command-line interface parser",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"CLI",
|
|
@@ -237,7 +237,7 @@
|
|
|
237
237
|
},
|
|
238
238
|
"sideEffects": false,
|
|
239
239
|
"devDependencies": {
|
|
240
|
-
"@optique/env": "1.3.0-dev.
|
|
240
|
+
"@optique/env": "1.3.0-dev.2583+d30dbb53",
|
|
241
241
|
"@types/node": "^24.0.0",
|
|
242
242
|
"fast-check": "^4.7.0",
|
|
243
243
|
"tsdown": "^0.13.0",
|
package/skills/optique/SKILL.md
CHANGED
|
@@ -41,11 +41,11 @@ Core rules
|
|
|
41
41
|
customize terminal output with `theme` or `messageFormatter`, preserving
|
|
42
42
|
`initialWidth`, quoting, and width options. Keep canonical errors unthemed.
|
|
43
43
|
- Use value parsers such as `integer()`, `choice()`, `biject()`, `regExp()`,
|
|
44
|
-
`url()`, and `uuid()` instead of validating raw strings after
|
|
45
|
-
`regExp({ flags })` for user-supplied
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
`url()`, `origin()`, and `uuid()` instead of validating raw strings after
|
|
45
|
+
parsing. Use `regExp({ flags })` for user-supplied sources, `biject()` for
|
|
46
|
+
one-to-one string-to-value choices, and `transform()` when an existing
|
|
47
|
+
value parser describes the accepted CLI spelling but your app needs a
|
|
48
|
+
different result type. Use `path()` from
|
|
49
49
|
`@optique/run/valueparser` for file-system paths. Write a custom
|
|
50
50
|
`{ mode, metavar, parse, format }` value parser only when the catalog does
|
|
51
51
|
not cover the domain.
|