@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.
- package/README.md +6 -3
- package/dist/node/index.js +2456 -301
- package/package.json +2 -1
package/dist/node/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
1078
|
+
snapshot22[index] = child.value;
|
|
1038
1079
|
}
|
|
1039
|
-
return { valid: true, value:
|
|
1080
|
+
return { valid: true, value: snapshot22 };
|
|
1040
1081
|
}
|
|
1041
|
-
const
|
|
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(
|
|
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:
|
|
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
|
|
1074
|
-
if (!
|
|
1114
|
+
const snapshot2 = snapshotOwnData(value);
|
|
1115
|
+
if (!snapshot2.valid) {
|
|
1075
1116
|
guardedProjectValidator.errors = [ownArrayEntriesError];
|
|
1076
1117
|
return false;
|
|
1077
1118
|
}
|
|
1078
|
-
const valid = compiledProjectValidator(
|
|
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
|
|
1625
|
-
if (!
|
|
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(
|
|
1679
|
+
if (!projectValidator(snapshot2.value)) {
|
|
1639
1680
|
return { valid: false, errors: ajvIssues(projectValidator.errors) };
|
|
1640
1681
|
}
|
|
1641
|
-
const errors = semanticIssues(
|
|
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
|
|
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
|
-
|
|
1832
|
+
snapshot22[index] = child.value;
|
|
1792
1833
|
}
|
|
1793
|
-
return { valid: true, value:
|
|
1834
|
+
return { valid: true, value: snapshot22 };
|
|
1794
1835
|
}
|
|
1795
|
-
const
|
|
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(
|
|
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:
|
|
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
|
|
1929
|
-
if (!
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
21749
|
+
snapshot22[index] = child.value;
|
|
21709
21750
|
}
|
|
21710
|
-
return { valid: true, value:
|
|
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
|
|
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(
|
|
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:
|
|
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
|
|
21812
|
-
if (!
|
|
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 =
|
|
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
|
|
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
|
-
|
|
22727
|
+
finish2({ ok: false, reason: "oversized" });
|
|
22666
22728
|
return;
|
|
22667
22729
|
}
|
|
22668
22730
|
chunks.push(chunk);
|
|
22669
22731
|
});
|
|
22670
22732
|
req.on("end", () => {
|
|
22671
|
-
|
|
22733
|
+
finish2({ ok: true, body: Buffer.concat(chunks) });
|
|
22672
22734
|
});
|
|
22673
22735
|
req.on("error", () => {
|
|
22674
|
-
|
|
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
|
|
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(
|
|
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
|
|
23845
|
-
if (!
|
|
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 =
|
|
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
|
|
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
|
-
|
|
24912
|
-
allowedOriginsCount = manifest.allowed_origins.length;
|
|
24973
|
+
allowedOrigins = [...parsed.allowed_origins];
|
|
24913
24974
|
try {
|
|
24914
|
-
const fileStat = await stat2(
|
|
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 (
|
|
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 &&
|
|
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
|
|
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
|
|
25711
|
-
|
|
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
|
|
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((
|
|
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
|
-
|
|
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((
|
|
25898
|
+
return await new Promise((resolve10) => {
|
|
25805
25899
|
let input = "";
|
|
25806
25900
|
let settled = false;
|
|
25807
|
-
const
|
|
25901
|
+
const finish2 = () => {
|
|
25808
25902
|
settled = true;
|
|
25809
25903
|
process.stdin.off("data", onData);
|
|
25810
25904
|
process.stdin.pause();
|
|
25811
|
-
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
26070
|
-
server.close(() =>
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
27136
|
+
resolve10(false);
|
|
27043
27137
|
} else {
|
|
27044
|
-
|
|
27138
|
+
resolve10(false);
|
|
27045
27139
|
}
|
|
27046
27140
|
});
|
|
27047
27141
|
child.on("close", (code) => {
|
|
27048
|
-
|
|
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((
|
|
27635
|
+
const exitCodePromise = new Promise((resolve10) => {
|
|
27531
27636
|
const checkShutdown = setInterval(() => {
|
|
27532
27637
|
if (shutdownCalled) {
|
|
27533
27638
|
clearInterval(checkShutdown);
|
|
27534
|
-
|
|
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 =
|
|
27543
|
-
|
|
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/
|
|
27767
|
-
import {
|
|
27768
|
-
import {
|
|
27769
|
-
|
|
27770
|
-
|
|
27771
|
-
|
|
27772
|
-
|
|
27773
|
-
|
|
27774
|
-
|
|
27775
|
-
|
|
27776
|
-
|
|
27777
|
-
|
|
27778
|
-
|
|
27779
|
-
|
|
27780
|
-
|
|
27781
|
-
|
|
27782
|
-
|
|
27783
|
-
|
|
27784
|
-
|
|
27785
|
-
|
|
27786
|
-
|
|
27787
|
-
|
|
27788
|
-
|
|
27789
|
-
|
|
27790
|
-
|
|
27791
|
-
|
|
27792
|
-
|
|
27793
|
-
|
|
27794
|
-
|
|
27795
|
-
|
|
27796
|
-
|
|
27797
|
-
|
|
27798
|
-
|
|
27799
|
-
|
|
27800
|
-
|
|
27801
|
-
|
|
27802
|
-
|
|
27803
|
-
|
|
27804
|
-
|
|
27805
|
-
|
|
27806
|
-
const
|
|
27807
|
-
|
|
27808
|
-
|
|
27809
|
-
|
|
27810
|
-
|
|
27811
|
-
|
|
27812
|
-
|
|
27813
|
-
|
|
27814
|
-
|
|
27815
|
-
|
|
27816
|
-
|
|
27817
|
-
|
|
27818
|
-
}
|
|
27819
|
-
|
|
27820
|
-
|
|
27821
|
-
|
|
27822
|
-
return
|
|
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
|
-
|
|
27825
|
-
|
|
27826
|
-
|
|
27827
|
-
|
|
27828
|
-
|
|
27829
|
-
|
|
27830
|
-
|
|
27831
|
-
|
|
27832
|
-
|
|
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
|
-
|
|
27835
|
-
|
|
27836
|
-
|
|
27837
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27843
|
-
|
|
27844
|
-
|
|
27845
|
-
return
|
|
27998
|
+
if (compileUrlRegex(pattern) === null) {
|
|
27999
|
+
return { ok: false, reason: "The source regular expression is invalid." };
|
|
28000
|
+
}
|
|
28001
|
+
return { ok: true };
|
|
27846
28002
|
}
|
|
27847
|
-
|
|
27848
|
-
|
|
27849
|
-
|
|
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
|
-
|
|
27866
|
-
|
|
27867
|
-
|
|
27868
|
-
|
|
27869
|
-
|
|
27870
|
-
|
|
27871
|
-
|
|
27872
|
-
|
|
27873
|
-
|
|
27874
|
-
|
|
27875
|
-
|
|
27876
|
-
|
|
27877
|
-
|
|
27878
|
-
|
|
27879
|
-
|
|
27880
|
-
|
|
27881
|
-
|
|
27882
|
-
|
|
27883
|
-
|
|
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
|
-
|
|
27887
|
-
|
|
27888
|
-
|
|
27889
|
-
|
|
27890
|
-
|
|
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
|
-
|
|
27903
|
-
|
|
27904
|
-
|
|
27905
|
-
|
|
27906
|
-
|
|
27907
|
-
|
|
27908
|
-
|
|
27909
|
-
|
|
27910
|
-
|
|
27911
|
-
|
|
27912
|
-
|
|
27913
|
-
|
|
27914
|
-
|
|
27915
|
-
|
|
27916
|
-
|
|
27917
|
-
|
|
27918
|
-
|
|
27919
|
-
|
|
27920
|
-
|
|
27921
|
-
|
|
27922
|
-
|
|
27923
|
-
|
|
27924
|
-
|
|
27925
|
-
|
|
27926
|
-
|
|
27927
|
-
|
|
27928
|
-
|
|
27929
|
-
|
|
27930
|
-
|
|
27931
|
-
|
|
27932
|
-
|
|
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
|
-
|
|
27946
|
-
|
|
27947
|
-
|
|
27948
|
-
|
|
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
|
-
|
|
27952
|
-
|
|
27953
|
-
|
|
27954
|
-
|
|
27955
|
-
|
|
27956
|
-
|
|
27957
|
-
|
|
27958
|
-
|
|
27959
|
-
|
|
27960
|
-
|
|
27961
|
-
|
|
27962
|
-
|
|
27963
|
-
|
|
27964
|
-
|
|
27965
|
-
|
|
27966
|
-
|
|
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 =
|
|
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
|
|
28077
|
-
import { resolve as
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
30652
|
+
filePath = resolve8(inputPath);
|
|
28483
30653
|
} else {
|
|
28484
|
-
filePath =
|
|
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
|
|
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 =
|
|
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(
|
|
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);
|