@rogatio/cli 6.6.4 → 6.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
 
3
3
  // packages/cli/src/index.ts
4
4
  import { realpathSync } from "node:fs";
5
- import { basename as basename3, dirname as dirname8, resolve as resolve8 } from "node:path";
5
+ import { basename as basename3, dirname as dirname8, resolve as resolve9 } from "node:path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7
7
 
8
8
  // packages/runtime/dist/node/index.js
@@ -219,6 +219,7 @@ var LIMITS = Object.freeze({
219
219
  maxRequestBodyOperations: 32,
220
220
  maxLocalOrigins: 32
221
221
  });
222
+ var NON_ALPHANUMERIC = /[^a-z0-9]+/u;
222
223
  var WHITESPACE_RUN = /\s+/gu;
223
224
  var FALLBACK_PREFIX = Object.freeze({
224
225
  group: "Group",
@@ -228,6 +229,46 @@ function normalizeNameKey(name) {
228
229
  if (typeof name !== "string") return "";
229
230
  return name.trim().replace(WHITESPACE_RUN, " ").toLowerCase();
230
231
  }
232
+ function pascalCase(value) {
233
+ const tokens = value.toLowerCase().split(NON_ALPHANUMERIC);
234
+ let result = "";
235
+ for (const token of tokens) {
236
+ const first = token.charAt(0);
237
+ if (first.length === 0) continue;
238
+ result += first.toUpperCase() + token.slice(1);
239
+ }
240
+ return result;
241
+ }
242
+ function allocate(stem, reserved, maxLength) {
243
+ for (let suffix = 1; ; suffix += 1) {
244
+ const suffixText = suffix === 1 ? "" : String(suffix);
245
+ const room = Math.max(1, maxLength - suffixText.length);
246
+ const candidate = `${stem.slice(0, room)}${suffixText}`;
247
+ if (reserved.has(candidate)) continue;
248
+ reserved.add(candidate);
249
+ return candidate;
250
+ }
251
+ }
252
+ function deriveEntityId(name, kind, reserved) {
253
+ const derived = pascalCase(typeof name === "string" ? name : "");
254
+ return allocate(
255
+ derived.length > 0 ? derived : FALLBACK_PREFIX[kind],
256
+ reserved,
257
+ LIMITS.maxIdLength
258
+ );
259
+ }
260
+ function uniqueName(base, reserved) {
261
+ const label2 = typeof base === "string" ? base : "";
262
+ for (let suffix = 1; ; suffix += 1) {
263
+ const suffixText = suffix === 1 ? "" : ` ${suffix}`;
264
+ const room = Math.max(1, LIMITS.maxLabelLength - suffixText.length);
265
+ const candidate = `${label2.slice(0, room)}${suffixText}`;
266
+ const key = normalizeNameKey(candidate);
267
+ if (reserved.has(key)) continue;
268
+ reserved.add(key);
269
+ return candidate;
270
+ }
271
+ }
231
272
  function normalizeSiteOrigin(value) {
232
273
  if (typeof value !== "string" || value.length === 0 || value.trim() !== value)
233
274
  return null;
@@ -1023,7 +1064,7 @@ function snapshotOwnData(value, ancestors = /* @__PURE__ */ new WeakSet()) {
1023
1064
  return { valid: false };
1024
1065
  }
1025
1066
  }
1026
- const snapshot2 = new Array(length);
1067
+ const snapshot22 = new Array(length);
1027
1068
  for (let index = 0; index < length; index += 1) {
1028
1069
  const descriptor = Object.getOwnPropertyDescriptor(
1029
1070
  value,
@@ -1034,11 +1075,11 @@ function snapshotOwnData(value, ancestors = /* @__PURE__ */ new WeakSet()) {
1034
1075
  }
1035
1076
  const child = snapshotOwnData(descriptor.value, ancestors);
1036
1077
  if (!child.valid) return child;
1037
- snapshot2[index] = child.value;
1078
+ snapshot22[index] = child.value;
1038
1079
  }
1039
- return { valid: true, value: snapshot2 };
1080
+ return { valid: true, value: snapshot22 };
1040
1081
  }
1041
- const snapshot = /* @__PURE__ */ Object.create(null);
1082
+ const snapshot2 = /* @__PURE__ */ Object.create(null);
1042
1083
  for (const key of Object.getOwnPropertyNames(value)) {
1043
1084
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
1044
1085
  if (descriptor === void 0 || !("value" in descriptor) || !descriptor.enumerable) {
@@ -1046,14 +1087,14 @@ function snapshotOwnData(value, ancestors = /* @__PURE__ */ new WeakSet()) {
1046
1087
  }
1047
1088
  const child = snapshotOwnData(descriptor.value, ancestors);
1048
1089
  if (!child.valid) return child;
1049
- Object.defineProperty(snapshot, key, {
1090
+ Object.defineProperty(snapshot2, key, {
1050
1091
  configurable: true,
1051
1092
  enumerable: true,
1052
1093
  value: child.value,
1053
1094
  writable: true
1054
1095
  });
1055
1096
  }
1056
- return { valid: true, value: snapshot };
1097
+ return { valid: true, value: snapshot2 };
1057
1098
  } catch {
1058
1099
  return { valid: false };
1059
1100
  } finally {
@@ -1070,12 +1111,12 @@ var ownArrayEntriesError = {
1070
1111
  };
1071
1112
  var guardedProjectValidator = Object.assign(
1072
1113
  (value) => {
1073
- const snapshot = snapshotOwnData(value);
1074
- if (!snapshot.valid) {
1114
+ const snapshot2 = snapshotOwnData(value);
1115
+ if (!snapshot2.valid) {
1075
1116
  guardedProjectValidator.errors = [ownArrayEntriesError];
1076
1117
  return false;
1077
1118
  }
1078
- const valid = compiledProjectValidator(snapshot.value);
1119
+ const valid = compiledProjectValidator(snapshot2.value);
1079
1120
  guardedProjectValidator.errors = compiledProjectValidator.errors ?? null;
1080
1121
  return valid;
1081
1122
  },
@@ -1621,8 +1662,8 @@ function validateRedirectDestination(destination, urlRegex) {
1621
1662
  return issues;
1622
1663
  }
1623
1664
  function validateProjectDetailed(value) {
1624
- const snapshot = snapshotOwnData(value);
1625
- if (!snapshot.valid) {
1665
+ const snapshot2 = snapshotOwnData(value);
1666
+ if (!snapshot2.valid) {
1626
1667
  return {
1627
1668
  valid: false,
1628
1669
  errors: [
@@ -1635,10 +1676,10 @@ function validateProjectDetailed(value) {
1635
1676
  ]
1636
1677
  };
1637
1678
  }
1638
- if (!projectValidator(snapshot.value)) {
1679
+ if (!projectValidator(snapshot2.value)) {
1639
1680
  return { valid: false, errors: ajvIssues(projectValidator.errors) };
1640
1681
  }
1641
- const errors = semanticIssues(snapshot.value);
1682
+ const errors = semanticIssues(snapshot2.value);
1642
1683
  return errors.length > 0 ? { valid: false, errors } : { valid: true, data: value };
1643
1684
  }
1644
1685
 
@@ -1777,7 +1818,7 @@ function snapshotOwnData2(value, ancestors = /* @__PURE__ */ new WeakSet()) {
1777
1818
  return { valid: false };
1778
1819
  }
1779
1820
  }
1780
- const snapshot2 = new Array(length);
1821
+ const snapshot22 = new Array(length);
1781
1822
  for (let index = 0; index < length; index += 1) {
1782
1823
  const descriptor = Object.getOwnPropertyDescriptor(
1783
1824
  value,
@@ -1788,11 +1829,11 @@ function snapshotOwnData2(value, ancestors = /* @__PURE__ */ new WeakSet()) {
1788
1829
  }
1789
1830
  const child = snapshotOwnData2(descriptor.value, ancestors);
1790
1831
  if (!child.valid) return child;
1791
- snapshot2[index] = child.value;
1832
+ snapshot22[index] = child.value;
1792
1833
  }
1793
- return { valid: true, value: snapshot2 };
1834
+ return { valid: true, value: snapshot22 };
1794
1835
  }
1795
- const snapshot = /* @__PURE__ */ Object.create(null);
1836
+ const snapshot2 = /* @__PURE__ */ Object.create(null);
1796
1837
  for (const key of Object.getOwnPropertyNames(value)) {
1797
1838
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
1798
1839
  if (descriptor === void 0 || !("value" in descriptor) || !descriptor.enumerable) {
@@ -1800,14 +1841,14 @@ function snapshotOwnData2(value, ancestors = /* @__PURE__ */ new WeakSet()) {
1800
1841
  }
1801
1842
  const child = snapshotOwnData2(descriptor.value, ancestors);
1802
1843
  if (!child.valid) return child;
1803
- Object.defineProperty(snapshot, key, {
1844
+ Object.defineProperty(snapshot2, key, {
1804
1845
  configurable: true,
1805
1846
  enumerable: true,
1806
1847
  value: child.value,
1807
1848
  writable: true
1808
1849
  });
1809
1850
  }
1810
- return { valid: true, value: snapshot };
1851
+ return { valid: true, value: snapshot2 };
1811
1852
  } catch {
1812
1853
  return { valid: false };
1813
1854
  } finally {
@@ -1925,8 +1966,8 @@ function compileOperations(project) {
1925
1966
  return operations;
1926
1967
  }
1927
1968
  function compileProject(value) {
1928
- const snapshot = snapshotOwnData2(value);
1929
- if (!snapshot.valid) {
1969
+ const snapshot2 = snapshotOwnData2(value);
1970
+ if (!snapshot2.valid) {
1930
1971
  return {
1931
1972
  ok: false,
1932
1973
  operations: [],
@@ -1942,7 +1983,7 @@ function compileProject(value) {
1942
1983
  }
1943
1984
  let validation;
1944
1985
  try {
1945
- validation = validateProjectDetailed(snapshot.value);
1986
+ validation = validateProjectDetailed(snapshot2.value);
1946
1987
  } catch {
1947
1988
  return { ok: false, operations: [], diagnostics: [invariantDiagnostic()] };
1948
1989
  }
@@ -2038,11 +2079,11 @@ import { Worker } from "node:worker_threads";
2038
2079
  import { lookup as dnsLookup } from "node:dns/promises";
2039
2080
  import { request as httpRequest } from "node:http";
2040
2081
  import { request as httpsRequest } from "node:https";
2041
- import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
2082
+ import { createHash as createHash2, randomBytes, timingSafeEqual } from "node:crypto";
2042
2083
  import { randomBytes as randomBytes2 } from "node:crypto";
2043
2084
  import { readFile as readFile2, realpath as realpath2, stat } from "node:fs/promises";
2044
2085
  import { isAbsolute as isAbsolute2, relative as relative2, resolve, sep } from "node:path";
2045
- import { createHash as createHash2 } from "node:crypto";
2086
+ import { createHash as createHash3 } from "node:crypto";
2046
2087
  import { mkdir as mkdir2, readFile as readFile3, rename, rm, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
2047
2088
  import { basename, dirname as dirname2, isAbsolute as isAbsolute3, join as join2, relative as relative3 } from "node:path";
2048
2089
  import { generateKeyPairSync, webcrypto } from "node:crypto";
@@ -21694,7 +21735,7 @@ function snapshotOwnData3(value, ancestors = /* @__PURE__ */ new WeakSet()) {
21694
21735
  return { valid: false };
21695
21736
  }
21696
21737
  }
21697
- const snapshot2 = new Array(length);
21738
+ const snapshot22 = new Array(length);
21698
21739
  for (let index = 0; index < length; index += 1) {
21699
21740
  const descriptor = Object.getOwnPropertyDescriptor(
21700
21741
  value,
@@ -21705,13 +21746,13 @@ function snapshotOwnData3(value, ancestors = /* @__PURE__ */ new WeakSet()) {
21705
21746
  }
21706
21747
  const child = snapshotOwnData3(descriptor.value, ancestors);
21707
21748
  if (!child.valid) return child;
21708
- snapshot2[index] = child.value;
21749
+ snapshot22[index] = child.value;
21709
21750
  }
21710
- return { valid: true, value: snapshot2 };
21751
+ return { valid: true, value: snapshot22 };
21711
21752
  }
21712
21753
  const names = Object.getOwnPropertyNames(value);
21713
21754
  if (names.length > MAX_OBJECT_PROPERTIES) return { valid: false };
21714
- const snapshot = /* @__PURE__ */ Object.create(null);
21755
+ const snapshot2 = /* @__PURE__ */ Object.create(null);
21715
21756
  for (const key of names) {
21716
21757
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
21717
21758
  if (descriptor === void 0 || !("value" in descriptor) || !descriptor.enumerable) {
@@ -21719,14 +21760,14 @@ function snapshotOwnData3(value, ancestors = /* @__PURE__ */ new WeakSet()) {
21719
21760
  }
21720
21761
  const child = snapshotOwnData3(descriptor.value, ancestors);
21721
21762
  if (!child.valid) return child;
21722
- Object.defineProperty(snapshot, key, {
21763
+ Object.defineProperty(snapshot2, key, {
21723
21764
  configurable: true,
21724
21765
  enumerable: true,
21725
21766
  value: child.value,
21726
21767
  writable: true
21727
21768
  });
21728
21769
  }
21729
- return { valid: true, value: snapshot };
21770
+ return { valid: true, value: snapshot2 };
21730
21771
  } catch {
21731
21772
  return { valid: false };
21732
21773
  } finally {
@@ -21808,10 +21849,10 @@ function validId(value) {
21808
21849
  return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(value);
21809
21850
  }
21810
21851
  function exactDescriptor(value) {
21811
- const snapshot = snapshotOwnData3(value);
21812
- if (!snapshot.valid || snapshot.value === null || typeof snapshot.value !== "object" || Array.isArray(snapshot.value))
21852
+ const snapshot2 = snapshotOwnData3(value);
21853
+ if (!snapshot2.valid || snapshot2.value === null || typeof snapshot2.value !== "object" || Array.isArray(snapshot2.value))
21813
21854
  return null;
21814
- const record = snapshot.value;
21855
+ const record = snapshot2.value;
21815
21856
  const keys = ["groupId", "ruleId", "operationId", "kind", "target", "method"];
21816
21857
  if (Object.keys(record).length !== keys.length || !keys.every((key) => hasOwn(record, key)))
21817
21858
  return null;
@@ -22015,6 +22056,27 @@ function parseEnvelope(json) {
22015
22056
  };
22016
22057
  return envelope;
22017
22058
  }
22059
+ var RELEASE_EXTENSION_ID = "dkngkciiiabbdjcopbipkpndfmpbmjom";
22060
+ var EXTENSION_ID_RE = /^[a-p]{32}$/;
22061
+ function extensionOrigin(extensionId) {
22062
+ return `chrome-extension://${extensionId}/`;
22063
+ }
22064
+ function extensionOriginListed(extensionId, allowedOrigins) {
22065
+ if (!EXTENSION_ID_RE.test(extensionId)) return false;
22066
+ const origin = extensionOrigin(extensionId);
22067
+ const bare = origin.slice(0, -1);
22068
+ return allowedOrigins.some((value) => value === origin || value === bare);
22069
+ }
22070
+ function describeExtensionIdMismatch(expectedExtensionId, allowedOrigins) {
22071
+ const allowed = allowedOrigins.length > 0 ? allowedOrigins.join(", ") : "(none)";
22072
+ const command = expectedExtensionId === RELEASE_EXTENSION_ID ? "rogatio runtime install" : `rogatio runtime install --extension-id ${expectedExtensionId}`;
22073
+ return [
22074
+ `Extension ID ${expectedExtensionId} is not in the host manifest allowed_origins.`,
22075
+ `The host manifest allows: ${allowed}.`,
22076
+ "Chrome refuses the connection until allowed_origins includes this ID (Access to the specified native messaging host is forbidden).",
22077
+ `Run: ${command}`
22078
+ ].join("\n");
22079
+ }
22018
22080
  function supportedContentType(value) {
22019
22081
  if (value === void 0) return false;
22020
22082
  const mediaType = value.split(";", 1)[0]?.trim().toLowerCase() ?? "";
@@ -22653,7 +22715,7 @@ function readBoundedBody(req, maxBytes) {
22653
22715
  const chunks = [];
22654
22716
  let total = 0;
22655
22717
  let settled = false;
22656
- const finish = (result) => {
22718
+ const finish2 = (result) => {
22657
22719
  if (settled) return;
22658
22720
  settled = true;
22659
22721
  resolve22(result);
@@ -22662,16 +22724,16 @@ function readBoundedBody(req, maxBytes) {
22662
22724
  total += chunk.byteLength;
22663
22725
  if (total > maxBytes) {
22664
22726
  req.destroy();
22665
- finish({ ok: false, reason: "oversized" });
22727
+ finish2({ ok: false, reason: "oversized" });
22666
22728
  return;
22667
22729
  }
22668
22730
  chunks.push(chunk);
22669
22731
  });
22670
22732
  req.on("end", () => {
22671
- finish({ ok: true, body: Buffer.concat(chunks) });
22733
+ finish2({ ok: true, body: Buffer.concat(chunks) });
22672
22734
  });
22673
22735
  req.on("error", () => {
22674
- finish({ ok: true, body: Buffer.concat(chunks) });
22736
+ finish2({ ok: true, body: Buffer.concat(chunks) });
22675
22737
  });
22676
22738
  });
22677
22739
  }
@@ -23398,7 +23460,7 @@ function createCapabilityState(preset, now) {
23398
23460
  };
23399
23461
  }
23400
23462
  function digestToken(value) {
23401
- return createHash("sha256").update(value, "utf8").digest();
23463
+ return createHash2("sha256").update(value, "utf8").digest();
23402
23464
  }
23403
23465
  function isToken(value) {
23404
23466
  if (typeof value !== "string" || value.length !== TOKEN_LENGTH) return false;
@@ -23616,7 +23678,7 @@ function canonicalPresetBytes(preset, grants = preset.grants) {
23616
23678
  return new TextEncoder().encode(canonical);
23617
23679
  }
23618
23680
  function digestBytes(bytes) {
23619
- return formatSha256(createHash2("sha256").update(bytes).digest("hex"));
23681
+ return formatSha256(createHash3("sha256").update(bytes).digest("hex"));
23620
23682
  }
23621
23683
  var ID_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
23622
23684
  function exactKeys(value, required) {
@@ -23841,12 +23903,12 @@ function makeGrant(value, matcherById) {
23841
23903
  });
23842
23904
  }
23843
23905
  function normalizeRuntimePreset(value) {
23844
- const snapshot = snapshotOwnData3(value);
23845
- if (!snapshot.valid || snapshot.value === null || typeof snapshot.value !== "object" || Array.isArray(snapshot.value)) {
23906
+ const snapshot2 = snapshotOwnData3(value);
23907
+ if (!snapshot2.valid || snapshot2.value === null || typeof snapshot2.value !== "object" || Array.isArray(snapshot2.value)) {
23846
23908
  console.error("[rogatio-host] preset: snapshot invalid");
23847
23909
  return failure("runtime.invalid-preset");
23848
23910
  }
23849
- const record = snapshot.value;
23911
+ const record = snapshot2.value;
23850
23912
  const allowedKeys = ["version", "limits", "matchers", "grants", "mocks"];
23851
23913
  const requiredKeys = ["version", "limits", "matchers", "grants"];
23852
23914
  const keyCount = Object.keys(record).length;
@@ -24901,17 +24963,16 @@ function createRequestBodyTrustController(options = {}) {
24901
24963
  let manifestValid = false;
24902
24964
  let binaryExists = false;
24903
24965
  let binaryExecutable = false;
24904
- let allowedOriginsCount = 0;
24966
+ let allowedOrigins = [];
24905
24967
  try {
24906
24968
  const raw = await readFile3(manifestPath(), "utf8");
24907
24969
  manifestExists = true;
24908
24970
  const parsed = JSON.parse(raw);
24909
24971
  if (isWellFormedManifest(parsed)) {
24910
24972
  manifestValid = true;
24911
- const manifest = parsed;
24912
- allowedOriginsCount = manifest.allowed_origins.length;
24973
+ allowedOrigins = [...parsed.allowed_origins];
24913
24974
  try {
24914
- const fileStat = await stat2(manifest.path);
24975
+ const fileStat = await stat2(parsed.path);
24915
24976
  binaryExists = fileStat.isFile();
24916
24977
  binaryExecutable = (fileStat.mode & 73) !== 0;
24917
24978
  if (!binaryExists) reasons.push("binary-not-found");
@@ -24920,7 +24981,7 @@ function createRequestBodyTrustController(options = {}) {
24920
24981
  binaryExists = false;
24921
24982
  reasons.push("binary-not-found");
24922
24983
  }
24923
- if (allowedOriginsCount === 0) reasons.push("no-allowed-origins");
24984
+ if (allowedOrigins.length === 0) reasons.push("no-allowed-origins");
24924
24985
  } else {
24925
24986
  reasons.push("manifest-invalid");
24926
24987
  }
@@ -24929,7 +24990,7 @@ function createRequestBodyTrustController(options = {}) {
24929
24990
  }
24930
24991
  const caTrusted = await existsFile(caKeyFile) && await existsFile(caCertFile);
24931
24992
  if (!caTrusted) reasons.push("ca-not-trusted");
24932
- const ok = manifestExists && manifestValid && binaryExists && binaryExecutable && caTrusted && allowedOriginsCount > 0;
24993
+ const ok = manifestExists && manifestValid && binaryExists && binaryExecutable && caTrusted && allowedOrigins.length > 0;
24933
24994
  return {
24934
24995
  ok,
24935
24996
  manifestExists,
@@ -24937,7 +24998,8 @@ function createRequestBodyTrustController(options = {}) {
24937
24998
  binaryExists,
24938
24999
  binaryExecutable,
24939
25000
  caTrusted,
24940
- allowedOriginsCount,
25001
+ allowedOriginsCount: allowedOrigins.length,
25002
+ allowedOrigins,
24941
25003
  reasons
24942
25004
  };
24943
25005
  }
@@ -25695,23 +25757,31 @@ Native messaging runtime control for response-body and request-body rules.
25695
25757
 
25696
25758
  Commands:
25697
25759
  install Install the native-messaging host manifest and (on capable
25698
- platforms) provision the device-local CA (requires --extension-id).
25760
+ platforms) provision the device-local CA. Uses the pinned release
25761
+ extension ID. Release users never pass --extension-id. That flag is
25762
+ only for development (a local unpacked build without the release
25763
+ key) and for forks.
25699
25764
  CA trust requires root/admin privileges (Linux: sudo, macOS: keychain
25700
25765
  password, Windows: Administrator). Transactional: when CA trust is
25701
25766
  unavailable the manifest is rolled back and the command exits 1.
25702
25767
  uninstall Remove the native-messaging host manifest and device-local CA trust
25703
25768
  (idempotent).
25704
25769
  verify Check that the manifest, runtime-host wrapper, allowed origins, and
25705
- device-local CA trust are all present and valid.
25770
+ device-local CA trust are all present and valid. Fails when
25771
+ allowed_origins does not include the release ID. Release users
25772
+ never pass --extension-id. That flag is only for development (a
25773
+ local unpacked build without the release key) and for forks.
25706
25774
  host [path] Run the native-messaging runtime host. The browser launches this
25707
25775
  process via the native-messaging manifest to handle rule matching.
25708
25776
 
25709
25777
  The lifecycle of the runtime (start/stop) is driven from the extension's
25710
- Start/Stop controls, not the CLI. Run 'rogatio runtime install --extension-id
25711
- <id>' once to register the host, then use the extension.
25778
+ Start/Stop controls, not the CLI. Run 'rogatio runtime install' once to
25779
+ register the host for a release build, then use the extension.
25712
25780
 
25713
25781
  Options:
25714
- --extension-id Extension ID for native messaging manifest (required for install)
25782
+ --extension-id Extension ID for the native messaging manifest. Release users
25783
+ never need this. It is only for development (a local unpacked
25784
+ build without the release key) and for forks.
25715
25785
  --root <dir> Root for confined runtime file access (host only; default: project directory)
25716
25786
  --mock-port <n> Bind the loopback mock-response faucet to this port (host only, 1-65535)
25717
25787
  --help, -h Show this help
@@ -25754,6 +25824,30 @@ Exit codes:
25754
25824
  1 Validation/compile/test errors
25755
25825
  2 Usage error (invalid arguments, missing input)`);
25756
25826
  }
25827
+ function showImportHelp() {
25828
+ console.log(`Usage: rogatio import requestly <export.json> [options]
25829
+
25830
+ Migrate a Requestly rule export into a Rogatio project file.
25831
+
25832
+ Arguments:
25833
+ export.json Requestly export (a JSON array of rules and groups, a single
25834
+ rule, or an object with a rules array)
25835
+
25836
+ Options:
25837
+ --out <path> Project file to write (default: .rogatio.json in the current directory)
25838
+ --merge Append imported groups onto an existing version-2 project
25839
+ --json Print the import report as JSON
25840
+ --help, -h Show this help
25841
+
25842
+ The command validates the mapped project with the schema and compiler before
25843
+ writing. An existing --out file is left untouched unless --merge is set.
25844
+ Skipped and changed rules are listed; nothing is dropped silently.
25845
+
25846
+ Exit codes:
25847
+ 0 The project was written (skipped rules do not fail the command)
25848
+ 1 The mapped or merged project failed validation. Nothing is written.
25849
+ 2 Usage, unreadable input, an export that is not Requestly data, or a refusal to overwrite`);
25850
+ }
25757
25851
  function showAIHelp() {
25758
25852
  console.log(`Usage: rogatio ai <command> [options]
25759
25853
 
@@ -25773,13 +25867,13 @@ Options:
25773
25867
  // packages/cli/src/commands/ai.ts
25774
25868
  var pendingInput = "";
25775
25869
  function readLine() {
25776
- return new Promise((resolve9) => {
25870
+ return new Promise((resolve10) => {
25777
25871
  const take = () => {
25778
25872
  const newline = pendingInput.search(/[\r\n]/u);
25779
25873
  if (newline === -1) return false;
25780
25874
  const line = pendingInput.slice(0, newline);
25781
25875
  pendingInput = pendingInput.slice(newline + 1);
25782
- resolve9(line);
25876
+ resolve10(line);
25783
25877
  return true;
25784
25878
  };
25785
25879
  if (take()) return;
@@ -25801,14 +25895,14 @@ async function promptSecret(prompt) {
25801
25895
  process.stdout.write(prompt);
25802
25896
  process.stdin.setRawMode(true);
25803
25897
  try {
25804
- return await new Promise((resolve9) => {
25898
+ return await new Promise((resolve10) => {
25805
25899
  let input = "";
25806
25900
  let settled = false;
25807
- const finish = () => {
25901
+ const finish2 = () => {
25808
25902
  settled = true;
25809
25903
  process.stdin.off("data", onData);
25810
25904
  process.stdin.pause();
25811
- resolve9(input.trim());
25905
+ resolve10(input.trim());
25812
25906
  };
25813
25907
  const consume = (text) => {
25814
25908
  if (settled) return;
@@ -25817,7 +25911,7 @@ async function promptSecret(prompt) {
25817
25911
  if (char === "\n" || char === "\r") {
25818
25912
  process.stdout.write("\n");
25819
25913
  pendingInput = text.slice(index + 1);
25820
- finish();
25914
+ finish2();
25821
25915
  return;
25822
25916
  }
25823
25917
  if (char === "") {
@@ -26011,11 +26105,11 @@ function createServer3(handler, options = {}) {
26011
26105
  let port = null;
26012
26106
  let started = false;
26013
26107
  async function listenOn(candidatePort) {
26014
- await new Promise((resolve9, reject) => {
26108
+ await new Promise((resolve10, reject) => {
26015
26109
  server.once("error", reject);
26016
26110
  server.listen(candidatePort, "127.0.0.1", () => {
26017
26111
  server.off("error", reject);
26018
- resolve9();
26112
+ resolve10();
26019
26113
  });
26020
26114
  });
26021
26115
  }
@@ -26066,8 +26160,8 @@ function createServer3(handler, options = {}) {
26066
26160
  },
26067
26161
  async stop() {
26068
26162
  if (!started) return;
26069
- await new Promise((resolve9) => {
26070
- server.close(() => resolve9());
26163
+ await new Promise((resolve10) => {
26164
+ server.close(() => resolve10());
26071
26165
  });
26072
26166
  started = false;
26073
26167
  }
@@ -26386,7 +26480,7 @@ var RequestBodyError = class extends Error {
26386
26480
  code = "request-body-too-large";
26387
26481
  };
26388
26482
  function getRequestBody(req) {
26389
- return new Promise((resolve9, reject) => {
26483
+ return new Promise((resolve10, reject) => {
26390
26484
  let body = "";
26391
26485
  let bytes = 0;
26392
26486
  let settled = false;
@@ -26416,7 +26510,7 @@ function getRequestBody(req) {
26416
26510
  req.on("end", () => {
26417
26511
  if (!settled) {
26418
26512
  settled = true;
26419
- resolve9(body);
26513
+ resolve10(body);
26420
26514
  }
26421
26515
  });
26422
26516
  req.on("error", (error) => {
@@ -27031,7 +27125,7 @@ async function launchBrowser(url) {
27031
27125
  `Unsupported platform: ${platform}`
27032
27126
  );
27033
27127
  }
27034
- return new Promise((resolve9) => {
27128
+ return new Promise((resolve10) => {
27035
27129
  const child = spawn4(command, args, {
27036
27130
  detached: true,
27037
27131
  stdio: "ignore"
@@ -27039,13 +27133,13 @@ async function launchBrowser(url) {
27039
27133
  child.unref();
27040
27134
  child.on("error", (err) => {
27041
27135
  if (err.code === "ENOENT") {
27042
- resolve9(false);
27136
+ resolve10(false);
27043
27137
  } else {
27044
- resolve9(false);
27138
+ resolve10(false);
27045
27139
  }
27046
27140
  });
27047
27141
  child.on("close", (code) => {
27048
- resolve9(code === 0);
27142
+ resolve10(code === 0);
27049
27143
  });
27050
27144
  });
27051
27145
  }
@@ -27374,6 +27468,17 @@ function createJsonFileProjectStorage() {
27374
27468
 
27375
27469
  // packages/cli/src/utils/file.ts
27376
27470
  var defaultStorage = createJsonFileProjectStorage();
27471
+ async function writeProject(path2, data) {
27472
+ try {
27473
+ await defaultStorage.update(path2, data);
27474
+ } catch (e) {
27475
+ if (e instanceof ProjectStorageError && e.code === "not-found") {
27476
+ await defaultStorage.create({ id: path2, data });
27477
+ return;
27478
+ }
27479
+ throw e;
27480
+ }
27481
+ }
27377
27482
 
27378
27483
  // packages/cli/src/commands/edit.ts
27379
27484
  async function editCommand(args, options = {}) {
@@ -27527,11 +27632,11 @@ async function editCommand(args, options = {}) {
27527
27632
  } else {
27528
27633
  console.log(`Editor available at: ${editorUrl}`);
27529
27634
  }
27530
- const exitCodePromise = new Promise((resolve9) => {
27635
+ const exitCodePromise = new Promise((resolve10) => {
27531
27636
  const checkShutdown = setInterval(() => {
27532
27637
  if (shutdownCalled) {
27533
27638
  clearInterval(checkShutdown);
27534
- resolve9(0);
27639
+ resolve10(0);
27535
27640
  }
27536
27641
  }, 100);
27537
27642
  const handleSignal = () => {
@@ -27539,8 +27644,8 @@ async function editCommand(args, options = {}) {
27539
27644
  };
27540
27645
  process.on("SIGINT", handleSignal);
27541
27646
  process.on("SIGTERM", handleSignal);
27542
- const originalResolve = resolve9;
27543
- resolve9 = (code) => {
27647
+ const originalResolve = resolve10;
27648
+ resolve10 = (code) => {
27544
27649
  clearInterval(checkShutdown);
27545
27650
  process.off("SIGINT", handleSignal);
27546
27651
  process.off("SIGTERM", handleSignal);
@@ -27763,207 +27868,2272 @@ function generateEditorHtml(apiBase, csrfToken, filePath, aiConfigured) {
27763
27868
  </html>`;
27764
27869
  }
27765
27870
 
27766
- // packages/cli/src/commands/runtime.ts
27767
- import { chmod, mkdir as mkdir4, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
27768
- import { dirname as dirname7, join as join7, resolve as resolve5 } from "node:path";
27769
- function toMatcherOperations2(operations) {
27770
- return operations.map(
27771
- ({ groupId, ruleId, name, matcher, redactSensitiveInLogs }) => ({
27772
- kind: "matcher",
27773
- groupId,
27774
- ruleId,
27775
- name,
27776
- matcher,
27777
- redactSensitiveInLogs
27778
- })
27779
- );
27780
- }
27781
- function generateHostWrapper(rogatioCommand, logPath) {
27782
- const parts = rogatioCommand.split(" ");
27783
- const nodeBin = parts[0] ?? "node";
27784
- const cliScript = parts.slice(1).join(" ");
27785
- return `#!/bin/sh
27786
- # Rogatio native messaging host wrapper \u2014 generated by \`rogatio runtime install\`
27787
- # Chrome launches this script when the extension connects via connectNative().
27788
- # The extension sends the project data as the first envelope after launch.
27789
- # stderr is redirected to a log file for debugging.
27790
- exec "${nodeBin}" "${cliScript}" runtime host 2>>"${logPath}"
27791
- `;
27792
- }
27793
- async function writeHostWrapper(hostPath, installRoot) {
27794
- await mkdir4(installRoot, { recursive: true });
27795
- const logPath = join7(installRoot, "runtime-host.log");
27796
- const script = generateHostWrapper(
27797
- `${process.execPath} ${process.argv[1] ?? "rogatio"}`,
27798
- logPath
27799
- );
27800
- await writeFile4(hostPath, script, "utf8");
27801
- await chmod(hostPath, 493);
27802
- }
27803
- function makeTrustController() {
27804
- const platform = process.platform;
27805
- const adapter = selectTrustPlatformAdapter(platform);
27806
- const installRoot = defaultTrustInstallRoot(platform);
27807
- return createRequestBodyTrustController({
27808
- platform,
27809
- installRoot,
27810
- hostPath: join7(installRoot, "runtime-host"),
27811
- hostName: "com.rogatio.runtime",
27812
- allowedOrigins: [],
27813
- manifestDir: adapter.defaultManifestDir(),
27814
- detectCapabilities: () => adapter.detect(),
27815
- caTrustInstaller: (cert) => adapter.caTrustInstaller(cert),
27816
- caTrustRemover: () => adapter.caTrustRemover()
27817
- });
27818
- }
27819
- function reportTrust(subcommand, result, okMessage) {
27820
- if (result.ok) {
27821
- console.log(okMessage);
27822
- return 0;
27871
+ // packages/cli/src/commands/import.ts
27872
+ import { access as access2, readFile as readFile6 } from "node:fs/promises";
27873
+ import { resolve as resolve5 } from "node:path";
27874
+
27875
+ // packages/requestly-import/dist/node/index.js
27876
+ function isRecord3(value) {
27877
+ return value !== null && typeof value === "object" && !Array.isArray(value);
27878
+ }
27879
+ function ownString3(record, key) {
27880
+ if (!Object.hasOwn(record, key)) return void 0;
27881
+ const value = record[key];
27882
+ return typeof value === "string" ? value : void 0;
27883
+ }
27884
+ function ownBoolean(record, key) {
27885
+ if (!Object.hasOwn(record, key)) return void 0;
27886
+ const value = record[key];
27887
+ return typeof value === "boolean" ? value : void 0;
27888
+ }
27889
+ function displayName(name) {
27890
+ const collapsed = name.replace(/\s+/g, " ").trim();
27891
+ return collapsed.length > 0 ? collapsed : "(unnamed)";
27892
+ }
27893
+ function cleanLabel(name, fallback) {
27894
+ const collapsed = name.replace(/\s+/g, " ").trim();
27895
+ return collapsed.length > 0 ? collapsed : fallback;
27896
+ }
27897
+ var REQUESTLY_REGEX = /^\/(.+)\/(|i|g|ig|gi)$/;
27898
+ var LITERAL_HOSTNAME = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;
27899
+ var RESOURCE_SET = new Set(RESOURCE_TYPES);
27900
+ var METHOD_SET = new Set(HTTP_METHODS);
27901
+ var SOURCE_CHANGE = {
27902
+ caseInsensitive: "The case-insensitive match flag was dropped. Rogatio regular expressions are case-sensitive.",
27903
+ hostPort: "Requestly compares the host and a non-default port. This host condition matches the hostname on any port.",
27904
+ hostAsUrl: "The host condition was imported as a URL regular expression so the port stays part of the match.",
27905
+ path: "The path condition was imported as a URL regular expression."
27906
+ };
27907
+ function escapeRegex(literal) {
27908
+ return literal.replace(/[\\^$*+?.()|[\]{}]/g, "\\$&");
27909
+ }
27910
+ function convertSource(source) {
27911
+ const key = ownString3(source, "key");
27912
+ const operator = ownString3(source, "operator");
27913
+ const value = ownString3(source, "value");
27914
+ if (key === void 0 || operator === void 0 || value === void 0) {
27915
+ return { ok: false, reason: "The source condition is missing." };
27916
+ }
27917
+ if (operator === "Equals" && value.length === 0) {
27918
+ return { ok: false, reason: "The source value is empty." };
27919
+ }
27920
+ if (key === "Url") return convertUrl(operator, value);
27921
+ if (key === "host") return convertHost(operator, value);
27922
+ if (key === "path") return convertPath(operator, value);
27923
+ return { ok: false, reason: `Unsupported source key "${key}".` };
27924
+ }
27925
+ function readFilters(source) {
27926
+ if (!Object.hasOwn(source, "filters") || source.filters === void 0) {
27927
+ return {
27928
+ ok: true,
27929
+ filters: {
27930
+ resourceTypes: [...RESOURCE_TYPES],
27931
+ methods: "all",
27932
+ changes: []
27933
+ }
27934
+ };
27823
27935
  }
27824
- if (result.state === "unsupported") {
27825
- const reasons = result.reasons ?? ["unknown"];
27826
- console.error(`trust unsupported: ${reasons.join(", ")}`);
27827
- const hints = {
27828
- "tooling-missing": "Hint: install required tooling (macOS: security; Linux: update-ca-certificates; Windows: certutil is built-in)",
27829
- "manifest-dir-unwritable": "Hint: check permissions on Chrome NativeMessagingHosts directory or run with appropriate access",
27830
- "keychain-unwritable": "Hint: macOS login keychain not writable; try running with appropriate keychain access or use sudo for system keychain",
27831
- "ca-store-unwritable": "Hint: /usr/local/share/ca-certificates not writable; check directory permissions",
27832
- "elevation-required": "Hint: device-local CA trust requires root (Linux: sudo, macOS: keychain password, Windows: Administrator). Re-run with elevated privileges"
27936
+ const raw = source.filters;
27937
+ const list = Array.isArray(raw) ? raw : [raw];
27938
+ const changes = [];
27939
+ let resourceTypes;
27940
+ let methods;
27941
+ for (const entry of list) {
27942
+ if (!isRecord3(entry)) {
27943
+ return { ok: false, reason: "A source filter is not an object." };
27944
+ }
27945
+ if (isActive(entry.pageUrl) || isActive(entry.pageDomains)) {
27946
+ return {
27947
+ ok: false,
27948
+ reason: "Page URL and page domain filters are not supported, so the rule was not imported."
27949
+ };
27950
+ }
27951
+ if (isActive(entry.requestPayload)) {
27952
+ return {
27953
+ ok: false,
27954
+ reason: "Request payload filters are not supported, so the rule was not imported."
27955
+ };
27956
+ }
27957
+ if (Object.hasOwn(entry, "resourceType")) {
27958
+ const read = readResourceTypes(entry.resourceType);
27959
+ if (!read.ok) return read;
27960
+ resourceTypes = intersect(resourceTypes, read.values);
27961
+ changes.push(...read.changes);
27962
+ }
27963
+ if (Object.hasOwn(entry, "requestMethod")) {
27964
+ const read = readMethods(entry.requestMethod);
27965
+ if (!read.ok) return read;
27966
+ methods = intersect(methods, read.values);
27967
+ changes.push(...read.changes);
27968
+ }
27969
+ }
27970
+ if (resourceTypes !== void 0 && resourceTypes.length === 0) {
27971
+ return {
27972
+ ok: false,
27973
+ reason: "The resource type filter matches no supported type."
27833
27974
  };
27834
- for (const reason of reasons) {
27835
- const hint = hints[reason];
27836
- if (hint) {
27837
- console.error(hint);
27838
- }
27975
+ }
27976
+ if (methods !== void 0 && methods.length === 0) {
27977
+ return {
27978
+ ok: false,
27979
+ reason: "The request method filter matches no supported method."
27980
+ };
27981
+ }
27982
+ return {
27983
+ ok: true,
27984
+ filters: {
27985
+ resourceTypes: resourceTypes ?? [...RESOURCE_TYPES],
27986
+ methods: methods ?? "all",
27987
+ changes
27839
27988
  }
27840
- return 1;
27989
+ };
27990
+ }
27991
+ function acceptRegex(pattern) {
27992
+ if (pattern.length === 0 || pattern.length > LIMITS.maxUrlRegexLength) {
27993
+ return {
27994
+ ok: false,
27995
+ reason: "The source regular expression exceeds the length limit."
27996
+ };
27841
27997
  }
27842
- console.error(
27843
- `runtime ${subcommand} failed: ${(result.reasons ?? ["unknown"]).join(", ")}`
27844
- );
27845
- return 1;
27998
+ if (compileUrlRegex(pattern) === null) {
27999
+ return { ok: false, reason: "The source regular expression is invalid." };
28000
+ }
28001
+ return { ok: true };
27846
28002
  }
27847
- async function trustRuntimeCommand(args) {
27848
- const subcommand = args[0];
27849
- let extensionId;
27850
- if (subcommand === "install") {
27851
- const extIdIndex = args.indexOf("--extension-id");
27852
- if (extIdIndex === -1 || extIdIndex + 1 >= args.length) {
27853
- console.error("Error: --extension-id is required for install command");
27854
- showRuntimeHelp();
27855
- return 2;
27856
- }
27857
- extensionId = args[extIdIndex + 1];
27858
- if (!/^[a-p]{32}$/.test(extensionId)) {
27859
- console.error(
27860
- "Error: --extension-id must be exactly 32 lowercase characters from a through p"
27861
- );
27862
- return 2;
27863
- }
28003
+ function convertUrl(operator, value) {
28004
+ if (operator === "Equals") {
28005
+ return finishUrl(`^${escapeRegex(value)}$`, []);
27864
28006
  }
27865
- const controller = makeTrustController();
27866
- const installRoot = defaultTrustInstallRoot(process.platform);
27867
- const hostPath = join7(installRoot, "runtime-host");
27868
- switch (subcommand) {
27869
- case "install": {
27870
- const installResult = await controller.install(extensionId ?? "");
27871
- if (installResult.ok) {
27872
- try {
27873
- await writeHostWrapper(hostPath, installRoot);
27874
- } catch (error) {
27875
- console.error(
27876
- `Warning: manifest and CA trusted, but failed to create runtime-host wrapper: ${error instanceof Error ? error.message : String(error)}`
27877
- );
27878
- }
27879
- }
27880
- return reportTrust(
27881
- "install",
27882
- installResult,
27883
- "runtime install complete: manifest + device-local CA trusted + runtime-host wrapper created\nNote: reload the extension at chrome://extensions for the native host to be detected."
28007
+ if (operator === "Contains") {
28008
+ const pattern = value.length === 0 ? "[\\s\\S]*" : escapeRegex(value);
28009
+ return finishUrl(pattern, []);
28010
+ }
28011
+ if (operator === "Wildcard_Matches") {
28012
+ return finishUrl(`^${wildcardBody(value)}$`, []);
28013
+ }
28014
+ if (operator === "Matches") return finishMatches(value);
28015
+ return { ok: false, reason: `Unsupported source operator "${operator}".` };
28016
+ }
28017
+ function convertHost(operator, value) {
28018
+ if (value.includes(":") && splitHostPort(value) === null) {
28019
+ return { ok: false, reason: "IPv6 host conditions are not supported." };
28020
+ }
28021
+ if (operator === "Equals") {
28022
+ if (isLiteralHostname(value)) {
28023
+ return finish(
28024
+ { key: "host", operator: "regex", value: `^${escapeRegex(value)}$` },
28025
+ [SOURCE_CHANGE.hostPort]
27884
28026
  );
27885
28027
  }
27886
- case "uninstall": {
27887
- const uninstallResult = await controller.uninstall();
27888
- try {
27889
- await rm2(hostPath, { force: true });
27890
- } catch {
27891
- }
27892
- try {
27893
- await rm2(join7(installRoot, "runtime-host.log"), { force: true });
27894
- } catch {
27895
- }
27896
- return reportTrust(
27897
- "uninstall",
27898
- uninstallResult,
27899
- "runtime uninstall complete: manifest + device-local CA removed"
27900
- );
28028
+ const port = splitHostPort(value);
28029
+ if (port !== null) {
28030
+ return finishUrl(`^https?:\\/\\/${escapeRegex(value)}(?:[/?#].*)?$`, [
28031
+ SOURCE_CHANGE.hostAsUrl
28032
+ ]);
27901
28033
  }
27902
- case "verify": {
27903
- const result = await controller.verify();
27904
- if (result.ok) {
27905
- console.log(
27906
- "runtime verification passed: manifest, binary, and CA trust all OK"
27907
- );
27908
- return 0;
27909
- }
27910
- console.error("runtime verification failed:");
27911
- if (!result.manifestExists) console.error(" manifest not found");
27912
- if (result.manifestExists && !result.manifestValid)
27913
- console.error(" manifest invalid");
27914
- if (!result.binaryExists)
27915
- console.error(" runtime-host binary not found");
27916
- if (result.binaryExists && !result.binaryExecutable)
27917
- console.error(" runtime-host binary not executable");
27918
- if (result.allowedOriginsCount === 0)
27919
- console.error(" no allowed_origins in manifest");
27920
- if (!result.caTrusted) console.error(" device-local CA not trusted");
27921
- for (const reason of result.reasons) {
27922
- if (reason === "manifest-not-found")
27923
- console.error(
27924
- " Hint: run 'rogatio runtime install --extension-id <id>' to register the host manifest"
27925
- );
27926
- if (reason === "binary-not-found")
27927
- console.error(
27928
- ` Hint: ensure the runtime-host binary exists at the path specified in the manifest`
27929
- );
27930
- if (reason === "binary-not-executable")
27931
- console.error(
27932
- " Hint: ensure the runtime-host binary has execute permission (chmod +x)"
27933
- );
27934
- if (reason === "no-allowed-origins")
27935
- console.error(
27936
- " Hint: re-run 'rogatio runtime install --extension-id <id>' to pin your extension ID"
27937
- );
27938
- if (reason === "ca-not-trusted")
27939
- console.error(
27940
- " Hint: re-run 'rogatio runtime install --extension-id <id>' with elevated privileges to trust the CA"
27941
- );
27942
- }
27943
- return 1;
28034
+ return {
28035
+ ok: false,
28036
+ reason: "The host value is not a literal hostname or hostname:port."
28037
+ };
28038
+ }
28039
+ if (operator === "Contains") {
28040
+ const body = value.length === 0 ? "[^/?#]*" : `[^/?#]*${escapeRegex(value)}[^/?#]*`;
28041
+ return finishUrl(`^https?:\\/\\/${body}(?:[/?#].*)?$`, [
28042
+ SOURCE_CHANGE.hostAsUrl
28043
+ ]);
28044
+ }
28045
+ if (operator === "Wildcard_Matches") {
28046
+ return finishUrl(`^https?:\\/\\/${wildcardBody(value)}(?:[/?#].*)?$`, [
28047
+ SOURCE_CHANGE.hostAsUrl
28048
+ ]);
28049
+ }
28050
+ if (operator === "Matches") {
28051
+ const parsed = parseRequestlyRegex(value);
28052
+ if (!parsed.ok) return parsed;
28053
+ return finishUrl(
28054
+ `^https?:\\/\\/(?:${parsed.source})(?:[/?#].*)?$`,
28055
+ parsed.caseInsensitive ? [SOURCE_CHANGE.hostAsUrl, SOURCE_CHANGE.caseInsensitive] : [SOURCE_CHANGE.hostAsUrl]
28056
+ );
28057
+ }
28058
+ return { ok: false, reason: `Unsupported source operator "${operator}".` };
28059
+ }
28060
+ function convertPath(operator, value) {
28061
+ const changes = [SOURCE_CHANGE.path];
28062
+ if (operator === "Equals") {
28063
+ if (value === "/" || value.length === 0) {
28064
+ return finishUrl("^https?:\\/\\/[^/?#]+\\/?(?:[?#].*)?$", changes);
27944
28065
  }
27945
- default:
27946
- console.error(`Error: unknown runtime subcommand: ${subcommand ?? ""}`);
27947
- showRuntimeHelp();
27948
- return 2;
28066
+ return finishUrl(
28067
+ `^https?:\\/\\/[^/?#]+${escapeRegex(value)}(?:[?#].*)?$`,
28068
+ changes
28069
+ );
28070
+ }
28071
+ if (operator === "Contains") {
28072
+ const body = value.length === 0 ? "[^?#]*" : `[^?#]*${escapeRegex(value)}[^?#]*`;
28073
+ return finishUrl(`^https?:\\/\\/[^/?#]+\\/${body}(?:[?#].*)?$`, changes);
28074
+ }
28075
+ if (operator === "Wildcard_Matches") {
28076
+ return finishUrl(
28077
+ `^https?:\\/\\/[^/?#]+${wildcardBody(value)}(?:[?#].*)?$`,
28078
+ changes
28079
+ );
27949
28080
  }
28081
+ if (operator === "Matches") {
28082
+ const parsed = parseRequestlyRegex(value);
28083
+ if (!parsed.ok) return parsed;
28084
+ const inner = stripAnchors(parsed.source);
28085
+ if (parsed.caseInsensitive) changes.push(SOURCE_CHANGE.caseInsensitive);
28086
+ return finishUrl(`^https?:\\/\\/[^/?#]+(?:${inner})(?:[?#].*)?$`, changes);
28087
+ }
28088
+ return { ok: false, reason: `Unsupported source operator "${operator}".` };
28089
+ }
28090
+ function finishMatches(value) {
28091
+ const parsed = parseRequestlyRegex(value);
28092
+ if (!parsed.ok) return parsed;
28093
+ return finishUrl(
28094
+ parsed.source,
28095
+ parsed.caseInsensitive ? [SOURCE_CHANGE.caseInsensitive] : []
28096
+ );
27950
28097
  }
27951
- async function runtimeHostCommand(args, options = {}) {
27952
- let root;
27953
- let mockPort;
27954
- const positional = [];
27955
- let argumentError;
27956
- for (let index = 0; index < args.length; index += 1) {
27957
- const arg = args[index];
27958
- if (arg === "--root" && index + 1 < args.length) {
27959
- root = resolve5(args[++index]);
27960
- } else if (arg === "--root") {
27961
- argumentError = "--root requires a value";
27962
- } else if (arg === "--mock-port" && index + 1 < args.length) {
27963
- const parsed = Number(args[++index]);
27964
- if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
27965
- argumentError = "--mock-port must be an integer port in 1..65535";
27966
- } else {
28098
+ function finishUrl(pattern, changes) {
28099
+ return finish({ key: "url", operator: "regex", value: pattern }, changes);
28100
+ }
28101
+ function finish(source, changes) {
28102
+ const accepted = acceptRegex(source.value);
28103
+ if (!accepted.ok) return accepted;
28104
+ return { ok: true, converted: { source, changes } };
28105
+ }
28106
+ function parseRequestlyRegex(value) {
28107
+ const match = REQUESTLY_REGEX.exec(value);
28108
+ const source = match?.[1];
28109
+ if (match === null || source === void 0) {
28110
+ return {
28111
+ ok: false,
28112
+ reason: "A Matches value must be a /pattern/flags regular expression with flags i, g, or both."
28113
+ };
28114
+ }
28115
+ const flags = match[2] ?? "";
28116
+ try {
28117
+ new RegExp(source);
28118
+ } catch {
28119
+ return { ok: false, reason: "The source regular expression is invalid." };
28120
+ }
28121
+ return { ok: true, source, caseInsensitive: flags.includes("i") };
28122
+ }
28123
+ function wildcardBody(pattern) {
28124
+ return pattern.split("*").map((part) => escapeRegex(part)).join("(.*?)");
28125
+ }
28126
+ function isRequestlyRegexLiteral(value) {
28127
+ return REQUESTLY_REGEX.test(value);
28128
+ }
28129
+ function stripAnchors(pattern) {
28130
+ let next = pattern;
28131
+ if (next.startsWith("^")) next = next.slice(1);
28132
+ if (next.endsWith("$")) next = next.slice(0, -1);
28133
+ return next;
28134
+ }
28135
+ function isLiteralHostname(value) {
28136
+ return LITERAL_HOSTNAME.test(value);
28137
+ }
28138
+ function splitHostPort(value) {
28139
+ const index = value.lastIndexOf(":");
28140
+ if (index <= 0) return null;
28141
+ const host = value.slice(0, index);
28142
+ const port = value.slice(index + 1);
28143
+ if (!/^[0-9]+$/.test(port) || host.includes(":") || !isLiteralHostname(host)) {
28144
+ return null;
28145
+ }
28146
+ return { host, port };
28147
+ }
28148
+ function isActive(value) {
28149
+ if (Array.isArray(value)) return value.length > 0;
28150
+ if (isRecord3(value)) return Object.keys(value).length > 0;
28151
+ if (typeof value === "string") return value.length > 0;
28152
+ return value !== void 0 && value !== null;
28153
+ }
28154
+ function readResourceTypes(value) {
28155
+ if (!Array.isArray(value)) {
28156
+ return { ok: false, reason: "The resource type filter is not a list." };
28157
+ }
28158
+ if (value.length === 0) {
28159
+ return {
28160
+ ok: false,
28161
+ reason: "The resource type filter matches no supported type."
28162
+ };
28163
+ }
28164
+ const known = [];
28165
+ const unknown = [];
28166
+ for (const entry of value) {
28167
+ if (typeof entry !== "string") continue;
28168
+ if (RESOURCE_SET.has(entry)) known.push(entry);
28169
+ else unknown.push(entry);
28170
+ }
28171
+ const ordered = RESOURCE_TYPES.filter((type) => known.includes(type));
28172
+ const changes = unknown.length > 0 ? [`Dropped unrecognized resource types: ${unknown.join(", ")}.`] : [];
28173
+ return { ok: true, values: ordered, changes };
28174
+ }
28175
+ function readMethods(value) {
28176
+ if (!Array.isArray(value)) {
28177
+ return { ok: false, reason: "The request method filter is not a list." };
28178
+ }
28179
+ if (value.length === 0) {
28180
+ return {
28181
+ ok: false,
28182
+ reason: "The request method filter matches no supported method."
28183
+ };
28184
+ }
28185
+ const known = [];
28186
+ const unknown = [];
28187
+ for (const entry of value) {
28188
+ if (typeof entry !== "string") continue;
28189
+ if (METHOD_SET.has(entry)) known.push(entry);
28190
+ else unknown.push(entry);
28191
+ }
28192
+ const ordered = HTTP_METHODS.filter((method) => known.includes(method));
28193
+ const changes = unknown.length > 0 ? [`Dropped unrecognized request methods: ${unknown.join(", ")}.`] : [];
28194
+ return { ok: true, values: ordered, changes };
28195
+ }
28196
+ function intersect(current, next) {
28197
+ if (current === void 0) return [...next];
28198
+ const allowed = new Set(next);
28199
+ return current.filter((entry) => allowed.has(entry));
28200
+ }
28201
+ var BASE_CHANGE = "Replace was imported as a redirect that substitutes the first literal occurrence.";
28202
+ function mapReplace(source, from, to) {
28203
+ if (from.length === 0) {
28204
+ return { ok: false, reason: "The replace text is empty." };
28205
+ }
28206
+ if (isRequestlyRegexLiteral(from)) {
28207
+ return {
28208
+ ok: false,
28209
+ reason: "A regular-expression replace cannot be represented as one redirect destination."
28210
+ };
28211
+ }
28212
+ if (to.includes("\\")) {
28213
+ return {
28214
+ ok: false,
28215
+ reason: "The replacement contains a backslash, which Rogatio would read as a capture reference."
28216
+ };
28217
+ }
28218
+ const key = ownString3(source, "key");
28219
+ const operator = ownString3(source, "operator");
28220
+ const value = ownString3(source, "value");
28221
+ if (key === void 0 || operator === void 0 || value === void 0) {
28222
+ return { ok: false, reason: "The source condition is missing." };
28223
+ }
28224
+ if (operator === "Matches") {
28225
+ return {
28226
+ ok: false,
28227
+ reason: "Replace combined with a Matches source cannot be represented as one redirect destination."
28228
+ };
28229
+ }
28230
+ const changes = [BASE_CHANGE];
28231
+ if (to.includes("$")) {
28232
+ changes.push(
28233
+ "A literal $ in the replacement was escaped so it is not read as a capture reference."
28234
+ );
28235
+ }
28236
+ const replacement = escapeDollars(to);
28237
+ if (key === "Url" && operator === "Equals") {
28238
+ return mapEquals(value, from, replacement, changes);
28239
+ }
28240
+ if (key === "Url" && operator === "Wildcard_Matches") {
28241
+ return mapWildcard(value, from, replacement, changes);
28242
+ }
28243
+ if (key === "Url" && operator === "Contains") {
28244
+ return mapContains(value, from, replacement, changes);
28245
+ }
28246
+ if (key === "host" && operator === "Equals") {
28247
+ return mapHostEquals(value, from, replacement, changes);
28248
+ }
28249
+ return {
28250
+ ok: false,
28251
+ reason: `Replace is not supported for source ${key} ${operator}.`
28252
+ };
28253
+ }
28254
+ function mapEquals(url, from, to, changes) {
28255
+ if (!isAbsoluteHttpUrl(url)) {
28256
+ return {
28257
+ ok: false,
28258
+ reason: "The exact URL is not an absolute http(s) URL."
28259
+ };
28260
+ }
28261
+ const index = url.indexOf(from);
28262
+ if (index < 0) {
28263
+ return {
28264
+ ok: false,
28265
+ reason: "The URL does not contain the text to replace."
28266
+ };
28267
+ }
28268
+ const destination = escapeDollars(
28269
+ url.slice(0, index) + unescapeDollars(to) + url.slice(index + from.length)
28270
+ );
28271
+ const pattern = `^${escapeRegex(url)}$`;
28272
+ const redirect = acceptRedirect(pattern, destination, "");
28273
+ if (redirect === null) {
28274
+ return {
28275
+ ok: false,
28276
+ reason: "The replacement does not produce an absolute http(s) redirect destination."
28277
+ };
28278
+ }
28279
+ return { ok: true, redirects: [redirect], changes };
28280
+ }
28281
+ function mapWildcard(pattern, from, to, changes) {
28282
+ if (!pattern.startsWith("http://") && !pattern.startsWith("https://")) {
28283
+ return {
28284
+ ok: false,
28285
+ reason: "A wildcard replace must start with an absolute http(s) URL."
28286
+ };
28287
+ }
28288
+ const pieces = pattern.split("*");
28289
+ if (pieces.length - 1 > LIMITS.maxCaptureGroups) {
28290
+ return {
28291
+ ok: false,
28292
+ reason: "The wildcard has more than 9 captures."
28293
+ };
28294
+ }
28295
+ const replaced = replaceFirstLiteral(pieces, from, unescapeDollars(to));
28296
+ if (replaced === null) {
28297
+ return {
28298
+ ok: false,
28299
+ reason: "The text to replace is not a fixed part of the URL."
28300
+ };
28301
+ }
28302
+ const source = `^${pieces.map((part) => escapeRegex(part)).join("(.*?)")}$`;
28303
+ const destination = templateFromPieces(replaced);
28304
+ const redirect = acceptRedirect(source, destination, "");
28305
+ if (redirect === null) {
28306
+ return {
28307
+ ok: false,
28308
+ reason: "The replacement does not produce an absolute http(s) redirect destination."
28309
+ };
28310
+ }
28311
+ return { ok: true, redirects: [redirect], changes };
28312
+ }
28313
+ function mapContains(contains, from, to, changes) {
28314
+ changes.push("Separate http and https rules were created.");
28315
+ const lookahead = contains.length > 0 && contains !== from ? `(?=.*${escapeRegex(contains)})` : "";
28316
+ const redirects = [];
28317
+ for (const scheme of ["https", "http"]) {
28318
+ const pattern = `^${lookahead}${scheme}://(.*?)${escapeRegex(from)}(.*)$`;
28319
+ const destination = `${scheme}://\\1${to}\\2`;
28320
+ const redirect = acceptRedirect(pattern, destination, scheme);
28321
+ if (redirect !== null) redirects.push(redirect);
28322
+ }
28323
+ if (redirects.length === 0) {
28324
+ return {
28325
+ ok: false,
28326
+ reason: "The replacement does not produce an absolute http(s) redirect destination."
28327
+ };
28328
+ }
28329
+ return { ok: true, redirects, changes };
28330
+ }
28331
+ function mapHostEquals(host, from, to, changes) {
28332
+ if (host.includes("/") || host.includes("?") || host.includes("#")) {
28333
+ return {
28334
+ ok: false,
28335
+ reason: "The host value is not a literal hostname or hostname:port."
28336
+ };
28337
+ }
28338
+ changes.push("Separate http and https rules were created.");
28339
+ const redirects = [];
28340
+ const schemes = [
28341
+ { scheme: "https", port: host.includes(":") ? "" : "(?::443)?" },
28342
+ { scheme: "http", port: host.includes(":") ? "" : "(?::80)?" }
28343
+ ];
28344
+ for (const entry of schemes) {
28345
+ const prefix = `${entry.scheme}://${host}`;
28346
+ const index = prefix.indexOf(from);
28347
+ if (index < 0) continue;
28348
+ const nextPrefix = prefix.slice(0, index) + unescapeDollars(to) + prefix.slice(index + from.length);
28349
+ const pattern = `^${escapeRegex(entry.scheme)}:\\/\\/${escapeRegex(host)}${entry.port}((?:[/?#].*)?)$`;
28350
+ const destination = `${escapeDollars(nextPrefix)}\\1`;
28351
+ const redirect = acceptRedirect(pattern, destination, entry.scheme);
28352
+ if (redirect !== null) redirects.push(redirect);
28353
+ }
28354
+ if (redirects.length === 0) {
28355
+ return {
28356
+ ok: false,
28357
+ reason: "The text to replace is not a fixed part of the host."
28358
+ };
28359
+ }
28360
+ return { ok: true, redirects, changes };
28361
+ }
28362
+ function acceptRedirect(pattern, destination, nameSuffix) {
28363
+ if (!acceptRegex(pattern).ok) return null;
28364
+ if (destination.length === 0 || destination.length > LIMITS.maxRedirectDestinationLength) {
28365
+ return null;
28366
+ }
28367
+ if (validateRedirectDestination(destination, pattern).length > 0) return null;
28368
+ return {
28369
+ source: { key: "url", operator: "regex", value: pattern },
28370
+ destination,
28371
+ nameSuffix
28372
+ };
28373
+ }
28374
+ function replaceFirstLiteral(pieces, from, to) {
28375
+ const next = [...pieces];
28376
+ for (let index = 0; index < next.length; index += 1) {
28377
+ const piece = next[index] ?? "";
28378
+ const at = piece.indexOf(from);
28379
+ if (at < 0) continue;
28380
+ next[index] = piece.slice(0, at) + to + piece.slice(at + from.length);
28381
+ return next;
28382
+ }
28383
+ return null;
28384
+ }
28385
+ function templateFromPieces(pieces) {
28386
+ let template = "";
28387
+ for (let index = 0; index < pieces.length; index += 1) {
28388
+ template += escapeDollars(pieces[index] ?? "");
28389
+ if (index < pieces.length - 1) template += `\\${index + 1}`;
28390
+ }
28391
+ return template;
28392
+ }
28393
+ function isAbsoluteHttpUrl(value) {
28394
+ try {
28395
+ const url = new URL(value);
28396
+ return (url.protocol === "http:" || url.protocol === "https:") && url.username.length === 0 && url.password.length === 0 && url.hostname.length > 0 && !url.hostname.includes("*");
28397
+ } catch {
28398
+ return false;
28399
+ }
28400
+ }
28401
+ function escapeDollars(value) {
28402
+ return value.replaceAll("$", "$$");
28403
+ }
28404
+ function unescapeDollars(value) {
28405
+ return value.replaceAll("$$", "$");
28406
+ }
28407
+ var REQUEST_BODY_METHODS = ["POST", "PUT", "PATCH"];
28408
+ var DYNAMIC_FUNCTION = /rq_[A-Za-z0-9_]*\s*\(/;
28409
+ function mapRequestlyRule(rule) {
28410
+ const ruleType = ownString3(rule, "ruleType") ?? "";
28411
+ if (ruleType === "Cancel") {
28412
+ return { status: "skipped", reason: "Cancel rules are not supported." };
28413
+ }
28414
+ if (ruleType === "Delay") {
28415
+ return { status: "skipped", reason: "Delay rules are not supported." };
28416
+ }
28417
+ if (ruleType === "Script") {
28418
+ return { status: "skipped", reason: "Script rules are not supported." };
28419
+ }
28420
+ if (ruleType === "Redirect") return mapRedirect(rule);
28421
+ if (ruleType === "Replace") return mapReplaceRule(rule);
28422
+ if (ruleType === "QueryParam") return mapQuery(rule);
28423
+ if (ruleType === "Headers") return mapHeaders(rule);
28424
+ if (ruleType === "UserAgent") return mapUserAgent(rule);
28425
+ if (ruleType === "Request") return mapRequest(rule);
28426
+ if (ruleType === "Response") return mapResponse(rule);
28427
+ if (ruleType.length === 0) {
28428
+ return { status: "skipped", reason: "The rule type is missing." };
28429
+ }
28430
+ return {
28431
+ status: "skipped",
28432
+ reason: `Unsupported rule type "${ruleType}".`
28433
+ };
28434
+ }
28435
+ function mapRedirect(rule) {
28436
+ const pairs = readPairs(rule);
28437
+ if (!pairs.ok) return { status: "skipped", reason: pairs.reason };
28438
+ const drafts = [];
28439
+ const changes = [];
28440
+ const failures = [];
28441
+ if (ownBoolean(rule, "preserveCookie") === true) {
28442
+ changes.push("preserveCookie was not imported.");
28443
+ }
28444
+ for (const pair of pairs.pairs) {
28445
+ const built = buildRedirectPair(pair);
28446
+ if (!built.ok) {
28447
+ failures.push(built.reason);
28448
+ continue;
28449
+ }
28450
+ changes.push(...built.changes);
28451
+ drafts.push(...built.drafts);
28452
+ }
28453
+ if (drafts.length === 0) {
28454
+ return {
28455
+ status: "skipped",
28456
+ reason: failures[0] ?? "The redirect rule has no supported pairs."
28457
+ };
28458
+ }
28459
+ if (pairs.pairs.length > 1) {
28460
+ changes.unshift(
28461
+ "Redirect pairs were split into separate rules. Requestly applies pairs in sequence to the rewritten URL; each imported rule matches the original request."
28462
+ );
28463
+ }
28464
+ return { status: "mapped", drafts: tagIds(drafts), changes: dedupe(changes) };
28465
+ }
28466
+ function buildRedirectPair(pair) {
28467
+ const destinationType = ownString3(pair, "destinationType") ?? "url";
28468
+ if (destinationType === "map_local" || destinationType === "mock_or_file_picker") {
28469
+ return {
28470
+ ok: false,
28471
+ reason: "A local-file or mock redirect destination is not supported."
28472
+ };
28473
+ }
28474
+ if (destinationType !== "url") {
28475
+ return {
28476
+ ok: false,
28477
+ reason: `Unsupported redirect destination type "${destinationType}".`
28478
+ };
28479
+ }
28480
+ const destination = ownString3(pair, "destination");
28481
+ if (destination === void 0 || destination.length === 0) {
28482
+ return { ok: false, reason: "The redirect destination is missing." };
28483
+ }
28484
+ const prepared = preparePair(pair);
28485
+ if (!prepared.ok) return prepared;
28486
+ const rewritten = rewriteDestination(destination);
28487
+ const issues = validateRedirectDestination(
28488
+ rewritten.destination,
28489
+ prepared.source.value
28490
+ );
28491
+ if (issues.length > 0) {
28492
+ return {
28493
+ ok: false,
28494
+ reason: "The redirect destination is not an absolute http(s) URL. Capture groups cannot stand in for the scheme or host."
28495
+ };
28496
+ }
28497
+ const applied = applyMethods(prepared, {
28498
+ source: prepared.base.source,
28499
+ resourceTypes: prepared.base.resourceTypes,
28500
+ type: "redirect",
28501
+ redirect: { destination: rewritten.destination }
28502
+ });
28503
+ const changes = [
28504
+ ...prepared.changes,
28505
+ ...rewritten.changes,
28506
+ ...applied.changes
28507
+ ];
28508
+ noteDynamic(destination, changes);
28509
+ return { ok: true, changes, drafts: applied.drafts };
28510
+ }
28511
+ function mapReplaceRule(rule) {
28512
+ const pairs = readPairs(rule);
28513
+ if (!pairs.ok) return { status: "skipped", reason: pairs.reason };
28514
+ const drafts = [];
28515
+ const changes = [];
28516
+ const failures = [];
28517
+ for (const pair of pairs.pairs) {
28518
+ const source = pairSource(pair);
28519
+ if (!source.ok) {
28520
+ failures.push(source.reason);
28521
+ continue;
28522
+ }
28523
+ const from = ownString3(pair, "from");
28524
+ const to = ownString3(pair, "to");
28525
+ if (from === void 0 || to === void 0) {
28526
+ failures.push("The replace pair is missing from or to.");
28527
+ continue;
28528
+ }
28529
+ const replaced = mapReplace(source.source, from, to);
28530
+ if (!replaced.ok) {
28531
+ failures.push(replaced.reason);
28532
+ continue;
28533
+ }
28534
+ const prepared = preparePair(pair);
28535
+ if (!prepared.ok) {
28536
+ failures.push(prepared.reason);
28537
+ continue;
28538
+ }
28539
+ changes.push(...prepared.filterChanges, ...replaced.changes);
28540
+ for (const redirect of replaced.redirects) {
28541
+ const applied = applyMethods(
28542
+ prepared,
28543
+ {
28544
+ source: redirect.source,
28545
+ resourceTypes: prepared.base.resourceTypes,
28546
+ type: "redirect",
28547
+ redirect: { destination: redirect.destination }
28548
+ },
28549
+ redirect.nameSuffix
28550
+ );
28551
+ changes.push(...applied.changes);
28552
+ drafts.push(...applied.drafts);
28553
+ }
28554
+ }
28555
+ if (drafts.length === 0) {
28556
+ return {
28557
+ status: "skipped",
28558
+ reason: failures[0] ?? "The replace rule has no supported pairs."
28559
+ };
28560
+ }
28561
+ if (pairs.pairs.length > 1) {
28562
+ changes.unshift(
28563
+ "Replace pairs were split into separate rules. Requestly applies pairs in sequence to the rewritten URL."
28564
+ );
28565
+ }
28566
+ return { status: "mapped", drafts: tagIds(drafts), changes: dedupe(changes) };
28567
+ }
28568
+ function mapQuery(rule) {
28569
+ const pairs = readPairs(rule);
28570
+ if (!pairs.ok) return { status: "skipped", reason: pairs.reason };
28571
+ const drafts = [];
28572
+ const changes = [];
28573
+ const failures = [];
28574
+ for (const pair of pairs.pairs) {
28575
+ const prepared = preparePair(pair);
28576
+ if (!prepared.ok) {
28577
+ failures.push(prepared.reason);
28578
+ continue;
28579
+ }
28580
+ const mods = readModificationList(pair);
28581
+ if (!mods.ok) {
28582
+ failures.push(mods.reason);
28583
+ continue;
28584
+ }
28585
+ if (mods.mods.some((mod) => ownString3(mod, "type") === "Remove All")) {
28586
+ failures.push("Remove All query parameters is not supported.");
28587
+ continue;
28588
+ }
28589
+ const params = [];
28590
+ const seen = /* @__PURE__ */ new Set();
28591
+ const pairFailures = [];
28592
+ let droppedDuplicate = false;
28593
+ for (const mod of mods.mods) {
28594
+ const type = ownString3(mod, "type");
28595
+ const name = ownString3(mod, "param");
28596
+ if (name === void 0 || name.length === 0 || name.length > LIMITS.maxQueryNameLength) {
28597
+ pairFailures.push("A query parameter name is missing or too long.");
28598
+ continue;
28599
+ }
28600
+ if (type === "Remove") {
28601
+ if (seen.has(name)) droppedDuplicate = true;
28602
+ seen.add(name);
28603
+ params.push({ name, operation: "remove" });
28604
+ continue;
28605
+ }
28606
+ if (type !== "Add") {
28607
+ pairFailures.push(
28608
+ type === void 0 ? "A query modification type is missing." : `Unsupported query modification "${type}".`
28609
+ );
28610
+ continue;
28611
+ }
28612
+ const value = ownString3(mod, "value") ?? "";
28613
+ if (value.length === 0 || value.length > LIMITS.maxQueryValueLength) {
28614
+ pairFailures.push("A query parameter value is missing or too long.");
28615
+ continue;
28616
+ }
28617
+ if (hasLoneSurrogate(value)) {
28618
+ pairFailures.push(
28619
+ "A query parameter value contains an invalid character."
28620
+ );
28621
+ continue;
28622
+ }
28623
+ const action = ownString3(mod, "actionWhenParamExists");
28624
+ if (action === "Ignore") {
28625
+ changes.push(
28626
+ "Add with Ignore was imported as set, which overwrites an existing parameter. Requestly leaves an existing parameter unchanged."
28627
+ );
28628
+ } else if (action !== "Overwrite") {
28629
+ changes.push(
28630
+ "Add was imported as set, which overwrites an existing parameter instead of appending another value."
28631
+ );
28632
+ }
28633
+ const escaped = escapeDollars2(value);
28634
+ if (escaped !== value) {
28635
+ changes.push(
28636
+ "A literal $ in a query value was escaped so it is not read as a capture reference."
28637
+ );
28638
+ }
28639
+ noteDynamic(value, changes);
28640
+ if (seen.has(name)) droppedDuplicate = true;
28641
+ seen.add(name);
28642
+ params.push({ name, operation: "set", value: escaped });
28643
+ }
28644
+ if (droppedDuplicate) {
28645
+ changes.push(
28646
+ "An earlier modification of the same query parameter was dropped."
28647
+ );
28648
+ }
28649
+ const deduped = lastParamWins(params);
28650
+ if (deduped.length === 0) continue;
28651
+ const limited = deduped.slice(0, LIMITS.maxQueryParamsPerRule);
28652
+ if (limited.length < deduped.length) {
28653
+ changes.push(
28654
+ `Query parameters after the first ${LIMITS.maxQueryParamsPerRule} were dropped.`
28655
+ );
28656
+ }
28657
+ if (limited.length === 0) {
28658
+ failures.push(
28659
+ ...pairFailures,
28660
+ "The query rule has no supported modifications."
28661
+ );
28662
+ continue;
28663
+ }
28664
+ for (const failure2 of pairFailures) {
28665
+ changes.push(`Dropped a query modification: ${failure2}`);
28666
+ }
28667
+ const applied = applyMethods(prepared, {
28668
+ source: prepared.base.source,
28669
+ resourceTypes: prepared.base.resourceTypes,
28670
+ type: "query",
28671
+ action: { type: "query", params: limited }
28672
+ });
28673
+ changes.push(...prepared.changes, ...applied.changes);
28674
+ drafts.push(...applied.drafts);
28675
+ }
28676
+ if (drafts.length === 0) {
28677
+ return {
28678
+ status: "skipped",
28679
+ reason: failures[0] ?? "The query rule has no supported modifications."
28680
+ };
28681
+ }
28682
+ if (drafts.length > 1) {
28683
+ changes.unshift(
28684
+ "Query pairs were split into separate rules. Requestly applies every matching pair."
28685
+ );
28686
+ }
28687
+ return { status: "mapped", drafts: tagIds(drafts), changes: dedupe(changes) };
28688
+ }
28689
+ function mapHeaders(rule) {
28690
+ const pairs = readPairs(rule);
28691
+ if (!pairs.ok) return { status: "skipped", reason: pairs.reason };
28692
+ const drafts = [];
28693
+ const changes = [];
28694
+ const failures = [];
28695
+ for (const pair of pairs.pairs) {
28696
+ const prepared = preparePair(pair);
28697
+ if (!prepared.ok) {
28698
+ failures.push(prepared.reason);
28699
+ continue;
28700
+ }
28701
+ const mods = headerModifications(pair);
28702
+ if (mods.length === 0) {
28703
+ failures.push("The header rule has no supported modifications.");
28704
+ continue;
28705
+ }
28706
+ const pairFailures = [];
28707
+ let produced = 0;
28708
+ for (const mod of mods) {
28709
+ const header = ownString3(mod.record, "header");
28710
+ const type = ownString3(mod.record, "type");
28711
+ if (header === void 0 || header.length === 0 || header.length > LIMITS.maxHeaderNameLength) {
28712
+ pairFailures.push("A header name is missing or too long.");
28713
+ continue;
28714
+ }
28715
+ if (isForbiddenHeader(header, mod.direction)) {
28716
+ pairFailures.push(
28717
+ `Header "${header}" is forbidden on ${mod.direction} headers.`
28718
+ );
28719
+ continue;
28720
+ }
28721
+ const operation = headerOperation(type);
28722
+ if (operation === null) {
28723
+ pairFailures.push(
28724
+ type === void 0 ? "A header modification type is missing." : `Unsupported header modification "${type}".`
28725
+ );
28726
+ continue;
28727
+ }
28728
+ if (operation.change !== void 0) changes.push(operation.change);
28729
+ const value = ownString3(mod.record, "value") ?? "";
28730
+ if (operation.kind !== "remove") {
28731
+ if (value.length > LIMITS.maxHeaderValueLength) {
28732
+ pairFailures.push("A header value exceeds the length limit.");
28733
+ continue;
28734
+ }
28735
+ if (hasLoneSurrogate(value)) {
28736
+ pairFailures.push("A header value contains an invalid character.");
28737
+ continue;
28738
+ }
28739
+ }
28740
+ const escaped = escapeDollars2(value);
28741
+ if (operation.kind !== "remove" && escaped !== value) {
28742
+ changes.push(
28743
+ "A literal $ in a header value was escaped so it is not read as a capture reference."
28744
+ );
28745
+ }
28746
+ if (operation.kind !== "remove") noteDynamic(value, changes);
28747
+ const suffix = `${mod.direction} ${header}`;
28748
+ const applied = applyMethods(
28749
+ prepared,
28750
+ {
28751
+ source: prepared.base.source,
28752
+ resourceTypes: prepared.base.resourceTypes,
28753
+ type: "header",
28754
+ headerDirection: mod.direction,
28755
+ headerOperation: operation.kind,
28756
+ headerName: header,
28757
+ ...operation.kind === "remove" ? {} : { headerValue: escaped }
28758
+ },
28759
+ suffix
28760
+ );
28761
+ changes.push(...applied.changes);
28762
+ drafts.push(...applied.drafts);
28763
+ produced += 1;
28764
+ }
28765
+ if (produced > 0) {
28766
+ changes.push(...prepared.changes);
28767
+ for (const failure2 of pairFailures) {
28768
+ changes.push(`Dropped a header modification: ${failure2}`);
28769
+ }
28770
+ } else {
28771
+ failures.push(...pairFailures);
28772
+ }
28773
+ if (produced > 1) {
28774
+ changes.push("Header modifications were split into one rule per header.");
28775
+ }
28776
+ }
28777
+ if (drafts.length === 0) {
28778
+ return {
28779
+ status: "skipped",
28780
+ reason: failures[0] ?? "The header rule has no supported modifications."
28781
+ };
28782
+ }
28783
+ const tagged = tagIds(drafts);
28784
+ if (tagged.length === 1 && tagged[0] !== void 0) {
28785
+ return {
28786
+ status: "mapped",
28787
+ drafts: [{ ...tagged[0], nameSuffix: "", keepId: true }],
28788
+ changes: dedupe(changes)
28789
+ };
28790
+ }
28791
+ return { status: "mapped", drafts: tagged, changes: dedupe(changes) };
28792
+ }
28793
+ function mapUserAgent(rule) {
28794
+ const pairs = readPairs(rule);
28795
+ if (!pairs.ok) return { status: "skipped", reason: pairs.reason };
28796
+ const drafts = [];
28797
+ const changes = [];
28798
+ const failures = [];
28799
+ for (const pair of pairs.pairs) {
28800
+ const prepared = preparePair(pair);
28801
+ if (!prepared.ok) {
28802
+ failures.push(prepared.reason);
28803
+ continue;
28804
+ }
28805
+ const userAgent = ownString3(pair, "userAgent");
28806
+ if (userAgent === void 0 || userAgent.length === 0) {
28807
+ failures.push("The user-agent string is missing.");
28808
+ continue;
28809
+ }
28810
+ if (userAgent.length > LIMITS.maxHeaderValueLength || hasLoneSurrogate(userAgent)) {
28811
+ failures.push("The user-agent string is too long or invalid.");
28812
+ continue;
28813
+ }
28814
+ if (isForbiddenHeader("User-Agent", "request")) {
28815
+ failures.push('Header "User-Agent" is forbidden on request headers.');
28816
+ continue;
28817
+ }
28818
+ const envType = ownString3(pair, "envType");
28819
+ if (envType === "browser" || envType === "device") {
28820
+ changes.push(
28821
+ "The browser or device preset was imported as the stored user-agent string."
28822
+ );
28823
+ }
28824
+ const escaped = escapeDollars2(userAgent);
28825
+ if (escaped !== userAgent) {
28826
+ changes.push(
28827
+ "A literal $ in the user-agent was escaped so it is not read as a capture reference."
28828
+ );
28829
+ }
28830
+ noteDynamic(userAgent, changes);
28831
+ const applied = applyMethods(prepared, {
28832
+ source: prepared.base.source,
28833
+ resourceTypes: prepared.base.resourceTypes,
28834
+ type: "header",
28835
+ headerDirection: "request",
28836
+ headerOperation: "set",
28837
+ headerName: "User-Agent",
28838
+ headerValue: escaped
28839
+ });
28840
+ changes.push(...prepared.changes, ...applied.changes);
28841
+ drafts.push(...applied.drafts);
28842
+ }
28843
+ if (drafts.length === 0) {
28844
+ return {
28845
+ status: "skipped",
28846
+ reason: failures[0] ?? "The user-agent rule has no supported pairs."
28847
+ };
28848
+ }
28849
+ if (drafts.length > 1) {
28850
+ changes.unshift("User-agent pairs were split into separate rules.");
28851
+ }
28852
+ return { status: "mapped", drafts: tagIds(drafts), changes: dedupe(changes) };
28853
+ }
28854
+ function mapRequest(rule) {
28855
+ return mapBody(rule, "request");
28856
+ }
28857
+ function mapResponse(rule) {
28858
+ return mapBody(rule, "response");
28859
+ }
28860
+ function mapBody(rule, kind) {
28861
+ const pairs = readPairs(rule);
28862
+ if (!pairs.ok) return { status: "skipped", reason: pairs.reason };
28863
+ const drafts = [];
28864
+ const changes = [];
28865
+ const failures = [];
28866
+ for (const pair of pairs.pairs) {
28867
+ const prepared = preparePair(pair);
28868
+ if (!prepared.ok) {
28869
+ failures.push(prepared.reason);
28870
+ continue;
28871
+ }
28872
+ const bodyRecord = pair[kind];
28873
+ if (!isRecord3(bodyRecord)) {
28874
+ failures.push(`The ${kind} body is missing.`);
28875
+ continue;
28876
+ }
28877
+ const type = ownString3(bodyRecord, "type");
28878
+ if (type === "code") {
28879
+ failures.push(`JavaScript ${kind} functions are not supported.`);
28880
+ continue;
28881
+ }
28882
+ if (type === "local_file") {
28883
+ failures.push("A local-file response is not supported.");
28884
+ continue;
28885
+ }
28886
+ if (type !== "static") {
28887
+ failures.push(
28888
+ type === void 0 ? `The ${kind} body type is missing.` : `Unsupported ${kind} body type "${type}".`
28889
+ );
28890
+ continue;
28891
+ }
28892
+ const value = ownString3(bodyRecord, "value") ?? "";
28893
+ const limit = kind === "request" ? LIMITS.maxRequestBodyBytes : LIMITS.maxResponseBodyBytes;
28894
+ if (value.length === 0 || value.length > limit || hasLoneSurrogate(value)) {
28895
+ failures.push(`The ${kind} body is empty, too large, or invalid.`);
28896
+ continue;
28897
+ }
28898
+ const escaped = escapeDollars2(value);
28899
+ if (escaped !== value) {
28900
+ changes.push(
28901
+ "A literal $ in the body was escaped so it is not read as a capture reference."
28902
+ );
28903
+ }
28904
+ noteDynamic(value, changes);
28905
+ if (kind === "response") {
28906
+ const status = ownString3(bodyRecord, "statusCode");
28907
+ const statusNote = status !== void 0 && status.length > 0 ? ` Status ${status} was not imported.` : "";
28908
+ changes.push(
28909
+ `Requestly static responses replace the response and may skip the upstream request.${statusNote} Rogatio fetches the upstream response and replaces its body, keeping the upstream status and headers.`
28910
+ );
28911
+ const applied = applyMethods(prepared, {
28912
+ source: prepared.base.source,
28913
+ resourceTypes: prepared.base.resourceTypes,
28914
+ type: "response-body",
28915
+ responseBody: { mode: "replace", body: escaped }
28916
+ });
28917
+ changes.push(...prepared.changes, ...applied.changes);
28918
+ drafts.push(...applied.drafts);
28919
+ continue;
28920
+ }
28921
+ const narrowed = narrowRequestBody(
28922
+ prepared.filters,
28923
+ prepared.base.resourceTypes
28924
+ );
28925
+ if (!narrowed.ok) {
28926
+ failures.push(narrowed.reason);
28927
+ continue;
28928
+ }
28929
+ changes.push(...prepared.changes, ...narrowed.changes);
28930
+ for (const method of narrowed.methods) {
28931
+ drafts.push(
28932
+ draftOf(
28933
+ {
28934
+ source: prepared.base.source,
28935
+ resourceTypes: ["xmlhttprequest"],
28936
+ method,
28937
+ type: "request-body",
28938
+ requestBody: { mode: "replace", body: escaped }
28939
+ },
28940
+ narrowed.methods.length > 1 ? method : ""
28941
+ )
28942
+ );
28943
+ }
28944
+ }
28945
+ if (drafts.length === 0) {
28946
+ return {
28947
+ status: "skipped",
28948
+ reason: failures[0] ?? `The ${kind} rule has no supported pairs.`
28949
+ };
28950
+ }
28951
+ if (kind === "response" && drafts.length > 1) {
28952
+ changes.unshift(
28953
+ "Response pairs were split into separate rules. Requestly runs the first pair only."
28954
+ );
28955
+ }
28956
+ return { status: "mapped", drafts: tagIds(drafts), changes: dedupe(changes) };
28957
+ }
28958
+ function preparePair(pair) {
28959
+ const sourceRecord = pairSource(pair);
28960
+ if (!sourceRecord.ok) return sourceRecord;
28961
+ const converted = convertSource(sourceRecord.source);
28962
+ if (!converted.ok) return converted;
28963
+ const filters = readFilters(sourceRecord.source);
28964
+ if (!filters.ok) return filters;
28965
+ const method = filters.filters.methods !== "all" && filters.filters.methods.length === 1 ? filters.filters.methods[0] : void 0;
28966
+ const sourceChanges = converted.converted.changes;
28967
+ const filterChanges = filters.filters.changes;
28968
+ return {
28969
+ ok: true,
28970
+ source: converted.converted.source,
28971
+ filters: filters.filters,
28972
+ sourceChanges,
28973
+ filterChanges,
28974
+ changes: [...sourceChanges, ...filterChanges],
28975
+ base: {
28976
+ source: converted.converted.source,
28977
+ resourceTypes: [...filters.filters.resourceTypes],
28978
+ ...method === void 0 ? {} : { method }
28979
+ }
28980
+ };
28981
+ }
28982
+ function narrowRequestBody(filters, resourceTypes) {
28983
+ const changes = [];
28984
+ if (resourceTypes.length !== 1 || resourceTypes[0] !== "xmlhttprequest") {
28985
+ if (!resourceTypes.includes("xmlhttprequest")) {
28986
+ return {
28987
+ ok: false,
28988
+ reason: "Request-body rules only match xmlhttprequest, which this rule does not include."
28989
+ };
28990
+ }
28991
+ changes.push(
28992
+ "Request-body rules only match xmlhttprequest. Other resource types were dropped."
28993
+ );
28994
+ }
28995
+ const allowed = REQUEST_BODY_METHODS;
28996
+ if (filters.methods === "all") {
28997
+ changes.push(
28998
+ "The request had no method filter. It was split into POST, PUT, and PATCH, the methods Rogatio request-body rules allow."
28999
+ );
29000
+ return { ok: true, methods: [...allowed], changes };
29001
+ }
29002
+ const methods = allowed.filter((method) => filters.methods.includes(method));
29003
+ const dropped = filters.methods.filter(
29004
+ (method) => !allowed.includes(method)
29005
+ );
29006
+ if (methods.length === 0) {
29007
+ return {
29008
+ ok: false,
29009
+ reason: "Request-body rules only support POST, PUT, and PATCH."
29010
+ };
29011
+ }
29012
+ if (dropped.length > 0) {
29013
+ changes.push(
29014
+ `Dropped request methods other than POST, PUT, and PATCH: ${dropped.join(", ")}.`
29015
+ );
29016
+ }
29017
+ if (methods.length > 1) {
29018
+ changes.push(
29019
+ "The request was split into one rule per method because a request-body rule stores one method."
29020
+ );
29021
+ }
29022
+ return { ok: true, methods, changes };
29023
+ }
29024
+ function pairSource(pair) {
29025
+ const source = pair.source;
29026
+ if (!isRecord3(source)) {
29027
+ return { ok: false, reason: "The source condition is missing." };
29028
+ }
29029
+ return { ok: true, source };
29030
+ }
29031
+ function readPairs(rule) {
29032
+ const pairs = rule.pairs;
29033
+ if (!Array.isArray(pairs) || pairs.length === 0) {
29034
+ return { ok: false, reason: "The rule has no pairs." };
29035
+ }
29036
+ const records = [];
29037
+ for (const pair of pairs) {
29038
+ if (!isRecord3(pair)) {
29039
+ return { ok: false, reason: "A rule pair is not an object." };
29040
+ }
29041
+ records.push(pair);
29042
+ }
29043
+ return { ok: true, pairs: records };
29044
+ }
29045
+ function readModificationList(pair) {
29046
+ const modifications = pair.modifications;
29047
+ if (!Array.isArray(modifications) || modifications.length === 0) {
29048
+ return { ok: false, reason: "The query rule has no modifications." };
29049
+ }
29050
+ const mods = [];
29051
+ for (const mod of modifications) {
29052
+ if (!isRecord3(mod)) {
29053
+ return { ok: false, reason: "A query modification is not an object." };
29054
+ }
29055
+ mods.push(mod);
29056
+ }
29057
+ return { ok: true, mods };
29058
+ }
29059
+ function headerModifications(pair) {
29060
+ const modifications = pair.modifications;
29061
+ if (isRecord3(modifications)) {
29062
+ const found = [];
29063
+ for (const direction of ["Request", "Response"]) {
29064
+ const list = modifications[direction];
29065
+ if (!Array.isArray(list)) continue;
29066
+ for (const entry of list) {
29067
+ if (!isRecord3(entry)) continue;
29068
+ found.push({
29069
+ record: entry,
29070
+ direction: direction === "Request" ? "request" : "response"
29071
+ });
29072
+ }
29073
+ }
29074
+ if (found.length > 0) return found;
29075
+ }
29076
+ if (ownString3(pair, "header") !== void 0) {
29077
+ const target = ownString3(pair, "target");
29078
+ return [
29079
+ {
29080
+ record: pair,
29081
+ direction: target === "Response" ? "response" : "request"
29082
+ }
29083
+ ];
29084
+ }
29085
+ return [];
29086
+ }
29087
+ function headerOperation(type) {
29088
+ if (type === "Add") return { kind: "append" };
29089
+ if (type === "Remove") return { kind: "remove" };
29090
+ if (type === "Modify") {
29091
+ return {
29092
+ kind: "set",
29093
+ change: "Modify updates an existing header only. It was imported as set, which also adds the header when it is absent."
29094
+ };
29095
+ }
29096
+ if (type === "Replace") {
29097
+ return {
29098
+ kind: "set",
29099
+ change: "Header Replace was imported as set."
29100
+ };
29101
+ }
29102
+ return null;
29103
+ }
29104
+ function rewriteDestination(destination) {
29105
+ let output = "";
29106
+ let captures = false;
29107
+ let dollars = false;
29108
+ for (let index = 0; index < destination.length; index += 1) {
29109
+ const char = destination[index];
29110
+ if (char !== "$") {
29111
+ output += char ?? "";
29112
+ continue;
29113
+ }
29114
+ const next = destination[index + 1];
29115
+ if (next !== void 0 && next >= "1" && next <= "9") {
29116
+ output += `\\${next}`;
29117
+ captures = true;
29118
+ index += 1;
29119
+ continue;
29120
+ }
29121
+ output += "$$";
29122
+ dollars = true;
29123
+ }
29124
+ const changes = [];
29125
+ if (captures) {
29126
+ changes.push(
29127
+ "Redirect capture references were rewritten from $1\u2013$9 to \\1\u2013\\9."
29128
+ );
29129
+ }
29130
+ if (dollars) {
29131
+ changes.push(
29132
+ "A literal $ in the redirect destination was escaped so it is not read as a capture reference."
29133
+ );
29134
+ }
29135
+ return { destination: output, changes };
29136
+ }
29137
+ function lastParamWins(params) {
29138
+ const order = [];
29139
+ const byName = /* @__PURE__ */ new Map();
29140
+ for (const param of params) {
29141
+ if (!byName.has(param.name)) order.push(param.name);
29142
+ byName.set(param.name, param);
29143
+ }
29144
+ const result = [];
29145
+ for (const name of order) {
29146
+ const param = byName.get(name);
29147
+ if (param !== void 0) result.push(param);
29148
+ }
29149
+ return result;
29150
+ }
29151
+ function noteDynamic(value, changes) {
29152
+ if (!DYNAMIC_FUNCTION.test(value)) return;
29153
+ const message = "A Requestly dynamic function was kept as literal text and is not evaluated.";
29154
+ if (!changes.includes(message)) changes.push(message);
29155
+ }
29156
+ function escapeDollars2(value) {
29157
+ return value.replaceAll("$", "$$");
29158
+ }
29159
+ function draftOf(rule, nameSuffix = "") {
29160
+ return { nameSuffix, keepId: false, rule };
29161
+ }
29162
+ function tagIds(drafts) {
29163
+ return drafts.map((draft, index) => ({
29164
+ ...draft,
29165
+ keepId: index === 0
29166
+ }));
29167
+ }
29168
+ function applyMethods(prepared, rule, nameSuffix = "") {
29169
+ const methodFilter = prepared.filters.methods;
29170
+ if (methodFilter === "all" || methodFilter.length <= 1) {
29171
+ const method = prepared.base.method;
29172
+ return {
29173
+ changes: [],
29174
+ drafts: [
29175
+ draftOf(
29176
+ {
29177
+ ...rule,
29178
+ ...method === void 0 ? {} : { method }
29179
+ },
29180
+ nameSuffix
29181
+ )
29182
+ ]
29183
+ };
29184
+ }
29185
+ return {
29186
+ changes: [
29187
+ `Request methods were split into separate rules (${methodFilter.join(", ")}). Rogatio stores one method on a rule.`
29188
+ ],
29189
+ drafts: methodFilter.map(
29190
+ (method) => draftOf(
29191
+ { ...rule, method },
29192
+ nameSuffix.length > 0 ? `${nameSuffix} ${method}` : method
29193
+ )
29194
+ )
29195
+ };
29196
+ }
29197
+ function dedupe(changes) {
29198
+ const seen = /* @__PURE__ */ new Set();
29199
+ const result = [];
29200
+ for (const change of changes) {
29201
+ if (seen.has(change)) continue;
29202
+ seen.add(change);
29203
+ result.push(change);
29204
+ }
29205
+ return result;
29206
+ }
29207
+ var MAX_DEPTH = 40;
29208
+ var MAX_STRING = 5e6;
29209
+ var MAX_ARRAY = 1e5;
29210
+ var BANNED_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
29211
+ function snapshotJson(value) {
29212
+ return snapshot(value, /* @__PURE__ */ new WeakSet(), 0);
29213
+ }
29214
+ function snapshot(value, ancestors, depth) {
29215
+ if (typeof value === "string") {
29216
+ if (value.length > MAX_STRING) return tooLarge();
29217
+ return { ok: true, value };
29218
+ }
29219
+ if (typeof value === "number") {
29220
+ if (!Number.isFinite(value)) {
29221
+ return {
29222
+ ok: false,
29223
+ error: "Requestly export contains a non-finite number."
29224
+ };
29225
+ }
29226
+ return { ok: true, value };
29227
+ }
29228
+ if (typeof value === "boolean" || value === null) {
29229
+ return { ok: true, value };
29230
+ }
29231
+ if (value === void 0) return { ok: true, value: void 0 };
29232
+ if (typeof value !== "object") {
29233
+ return {
29234
+ ok: false,
29235
+ error: "Requestly export contains a value that is not JSON data."
29236
+ };
29237
+ }
29238
+ if (depth > MAX_DEPTH) {
29239
+ return { ok: false, error: "Requestly export is nested too deeply." };
29240
+ }
29241
+ if (ancestors.has(value)) {
29242
+ return { ok: false, error: "Requestly export contains a cycle." };
29243
+ }
29244
+ if (Object.getOwnPropertySymbols(value).length > 0) {
29245
+ return {
29246
+ ok: false,
29247
+ error: "Requestly export contains a value that is not JSON data."
29248
+ };
29249
+ }
29250
+ ancestors.add(value);
29251
+ try {
29252
+ if (Array.isArray(value)) return snapshotArray(value, ancestors, depth);
29253
+ return snapshotObject(value, ancestors, depth);
29254
+ } finally {
29255
+ ancestors.delete(value);
29256
+ }
29257
+ }
29258
+ function snapshotArray(value, ancestors, depth) {
29259
+ if (Object.getPrototypeOf(value) !== Array.prototype) return notJson();
29260
+ const length = Object.getOwnPropertyDescriptor(value, "length");
29261
+ if (length === void 0 || !("value" in length) || !Number.isSafeInteger(length.value) || length.value < 0 || length.value > MAX_ARRAY) {
29262
+ return tooLarge();
29263
+ }
29264
+ const result = [];
29265
+ for (let index = 0; index < length.value; index += 1) {
29266
+ const entry = Object.getOwnPropertyDescriptor(value, String(index));
29267
+ if (entry === void 0 || !("value" in entry) || !entry.enumerable) {
29268
+ return {
29269
+ ok: false,
29270
+ error: "Requestly export contains a sparse array."
29271
+ };
29272
+ }
29273
+ const child = snapshot(entry.value, ancestors, depth + 1);
29274
+ if (!child.ok) return child;
29275
+ result.push(child.value);
29276
+ }
29277
+ return { ok: true, value: result };
29278
+ }
29279
+ function snapshotObject(value, ancestors, depth) {
29280
+ const proto = Object.getPrototypeOf(value);
29281
+ if (proto !== Object.prototype && proto !== null) return notJson();
29282
+ const result = /* @__PURE__ */ Object.create(null);
29283
+ for (const key of Object.getOwnPropertyNames(value)) {
29284
+ if (BANNED_KEYS.has(key)) continue;
29285
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
29286
+ if (descriptor === void 0 || !("value" in descriptor) || !descriptor.enumerable) {
29287
+ return {
29288
+ ok: false,
29289
+ error: "Requestly export contains a value that is not JSON data."
29290
+ };
29291
+ }
29292
+ if (descriptor.value === void 0) continue;
29293
+ const child = snapshot(descriptor.value, ancestors, depth + 1);
29294
+ if (!child.ok) return child;
29295
+ Object.defineProperty(result, key, {
29296
+ configurable: true,
29297
+ enumerable: true,
29298
+ writable: true,
29299
+ value: child.value
29300
+ });
29301
+ }
29302
+ return { ok: true, value: result };
29303
+ }
29304
+ function tooLarge() {
29305
+ return {
29306
+ ok: false,
29307
+ error: "Requestly export contains a value that is too large."
29308
+ };
29309
+ }
29310
+ function notJson() {
29311
+ return {
29312
+ ok: false,
29313
+ error: "Requestly export contains a value that is not JSON data."
29314
+ };
29315
+ }
29316
+ function asRecords(value) {
29317
+ if (!Array.isArray(value)) return null;
29318
+ const records = [];
29319
+ for (const entry of value) {
29320
+ if (!isRecord3(entry)) return null;
29321
+ records.push(entry);
29322
+ }
29323
+ return records;
29324
+ }
29325
+ function parseRequestlyExport(value) {
29326
+ const snapshot2 = snapshotJson(value);
29327
+ if (!snapshot2.ok) return snapshot2;
29328
+ const records = rootRecords(snapshot2.value);
29329
+ if (records === null) {
29330
+ return {
29331
+ ok: false,
29332
+ error: "Requestly export must be a JSON array of rules and groups, a single rule, or an object with a rules array."
29333
+ };
29334
+ }
29335
+ const groups = [];
29336
+ const rules = [];
29337
+ const unknown = [];
29338
+ const seenRuleIds = /* @__PURE__ */ new Set();
29339
+ const addRule = (record, groupId) => {
29340
+ const id2 = ownString3(record, "id");
29341
+ if (id2 !== void 0 && id2.length > 0) {
29342
+ if (seenRuleIds.has(id2)) return;
29343
+ seenRuleIds.add(id2);
29344
+ }
29345
+ const ruleType = ownString3(record, "ruleType") ?? "";
29346
+ rules.push({
29347
+ raw: record,
29348
+ id: id2 !== void 0 && id2.length > 0 ? id2 : void 0,
29349
+ name: ownString3(record, "name") ?? "",
29350
+ ruleType,
29351
+ groupId
29352
+ });
29353
+ };
29354
+ for (const record of records) {
29355
+ const objectType = ownString3(record, "objectType");
29356
+ if (objectType === "group") {
29357
+ const id2 = ownString3(record, "id");
29358
+ if (id2 === void 0 || id2.length === 0) {
29359
+ unknown.push({
29360
+ name: displayName(ownString3(record, "name") ?? ""),
29361
+ type: "group"
29362
+ });
29363
+ continue;
29364
+ }
29365
+ groups.push({
29366
+ id: id2,
29367
+ name: cleanLabel(ownString3(record, "name") ?? "", "Imported group"),
29368
+ status: ownString3(record, "status")
29369
+ });
29370
+ const children = record.children;
29371
+ if (Array.isArray(children)) {
29372
+ for (const child of children) {
29373
+ if (!isRecord3(child)) continue;
29374
+ const childType = ownString3(child, "objectType");
29375
+ const childRuleType = ownString3(child, "ruleType");
29376
+ if (childType === "group" || childRuleType === void 0) continue;
29377
+ const childGroup = ownString3(child, "groupId");
29378
+ addRule(
29379
+ child,
29380
+ childGroup !== void 0 && childGroup.length > 0 ? childGroup : id2
29381
+ );
29382
+ }
29383
+ }
29384
+ continue;
29385
+ }
29386
+ if (objectType === "rule" || ownString3(record, "ruleType") !== void 0) {
29387
+ addRule(record, ownString3(record, "groupId") ?? "");
29388
+ continue;
29389
+ }
29390
+ unknown.push({
29391
+ name: displayName(ownString3(record, "name") ?? ""),
29392
+ type: objectType ?? "unknown"
29393
+ });
29394
+ }
29395
+ return { ok: true, export: { groups, rules, unknown } };
29396
+ }
29397
+ function rootRecords(value) {
29398
+ if (Array.isArray(value)) return asRecords(value);
29399
+ if (!isRecord3(value)) return null;
29400
+ if (ownString3(value, "objectType") === "rule" || ownString3(value, "ruleType") !== void 0) {
29401
+ return [value];
29402
+ }
29403
+ if (!Object.hasOwn(value, "rules")) return null;
29404
+ const rules = asRecords(value.rules);
29405
+ if (rules === null) return null;
29406
+ const groupsValue = Object.hasOwn(value, "groups") ? value.groups : Object.hasOwn(value, "updatedGroups") ? value.updatedGroups : [];
29407
+ if (groupsValue === void 0) return rules;
29408
+ if (!Array.isArray(groupsValue)) return null;
29409
+ const groups = asRecords(groupsValue);
29410
+ if (groups === null) return null;
29411
+ return [...rules, ...groups];
29412
+ }
29413
+ var ID_PATTERN3 = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
29414
+ var UNGROUPED_NAME = "Ungrouped";
29415
+ var INACTIVE_CHANGE = "The rule was inactive in Requestly. Rogatio project files have no disabled flag, so the rule is active.";
29416
+ var DESCRIPTION_CHANGE = "The rule description was not imported.";
29417
+ var MISSING_GROUP_CHANGE = "The rule's group was not in the export. It was placed in Ungrouped.";
29418
+ var PRIORITY_NOTE = "Priorities were assigned from 1000 downward in export order so an earlier rule wins.";
29419
+ var PRIORITY_FLOOR_NOTE = "More than 1000 rules were imported, so the remaining rules share priority 1.";
29420
+ function emptyBucket() {
29421
+ return {
29422
+ requestlyId: void 0,
29423
+ id: "",
29424
+ name: "",
29425
+ inactive: false,
29426
+ rules: []
29427
+ };
29428
+ }
29429
+ function importRequestlyExport(value) {
29430
+ const parsed = parseRequestlyExport(value);
29431
+ if (!parsed.ok) return parsed;
29432
+ const ids = /* @__PURE__ */ new Set();
29433
+ const names = /* @__PURE__ */ new Set();
29434
+ const buckets = /* @__PURE__ */ new Map();
29435
+ const ordered = [];
29436
+ for (const group of parsed.export.groups) {
29437
+ if (buckets.has(group.id)) continue;
29438
+ const name = uniqueName(group.name, names);
29439
+ const id2 = takeId(group.id, name, "group", ids, true);
29440
+ const bucket = {
29441
+ requestlyId: group.id,
29442
+ id: id2,
29443
+ name,
29444
+ inactive: group.status === "Inactive",
29445
+ rules: []
29446
+ };
29447
+ buckets.set(group.id, bucket);
29448
+ ordered.push(bucket);
29449
+ }
29450
+ let ungrouped;
29451
+ const ensureUngrouped = () => {
29452
+ if (ungrouped !== void 0) return ungrouped;
29453
+ const name = uniqueName(UNGROUPED_NAME, names);
29454
+ const id2 = takeId("Ungrouped", name, "group", ids, true);
29455
+ ungrouped = {
29456
+ requestlyId: void 0,
29457
+ id: id2,
29458
+ name,
29459
+ inactive: false,
29460
+ rules: []
29461
+ };
29462
+ return ungrouped;
29463
+ };
29464
+ const rows = [];
29465
+ let priority = LIMITS.maxPriority;
29466
+ let assignedAtFloor = 0;
29467
+ let produced = 0;
29468
+ for (const rule of parsed.export.rules) {
29469
+ const placed = placeRule(rule, buckets, ensureUngrouped);
29470
+ const changes = [...placed.changes];
29471
+ const rogatioRuleIds = [];
29472
+ for (const draft of placed.drafts) {
29473
+ const labeled = draft.nameSuffix.length > 0 ? `${cleanLabel(rule.name, "Imported rule")} (${draft.nameSuffix})` : cleanLabel(rule.name, "Imported rule");
29474
+ const storedName = uniqueName(labeled, names);
29475
+ if (storedName !== labeled) {
29476
+ changes.push(
29477
+ `The name was already used and was renamed to "${storedName}".`
29478
+ );
29479
+ }
29480
+ const id2 = takeId(rule.id, storedName, "rule", ids, draft.keepId);
29481
+ if (draft.keepId && rule.id !== void 0 && id2 !== rule.id) {
29482
+ changes.push(
29483
+ isRogatioId(rule.id) ? `The Requestly id "${rule.id}" was already used and became "${id2}".` : `The Requestly id "${rule.id}" is not a Rogatio id and became "${id2}".`
29484
+ );
29485
+ }
29486
+ const next = priority;
29487
+ if (next === LIMITS.minPriority) assignedAtFloor += 1;
29488
+ else priority -= 1;
29489
+ produced += 1;
29490
+ rogatioRuleIds.push(id2);
29491
+ placed.bucket.rules.push({
29492
+ ...draft.rule,
29493
+ id: id2,
29494
+ name: storedName,
29495
+ priority: next,
29496
+ resourceTypes: [...draft.rule.resourceTypes]
29497
+ });
29498
+ }
29499
+ if (placed.drafts.length === 0) {
29500
+ rows.push({
29501
+ ...rule.id === void 0 ? {} : { requestlyId: rule.id },
29502
+ name: displayName(rule.name),
29503
+ ruleType: rule.ruleType.length > 0 ? rule.ruleType : "unknown",
29504
+ status: "skipped",
29505
+ reason: placed.reason ?? "The rule was not imported."
29506
+ });
29507
+ continue;
29508
+ }
29509
+ const uniqueChanges = dedupe2(changes);
29510
+ rows.push({
29511
+ ...rule.id === void 0 ? {} : { requestlyId: rule.id },
29512
+ name: displayName(rule.name),
29513
+ ruleType: rule.ruleType.length > 0 ? rule.ruleType : "unknown",
29514
+ status: uniqueChanges.length === 0 ? "imported" : "changed",
29515
+ ...uniqueChanges.length === 0 ? {} : { changes: uniqueChanges },
29516
+ rogatioRuleIds
29517
+ });
29518
+ }
29519
+ for (const record of parsed.export.unknown) {
29520
+ rows.push({
29521
+ name: record.name,
29522
+ ruleType: record.type,
29523
+ status: "skipped",
29524
+ reason: "The record is not a Requestly rule."
29525
+ });
29526
+ }
29527
+ if (ungrouped !== void 0 && ungrouped.rules.length > 0) {
29528
+ ordered.push(ungrouped);
29529
+ }
29530
+ const groups = ordered.map((bucket) => ({
29531
+ id: bucket.id,
29532
+ name: bucket.name,
29533
+ rules: bucket.rules
29534
+ }));
29535
+ const limited = limitError(groups);
29536
+ if (limited !== void 0) return { ok: false, error: limited };
29537
+ const notes = [];
29538
+ if (produced > 0) notes.push(PRIORITY_NOTE);
29539
+ if (assignedAtFloor > 1) notes.push(PRIORITY_FLOOR_NOTE);
29540
+ return {
29541
+ ok: true,
29542
+ project: {
29543
+ version: PROJECT_VERSION,
29544
+ name: "Imported from Requestly",
29545
+ description: "Imported from a Requestly export.",
29546
+ groups
29547
+ },
29548
+ report: summarize(rows, notes, parsed.export.unknown)
29549
+ };
29550
+ }
29551
+ function placeRule(rule, buckets, ensureUngrouped) {
29552
+ const mapped = mapRequestlyRule(rule.raw);
29553
+ const known = rule.groupId.length > 0 ? buckets.get(rule.groupId) : void 0;
29554
+ const missingGroup = rule.groupId.length > 0 && known === void 0;
29555
+ if (mapped.status === "skipped") {
29556
+ return {
29557
+ drafts: [],
29558
+ changes: [],
29559
+ reason: mapped.reason,
29560
+ bucket: known ?? emptyBucket()
29561
+ };
29562
+ }
29563
+ const bucket = known ?? ensureUngrouped();
29564
+ const changes = [...mapped.changes];
29565
+ if (ownString3(rule.raw, "status") === "Inactive" || bucket.inactive) {
29566
+ changes.push(INACTIVE_CHANGE);
29567
+ }
29568
+ const description = ownString3(rule.raw, "description") ?? "";
29569
+ if (description.trim().length > 0) changes.push(DESCRIPTION_CHANGE);
29570
+ if (missingGroup) changes.push(MISSING_GROUP_CHANGE);
29571
+ return { drafts: mapped.drafts, changes, bucket };
29572
+ }
29573
+ function summarize(rows, notes, unknown) {
29574
+ let imported = 0;
29575
+ let changed = 0;
29576
+ let skipped = 0;
29577
+ for (const row of rows) {
29578
+ if (row.status === "imported") imported += 1;
29579
+ else if (row.status === "changed") changed += 1;
29580
+ else skipped += 1;
29581
+ }
29582
+ return { imported, changed, skipped, rules: rows, notes, unknown };
29583
+ }
29584
+ function isRogatioId(value) {
29585
+ return value.length > 0 && value.length <= LIMITS.maxIdLength && ID_PATTERN3.test(value);
29586
+ }
29587
+ function takeId(preferred, name, kind, reserved, keep) {
29588
+ if (keep && preferred !== void 0 && isRogatioId(preferred) && !reserved.has(preferred)) {
29589
+ reserved.add(preferred);
29590
+ return preferred;
29591
+ }
29592
+ return deriveEntityId(name, kind, reserved);
29593
+ }
29594
+ function limitError(groups) {
29595
+ if (groups.length > LIMITS.maxGroups) {
29596
+ return `The import needs ${groups.length} groups, above the limit of ${LIMITS.maxGroups}.`;
29597
+ }
29598
+ let total = 0;
29599
+ for (const group of groups) {
29600
+ if (group.rules.length > LIMITS.maxRulesPerGroup) {
29601
+ return `Group "${group.name}" has ${group.rules.length} rules, above the limit of ${LIMITS.maxRulesPerGroup}.`;
29602
+ }
29603
+ total += group.rules.length;
29604
+ }
29605
+ if (total > LIMITS.maxRulesPerProject) {
29606
+ return `The import has ${total} rules, above the limit of ${LIMITS.maxRulesPerProject}.`;
29607
+ }
29608
+ return void 0;
29609
+ }
29610
+ function dedupe2(changes) {
29611
+ const seen = /* @__PURE__ */ new Set();
29612
+ const result = [];
29613
+ for (const change of changes) {
29614
+ if (seen.has(change)) continue;
29615
+ seen.add(change);
29616
+ result.push(change);
29617
+ }
29618
+ return result;
29619
+ }
29620
+ function collectIdentity(project) {
29621
+ const ids = /* @__PURE__ */ new Set();
29622
+ const names = /* @__PURE__ */ new Set();
29623
+ for (const group of project.groups) {
29624
+ ids.add(group.id);
29625
+ names.add(normalizeNameKey(group.name));
29626
+ for (const rule of group.rules) {
29627
+ ids.add(rule.id);
29628
+ names.add(normalizeNameKey(rule.name));
29629
+ }
29630
+ }
29631
+ return { ids, names };
29632
+ }
29633
+ function mergeProjects(existing, imported) {
29634
+ const current = cloneProject(existing);
29635
+ if (current === void 0) {
29636
+ return {
29637
+ ok: false,
29638
+ error: "The existing project is not a version-2 Rogatio project."
29639
+ };
29640
+ }
29641
+ const incoming = cloneProject(imported);
29642
+ if (incoming === void 0) {
29643
+ return { ok: false, error: "The imported project is not JSON data." };
29644
+ }
29645
+ const { ids, names } = collectIdentity(current);
29646
+ const renamed = [];
29647
+ const appended = [];
29648
+ for (const group of incoming.groups) {
29649
+ const name = uniqueName(group.name, names);
29650
+ if (name !== group.name) {
29651
+ renamed.push(
29652
+ `Imported group name "${group.name}" was already used and became "${name}".`
29653
+ );
29654
+ }
29655
+ const id2 = retakeId(group.id, name, "group", ids, renamed, "group");
29656
+ const rules = [];
29657
+ for (const rule of group.rules) {
29658
+ const ruleName = uniqueName(rule.name, names);
29659
+ if (ruleName !== rule.name) {
29660
+ renamed.push(
29661
+ `Imported rule name "${rule.name}" was already used and became "${ruleName}".`
29662
+ );
29663
+ }
29664
+ const ruleId = retakeId(rule.id, ruleName, "rule", ids, renamed, "rule");
29665
+ rules.push({ ...rule, id: ruleId, name: ruleName });
29666
+ }
29667
+ appended.push({ id: id2, name, rules });
29668
+ }
29669
+ const groups = [...current.groups, ...appended];
29670
+ const limited = limitError(groups);
29671
+ if (limited !== void 0) return { ok: false, error: limited };
29672
+ const project = {
29673
+ version: PROJECT_VERSION,
29674
+ name: current.name,
29675
+ ...current.description === void 0 ? {} : { description: current.description },
29676
+ groups,
29677
+ ...current.requestBodyPolicy === void 0 ? {} : { requestBodyPolicy: current.requestBodyPolicy }
29678
+ };
29679
+ return { ok: true, project, renamed };
29680
+ }
29681
+ function retakeId(preferred, name, kind, reserved, renamed, label2) {
29682
+ if (isRogatioId(preferred) && !reserved.has(preferred)) {
29683
+ reserved.add(preferred);
29684
+ return preferred;
29685
+ }
29686
+ const next = deriveEntityId(name, kind, reserved);
29687
+ renamed.push(
29688
+ isRogatioId(preferred) ? `Imported ${label2} id "${preferred}" was already used and became "${next}".` : `Imported ${label2} id "${preferred}" is not a Rogatio id and became "${next}".`
29689
+ );
29690
+ return next;
29691
+ }
29692
+ function cloneProject(value) {
29693
+ let cloned;
29694
+ try {
29695
+ cloned = JSON.parse(JSON.stringify(value));
29696
+ } catch {
29697
+ return void 0;
29698
+ }
29699
+ if (cloned === null || typeof cloned !== "object" || Array.isArray(cloned)) {
29700
+ return void 0;
29701
+ }
29702
+ const record = cloned;
29703
+ if (record.version !== PROJECT_VERSION || typeof record.name !== "string") {
29704
+ return void 0;
29705
+ }
29706
+ if (!Array.isArray(record.groups)) return void 0;
29707
+ return record;
29708
+ }
29709
+
29710
+ // packages/cli/src/commands/diagnose.ts
29711
+ function diagnoseProject(projectData) {
29712
+ const diagnostics = [];
29713
+ const schemaResult = validateProjectDetailed(projectData);
29714
+ if (!schemaResult.valid) {
29715
+ for (const error of schemaResult.errors) {
29716
+ diagnostics.push({
29717
+ code: `schema.${error.keyword}`,
29718
+ severity: "error",
29719
+ path: error.instancePath || "/",
29720
+ message: error.message,
29721
+ params: error.params
29722
+ });
29723
+ }
29724
+ return { diagnostics };
29725
+ }
29726
+ const compileResult = compileProject(schemaResult.data);
29727
+ if (!compileResult.ok) {
29728
+ for (const diag of compileResult.diagnostics) {
29729
+ diagnostics.push({
29730
+ code: diag.code,
29731
+ severity: diag.severity,
29732
+ path: diag.path,
29733
+ message: diag.message,
29734
+ params: { ...diag.params }
29735
+ });
29736
+ }
29737
+ return { diagnostics };
29738
+ }
29739
+ return { diagnostics, project: schemaResult.data };
29740
+ }
29741
+ function formatDiagnostics(diagnostics) {
29742
+ return diagnostics.map((diag) => `${diag.path}: ${diag.message} (${diag.code})`).join("\n");
29743
+ }
29744
+
29745
+ // packages/cli/src/commands/import.ts
29746
+ async function importCommand(args) {
29747
+ const parsed = parseImportArgs(args);
29748
+ if (!parsed.ok) {
29749
+ console.error(parsed.error);
29750
+ return 2;
29751
+ }
29752
+ const flags = parsed.flags;
29753
+ let exportData;
29754
+ try {
29755
+ exportData = JSON.parse(await readFile6(flags.sourcePath, "utf8"));
29756
+ } catch (error) {
29757
+ console.error(`Error: ${errorMessage(error)}`);
29758
+ return 2;
29759
+ }
29760
+ const imported = importRequestlyExport(exportData);
29761
+ if (!imported.ok) {
29762
+ console.error(`Error: ${imported.error}`);
29763
+ return 2;
29764
+ }
29765
+ let project = imported.project;
29766
+ let renamed = [];
29767
+ if (flags.merge) {
29768
+ if (!await pathExists2(flags.outputPath)) {
29769
+ console.error(`Error: No project to merge at ${flags.outputPath}`);
29770
+ return 2;
29771
+ }
29772
+ let existing;
29773
+ try {
29774
+ existing = JSON.parse(await readFile6(flags.outputPath, "utf8"));
29775
+ } catch (error) {
29776
+ console.error(`Error: ${errorMessage(error)}`);
29777
+ return 2;
29778
+ }
29779
+ const existingDiagnosis = diagnoseProject(existing);
29780
+ if (existingDiagnosis.diagnostics.length > 0 || existingDiagnosis.project === void 0) {
29781
+ console.error(formatDiagnostics(existingDiagnosis.diagnostics));
29782
+ return 1;
29783
+ }
29784
+ const merged = mergeProjects(existingDiagnosis.project, project);
29785
+ if (!merged.ok) {
29786
+ console.error(`Error: ${merged.error}`);
29787
+ return 2;
29788
+ }
29789
+ project = merged.project;
29790
+ renamed = merged.renamed;
29791
+ } else if (await pathExists2(flags.outputPath)) {
29792
+ console.error(
29793
+ `Error: ${flags.outputPath} already exists. Pass --merge to append imported groups, or choose another --out path.`
29794
+ );
29795
+ return 2;
29796
+ }
29797
+ const diagnostics = diagnoseProject(project).diagnostics;
29798
+ if (diagnostics.length > 0) {
29799
+ console.error(formatDiagnostics(diagnostics));
29800
+ return 1;
29801
+ }
29802
+ try {
29803
+ await writeProject(flags.outputPath, project);
29804
+ } catch (error) {
29805
+ console.error(`Error: ${errorMessage(error)}`);
29806
+ return 2;
29807
+ }
29808
+ if (flags.json) {
29809
+ console.log(
29810
+ `${JSON.stringify(jsonReport(imported.report, flags.outputPath, renamed), null, 2)}
29811
+ `
29812
+ );
29813
+ } else {
29814
+ console.log(formatReport(imported.report, flags.outputPath, renamed));
29815
+ }
29816
+ return 0;
29817
+ }
29818
+ function jsonReport(report, outputPath, renamed) {
29819
+ return { ...report, renamed, outputPath };
29820
+ }
29821
+ function formatReport(report, outputPath, renamed) {
29822
+ const lines = [
29823
+ `Imported ${report.imported}, changed ${report.changed}, skipped ${report.skipped}.`
29824
+ ];
29825
+ for (const note of report.notes) lines.push(note);
29826
+ const changed = report.rules.filter((row) => row.status === "changed");
29827
+ const skipped = report.rules.filter((row) => row.status === "skipped");
29828
+ if (changed.length > 0) {
29829
+ lines.push("", "Changed:");
29830
+ for (const row of changed) {
29831
+ lines.push(` ${row.name} (${row.ruleType})`);
29832
+ for (const change of row.changes ?? []) lines.push(` - ${change}`);
29833
+ }
29834
+ }
29835
+ if (skipped.length > 0) {
29836
+ lines.push("", "Skipped:");
29837
+ for (const row of skipped) {
29838
+ lines.push(` ${row.name} (${row.ruleType}): ${row.reason ?? ""}`);
29839
+ }
29840
+ }
29841
+ if (renamed.length > 0) {
29842
+ lines.push("", "Renamed:");
29843
+ for (const line of renamed) lines.push(` ${line}`);
29844
+ }
29845
+ lines.push("", `Wrote ${outputPath}`);
29846
+ return lines.join("\n");
29847
+ }
29848
+ function parseImportArgs(args) {
29849
+ const positionals = [];
29850
+ let outputPath;
29851
+ let merge = false;
29852
+ let json = false;
29853
+ for (let index = 0; index < args.length; index += 1) {
29854
+ const arg = args[index] ?? "";
29855
+ if (arg === "--merge") {
29856
+ merge = true;
29857
+ continue;
29858
+ }
29859
+ if (arg === "--json") {
29860
+ json = true;
29861
+ continue;
29862
+ }
29863
+ if (arg === "--out") {
29864
+ const value = args[index + 1];
29865
+ if (value === void 0 || value.startsWith("-")) {
29866
+ return { ok: false, error: "Error: --out requires a path" };
29867
+ }
29868
+ outputPath = resolve5(value);
29869
+ index += 1;
29870
+ continue;
29871
+ }
29872
+ if (arg.startsWith("-")) {
29873
+ return { ok: false, error: `Error: Unknown option: ${arg}` };
29874
+ }
29875
+ positionals.push(arg);
29876
+ }
29877
+ if (positionals[0] !== "requestly") {
29878
+ return {
29879
+ ok: false,
29880
+ error: "Error: Usage: rogatio import requestly <export.json> [--out <path>] [--merge] [--json]"
29881
+ };
29882
+ }
29883
+ const source = positionals[1];
29884
+ if (source === void 0) {
29885
+ return { ok: false, error: "Error: Missing Requestly export path" };
29886
+ }
29887
+ if (positionals.length > 2) {
29888
+ return { ok: false, error: "Error: Too many arguments" };
29889
+ }
29890
+ return {
29891
+ ok: true,
29892
+ flags: {
29893
+ sourcePath: resolve5(source),
29894
+ outputPath: outputPath ?? resolve5(process.cwd(), ".rogatio.json"),
29895
+ merge,
29896
+ json
29897
+ }
29898
+ };
29899
+ }
29900
+ async function pathExists2(path2) {
29901
+ try {
29902
+ await access2(path2);
29903
+ return true;
29904
+ } catch {
29905
+ return false;
29906
+ }
29907
+ }
29908
+ function errorMessage(error) {
29909
+ return error instanceof Error ? error.message : "Unknown error";
29910
+ }
29911
+
29912
+ // packages/cli/src/commands/runtime.ts
29913
+ import { chmod, mkdir as mkdir4, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
29914
+ import { dirname as dirname7, join as join7, resolve as resolve6 } from "node:path";
29915
+ function toMatcherOperations2(operations) {
29916
+ return operations.map(
29917
+ ({ groupId, ruleId, name, matcher, redactSensitiveInLogs }) => ({
29918
+ kind: "matcher",
29919
+ groupId,
29920
+ ruleId,
29921
+ name,
29922
+ matcher,
29923
+ redactSensitiveInLogs
29924
+ })
29925
+ );
29926
+ }
29927
+ function generateHostWrapper(rogatioCommand, logPath) {
29928
+ const parts = rogatioCommand.split(" ");
29929
+ const nodeBin = parts[0] ?? "node";
29930
+ const cliScript = parts.slice(1).join(" ");
29931
+ return `#!/bin/sh
29932
+ # Rogatio native messaging host wrapper \u2014 generated by \`rogatio runtime install\`
29933
+ # Chrome launches this script when the extension connects via connectNative().
29934
+ # The extension sends the project data as the first envelope after launch.
29935
+ # stderr is redirected to a log file for debugging.
29936
+ exec "${nodeBin}" "${cliScript}" runtime host 2>>"${logPath}"
29937
+ `;
29938
+ }
29939
+ async function writeHostWrapper(hostPath, installRoot) {
29940
+ await mkdir4(installRoot, { recursive: true });
29941
+ const logPath = join7(installRoot, "runtime-host.log");
29942
+ const script = generateHostWrapper(
29943
+ `${process.execPath} ${process.argv[1] ?? "rogatio"}`,
29944
+ logPath
29945
+ );
29946
+ await writeFile4(hostPath, script, "utf8");
29947
+ await chmod(hostPath, 493);
29948
+ }
29949
+ function makeTrustController() {
29950
+ const platform = process.platform;
29951
+ const adapter = selectTrustPlatformAdapter(platform);
29952
+ const installRoot = defaultTrustInstallRoot(platform);
29953
+ return createRequestBodyTrustController({
29954
+ platform,
29955
+ installRoot,
29956
+ hostPath: join7(installRoot, "runtime-host"),
29957
+ hostName: "com.rogatio.runtime",
29958
+ allowedOrigins: [],
29959
+ manifestDir: adapter.defaultManifestDir(),
29960
+ detectCapabilities: () => adapter.detect(),
29961
+ caTrustInstaller: (cert) => adapter.caTrustInstaller(cert),
29962
+ caTrustRemover: () => adapter.caTrustRemover()
29963
+ });
29964
+ }
29965
+ function reportTrust(subcommand, result, okMessage) {
29966
+ if (result.ok) {
29967
+ console.log(okMessage);
29968
+ return 0;
29969
+ }
29970
+ if (result.state === "unsupported") {
29971
+ const reasons = result.reasons ?? ["unknown"];
29972
+ console.error(`trust unsupported: ${reasons.join(", ")}`);
29973
+ const hints = {
29974
+ "tooling-missing": "Hint: install required tooling (macOS: security; Linux: update-ca-certificates; Windows: certutil is built-in)",
29975
+ "manifest-dir-unwritable": "Hint: check permissions on Chrome NativeMessagingHosts directory or run with appropriate access",
29976
+ "keychain-unwritable": "Hint: macOS login keychain not writable; try running with appropriate keychain access or use sudo for system keychain",
29977
+ "ca-store-unwritable": "Hint: /usr/local/share/ca-certificates not writable; check directory permissions",
29978
+ "elevation-required": "Hint: device-local CA trust requires root (Linux: sudo, macOS: keychain password, Windows: Administrator). Re-run with elevated privileges"
29979
+ };
29980
+ for (const reason of reasons) {
29981
+ const hint = hints[reason];
29982
+ if (hint) {
29983
+ console.error(hint);
29984
+ }
29985
+ }
29986
+ return 1;
29987
+ }
29988
+ console.error(
29989
+ `runtime ${subcommand} failed: ${(result.reasons ?? ["unknown"]).join(", ")}`
29990
+ );
29991
+ return 1;
29992
+ }
29993
+ function parseExtensionIdFlag(args) {
29994
+ const extIdIndex = args.indexOf("--extension-id");
29995
+ if (extIdIndex === -1) return { extensionId: RELEASE_EXTENSION_ID };
29996
+ const extensionId = args[extIdIndex + 1];
29997
+ if (extensionId === void 0 || extensionId.startsWith("-")) {
29998
+ return {
29999
+ error: "Error: --extension-id requires a value",
30000
+ showHelp: true
30001
+ };
30002
+ }
30003
+ if (!/^[a-p]{32}$/.test(extensionId)) {
30004
+ return {
30005
+ error: "Error: --extension-id must be exactly 32 lowercase characters from a through p",
30006
+ showHelp: false
30007
+ };
30008
+ }
30009
+ return { extensionId };
30010
+ }
30011
+ async function trustRuntimeCommand(args) {
30012
+ const subcommand = args[0];
30013
+ let extensionId = RELEASE_EXTENSION_ID;
30014
+ if (subcommand === "install" || subcommand === "verify") {
30015
+ const parsed = parseExtensionIdFlag(args);
30016
+ if ("error" in parsed) {
30017
+ console.error(parsed.error);
30018
+ if (parsed.showHelp) showRuntimeHelp();
30019
+ return 2;
30020
+ }
30021
+ extensionId = parsed.extensionId;
30022
+ }
30023
+ const controller = makeTrustController();
30024
+ const installRoot = defaultTrustInstallRoot(process.platform);
30025
+ const hostPath = join7(installRoot, "runtime-host");
30026
+ switch (subcommand) {
30027
+ case "install": {
30028
+ const installResult = await controller.install(extensionId);
30029
+ if (installResult.ok) {
30030
+ try {
30031
+ await writeHostWrapper(hostPath, installRoot);
30032
+ } catch (error) {
30033
+ console.error(
30034
+ `Warning: manifest and CA trusted, but failed to create runtime-host wrapper: ${error instanceof Error ? error.message : String(error)}`
30035
+ );
30036
+ }
30037
+ }
30038
+ return reportTrust(
30039
+ "install",
30040
+ installResult,
30041
+ `runtime install complete: manifest + device-local CA trusted + runtime-host wrapper created
30042
+ Pinned extension ID: ${extensionId}
30043
+ Note: reload the extension at chrome://extensions for the native host to be detected.`
30044
+ );
30045
+ }
30046
+ case "uninstall": {
30047
+ const uninstallResult = await controller.uninstall();
30048
+ try {
30049
+ await rm2(hostPath, { force: true });
30050
+ } catch {
30051
+ }
30052
+ try {
30053
+ await rm2(join7(installRoot, "runtime-host.log"), { force: true });
30054
+ } catch {
30055
+ }
30056
+ return reportTrust(
30057
+ "uninstall",
30058
+ uninstallResult,
30059
+ "runtime uninstall complete: manifest + device-local CA removed"
30060
+ );
30061
+ }
30062
+ case "verify": {
30063
+ const result = await controller.verify();
30064
+ const originMismatch = result.manifestValid && result.allowedOrigins.length > 0 && !extensionOriginListed(extensionId, result.allowedOrigins);
30065
+ if (result.ok && !originMismatch) {
30066
+ console.log(
30067
+ `runtime verification passed: manifest, binary, and CA trust all OK
30068
+ allowed_origins includes chrome-extension://${extensionId}/`
30069
+ );
30070
+ return 0;
30071
+ }
30072
+ console.error("runtime verification failed:");
30073
+ if (!result.manifestExists) console.error(" manifest not found");
30074
+ if (result.manifestExists && !result.manifestValid)
30075
+ console.error(" manifest invalid");
30076
+ if (!result.binaryExists)
30077
+ console.error(" runtime-host binary not found");
30078
+ if (result.binaryExists && !result.binaryExecutable)
30079
+ console.error(" runtime-host binary not executable");
30080
+ if (result.allowedOriginsCount === 0)
30081
+ console.error(" no allowed_origins in manifest");
30082
+ if (!result.caTrusted) console.error(" device-local CA not trusted");
30083
+ if (originMismatch) {
30084
+ for (const line of describeExtensionIdMismatch(
30085
+ extensionId,
30086
+ result.allowedOrigins
30087
+ ).split("\n")) {
30088
+ console.error(` ${line}`);
30089
+ }
30090
+ }
30091
+ for (const reason of result.reasons) {
30092
+ if (reason === "manifest-not-found")
30093
+ console.error(
30094
+ " Hint: run 'rogatio runtime install' to register the host manifest (release users never pass --extension-id; it is only for development without the release key, and for forks)"
30095
+ );
30096
+ if (reason === "binary-not-found")
30097
+ console.error(
30098
+ ` Hint: ensure the runtime-host binary exists at the path specified in the manifest`
30099
+ );
30100
+ if (reason === "binary-not-executable")
30101
+ console.error(
30102
+ " Hint: ensure the runtime-host binary has execute permission (chmod +x)"
30103
+ );
30104
+ if (reason === "no-allowed-origins")
30105
+ console.error(
30106
+ " Hint: re-run 'rogatio runtime install' to pin your extension ID (release users never pass --extension-id; it is only for development without the release key, and for forks)"
30107
+ );
30108
+ if (reason === "ca-not-trusted")
30109
+ console.error(
30110
+ " Hint: re-run 'rogatio runtime install' with elevated privileges to trust the CA"
30111
+ );
30112
+ }
30113
+ return 1;
30114
+ }
30115
+ default:
30116
+ console.error(`Error: unknown runtime subcommand: ${subcommand ?? ""}`);
30117
+ showRuntimeHelp();
30118
+ return 2;
30119
+ }
30120
+ }
30121
+ async function runtimeHostCommand(args, options = {}) {
30122
+ let root;
30123
+ let mockPort;
30124
+ const positional = [];
30125
+ let argumentError;
30126
+ for (let index = 0; index < args.length; index += 1) {
30127
+ const arg = args[index];
30128
+ if (arg === "--root" && index + 1 < args.length) {
30129
+ root = resolve6(args[++index]);
30130
+ } else if (arg === "--root") {
30131
+ argumentError = "--root requires a value";
30132
+ } else if (arg === "--mock-port" && index + 1 < args.length) {
30133
+ const parsed = Number(args[++index]);
30134
+ if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
30135
+ argumentError = "--mock-port must be an integer port in 1..65535";
30136
+ } else {
27967
30137
  mockPort = parsed;
27968
30138
  }
27969
30139
  } else if (arg === "--mock-port") {
@@ -28004,7 +30174,7 @@ async function runtimeHostCommand(args, options = {}) {
28004
30174
  filePath = "<stdin>";
28005
30175
  projectData = JSON.parse(chunks.join(""));
28006
30176
  } else {
28007
- filePath = resolve5(inputPath);
30177
+ filePath = resolve6(inputPath);
28008
30178
  projectData = await storage.get(filePath);
28009
30179
  }
28010
30180
  } catch (error) {
@@ -28073,8 +30243,8 @@ async function runRuntimeHostEntry() {
28073
30243
  }
28074
30244
 
28075
30245
  // packages/cli/src/commands/test.ts
28076
- import { readFile as readFile6 } from "node:fs/promises";
28077
- import { resolve as resolve6 } from "node:path";
30246
+ import { readFile as readFile7 } from "node:fs/promises";
30247
+ import { resolve as resolve7 } from "node:path";
28078
30248
  function usageError(message) {
28079
30249
  return `Error: ${message}
28080
30250
  `;
@@ -28236,7 +30406,7 @@ function testCommandNeedsStdin(args) {
28236
30406
  return !hasUrlSource && positionalUrls.length === 0;
28237
30407
  }
28238
30408
  async function testCommandImpl(args, stdinInput, captureOutput, options = {}) {
28239
- let filePath = resolve6(process.cwd(), ".rogatio.json");
30409
+ let filePath = resolve7(process.cwd(), ".rogatio.json");
28240
30410
  let jsonMode = false;
28241
30411
  let maxCases;
28242
30412
  const urlCases = [];
@@ -28296,12 +30466,12 @@ async function testCommandImpl(args, stdinInput, captureOutput, options = {}) {
28296
30466
  }
28297
30467
  filePath = "<stdin>";
28298
30468
  } else if (inputPath) {
28299
- filePath = resolve6(inputPath);
30469
+ filePath = resolve7(inputPath);
28300
30470
  }
28301
30471
  for (const url of positionalUrls) urlCases.push({ url });
28302
30472
  if (urlsFile) {
28303
30473
  try {
28304
- const content = urlsFile === "-" ? stdinInput ?? "" : await readFile6(urlsFile, "utf8");
30474
+ const content = urlsFile === "-" ? stdinInput ?? "" : await readFile7(urlsFile, "utf8");
28305
30475
  const parsed = JSON.parse(content);
28306
30476
  if (!Array.isArray(parsed)) {
28307
30477
  const output = usageError(`${urlsFile} must contain a JSON array`);
@@ -28452,7 +30622,7 @@ async function testCommand(args, stdinInput, captureOutput = false, options = {}
28452
30622
  }
28453
30623
 
28454
30624
  // packages/cli/src/commands/verify.ts
28455
- import { resolve as resolve7 } from "node:path";
30625
+ import { resolve as resolve8 } from "node:path";
28456
30626
  async function verifyCommandImpl(args, stdinInput, captureOutput, options = {}) {
28457
30627
  let filePath;
28458
30628
  let jsonOutput2 = false;
@@ -28479,9 +30649,9 @@ async function verifyCommandImpl(args, stdinInput, captureOutput, options = {})
28479
30649
  }
28480
30650
  filePath = "<stdin>";
28481
30651
  } else if (inputPath) {
28482
- filePath = resolve7(inputPath);
30652
+ filePath = resolve8(inputPath);
28483
30653
  } else {
28484
- filePath = resolve7(process.cwd(), ".rogatio.json");
30654
+ filePath = resolve8(process.cwd(), ".rogatio.json");
28485
30655
  }
28486
30656
  let projectData;
28487
30657
  try {
@@ -28499,32 +30669,7 @@ async function verifyCommandImpl(args, stdinInput, captureOutput, options = {})
28499
30669
  console.error(output2.trim());
28500
30670
  return 2;
28501
30671
  }
28502
- const schemaResult = validateProjectDetailed(projectData);
28503
- const diagnostics = [];
28504
- if (!schemaResult.valid) {
28505
- for (const error of schemaResult.errors) {
28506
- diagnostics.push({
28507
- code: `schema.${error.keyword}`,
28508
- severity: "error",
28509
- path: error.instancePath || "/",
28510
- message: error.message,
28511
- params: error.params
28512
- });
28513
- }
28514
- } else {
28515
- const compileResult = compileProject(schemaResult.data);
28516
- if (!compileResult.ok) {
28517
- for (const diag of compileResult.diagnostics) {
28518
- diagnostics.push({
28519
- code: diag.code,
28520
- severity: diag.severity,
28521
- path: diag.path,
28522
- message: diag.message,
28523
- params: diag.params
28524
- });
28525
- }
28526
- }
28527
- }
30672
+ const diagnostics = diagnoseProject(projectData).diagnostics;
28528
30673
  let output = "";
28529
30674
  if (jsonOutput2) {
28530
30675
  output = `${JSON.stringify(diagnostics, null, 2)}
@@ -28550,7 +30695,7 @@ async function verifyCommand(args, stdinInput, captureOutput = false, options =
28550
30695
 
28551
30696
  // packages/cli/src/index.ts
28552
30697
  var __dirname = dirname8(fileURLToPath2(import.meta.url));
28553
- var packageJsonPath = resolve8(
30698
+ var packageJsonPath = resolve9(
28554
30699
  __dirname,
28555
30700
  isDistBuild(__dirname) ? "../../package.json" : "../package.json"
28556
30701
  );
@@ -28577,6 +30722,8 @@ async function cli(args = process.argv.slice(2)) {
28577
30722
  return handleRuntime(commandArgs);
28578
30723
  case "ai":
28579
30724
  return handleAI(commandArgs);
30725
+ case "import":
30726
+ return handleImport(commandArgs);
28580
30727
  case "--help":
28581
30728
  case "-h":
28582
30729
  return showHelp();
@@ -28621,6 +30768,13 @@ async function handleAI(args) {
28621
30768
  }
28622
30769
  return await aiCommand(args);
28623
30770
  }
30771
+ async function handleImport(args) {
30772
+ if (args.includes("--help") || args.includes("-h")) {
30773
+ showImportHelp();
30774
+ return 0;
30775
+ }
30776
+ return importCommand(args);
30777
+ }
28624
30778
  async function handleTest(args) {
28625
30779
  if (args.includes("--help") || args.includes("-h")) {
28626
30780
  showTestHelp();
@@ -28653,6 +30807,7 @@ Commands:
28653
30807
  edit [path] Launch browser editor for .rogatio.json
28654
30808
  test [path] [url...] Run offline dry-run tests against .rogatio.json
28655
30809
  verify [path] Validate .rogatio.json file
30810
+ import requestly <export.json> Migrate a Requestly export into .rogatio.json
28656
30811
  runtime <install|uninstall|verify|host> Native messaging runtime control
28657
30812
  runtime host [path] Run the native-messaging runtime host
28658
30813
  ai <setup|ls|show|delete|test> AI provider configuration
@@ -28664,7 +30819,7 @@ Global Options:
28664
30819
  Run 'rogatio <command> --help' for command-specific help.`);
28665
30820
  return 0;
28666
30821
  }
28667
- if (process.argv[1] !== void 0 && realpathSync.native(fileURLToPath2(import.meta.url)) === realpathSync.native(resolve8(process.argv[1]))) {
30822
+ if (process.argv[1] !== void 0 && realpathSync.native(fileURLToPath2(import.meta.url)) === realpathSync.native(resolve9(process.argv[1]))) {
28668
30823
  if (basename3(process.argv[1]) === "runtime-host") {
28669
30824
  runRuntimeHostEntry().catch((err) => {
28670
30825
  console.error(err);