@optique/core 1.3.0-dev.2583 → 1.3.0-dev.2587

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.
@@ -2756,6 +2756,18 @@ function url(options = {}) {
2756
2756
  */
2757
2757
  const IPV4_PATTERN = /^\d{1,3}(?:\.\d{1,3}){3}$/u;
2758
2758
  /**
2759
+ * Whether a URL scheme can yield a tuple origin. Opaque schemes such as
2760
+ * `file:`, `blob:`, `mailto:`, and `data:` cannot, and an origin parser can
2761
+ * never accept them, so they are rejected when listed in `allowedProtocols`.
2762
+ */
2763
+ function canProduceOrigin(protocol) {
2764
+ try {
2765
+ return new URL(`${protocol}//0.invalid`).origin !== "null";
2766
+ } catch {
2767
+ return false;
2768
+ }
2769
+ }
2770
+ /**
2759
2771
  * Creates a {@link ValueParser} for web origins.
2760
2772
  *
2761
2773
  * An origin is a scheme, a host, and an optional port, and nothing else. The
@@ -2804,6 +2816,7 @@ function origin(options = {}) {
2804
2816
  const rendered = typeof protocol === "string" ? JSON.stringify(protocol) : String(protocol);
2805
2817
  throw new TypeError(`Each allowed protocol must be a valid protocol ending with a colon (e.g., "https:"), got: ${rendered}.`);
2806
2818
  }
2819
+ if (!canProduceOrigin(protocol)) throw new TypeError(`Each allowed protocol must name a scheme that can produce an origin, but ${JSON.stringify(protocol)} cannot.`);
2807
2820
  const normalized = protocol.toLowerCase();
2808
2821
  if (seen.has(normalized)) continue;
2809
2822
  seen.add(normalized);
@@ -2817,7 +2830,7 @@ function origin(options = {}) {
2817
2830
  const invalidOrigin = options.errors?.invalidOrigin;
2818
2831
  const disallowedProtocol = options.errors?.disallowedProtocol;
2819
2832
  function invalidOriginError(input) {
2820
- return invalidOrigin ? typeof invalidOrigin === "function" ? invalidOrigin(input) : invalidOrigin : require_message.message`Invalid origin: ${input}.`;
2833
+ return invalidOrigin ? typeof invalidOrigin === "function" ? invalidOrigin(input) : invalidOrigin : require_message.message`Invalid origin.`;
2821
2834
  }
2822
2835
  function disallowedProtocolError(protocol) {
2823
2836
  return disallowedProtocol ? typeof disallowedProtocol === "function" ? disallowedProtocol(protocol, originalProtocols) : disallowedProtocol : [
@@ -2853,26 +2866,26 @@ function origin(options = {}) {
2853
2866
  } else if (!hostname$1.endsWith(".")) url$1.hostname = `${hostname$1}.`;
2854
2867
  return url$1;
2855
2868
  }
2856
- function resolve(url$1, label) {
2869
+ function resolve(url$1) {
2857
2870
  if (allowedProtocols != null && !allowedProtocols.includes(url$1.protocol)) return {
2858
2871
  success: false,
2859
2872
  error: disallowedProtocolError(url$1.protocol)
2860
2873
  };
2861
2874
  if (url$1.protocol === "blob:") return {
2862
2875
  success: false,
2863
- error: require_message.message`The URL ${label} wraps another origin. Pass that origin instead.`
2876
+ error: require_message.message`The ${url$1.protocol} URL wraps another origin. Pass that origin instead.`
2864
2877
  };
2865
2878
  if (url$1.origin === "null") return {
2866
2879
  success: false,
2867
- error: require_message.message`The URL ${label} has no origin.`
2880
+ error: require_message.message`The ${url$1.protocol} URL has no origin.`
2868
2881
  };
2869
2882
  if (url$1.username !== "" || url$1.password !== "") return {
2870
2883
  success: false,
2871
2884
  error: require_message.message`An origin must not contain credentials: ${url$1.origin}`
2872
2885
  };
2873
- if (extraComponents === "reject" && (url$1.pathname !== "/" || url$1.search !== "" || url$1.hash !== "")) return {
2886
+ if (extraComponents === "reject" && (url$1.pathname !== "/" || url$1.search !== "" || url$1.hash !== "" || url$1.href !== new URL(url$1.origin).href)) return {
2874
2887
  success: false,
2875
- error: require_message.message`Expected only an origin, but got: ${label}`
2888
+ error: require_message.message`Expected only an origin, but got extra components on ${url$1.origin}.`
2876
2889
  };
2877
2890
  return {
2878
2891
  success: true,
@@ -2890,21 +2903,21 @@ function origin(options = {}) {
2890
2903
  success: false,
2891
2904
  error: invalidOriginError(input)
2892
2905
  };
2893
- return resolve(new URL(input), input);
2906
+ return resolve(new URL(input));
2894
2907
  },
2895
2908
  validate(value) {
2896
2909
  if (!(value instanceof URL)) return {
2897
2910
  success: false,
2898
2911
  error: invalidOriginError(String(value))
2899
2912
  };
2900
- return resolve(value, value.href);
2913
+ return resolve(value);
2901
2914
  },
2902
2915
  format(value) {
2903
2916
  return value.origin;
2904
2917
  },
2905
2918
  normalize(value) {
2906
2919
  if (!(value instanceof URL)) return value;
2907
- const result = resolve(value, value.href);
2920
+ const result = resolve(value);
2908
2921
  return result.success ? result.value : value;
2909
2922
  },
2910
2923
  *suggest(prefix) {
@@ -1191,6 +1191,8 @@ interface UrlOptions {
1191
1191
  declare function url(options?: UrlOptions): ValueParser<"sync", URL>;
1192
1192
  /**
1193
1193
  * Options for creating an {@link origin} parser.
1194
+ *
1195
+ * @since 1.3.0
1194
1196
  */
1195
1197
  interface OriginOptions {
1196
1198
  /**
@@ -1203,7 +1205,9 @@ interface OriginOptions {
1203
1205
  /**
1204
1206
  * List of allowed URL protocols (e.g., `["http:", "https:"]`).
1205
1207
  * If specified, the parsed URL must use one of these protocols.
1206
- * Protocol names should include the trailing colon (e.g., `"https:"`).
1208
+ * Protocol names should include the trailing colon (e.g., `"https:"`), and
1209
+ * each must name a scheme that can produce an origin; opaque schemes such as
1210
+ * `file:` and `blob:` are rejected at construction.
1207
1211
  * If not specified, any protocol with a tuple origin is allowed.
1208
1212
  */
1209
1213
  readonly allowedProtocols?: readonly string[];
@@ -1212,8 +1216,8 @@ interface OriginOptions {
1212
1216
  *
1213
1217
  * - `"strip"`: return just the origin, discarding any path, query, or
1214
1218
  * fragment.
1215
- * - `"reject"`: fail when the input has a path other than `/`, a query, or
1216
- * a fragment.
1219
+ * - `"reject"`: fail when the input has a path other than `/`, or a query
1220
+ * or fragment delimiter (including an empty `?` or `#`).
1217
1221
  *
1218
1222
  * Credentials are always rejected, under either setting.
1219
1223
  * @default `"strip"`
@@ -1191,6 +1191,8 @@ interface UrlOptions {
1191
1191
  declare function url(options?: UrlOptions): ValueParser<"sync", URL>;
1192
1192
  /**
1193
1193
  * Options for creating an {@link origin} parser.
1194
+ *
1195
+ * @since 1.3.0
1194
1196
  */
1195
1197
  interface OriginOptions {
1196
1198
  /**
@@ -1203,7 +1205,9 @@ interface OriginOptions {
1203
1205
  /**
1204
1206
  * List of allowed URL protocols (e.g., `["http:", "https:"]`).
1205
1207
  * If specified, the parsed URL must use one of these protocols.
1206
- * Protocol names should include the trailing colon (e.g., `"https:"`).
1208
+ * Protocol names should include the trailing colon (e.g., `"https:"`), and
1209
+ * each must name a scheme that can produce an origin; opaque schemes such as
1210
+ * `file:` and `blob:` are rejected at construction.
1207
1211
  * If not specified, any protocol with a tuple origin is allowed.
1208
1212
  */
1209
1213
  readonly allowedProtocols?: readonly string[];
@@ -1212,8 +1216,8 @@ interface OriginOptions {
1212
1216
  *
1213
1217
  * - `"strip"`: return just the origin, discarding any path, query, or
1214
1218
  * fragment.
1215
- * - `"reject"`: fail when the input has a path other than `/`, a query, or
1216
- * a fragment.
1219
+ * - `"reject"`: fail when the input has a path other than `/`, or a query
1220
+ * or fragment delimiter (including an empty `?` or `#`).
1217
1221
  *
1218
1222
  * Credentials are always rejected, under either setting.
1219
1223
  * @default `"strip"`
@@ -2756,6 +2756,18 @@ function url(options = {}) {
2756
2756
  */
2757
2757
  const IPV4_PATTERN = /^\d{1,3}(?:\.\d{1,3}){3}$/u;
2758
2758
  /**
2759
+ * Whether a URL scheme can yield a tuple origin. Opaque schemes such as
2760
+ * `file:`, `blob:`, `mailto:`, and `data:` cannot, and an origin parser can
2761
+ * never accept them, so they are rejected when listed in `allowedProtocols`.
2762
+ */
2763
+ function canProduceOrigin(protocol) {
2764
+ try {
2765
+ return new URL(`${protocol}//0.invalid`).origin !== "null";
2766
+ } catch {
2767
+ return false;
2768
+ }
2769
+ }
2770
+ /**
2759
2771
  * Creates a {@link ValueParser} for web origins.
2760
2772
  *
2761
2773
  * An origin is a scheme, a host, and an optional port, and nothing else. The
@@ -2804,6 +2816,7 @@ function origin(options = {}) {
2804
2816
  const rendered = typeof protocol === "string" ? JSON.stringify(protocol) : String(protocol);
2805
2817
  throw new TypeError(`Each allowed protocol must be a valid protocol ending with a colon (e.g., "https:"), got: ${rendered}.`);
2806
2818
  }
2819
+ if (!canProduceOrigin(protocol)) throw new TypeError(`Each allowed protocol must name a scheme that can produce an origin, but ${JSON.stringify(protocol)} cannot.`);
2807
2820
  const normalized = protocol.toLowerCase();
2808
2821
  if (seen.has(normalized)) continue;
2809
2822
  seen.add(normalized);
@@ -2817,7 +2830,7 @@ function origin(options = {}) {
2817
2830
  const invalidOrigin = options.errors?.invalidOrigin;
2818
2831
  const disallowedProtocol = options.errors?.disallowedProtocol;
2819
2832
  function invalidOriginError(input) {
2820
- return invalidOrigin ? typeof invalidOrigin === "function" ? invalidOrigin(input) : invalidOrigin : message`Invalid origin: ${input}.`;
2833
+ return invalidOrigin ? typeof invalidOrigin === "function" ? invalidOrigin(input) : invalidOrigin : message`Invalid origin.`;
2821
2834
  }
2822
2835
  function disallowedProtocolError(protocol) {
2823
2836
  return disallowedProtocol ? typeof disallowedProtocol === "function" ? disallowedProtocol(protocol, originalProtocols) : disallowedProtocol : [
@@ -2853,26 +2866,26 @@ function origin(options = {}) {
2853
2866
  } else if (!hostname$1.endsWith(".")) url$1.hostname = `${hostname$1}.`;
2854
2867
  return url$1;
2855
2868
  }
2856
- function resolve(url$1, label) {
2869
+ function resolve(url$1) {
2857
2870
  if (allowedProtocols != null && !allowedProtocols.includes(url$1.protocol)) return {
2858
2871
  success: false,
2859
2872
  error: disallowedProtocolError(url$1.protocol)
2860
2873
  };
2861
2874
  if (url$1.protocol === "blob:") return {
2862
2875
  success: false,
2863
- error: message`The URL ${label} wraps another origin. Pass that origin instead.`
2876
+ error: message`The ${url$1.protocol} URL wraps another origin. Pass that origin instead.`
2864
2877
  };
2865
2878
  if (url$1.origin === "null") return {
2866
2879
  success: false,
2867
- error: message`The URL ${label} has no origin.`
2880
+ error: message`The ${url$1.protocol} URL has no origin.`
2868
2881
  };
2869
2882
  if (url$1.username !== "" || url$1.password !== "") return {
2870
2883
  success: false,
2871
2884
  error: message`An origin must not contain credentials: ${url$1.origin}`
2872
2885
  };
2873
- if (extraComponents === "reject" && (url$1.pathname !== "/" || url$1.search !== "" || url$1.hash !== "")) return {
2886
+ if (extraComponents === "reject" && (url$1.pathname !== "/" || url$1.search !== "" || url$1.hash !== "" || url$1.href !== new URL(url$1.origin).href)) return {
2874
2887
  success: false,
2875
- error: message`Expected only an origin, but got: ${label}`
2888
+ error: message`Expected only an origin, but got extra components on ${url$1.origin}.`
2876
2889
  };
2877
2890
  return {
2878
2891
  success: true,
@@ -2890,21 +2903,21 @@ function origin(options = {}) {
2890
2903
  success: false,
2891
2904
  error: invalidOriginError(input)
2892
2905
  };
2893
- return resolve(new URL(input), input);
2906
+ return resolve(new URL(input));
2894
2907
  },
2895
2908
  validate(value) {
2896
2909
  if (!(value instanceof URL)) return {
2897
2910
  success: false,
2898
2911
  error: invalidOriginError(String(value))
2899
2912
  };
2900
- return resolve(value, value.href);
2913
+ return resolve(value);
2901
2914
  },
2902
2915
  format(value) {
2903
2916
  return value.origin;
2904
2917
  },
2905
2918
  normalize(value) {
2906
2919
  if (!(value instanceof URL)) return value;
2907
- const result = resolve(value, value.href);
2920
+ const result = resolve(value);
2908
2921
  return result.success ? result.value : value;
2909
2922
  },
2910
2923
  *suggest(prefix) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.3.0-dev.2583",
3
+ "version": "1.3.0-dev.2587",
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.2583+d30dbb53",
240
+ "@optique/env": "1.3.0-dev.2587+ab6dc7e5",
241
241
  "@types/node": "^24.0.0",
242
242
  "fast-check": "^4.7.0",
243
243
  "tsdown": "^0.13.0",