@rogatio/cli 6.13.0 → 6.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
 
3
3
  // packages/cli/src/index.ts
4
4
  import { realpathSync } from "node:fs";
5
- import { basename as basename3, dirname as dirname8, resolve as resolve9 } from "node:path";
5
+ import { basename as basename3, dirname as dirname10, 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
@@ -701,7 +701,8 @@ var projectSchemaDefinition = {
701
701
  "query",
702
702
  "header",
703
703
  "response-body",
704
- "request-body"
704
+ "request-body",
705
+ "mock"
705
706
  ]
706
707
  },
707
708
  redirect: {
@@ -726,6 +727,7 @@ var projectSchemaDefinition = {
726
727
  },
727
728
  responseBody: { $ref: "#/$defs/responseBodyAction" },
728
729
  requestBody: { $ref: "#/$defs/requestBodyAction" },
730
+ mock: { $ref: "#/$defs/mockAction" },
729
731
  redactSensitiveInLogs: { type: "boolean" }
730
732
  },
731
733
  allOf: [
@@ -808,9 +810,66 @@ var projectSchemaDefinition = {
808
810
  then: {
809
811
  required: ["requestBody", "method", "resourceTypes"]
810
812
  }
813
+ },
814
+ {
815
+ if: {
816
+ required: ["type"],
817
+ properties: { type: { const: "mock" } }
818
+ },
819
+ // biome-ignore lint/suspicious/noThenProperty: AJV conditional schema keyword
820
+ then: {
821
+ required: ["mock"]
822
+ }
811
823
  }
812
824
  ]
813
825
  },
826
+ mockHeader: {
827
+ type: "object",
828
+ additionalProperties: false,
829
+ required: ["name", "value"],
830
+ properties: {
831
+ name: {
832
+ type: "string",
833
+ minLength: 1,
834
+ maxLength: LIMITS.maxMockHeaderNameLength
835
+ },
836
+ value: {
837
+ type: "string",
838
+ maxLength: LIMITS.maxMockHeaderValueLength
839
+ }
840
+ }
841
+ },
842
+ mockAction: {
843
+ type: "object",
844
+ additionalProperties: false,
845
+ required: ["status"],
846
+ properties: {
847
+ status: {
848
+ type: "integer",
849
+ minimum: LIMITS.minMockStatus,
850
+ maximum: LIMITS.maxMockStatus
851
+ },
852
+ headers: {
853
+ type: "array",
854
+ maxItems: LIMITS.maxMockHeadersPerRule,
855
+ items: { $ref: "#/$defs/mockHeader" }
856
+ },
857
+ delayMs: {
858
+ type: "integer",
859
+ minimum: 0,
860
+ maximum: LIMITS.maxMockDelayMs
861
+ },
862
+ body: {
863
+ type: "string",
864
+ maxLength: LIMITS.maxMockInlineBodyLength
865
+ },
866
+ file: {
867
+ type: "string",
868
+ minLength: 1,
869
+ maxLength: LIMITS.maxMockFilePathLength
870
+ }
871
+ }
872
+ },
814
873
  queryParam: {
815
874
  type: "object",
816
875
  additionalProperties: false,
@@ -1178,18 +1237,19 @@ function semanticIssues(project) {
1178
1237
  ids.set(rule.id, `${rulePath}/id`);
1179
1238
  }
1180
1239
  claimName(rule.name, `${rulePath}/name`);
1181
- if (rule.type !== void 0 && rule.type !== "redirect" && rule.type !== "query" && rule.type !== "header" && rule.type !== "response-body" && rule.type !== "request-body") {
1240
+ if (rule.type !== void 0 && rule.type !== "redirect" && rule.type !== "query" && rule.type !== "header" && rule.type !== "response-body" && rule.type !== "request-body" && rule.type !== "mock") {
1182
1241
  issues.push({
1183
1242
  instancePath: `${rulePath}/type`,
1184
1243
  keyword: "enum",
1185
- message: 'must be "redirect", "query", "header", "response-body", or "request-body"',
1244
+ message: 'must be "redirect", "query", "header", "response-body", "request-body", or "mock"',
1186
1245
  params: {
1187
1246
  allowedValues: [
1188
1247
  "redirect",
1189
1248
  "query",
1190
1249
  "header",
1191
1250
  "response-body",
1192
- "request-body"
1251
+ "request-body",
1252
+ "mock"
1193
1253
  ]
1194
1254
  }
1195
1255
  });
@@ -1481,6 +1541,86 @@ function semanticIssues(project) {
1481
1541
  });
1482
1542
  }
1483
1543
  }
1544
+ if (rule.type === "mock") {
1545
+ const mock = rule.mock;
1546
+ const mockPath = `${rulePath}/mock`;
1547
+ if (mock !== void 0) {
1548
+ const forbiddenMockStatuses = /* @__PURE__ */ new Set([204, 205, 304]);
1549
+ if (forbiddenMockStatuses.has(mock.status)) {
1550
+ issues.push({
1551
+ instancePath: `${mockPath}/status`,
1552
+ keyword: "mock-status",
1553
+ message: "Mock status must not be 204, 205, or 304 because those responses require special body handling.",
1554
+ params: { status: mock.status }
1555
+ });
1556
+ }
1557
+ const bodySet = mock.body !== void 0;
1558
+ const fileSet = mock.file !== void 0;
1559
+ if (bodySet === fileSet) {
1560
+ issues.push({
1561
+ instancePath: mockPath,
1562
+ keyword: "mock-body-source",
1563
+ message: "Mock rules require exactly one of body or file.",
1564
+ params: {}
1565
+ });
1566
+ } else if (fileSet && typeof mock.file === "string" && !isValidMockLogicalPath(mock.file)) {
1567
+ issues.push({
1568
+ instancePath: `${mockPath}/file`,
1569
+ keyword: "mock-file-path",
1570
+ message: "Mock file must be a relative logical path without absolute segments, backslashes, percent escapes, control characters, dot segments, colons, or glob characters.",
1571
+ params: {}
1572
+ });
1573
+ }
1574
+ if (mock.headers !== void 0) {
1575
+ const seenHeaderNames = /* @__PURE__ */ new Set();
1576
+ for (let index = 0; index < mock.headers.length; index += 1) {
1577
+ const header = mock.headers[index];
1578
+ if (header === void 0) continue;
1579
+ const namePath = `${mockPath}/headers/${index}/name`;
1580
+ const valuePath = `${mockPath}/headers/${index}/value`;
1581
+ if (hasControl(header.name)) {
1582
+ issues.push({
1583
+ instancePath: namePath,
1584
+ keyword: "mock-header-control",
1585
+ message: "Mock header names must not contain control characters.",
1586
+ params: {}
1587
+ });
1588
+ }
1589
+ if (hasControl(header.value)) {
1590
+ issues.push({
1591
+ instancePath: valuePath,
1592
+ keyword: "mock-header-control",
1593
+ message: "Mock header values must not contain control characters.",
1594
+ params: {}
1595
+ });
1596
+ }
1597
+ const comparableName = header.name.trim();
1598
+ if (isForbiddenHeader(comparableName, "response")) {
1599
+ issues.push({
1600
+ instancePath: namePath,
1601
+ keyword: "forbiddenHeader",
1602
+ message: `Header "${header.name}" is forbidden for response headers.`,
1603
+ params: {
1604
+ headerName: header.name,
1605
+ headerDirection: "response"
1606
+ }
1607
+ });
1608
+ }
1609
+ const normalizedName = comparableName.toLowerCase();
1610
+ if (seenHeaderNames.has(normalizedName)) {
1611
+ issues.push({
1612
+ instancePath: namePath,
1613
+ keyword: "uniqueMockHeaderName",
1614
+ message: `mock header name must be unique; duplicate "${header.name}"`,
1615
+ params: { name: header.name }
1616
+ });
1617
+ } else {
1618
+ seenHeaderNames.add(normalizedName);
1619
+ }
1620
+ }
1621
+ }
1622
+ }
1623
+ }
1484
1624
  }
1485
1625
  }
1486
1626
  if (project.requestBodyPolicy !== void 0) {
@@ -1546,6 +1686,22 @@ function semanticIssues(project) {
1546
1686
  }
1547
1687
  return issues;
1548
1688
  }
1689
+ function isValidMockLogicalPath(value) {
1690
+ if (value.length === 0) return false;
1691
+ if (value.includes("\\") || value.includes("%") || hasControl(value)) {
1692
+ return false;
1693
+ }
1694
+ if (value.startsWith("/") || value.endsWith("/") || value.includes("//")) {
1695
+ return false;
1696
+ }
1697
+ const parts = value.split("/");
1698
+ if (parts.some(
1699
+ (part) => part.length === 0 || part === "." || part === ".." || part.includes(":") || /[*?[\]]/.test(part)
1700
+ )) {
1701
+ return false;
1702
+ }
1703
+ return true;
1704
+ }
1549
1705
  function captureValidationIssues(value, urlRegex, instancePath) {
1550
1706
  return validateCaptureTemplate(value, urlRegex).map((issue) => ({
1551
1707
  instancePath,
@@ -1739,7 +1895,9 @@ var MESSAGES = {
1739
1895
  "compiler.header-value-required": "Header value is required for set and append operations.",
1740
1896
  "compiler.header-value-unexpected": "Header value must not be provided for remove operation.",
1741
1897
  "compiler.invalid-header-direction": "Invalid header direction.",
1742
- "compiler.invalid-header-operation": "Invalid header operation."
1898
+ "compiler.invalid-header-operation": "Invalid header operation.",
1899
+ "compiler.mock-body-source": "Mock rules require exactly one of body or file.",
1900
+ "compiler.mock-forbidden-header": "The header name is forbidden for mock response headers."
1743
1901
  };
1744
1902
  function copySafeParams(params) {
1745
1903
  const safe = {};
@@ -1777,9 +1935,22 @@ function stableParams(params) {
1777
1935
  const keys = Object.keys(params).sort(compareCodeUnits);
1778
1936
  return keys.map((key) => `${JSON.stringify(key)}:${JSON.stringify(params[key])}`).join(",");
1779
1937
  }
1938
+ var MOCK_HEADER_NAME_PATH = /^\/groups\/\d+\/rules\/\d+\/mock\/headers\/\d+\/name$/;
1939
+ function validationIssueCode(issue) {
1940
+ if (issue.keyword === "mock-body-source") {
1941
+ return "compiler.mock-body-source";
1942
+ }
1943
+ if (issue.keyword === "forbiddenHeader" && MOCK_HEADER_NAME_PATH.test(issue.instancePath)) {
1944
+ return "compiler.mock-forbidden-header";
1945
+ }
1946
+ if (Object.hasOwn(ISSUE_CODES, issue.keyword)) {
1947
+ return ISSUE_CODES[issue.keyword];
1948
+ }
1949
+ return "schema.invalid-value";
1950
+ }
1780
1951
  function mapValidationIssues(issues) {
1781
1952
  return issues.map((issue) => {
1782
- const code = Object.hasOwn(ISSUE_CODES, issue.keyword) ? ISSUE_CODES[issue.keyword] : "schema.invalid-value";
1953
+ const code = validationIssueCode(issue);
1783
1954
  return {
1784
1955
  code,
1785
1956
  severity: "error",
@@ -1953,6 +2124,21 @@ function compileOperations(project) {
1953
2124
  requestBody: rule.requestBody ?? { mode: "replace", body: "" }
1954
2125
  };
1955
2126
  operations.push(operation);
2127
+ } else if (rule.type === "mock") {
2128
+ const mock = rule.mock;
2129
+ if (mock === void 0) {
2130
+ throw new Error("validated mock rule missing mock payload");
2131
+ }
2132
+ const operation = {
2133
+ kind: "mock",
2134
+ groupId: group.id,
2135
+ ruleId: rule.id,
2136
+ name: rule.name,
2137
+ redactSensitiveInLogs,
2138
+ matcher,
2139
+ mock
2140
+ };
2141
+ operations.push(operation);
1956
2142
  } else {
1957
2143
  const operation = {
1958
2144
  kind: "matcher",
@@ -2070,9 +2256,16 @@ function sameOrigin(a, b) {
2070
2256
 
2071
2257
  // packages/runtime/dist/node/index.js
2072
2258
  import { isIP as isIP2 } from "node:net";
2259
+ import { constants, existsSync } from "node:fs";
2260
+ import { open, readlink, realpath } from "node:fs/promises";
2261
+ import { isAbsolute, relative } from "node:path";
2262
+ import { close, fstat, read } from "node:fs";
2263
+ import { createRequire } from "node:module";
2264
+ import { constants as constants2 } from "node:fs";
2265
+ import { open as open2, realpath as realpath2 } from "node:fs/promises";
2266
+ import { isAbsolute as isAbsolute2 } from "node:path";
2073
2267
  import { Buffer as Buffer2 } from "node:buffer";
2074
- import { existsSync } from "node:fs";
2075
- import { createServer as createServer2 } from "node:http";
2268
+ import { existsSync as existsSync2 } from "node:fs";
2076
2269
  import { join as join3 } from "node:path";
2077
2270
  import {
2078
2271
  createServer
@@ -2083,24 +2276,24 @@ import { lookup as dnsLookup } from "node:dns/promises";
2083
2276
  import { request as httpRequest } from "node:http";
2084
2277
  import { request as httpsRequest } from "node:https";
2085
2278
  import { createHash as createHash2, randomBytes, timingSafeEqual } from "node:crypto";
2279
+ import { spawn } from "node:child_process";
2086
2280
  import { randomBytes as randomBytes2 } from "node:crypto";
2087
- import { readFile as readFile2, realpath as realpath2, stat } from "node:fs/promises";
2088
- import { isAbsolute as isAbsolute2, relative as relative2, resolve, sep } from "node:path";
2089
2281
  import { createHash as createHash3 } from "node:crypto";
2090
- import { mkdir as mkdir2, readFile as readFile3, rename, rm, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
2091
- import { basename, dirname as dirname2, isAbsolute as isAbsolute3, join as join2, relative as relative3 } from "node:path";
2282
+ import { createHash as createHash4 } from "node:crypto";
2283
+ import { mkdir as mkdir2, readFile as readFile2, rename, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
2284
+ import { basename, dirname as dirname2, isAbsolute as isAbsolute3, join as join2, relative as relative2 } from "node:path";
2092
2285
  import { generateKeyPairSync, webcrypto } from "node:crypto";
2093
- import { spawn, spawnSync } from "node:child_process";
2094
- import { accessSync, constants as constants2, unlinkSync, writeFileSync } from "node:fs";
2286
+ import { spawn as spawn2, spawnSync } from "node:child_process";
2287
+ import { accessSync, constants as constants3, unlinkSync, writeFileSync } from "node:fs";
2095
2288
  import { tmpdir } from "node:os";
2096
2289
  import { dirname as dirname3, join as join4 } from "node:path";
2097
2290
  import { join as posixJoin } from "node:path/posix";
2098
- import { spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
2099
- import { accessSync as accessSync2, constants as constants3, mkdirSync, unlinkSync as unlinkSync2 } from "node:fs";
2291
+ import { spawn as spawn3, spawnSync as spawnSync2 } from "node:child_process";
2292
+ import { accessSync as accessSync2, constants as constants4, mkdirSync, unlinkSync as unlinkSync2 } from "node:fs";
2100
2293
  import { dirname as dirname4 } from "node:path";
2101
2294
  import { join as posixJoin2 } from "node:path/posix";
2102
- import { spawn as spawn3, spawnSync as spawnSync3 } from "node:child_process";
2103
- import { accessSync as accessSync3, constants as constants4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
2295
+ import { spawn as spawn4, spawnSync as spawnSync3 } from "node:child_process";
2296
+ import { accessSync as accessSync3, constants as constants5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync2 } from "node:fs";
2104
2297
  import { tmpdir as tmpdir2 } from "node:os";
2105
2298
  import { join as join5 } from "node:path";
2106
2299
  import { join as posixJoin3 } from "node:path/posix";
@@ -3347,11 +3540,11 @@ function __metadata(metadataKey, metadataValue) {
3347
3540
  }
3348
3541
  function __awaiter(thisArg, _arguments, P, generator) {
3349
3542
  function adopt(value) {
3350
- return value instanceof P ? value : new P(function(resolve22) {
3351
- resolve22(value);
3543
+ return value instanceof P ? value : new P(function(resolve10) {
3544
+ resolve10(value);
3352
3545
  });
3353
3546
  }
3354
- return new (P || (P = Promise))(function(resolve22, reject) {
3547
+ return new (P || (P = Promise))(function(resolve10, reject) {
3355
3548
  function fulfilled(value) {
3356
3549
  try {
3357
3550
  step(generator.next(value));
@@ -3367,7 +3560,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
3367
3560
  }
3368
3561
  }
3369
3562
  function step(result) {
3370
- result.done ? resolve22(result.value) : adopt(result.value).then(fulfilled, rejected);
3563
+ result.done ? resolve10(result.value) : adopt(result.value).then(fulfilled, rejected);
3371
3564
  }
3372
3565
  step((generator = generator.apply(thisArg, _arguments || [])).next());
3373
3566
  });
@@ -3558,14 +3751,14 @@ function __asyncValues(o) {
3558
3751
  }, i);
3559
3752
  function verb(n) {
3560
3753
  i[n] = o[n] && function(v) {
3561
- return new Promise(function(resolve22, reject) {
3562
- v = o[n](v), settle(resolve22, reject, v.done, v.value);
3754
+ return new Promise(function(resolve10, reject) {
3755
+ v = o[n](v), settle(resolve10, reject, v.done, v.value);
3563
3756
  });
3564
3757
  };
3565
3758
  }
3566
- function settle(resolve22, reject, d, v) {
3759
+ function settle(resolve10, reject, d, v) {
3567
3760
  Promise.resolve(v).then(function(v2) {
3568
- resolve22({ value: v2, done: d });
3761
+ resolve10({ value: v2, done: d });
3569
3762
  }, reject);
3570
3763
  }
3571
3764
  }
@@ -3950,8 +4143,8 @@ var require_build = __commonJS({
3950
4143
  return Buffer.from(buf).toString("base64");
3951
4144
  }
3952
4145
  }
3953
- static FromBase64(base642) {
3954
- const formatted = this.formatString(base642);
4146
+ static FromBase64(base64) {
4147
+ const formatted = this.formatString(base64);
3955
4148
  if (!formatted) {
3956
4149
  return new ArrayBuffer(0);
3957
4150
  }
@@ -4062,14 +4255,14 @@ var require_build = __commonJS({
4062
4255
  static FromUtf16String(text, littleEndian = false) {
4063
4256
  return Utf16Converter.fromString(text, littleEndian);
4064
4257
  }
4065
- static Base64Padding(base642) {
4066
- const padCount = 4 - base642.length % 4;
4258
+ static Base64Padding(base64) {
4259
+ const padCount = 4 - base64.length % 4;
4067
4260
  if (padCount < 4) {
4068
4261
  for (let i = 0; i < padCount; i++) {
4069
- base642 += "=";
4262
+ base64 += "=";
4070
4263
  }
4071
4264
  }
4072
- return base642;
4265
+ return base64;
4073
4266
  }
4074
4267
  static formatString(data) {
4075
4268
  return (data === null || data === void 0 ? void 0 : data.replace(/[\n\r\t ]/g, "")) || "";
@@ -9054,8 +9247,8 @@ var require_convert2 = __commonJS({
9054
9247
  static ToBase64(buffer) {
9055
9248
  return index_js_1.convert.toBase64(buffer);
9056
9249
  }
9057
- static FromBase64(base642) {
9058
- return index_js_1.convert.fromBase64(base642);
9250
+ static FromBase64(base64) {
9251
+ return index_js_1.convert.fromBase64(base64);
9059
9252
  }
9060
9253
  static FromBase64Url(base64url) {
9061
9254
  return index_js_1.convert.fromBase64Url(base64url);
@@ -9087,9 +9280,9 @@ var require_convert2 = __commonJS({
9087
9280
  static FromUtf16String(text, littleEndian = false) {
9088
9281
  return index_js_1.convert.fromUtf16String(text, littleEndian);
9089
9282
  }
9090
- static Base64Padding(base642) {
9091
- const padCount = 4 - base642.length % 4;
9092
- return padCount < 4 ? base642 + "=".repeat(padCount) : base642;
9283
+ static Base64Padding(base64) {
9284
+ const padCount = 4 - base64.length % 4;
9285
+ return padCount < 4 ? base64 + "=".repeat(padCount) : base64;
9093
9286
  }
9094
9287
  static formatString(data) {
9095
9288
  return index_js_1.convert.formatString(data);
@@ -15043,11 +15236,11 @@ function __metadata2(metadataKey, metadataValue) {
15043
15236
  }
15044
15237
  function __awaiter2(thisArg, _arguments, P, generator) {
15045
15238
  function adopt(value) {
15046
- return value instanceof P ? value : new P(function(resolve22) {
15047
- resolve22(value);
15239
+ return value instanceof P ? value : new P(function(resolve10) {
15240
+ resolve10(value);
15048
15241
  });
15049
15242
  }
15050
- return new (P || (P = Promise))(function(resolve22, reject) {
15243
+ return new (P || (P = Promise))(function(resolve10, reject) {
15051
15244
  function fulfilled(value) {
15052
15245
  try {
15053
15246
  step(generator.next(value));
@@ -15063,7 +15256,7 @@ function __awaiter2(thisArg, _arguments, P, generator) {
15063
15256
  }
15064
15257
  }
15065
15258
  function step(result) {
15066
- result.done ? resolve22(result.value) : adopt(result.value).then(fulfilled, rejected);
15259
+ result.done ? resolve10(result.value) : adopt(result.value).then(fulfilled, rejected);
15067
15260
  }
15068
15261
  step((generator = generator.apply(thisArg, _arguments || [])).next());
15069
15262
  });
@@ -15241,14 +15434,14 @@ function __asyncValues2(o) {
15241
15434
  }, i);
15242
15435
  function verb(n) {
15243
15436
  i[n] = o[n] && function(v) {
15244
- return new Promise(function(resolve22, reject) {
15245
- v = o[n](v), settle(resolve22, reject, v.done, v.value);
15437
+ return new Promise(function(resolve10, reject) {
15438
+ v = o[n](v), settle(resolve10, reject, v.done, v.value);
15246
15439
  });
15247
15440
  };
15248
15441
  }
15249
- function settle(resolve22, reject, d, v) {
15442
+ function settle(resolve10, reject, d, v) {
15250
15443
  Promise.resolve(v).then(function(v2) {
15251
- resolve22({ value: v2, done: d });
15444
+ resolve10({ value: v2, done: d });
15252
15445
  }, reject);
15253
15446
  }
15254
15447
  }
@@ -15433,13 +15626,13 @@ var require_lazy_helpers = __commonJS({
15433
15626
  }
15434
15627
  };
15435
15628
  exports.DelayedConstructor = DelayedConstructor;
15436
- function delay2(wrappedConstructor) {
15629
+ function delay(wrappedConstructor) {
15437
15630
  if (typeof wrappedConstructor === "undefined") {
15438
15631
  throw new Error("Attempt to `delay` undefined. Constructor must be wrapped in a callback");
15439
15632
  }
15440
15633
  return new DelayedConstructor(wrappedConstructor);
15441
15634
  }
15442
- exports.delay = delay2;
15635
+ exports.delay = delay;
15443
15636
  }
15444
15637
  });
15445
15638
  var require_injection_token = __commonJS({
@@ -18445,11 +18638,11 @@ var require_x509_cjs = __commonJS({
18445
18638
  const res = [];
18446
18639
  let matches = null;
18447
18640
  while (matches = pattern.exec(pem)) {
18448
- const base642 = matches[3].replace(rEolPattern, "");
18641
+ const base64 = matches[3].replace(rEolPattern, "");
18449
18642
  const pemStruct = {
18450
18643
  type: matches[1],
18451
18644
  headers: [],
18452
- rawData: pvtsutils.Convert.FromBase64(base642)
18645
+ rawData: pvtsutils.Convert.FromBase64(base64)
18453
18646
  };
18454
18647
  const headersString = matches[2];
18455
18648
  if (headersString) {
@@ -18534,9 +18727,9 @@ var require_x509_cjs = __commonJS({
18534
18727
  }
18535
18728
  res.push("");
18536
18729
  }
18537
- const base642 = pvtsutils.Convert.ToBase64(pem.rawData);
18538
- for (let i = 0; i < base642.length; i += 64) {
18539
- res.push(base642.substring(i, i + 64));
18730
+ const base64 = pvtsutils.Convert.ToBase64(pem.rawData);
18731
+ for (let i = 0; i < base64.length; i += 64) {
18732
+ res.push(base64.substring(i, i + 64));
18540
18733
  }
18541
18734
  res.push(`-----END ${upperCaseType}-----`);
18542
18735
  return res.join("\n");
@@ -20915,7 +21108,7 @@ var RETRY_DELAY_BASE = 1e3;
20915
21108
  var COMPLETE_TIMEOUT = 6e4;
20916
21109
  var STREAM_TIMEOUT = 12e4;
20917
21110
  function sleep(ms) {
20918
- return new Promise((resolve22) => setTimeout(resolve22, ms));
21111
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
20919
21112
  }
20920
21113
  function calculateRetryDelay(attempt) {
20921
21114
  return RETRY_DELAY_BASE * 2 ** attempt + Math.random() * 100;
@@ -20943,8 +21136,8 @@ async function fetchWithRetry(url, init, signal) {
20943
21136
  const shouldRetry = response.status === 429 || response.status >= 500;
20944
21137
  if (shouldRetry && attempt < MAX_RETRIES) {
20945
21138
  const retryAfter = response.headers.get("Retry-After");
20946
- const delay2 = retryAfter ? parseInt(retryAfter, 10) * 1e3 : calculateRetryDelay(attempt);
20947
- await sleep(Math.min(delay2, 1e4));
21139
+ const delay = retryAfter ? parseInt(retryAfter, 10) * 1e3 : calculateRetryDelay(attempt);
21140
+ await sleep(Math.min(delay, 1e4));
20948
21141
  continue;
20949
21142
  }
20950
21143
  const errorText = await response.text().catch(() => "");
@@ -20986,8 +21179,8 @@ async function fetchStreamWithRetry(url, init, signal) {
20986
21179
  const shouldRetry = response.status === 429 || response.status >= 500;
20987
21180
  if (shouldRetry && attempt < MAX_RETRIES) {
20988
21181
  const retryAfter = response.headers.get("Retry-After");
20989
- const delay2 = retryAfter ? parseInt(retryAfter, 10) * 1e3 : calculateRetryDelay(attempt);
20990
- await sleep(Math.min(delay2, 1e4));
21182
+ const delay = retryAfter ? parseInt(retryAfter, 10) * 1e3 : calculateRetryDelay(attempt);
21183
+ await sleep(Math.min(delay, 1e4));
20991
21184
  continue;
20992
21185
  }
20993
21186
  const errorText = await response.text().catch(() => "");
@@ -21939,12 +22132,299 @@ var RUNTIME_LIMITS = Object.freeze({
21939
22132
  maxRegexDeadlineMs: 250,
21940
22133
  maxLocalOrigins: 32
21941
22134
  });
22135
+ var F_GETPATH = 50;
22136
+ var MAXPATHLEN = 1024;
22137
+ var O_CLOEXEC = 16777216;
22138
+ var LIB_SYSTEM = "/usr/lib/libSystem.B.dylib";
22139
+ var binding;
22140
+ function loadKoffi() {
22141
+ const require2 = createRequire(import.meta.url);
22142
+ const loaded = require2("koffi");
22143
+ if (typeof loaded.load === "function") {
22144
+ return loaded;
22145
+ }
22146
+ return loaded.default;
22147
+ }
22148
+ function decodePath(value) {
22149
+ if (typeof value !== "string" || value.length === 0 || value.length >= MAXPATHLEN) {
22150
+ return null;
22151
+ }
22152
+ const end = value.indexOf("\0");
22153
+ const path2 = end === -1 ? value : value.slice(0, end);
22154
+ return path2.length > 0 ? path2 : null;
22155
+ }
22156
+ function loadDarwinConfined() {
22157
+ if (binding) return binding;
22158
+ const koffi = loadKoffi();
22159
+ const lib = koffi.load(LIB_SYSTEM);
22160
+ const openat = lib.func(
22161
+ "int openat(int dirfd, const char *pathname, int flags, ...)"
22162
+ );
22163
+ const fcntl = lib.func("int fcntl(int fd, int cmd, ...)");
22164
+ binding = {
22165
+ openat(dirfd, path2, flags) {
22166
+ const fd = openat(dirfd, path2, flags | O_CLOEXEC);
22167
+ if (typeof fd !== "number" || fd < 0) {
22168
+ throw new Error("openat failed");
22169
+ }
22170
+ return fd;
22171
+ },
22172
+ pathOf(fd) {
22173
+ try {
22174
+ const out = ["\0".repeat(MAXPATHLEN)];
22175
+ const result = fcntl(fd, F_GETPATH, "_Out_ char *", out);
22176
+ if (typeof result !== "number" || result < 0) return null;
22177
+ return decodePath(out[0]);
22178
+ } catch {
22179
+ return null;
22180
+ }
22181
+ }
22182
+ };
22183
+ return binding;
22184
+ }
22185
+ function darwinConfinedAvailable() {
22186
+ if (process.platform !== "darwin") return false;
22187
+ try {
22188
+ loadDarwinConfined();
22189
+ return true;
22190
+ } catch {
22191
+ return false;
22192
+ }
22193
+ }
22194
+ function isSingleComponent(component) {
22195
+ return component.length > 0 && component !== "." && component !== ".." && !component.includes("/") && !component.includes("\\") && !component.includes("\0");
22196
+ }
22197
+ function handleFromFd(fd) {
22198
+ let closed = false;
22199
+ return {
22200
+ fd,
22201
+ read(buffer, offset, length, position) {
22202
+ return new Promise((resolve10, reject) => {
22203
+ read(fd, buffer, offset, length, position, (error, bytesRead) => {
22204
+ if (error) reject(error);
22205
+ else resolve10({ bytesRead });
22206
+ });
22207
+ });
22208
+ },
22209
+ stat() {
22210
+ return new Promise((resolve10, reject) => {
22211
+ fstat(fd, (error, stats) => {
22212
+ if (error) reject(error);
22213
+ else resolve10(stats);
22214
+ });
22215
+ });
22216
+ },
22217
+ async close() {
22218
+ if (closed) return;
22219
+ closed = true;
22220
+ await new Promise((resolve10, reject) => {
22221
+ close(fd, (error) => {
22222
+ if (error) reject(error);
22223
+ else resolve10();
22224
+ });
22225
+ });
22226
+ }
22227
+ };
22228
+ }
22229
+ async function openDarwinRelative(dirFd, component, flags) {
22230
+ if (!isSingleComponent(component)) throw new Error("openat rejected");
22231
+ const fd = loadDarwinConfined().openat(dirFd, component, flags);
22232
+ return handleFromFd(fd);
22233
+ }
22234
+ async function darwinPathOf(fd) {
22235
+ return loadDarwinConfined().pathOf(fd);
22236
+ }
22237
+ function isConfinedFileSupported() {
22238
+ if (process.platform === "linux") {
22239
+ return typeof constants.O_NOFOLLOW === "number" && typeof constants.O_DIRECTORY === "number" && typeof constants.O_NONBLOCK === "number" && existsSync("/proc/self/fd");
22240
+ }
22241
+ return darwinConfinedAvailable();
22242
+ }
22243
+ function fdRelativePath(dirFd, component) {
22244
+ return `/proc/self/fd/${dirFd}/${component}`;
22245
+ }
22246
+ function withinRoot(root, candidate) {
22247
+ const rest = relative(root, candidate);
22248
+ return rest.length > 0 && rest !== ".." && !rest.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) && !isAbsolute(rest);
22249
+ }
22250
+ function fromNodeHandle(file) {
22251
+ return {
22252
+ fd: file.fd,
22253
+ read: (buffer, offset, length, position) => file.read(buffer, offset, length, position),
22254
+ stat: () => file.stat(),
22255
+ close: () => file.close()
22256
+ };
22257
+ }
22258
+ var linuxOps = {
22259
+ async openRelative(dirFd, component, flags) {
22260
+ return fromNodeHandle(await open(fdRelativePath(dirFd, component), flags));
22261
+ },
22262
+ async pathOf(fd) {
22263
+ try {
22264
+ return await readlink(`/proc/self/fd/${fd}`);
22265
+ } catch {
22266
+ return null;
22267
+ }
22268
+ }
22269
+ };
22270
+ function platformOps() {
22271
+ if (process.platform === "linux" && isConfinedFileSupported())
22272
+ return linuxOps;
22273
+ if (process.platform === "darwin" && darwinConfinedAvailable()) {
22274
+ return { openRelative: openDarwinRelative, pathOf: darwinPathOf };
22275
+ }
22276
+ return void 0;
22277
+ }
22278
+ async function closeQuietly(file) {
22279
+ try {
22280
+ await file.close();
22281
+ } catch {
22282
+ }
22283
+ }
22284
+ async function proveDescriptorWithinRoot(file, canonicalRoot, pathOf) {
22285
+ const resolved = await pathOf(file.fd);
22286
+ if (resolved === null || !withinRoot(canonicalRoot, resolved)) {
22287
+ return failure("runtime.file-race-rejected");
22288
+ }
22289
+ return { ok: true, value: void 0 };
22290
+ }
22291
+ function openFlags(directory) {
22292
+ if (directory) {
22293
+ return constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW;
22294
+ }
22295
+ return constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
22296
+ }
22297
+ async function openConfinedUsing(root, logicalPath, ops) {
22298
+ const normalized = normalizeLogicalPath(logicalPath);
22299
+ if (normalized === null) return failure("runtime.file-denied");
22300
+ const components = normalized.split("/");
22301
+ let parent;
22302
+ try {
22303
+ const canonicalRoot = await realpath(root);
22304
+ parent = fromNodeHandle(
22305
+ await open(canonicalRoot, constants.O_RDONLY | constants.O_DIRECTORY)
22306
+ );
22307
+ for (let index = 0; index < components.length - 1; index += 1) {
22308
+ const component = components[index] ?? "";
22309
+ const next = await ops.openRelative(
22310
+ parent.fd,
22311
+ component,
22312
+ openFlags(true)
22313
+ );
22314
+ await closeQuietly(parent);
22315
+ parent = next;
22316
+ }
22317
+ const leaf = components[components.length - 1] ?? "";
22318
+ const file = await ops.openRelative(parent.fd, leaf, openFlags(false));
22319
+ await closeQuietly(parent);
22320
+ parent = void 0;
22321
+ try {
22322
+ const containment = await proveDescriptorWithinRoot(
22323
+ file,
22324
+ canonicalRoot,
22325
+ ops.pathOf
22326
+ );
22327
+ if (!containment.ok) {
22328
+ await closeQuietly(file);
22329
+ return containment;
22330
+ }
22331
+ const stat22 = await file.stat();
22332
+ if (!stat22.isFile()) {
22333
+ await closeQuietly(file);
22334
+ return failure("runtime.file-denied");
22335
+ }
22336
+ if (stat22.nlink !== 1) {
22337
+ await closeQuietly(file);
22338
+ return failure("runtime.file-race-rejected");
22339
+ }
22340
+ if (stat22.size > RUNTIME_LIMITS.maxFileBytes) {
22341
+ await closeQuietly(file);
22342
+ return failure("runtime.size-limit");
22343
+ }
22344
+ return { ok: true, value: file };
22345
+ } catch {
22346
+ await closeQuietly(file);
22347
+ return failure("runtime.file-denied");
22348
+ }
22349
+ } catch {
22350
+ return failure("runtime.file-denied");
22351
+ } finally {
22352
+ if (parent !== void 0) await closeQuietly(parent);
22353
+ }
22354
+ }
22355
+ async function openConfinedFile(root, logicalPath) {
22356
+ if (normalizeLogicalPath(logicalPath) === null) {
22357
+ return failure("runtime.file-denied");
22358
+ }
22359
+ const ops = platformOps();
22360
+ if (ops === void 0) return failure("runtime.platform-unsupported");
22361
+ return openConfinedUsing(root, logicalPath, ops);
22362
+ }
22363
+ async function closeQuietly2(file) {
22364
+ try {
22365
+ await file.close();
22366
+ } catch {
22367
+ }
22368
+ }
22369
+ async function readOpenedConfinedFile(file, signal) {
22370
+ try {
22371
+ const chunks = [];
22372
+ let total = 0;
22373
+ let position = 0;
22374
+ while (true) {
22375
+ if (signal?.aborted) return failure("runtime.timeout");
22376
+ const buffer = Buffer.allocUnsafe(
22377
+ Math.min(64 * 1024, RUNTIME_LIMITS.maxFileBytes - total + 1)
22378
+ );
22379
+ const read2 = await file.read(buffer, 0, buffer.byteLength, position);
22380
+ if (read2.bytesRead === 0) break;
22381
+ total += read2.bytesRead;
22382
+ if (total > RUNTIME_LIMITS.maxFileBytes)
22383
+ return failure("runtime.size-limit");
22384
+ chunks.push(Uint8Array.from(buffer.subarray(0, read2.bytesRead)));
22385
+ position += read2.bytesRead;
22386
+ }
22387
+ const finalStat = await file.stat();
22388
+ if (!finalStat.isFile() || finalStat.nlink !== 1 || finalStat.size > RUNTIME_LIMITS.maxFileBytes || finalStat.size !== total) {
22389
+ return failure(
22390
+ finalStat.size > RUNTIME_LIMITS.maxFileBytes ? "runtime.size-limit" : "runtime.file-race-rejected"
22391
+ );
22392
+ }
22393
+ const output = new Uint8Array(total);
22394
+ let offset = 0;
22395
+ for (const chunk of chunks) {
22396
+ output.set(chunk, offset);
22397
+ offset += chunk.byteLength;
22398
+ }
22399
+ return { ok: true, value: output };
22400
+ } catch {
22401
+ return signal?.aborted ? failure("runtime.timeout") : failure("runtime.file-denied");
22402
+ }
22403
+ }
22404
+ async function readConfinedPathBytes(root, logicalPath, signal) {
22405
+ const normalized = normalizeLogicalPath(logicalPath);
22406
+ if (normalized === null) return failure("runtime.file-denied");
22407
+ if (!isConfinedFileSupported())
22408
+ return failure("runtime.platform-unsupported");
22409
+ if (signal?.aborted) return failure("runtime.timeout");
22410
+ const opened = await openConfinedFile(root, normalized);
22411
+ if (!opened.ok) return opened;
22412
+ const file = opened.value;
22413
+ try {
22414
+ return await readOpenedConfinedFile(file, signal);
22415
+ } finally {
22416
+ await closeQuietly2(file);
22417
+ }
22418
+ }
21942
22419
  var PROTOCOL = "v1";
21943
22420
  var ENVELOPE_MAX_BYTES = 64 * 1024;
21944
22421
  var MAX_PAC_ROUTES = 256;
21945
- var FORBIDDEN_BODY_KEYS = /* @__PURE__ */ new Set(["body", "requestBody", "responseBody"]);
21946
- var MOCK_BODY_KEY = "mockBody";
21947
- var MOCK_BODY_ALLOWED_TYPES = /* @__PURE__ */ new Set(["mock.response"]);
22422
+ var FORBIDDEN_BODY_KEYS = /* @__PURE__ */ new Set([
22423
+ "body",
22424
+ "requestBody",
22425
+ "responseBody",
22426
+ "mockBody"
22427
+ ]);
21948
22428
  var BODY_CHECK_EXEMPT_TYPES = /* @__PURE__ */ new Set(["runtime.project.set"]);
21949
22429
  var ENVELOPE_MESSAGE_TYPES = /* @__PURE__ */ new Set([
21950
22430
  "runtime.start",
@@ -21962,12 +22442,13 @@ var ENVELOPE_MESSAGE_TYPES = /* @__PURE__ */ new Set([
21962
22442
  "authorize.request",
21963
22443
  "authorize.response",
21964
22444
  "mock.connect",
21965
- "mock.request",
21966
- "mock.response",
21967
22445
  "ai.complete",
21968
22446
  "ai.stream.chunk",
21969
22447
  "ai.error",
21970
- "ai.status"
22448
+ "ai.status",
22449
+ "runtime.pick-directory",
22450
+ "runtime.check-directory",
22451
+ "runtime.set-file-root"
21971
22452
  ]);
21972
22453
  var EnvelopeError = class extends Error {
21973
22454
  constructor(message) {
@@ -21996,11 +22477,6 @@ function assertNoBodyContent(type, metadata) {
21996
22477
  "envelope must not carry request or response body content"
21997
22478
  );
21998
22479
  }
21999
- if (MOCK_BODY_KEY in metadata && !MOCK_BODY_ALLOWED_TYPES.has(type)) {
22000
- throw new EnvelopeError(
22001
- "mockBody is only permitted on the mock.response envelope"
22002
- );
22003
- }
22004
22480
  }
22005
22481
  function utf8ByteLength(value) {
22006
22482
  return Buffer.byteLength(value, "utf8");
@@ -22080,6 +22556,45 @@ function describeExtensionIdMismatch(expectedExtensionId, allowedOrigins) {
22080
22556
  `Run: ${command}`
22081
22557
  ].join("\n");
22082
22558
  }
22559
+ async function resolveConfinedRoot(value) {
22560
+ if (typeof value !== "string" || value.length === 0 || !isAbsolute2(value)) {
22561
+ return failure("runtime.root-invalid");
22562
+ }
22563
+ let directory;
22564
+ try {
22565
+ directory = await open2(value, constants2.O_RDONLY | constants2.O_DIRECTORY);
22566
+ const info = await directory.stat();
22567
+ if (!info.isDirectory()) return failure("runtime.root-invalid");
22568
+ return { ok: true, value: await realpath2(value) };
22569
+ } catch {
22570
+ return failure("runtime.root-invalid");
22571
+ } finally {
22572
+ await directory?.close().catch(() => void 0);
22573
+ }
22574
+ }
22575
+ var MOCK_LISTENER_PREFIX = "/.rogatio/mock/";
22576
+ function isSingleSegment(value) {
22577
+ return value.length > 0 && value.length <= 2048 && value !== "." && value !== ".." && !value.includes("/") && !value.includes("\\") && !value.includes("?") && !hasControl(value);
22578
+ }
22579
+ function parseMockRedirect(rawUrl) {
22580
+ const queryAt = rawUrl.indexOf("?");
22581
+ const path2 = queryAt === -1 ? rawUrl : rawUrl.slice(0, queryAt);
22582
+ if (!path2.startsWith(MOCK_LISTENER_PREFIX)) return null;
22583
+ const rest = path2.slice(MOCK_LISTENER_PREFIX.length);
22584
+ const slash = rest.indexOf("/");
22585
+ if (slash <= 0) return null;
22586
+ if (rest.indexOf("/", slash + 1) !== -1) return null;
22587
+ let token;
22588
+ let digest;
22589
+ try {
22590
+ token = decodeURIComponent(rest.slice(0, slash));
22591
+ digest = decodeURIComponent(rest.slice(slash + 1));
22592
+ } catch {
22593
+ return null;
22594
+ }
22595
+ if (!isSingleSegment(token) || !isSingleSegment(digest)) return null;
22596
+ return { token, digest };
22597
+ }
22083
22598
  function supportedContentType(value) {
22084
22599
  if (value === void 0) return false;
22085
22600
  const mediaType = value.split(";", 1)[0]?.trim().toLowerCase() ?? "";
@@ -22124,7 +22639,7 @@ function discardRegexWorker(worker) {
22124
22639
  function bootRegexWorker() {
22125
22640
  if (regexWorker !== null) return Promise.resolve(regexWorker);
22126
22641
  if (regexWorkerBoot !== null) return regexWorkerBoot;
22127
- const boot = new Promise((resolve22, reject) => {
22642
+ const boot = new Promise((resolve10, reject) => {
22128
22643
  const start = () => {
22129
22644
  let worker;
22130
22645
  try {
@@ -22158,7 +22673,7 @@ function bootRegexWorker() {
22158
22673
  }
22159
22674
  });
22160
22675
  regexWorker = worker;
22161
- resolve22(worker);
22676
+ resolve10(worker);
22162
22677
  });
22163
22678
  worker.once("error", (error) => {
22164
22679
  if (settled) return;
@@ -22176,9 +22691,9 @@ function bootRegexWorker() {
22176
22691
  });
22177
22692
  };
22178
22693
  if (regexWorkerBootDelayForTestsMs > 0) {
22179
- const delay2 = regexWorkerBootDelayForTestsMs;
22694
+ const delay = regexWorkerBootDelayForTestsMs;
22180
22695
  regexWorkerBootDelayForTestsMs = 0;
22181
- setTimeout(start, delay2).unref();
22696
+ setTimeout(start, delay).unref();
22182
22697
  } else {
22183
22698
  start();
22184
22699
  }
@@ -22190,7 +22705,7 @@ function bootRegexWorker() {
22190
22705
  return boot;
22191
22706
  }
22192
22707
  function runOnRegexWorker(worker, id2, text, pattern, replacement, deadlineMs) {
22193
- return new Promise((resolve22, reject) => {
22708
+ return new Promise((resolve10, reject) => {
22194
22709
  let timer = null;
22195
22710
  const cleanup = () => {
22196
22711
  if (timer !== null) clearTimeout(timer);
@@ -22210,7 +22725,7 @@ function runOnRegexWorker(worker, id2, text, pattern, replacement, deadlineMs) {
22210
22725
  }
22211
22726
  if (msg.type === "result") {
22212
22727
  cleanup();
22213
- if (msg.ok && typeof msg.out === "string") resolve22(msg.out);
22728
+ if (msg.ok && typeof msg.out === "string") resolve10(msg.out);
22214
22729
  else reject(new Error(msg.error || "regex-invalid"));
22215
22730
  }
22216
22731
  };
@@ -22336,7 +22851,7 @@ function responseHeaders(response) {
22336
22851
  }
22337
22852
  var defaultTransport = {
22338
22853
  request(options) {
22339
- return new Promise((resolve22, reject) => {
22854
+ return new Promise((resolve10, reject) => {
22340
22855
  const lookup = (_hostname, _options, callback) => {
22341
22856
  callback(null, options.address, options.address.includes(":") ? 6 : 4);
22342
22857
  };
@@ -22355,14 +22870,14 @@ var defaultTransport = {
22355
22870
  const request = options.protocol === "https:" ? httpsRequest(
22356
22871
  { ...requestOptions, servername: options.servername },
22357
22872
  (response) => {
22358
- resolve22({
22873
+ resolve10({
22359
22874
  status: response.statusCode ?? 0,
22360
22875
  headers: responseHeaders(response),
22361
22876
  body: response
22362
22877
  });
22363
22878
  }
22364
22879
  ) : httpRequest(requestOptions, (response) => {
22365
- resolve22({
22880
+ resolve10({
22366
22881
  status: response.statusCode ?? 0,
22367
22882
  headers: responseHeaders(response),
22368
22883
  body: response
@@ -22374,7 +22889,7 @@ var defaultTransport = {
22374
22889
  }
22375
22890
  };
22376
22891
  function headerValue(headers, name) {
22377
- return headers.find(([headerName]) => headerName.toLowerCase() === name)?.[1];
22892
+ return headers.find(([headerName2]) => headerName2.toLowerCase() === name)?.[1];
22378
22893
  }
22379
22894
  function responseHeaderBytes(headers) {
22380
22895
  let bytes = 0;
@@ -22714,14 +23229,14 @@ function headerValue2(headers, name) {
22714
23229
  return raw;
22715
23230
  }
22716
23231
  function readBoundedBody(req, maxBytes) {
22717
- return new Promise((resolve22) => {
23232
+ return new Promise((resolve10) => {
22718
23233
  const chunks = [];
22719
23234
  let total = 0;
22720
23235
  let settled = false;
22721
23236
  const finish2 = (result) => {
22722
23237
  if (settled) return;
22723
23238
  settled = true;
22724
- resolve22(result);
23239
+ resolve10(result);
22725
23240
  };
22726
23241
  req.on("data", (chunk) => {
22727
23242
  total += chunk.byteLength;
@@ -22785,7 +23300,7 @@ function pipeSockets(a, b) {
22785
23300
  async function upstreamHttp(method, target, headers, body) {
22786
23301
  const port = target.port ? Number(target.port) : 80;
22787
23302
  const path2 = `${target.pathname}${target.search}`;
22788
- return new Promise((resolve22, reject) => {
23303
+ return new Promise((resolve10, reject) => {
22789
23304
  const socket = connect(port, target.hostname);
22790
23305
  let buf = Buffer.alloc(0);
22791
23306
  let settled = false;
@@ -22809,9 +23324,9 @@ ${lines}\r
22809
23324
  socket.on("data", (chunk) => {
22810
23325
  const piece = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
22811
23326
  buf = Buffer.concat([buf, piece]);
22812
- const sep2 = buf.indexOf("\r\n\r\n");
22813
- if (sep2 < 0) return;
22814
- const head = buf.subarray(0, sep2).toString("latin1");
23327
+ const sep = buf.indexOf("\r\n\r\n");
23328
+ if (sep < 0) return;
23329
+ const head = buf.subarray(0, sep).toString("latin1");
22815
23330
  const statusMatch = /^HTTP\/1\.[01] (\d+)(?: (.*))?/.exec(
22816
23331
  head.split("\r\n")[0] ?? ""
22817
23332
  );
@@ -22835,13 +23350,13 @@ ${lines}\r
22835
23350
  const clRaw = headerMap["content-length"];
22836
23351
  const hasContentLength = clRaw !== void 0;
22837
23352
  const cl = typeof clRaw === "string" ? Number(clRaw) : Array.isArray(clRaw) ? Number(clRaw[0]) : Number.NaN;
22838
- const bodyStart = sep2 + 4;
23353
+ const bodyStart = sep + 4;
22839
23354
  if (hasContentLength && Number.isFinite(cl) && cl >= 0) {
22840
23355
  if (buf.length - bodyStart < cl) return;
22841
23356
  if (settled) return;
22842
23357
  settled = true;
22843
23358
  socket.destroy();
22844
- resolve22({
23359
+ resolve10({
22845
23360
  status,
22846
23361
  statusMessage,
22847
23362
  headers: headerMap,
@@ -22852,12 +23367,12 @@ ${lines}\r
22852
23367
  });
22853
23368
  socket.on("end", () => {
22854
23369
  if (settled) return;
22855
- const sep2 = buf.indexOf("\r\n\r\n");
22856
- if (sep2 < 0) {
23370
+ const sep = buf.indexOf("\r\n\r\n");
23371
+ if (sep < 0) {
22857
23372
  fail(new Error("upstream closed early"));
22858
23373
  return;
22859
23374
  }
22860
- const head = buf.subarray(0, sep2).toString("latin1");
23375
+ const head = buf.subarray(0, sep).toString("latin1");
22861
23376
  const statusMatch = /^HTTP\/1\.[01] (\d+)(?: (.*))?/.exec(
22862
23377
  head.split("\r\n")[0] ?? ""
22863
23378
  );
@@ -22871,7 +23386,7 @@ ${lines}\r
22871
23386
  if (i <= 0) continue;
22872
23387
  headerMap[line.slice(0, i).trim().toLowerCase()] = line.slice(i + 1).trim();
22873
23388
  }
22874
- let body2 = Buffer.from(buf.subarray(sep2 + 4));
23389
+ let body2 = Buffer.from(buf.subarray(sep + 4));
22875
23390
  const te = headerMap["transfer-encoding"];
22876
23391
  const teValue = Array.isArray(te) ? te[0] : te;
22877
23392
  if (typeof teValue === "string" && teValue.toLowerCase().includes("chunked")) {
@@ -22880,7 +23395,7 @@ ${lines}\r
22880
23395
  headerMap["content-length"] = String(body2.byteLength);
22881
23396
  }
22882
23397
  settled = true;
22883
- resolve22({
23398
+ resolve10({
22884
23399
  status: Number(statusMatch[1]),
22885
23400
  statusMessage: statusMatch[2]?.trim() ?? "",
22886
23401
  headers: headerMap,
@@ -22955,6 +23470,10 @@ function authorizeOp(policy, op, url, method, headers) {
22955
23470
  }
22956
23471
  async function startInterceptProxy(options = {}) {
22957
23472
  let policy = options.policy ?? null;
23473
+ const serveMock = options.serveMock;
23474
+ let activeMocks = 0;
23475
+ const mockAborts = /* @__PURE__ */ new Set();
23476
+ let stopped = false;
22958
23477
  const server = createServer((req, res) => {
22959
23478
  void handleHttp(req, res);
22960
23479
  });
@@ -22968,6 +23487,10 @@ async function startInterceptProxy(options = {}) {
22968
23487
  await handleResponseBodyRedirect(req, res, targetUrl);
22969
23488
  return;
22970
23489
  }
23490
+ if (targetUrl.startsWith(MOCK_LISTENER_PREFIX.slice(0, -1))) {
23491
+ await handleMockRedirect(req, res, targetUrl);
23492
+ return;
23493
+ }
22971
23494
  if (!/^https?:\/\//i.test(targetUrl)) {
22972
23495
  res.writeHead(400);
22973
23496
  res.end();
@@ -23083,6 +23606,71 @@ async function startInterceptProxy(options = {}) {
23083
23606
  }
23084
23607
  }
23085
23608
  }
23609
+ function endEmpty(res, status) {
23610
+ if (res.writableEnded) return;
23611
+ res.writeHead(status, {
23612
+ "cache-control": "no-store",
23613
+ "content-length": "0"
23614
+ });
23615
+ res.end();
23616
+ }
23617
+ function discardRequestBody(req) {
23618
+ req.on("data", () => {
23619
+ });
23620
+ req.resume();
23621
+ }
23622
+ async function handleMockRedirect(req, res, rawUrl) {
23623
+ discardRequestBody(req);
23624
+ const parsed = parseMockRedirect(rawUrl);
23625
+ if (parsed === null) {
23626
+ endEmpty(res, 400);
23627
+ return;
23628
+ }
23629
+ const active = policy;
23630
+ if (serveMock === void 0 || active === null || active.presetDigest === void 0 || active.presetDigest.length === 0) {
23631
+ endEmpty(res, 403);
23632
+ return;
23633
+ }
23634
+ if (parsed.digest !== active.presetDigest) {
23635
+ endEmpty(res, 403);
23636
+ return;
23637
+ }
23638
+ if (activeMocks >= RUNTIME_LIMITS.maxConcurrentOperations) {
23639
+ endEmpty(res, 429);
23640
+ return;
23641
+ }
23642
+ activeMocks += 1;
23643
+ const abort = new AbortController();
23644
+ mockAborts.add(abort);
23645
+ const cancel = () => {
23646
+ if (!res.writableFinished) abort.abort();
23647
+ };
23648
+ req.on("aborted", cancel);
23649
+ res.on("close", cancel);
23650
+ try {
23651
+ const rendered = await serveMock({
23652
+ token: parsed.token,
23653
+ method: (req.method ?? "GET").toUpperCase(),
23654
+ signal: abort.signal
23655
+ });
23656
+ if (abort.signal.aborted || res.writableEnded) return;
23657
+ if (!rendered.ok) {
23658
+ const status = rendered.error.code === "runtime.unsupported-method" ? 405 : rendered.error.code === "runtime.mock-unknown" ? 404 : 502;
23659
+ endEmpty(res, status);
23660
+ return;
23661
+ }
23662
+ res.writeHead(
23663
+ rendered.value.status,
23664
+ rendered.value.headers.map(([name, value]) => [name, value])
23665
+ );
23666
+ res.end(Buffer.from(rendered.value.bodyBytes));
23667
+ } catch {
23668
+ endEmpty(res, 502);
23669
+ } finally {
23670
+ mockAborts.delete(abort);
23671
+ activeMocks = Math.max(0, activeMocks - 1);
23672
+ }
23673
+ }
23086
23674
  async function handleResponseBodyRedirect(req, res, rawUrl) {
23087
23675
  const method = (req.method ?? "GET").toUpperCase();
23088
23676
  if (method !== "GET") {
@@ -23194,7 +23782,7 @@ async function startInterceptProxy(options = {}) {
23194
23782
  });
23195
23783
  clientSocket.on("error", () => upstream.destroy());
23196
23784
  }
23197
- const endpoint = await new Promise((resolve22, reject) => {
23785
+ const endpoint = await new Promise((resolve10, reject) => {
23198
23786
  server.once("error", reject);
23199
23787
  server.listen(0, "127.0.0.1", () => {
23200
23788
  const addr = server.address();
@@ -23202,7 +23790,7 @@ async function startInterceptProxy(options = {}) {
23202
23790
  reject(new Error("intercept-proxy bind failed"));
23203
23791
  return;
23204
23792
  }
23205
- resolve22({ host: "127.0.0.1", port: addr.port });
23793
+ resolve10({ host: "127.0.0.1", port: addr.port });
23206
23794
  });
23207
23795
  });
23208
23796
  return {
@@ -23211,8 +23799,12 @@ async function startInterceptProxy(options = {}) {
23211
23799
  policy = next;
23212
23800
  },
23213
23801
  async stop() {
23214
- await new Promise((resolve22, reject) => {
23215
- server.close((err) => err ? reject(err) : resolve22());
23802
+ if (stopped) return;
23803
+ stopped = true;
23804
+ for (const abort of mockAborts) abort.abort();
23805
+ server.closeAllConnections();
23806
+ await new Promise((resolve10, reject) => {
23807
+ server.close((err) => err ? reject(err) : resolve10());
23216
23808
  });
23217
23809
  }
23218
23810
  };
@@ -23315,7 +23907,7 @@ async function startInterception(activation, policyDigest, extensionId, pacRoute
23315
23907
  return { kind: "unsupported", reasons: ["no-interception-provider"] };
23316
23908
  }
23317
23909
  const capabilities = registeredProvider.detect();
23318
- if (!capabilities.supported) {
23910
+ if (pacRoutes.length > 0 && !capabilities.supported) {
23319
23911
  return { kind: "unsupported", reasons: capabilities.reasons };
23320
23912
  }
23321
23913
  if (currentSession !== null) {
@@ -23546,68 +24138,194 @@ function closeCapabilityState(state) {
23546
24138
  state.sessions.length = 0;
23547
24139
  state.bootstrapDigest.fill(0);
23548
24140
  }
23549
- function withinRoot2(root, candidate) {
23550
- const rest = relative2(root, candidate);
23551
- return rest.length > 0 && rest !== ".." && !rest.startsWith(`..${sep}`) && !isAbsolute2(rest);
23552
- }
23553
- async function readMockFile(root, logicalPath) {
23554
- const normalized = normalizeLogicalPath(logicalPath);
23555
- if (normalized === null) return failure("runtime.file-denied");
23556
- try {
23557
- const canonicalRoot = await realpath2(root);
23558
- const candidate = resolve(canonicalRoot, ...normalized.split("/"));
23559
- const actualPath = await realpath2(candidate);
23560
- if (!withinRoot2(canonicalRoot, actualPath))
23561
- return failure("runtime.file-denied");
23562
- const metadata = await stat(actualPath);
23563
- if (!metadata.isFile()) return failure("runtime.file-denied");
23564
- if (metadata.size > RUNTIME_LIMITS.maxFileBytes)
23565
- return failure("runtime.size-limit");
23566
- const bytes = await readFile2(actualPath);
23567
- if (bytes.byteLength > RUNTIME_LIMITS.maxFileBytes)
23568
- return failure("runtime.size-limit");
23569
- new TextDecoder("utf-8", { fatal: true }).decode(bytes);
23570
- return { ok: true, value: bytes };
23571
- } catch {
23572
- return failure("runtime.file-denied");
24141
+ var WINDOWS_FOLDER_SCRIPT = [
24142
+ "Add-Type -AssemblyName System.Windows.Forms",
24143
+ "$dialog = New-Object System.Windows.Forms.FolderBrowserDialog",
24144
+ "$dialog.Description = 'Mock files folder'",
24145
+ "if ($dialog.ShowDialog() -eq 'OK') { $dialog.SelectedPath }"
24146
+ ].join("; ");
24147
+ function directoryDialogCommands(platform = process.platform) {
24148
+ if (platform === "darwin") {
24149
+ return [
24150
+ {
24151
+ command: "osascript",
24152
+ args: [
24153
+ "-e",
24154
+ 'POSIX path of (choose folder with prompt "Choose the mock files folder")'
24155
+ ]
24156
+ }
24157
+ ];
24158
+ }
24159
+ if (platform === "win32") {
24160
+ return [
24161
+ {
24162
+ command: "powershell.exe",
24163
+ args: ["-NoProfile", "-STA", "-Command", WINDOWS_FOLDER_SCRIPT]
24164
+ }
24165
+ ];
23573
24166
  }
24167
+ if (platform === "linux") {
24168
+ return [
24169
+ {
24170
+ command: "zenity",
24171
+ args: ["--file-selection", "--directory", "--title=Mock files folder"]
24172
+ },
24173
+ {
24174
+ command: "kdialog",
24175
+ args: ["--getexistingdirectory", "--title", "Mock files folder"]
24176
+ }
24177
+ ];
24178
+ }
24179
+ return [];
24180
+ }
24181
+ var MAX_PICK_STDOUT = 4096;
24182
+ function runDialog(command, args) {
24183
+ return new Promise((resolve10) => {
24184
+ let settled = false;
24185
+ const finish2 = (result) => {
24186
+ if (settled) return;
24187
+ settled = true;
24188
+ resolve10(result);
24189
+ };
24190
+ const options = {
24191
+ stdio: ["ignore", "pipe", "ignore"],
24192
+ windowsHide: false
24193
+ };
24194
+ const child = spawn(command, [...args], options);
24195
+ const chunks = [];
24196
+ let size = 0;
24197
+ child.stdout?.on("data", (chunk) => {
24198
+ size += chunk.length;
24199
+ if (size <= MAX_PICK_STDOUT) chunks.push(chunk);
24200
+ });
24201
+ child.on("error", (error) => {
24202
+ finish2({ code: null, stdout: "", errorCode: error.code });
24203
+ });
24204
+ child.on("close", (code) => {
24205
+ finish2({
24206
+ code,
24207
+ stdout: Buffer.concat(chunks).toString("utf8"),
24208
+ ...size > MAX_PICK_STDOUT ? { errorCode: "too-large" } : {}
24209
+ });
24210
+ });
24211
+ });
24212
+ }
24213
+ async function pickDirectory(run = runDialog, platform = process.platform) {
24214
+ const commands = directoryDialogCommands(platform);
24215
+ if (commands.length === 0) {
24216
+ return { ok: false, code: "runtime.picker-unavailable" };
24217
+ }
24218
+ for (const dialog of commands) {
24219
+ const result = await run(dialog.command, dialog.args);
24220
+ if (result.errorCode === "ENOENT") continue;
24221
+ if (result.errorCode !== void 0) {
24222
+ return { ok: false, code: "runtime.picker-unavailable" };
24223
+ }
24224
+ if (result.code === 0) {
24225
+ const path2 = result.stdout.trim();
24226
+ return { ok: true, path: path2.length === 0 ? null : path2 };
24227
+ }
24228
+ if (result.code === 1) return { ok: true, path: null };
24229
+ return { ok: false, code: "runtime.picker-unavailable" };
24230
+ }
24231
+ return { ok: false, code: "runtime.picker-unavailable" };
24232
+ }
24233
+ async function readMockFile(root, logicalPath, signal) {
24234
+ return readConfinedPathBytes(root, logicalPath, signal);
23574
24235
  }
23575
24236
  function mintToken() {
23576
24237
  return randomBytes2(32).toString("hex");
23577
24238
  }
24239
+ function headerName(name) {
24240
+ return name.toLowerCase();
24241
+ }
24242
+ function waitForDelay(delayMs, signal) {
24243
+ if (signal?.aborted) return Promise.resolve(failure("runtime.timeout"));
24244
+ if (delayMs === void 0 || delayMs <= 0) {
24245
+ return Promise.resolve({ ok: true, value: void 0 });
24246
+ }
24247
+ return new Promise((resolve10) => {
24248
+ const onAbort = () => {
24249
+ clearTimeout(timer);
24250
+ resolve10(failure("runtime.timeout"));
24251
+ };
24252
+ const timer = setTimeout(() => {
24253
+ signal?.removeEventListener("abort", onAbort);
24254
+ resolve10({ ok: true, value: void 0 });
24255
+ }, delayMs);
24256
+ signal?.addEventListener("abort", onAbort, { once: true });
24257
+ });
24258
+ }
23578
24259
  async function renderMockResponse(options) {
23579
- const { mock, fileRoot } = options;
23580
- let bodyBytes;
24260
+ const { mock, fileRoot, method, signal } = options;
24261
+ const waited = await waitForDelay(mock.delayMs, signal);
24262
+ if (!waited.ok) return waited;
24263
+ let fullBytes;
23581
24264
  if (mock.body !== void 0) {
23582
- bodyBytes = new TextEncoder().encode(mock.body);
24265
+ fullBytes = new TextEncoder().encode(mock.body);
23583
24266
  } else if (mock.file !== void 0) {
23584
24267
  if (fileRoot === void 0) return failure("runtime.file-denied");
23585
- const read = await readMockFile(fileRoot, mock.file);
23586
- if (!read.ok) return failure("runtime.file-denied");
23587
- bodyBytes = read.value;
24268
+ if (!isConfinedFileSupported()) {
24269
+ return failure("runtime.platform-unsupported");
24270
+ }
24271
+ const read2 = await readMockFile(fileRoot, mock.file, signal);
24272
+ if (!read2.ok) return read2;
24273
+ fullBytes = read2.value;
23588
24274
  } else {
23589
24275
  return failure("runtime.file-denied");
23590
24276
  }
23591
24277
  const headers = [];
23592
24278
  let hasContentType = false;
24279
+ let hasCacheControl = false;
23593
24280
  for (const header of mock.headers ?? []) {
24281
+ const name = headerName(header.name);
24282
+ if (name === "content-length") continue;
24283
+ if (name === "content-type") hasContentType = true;
24284
+ if (name === "cache-control") hasCacheControl = true;
23594
24285
  headers.push([header.name, header.value]);
23595
- if (header.name.toLowerCase() === "content-type") hasContentType = true;
23596
24286
  }
23597
24287
  if (!hasContentType) {
23598
- headers.push(["Content-Type", "text/plain; charset=UTF-8"]);
24288
+ headers.push(["Content-Type", "application/octet-stream"]);
23599
24289
  }
23600
- headers.push(["Content-Length", String(bodyBytes.byteLength)]);
23601
- headers.push(["Cache-Control", "no-store"]);
24290
+ headers.push(["Content-Length", String(fullBytes.byteLength)]);
24291
+ if (!hasCacheControl) headers.push(["Cache-Control", "no-store"]);
23602
24292
  return {
23603
24293
  ok: true,
23604
24294
  value: {
23605
24295
  status: mock.status,
23606
24296
  headers,
23607
- bodyBytes
24297
+ bodyBytes: method === "HEAD" ? new Uint8Array() : fullBytes
23608
24298
  }
23609
24299
  };
23610
24300
  }
24301
+ var MAX_MOCK_FILE_ERRORS = 64;
24302
+ var FILE_ERROR_CODES = /* @__PURE__ */ new Set([
24303
+ "runtime.file-denied",
24304
+ "runtime.file-race-rejected",
24305
+ "runtime.size-limit",
24306
+ "runtime.platform-unsupported"
24307
+ ]);
24308
+ function isMockFileError(code) {
24309
+ return FILE_ERROR_CODES.has(code);
24310
+ }
24311
+ function recordMockFileError(errors, ruleId, code) {
24312
+ if (!isMockFileError(code)) return;
24313
+ if (errors.has(ruleId)) errors.delete(ruleId);
24314
+ errors.set(ruleId, code);
24315
+ while (errors.size > MAX_MOCK_FILE_ERRORS) {
24316
+ const oldest = errors.keys().next().value;
24317
+ if (oldest === void 0) break;
24318
+ errors.delete(oldest);
24319
+ }
24320
+ }
24321
+ function clearMockFileError(errors, ruleId) {
24322
+ errors.delete(ruleId);
24323
+ }
24324
+ function listMockFileErrors(errors) {
24325
+ return [...errors.entries()].map(([ruleId, code]) => ({ ruleId, code })).sort(
24326
+ (left, right) => left.ruleId < right.ruleId ? -1 : left.ruleId > right.ruleId ? 1 : 0
24327
+ );
24328
+ }
23611
24329
  function stringValue(value) {
23612
24330
  const encoded = JSON.stringify(value);
23613
24331
  if (encoded === void 0) throw new Error("invalid string");
@@ -23654,10 +24372,10 @@ function mockHeaderValue(header) {
23654
24372
  }
23655
24373
  function mockValue(mock) {
23656
24374
  const headers = mock.headers === void 0 ? "" : `,"headers":[${mock.headers.map(mockHeaderValue).join(",")}]`;
23657
- const delay2 = mock.delayMs === void 0 ? "" : `,"delayMs":${String(mock.delayMs)}`;
24375
+ const delay = mock.delayMs === void 0 ? "" : `,"delayMs":${String(mock.delayMs)}`;
23658
24376
  const body = mock.body !== void 0 ? `,"body":${stringValue(mock.body)}` : "";
23659
24377
  const file = mock.file !== void 0 ? `,"file":${stringValue(mock.file)}` : "";
23660
- return `{"ruleId":${stringValue(mock.ruleId)},"status":${String(mock.status)}${headers}${delay2}${body}${file}}`;
24378
+ return `{"ruleId":${stringValue(mock.ruleId)},"status":${String(mock.status)}${headers}${delay}${body}${file}}`;
23661
24379
  }
23662
24380
  function sortMocks(mocks) {
23663
24381
  return [...mocks].sort(
@@ -23701,7 +24419,7 @@ function normalizeMockHeader(value) {
23701
24419
  const record = value;
23702
24420
  if (typeof record.name !== "string" || record.name.length === 0 || record.name.length > LIMITS.maxMockHeaderNameLength || hasControl(record.name) || record.name.includes(":"))
23703
24421
  return null;
23704
- if (typeof record.value !== "string" || record.value.length > LIMITS.maxMockHeaderValueLength)
24422
+ if (typeof record.value !== "string" || record.value.length > LIMITS.maxMockHeaderValueLength || hasControl(record.value))
23705
24423
  return null;
23706
24424
  return { name: record.name, value: record.value };
23707
24425
  }
@@ -23905,6 +24623,26 @@ function makeGrant(value, matcherById) {
23905
24623
  method: record.method
23906
24624
  });
23907
24625
  }
24626
+ function parseEnabledGroupIds(metadata, groupIds) {
24627
+ if (!hasOwn(metadata, "enabledGroupIds")) {
24628
+ return { ok: true, value: void 0 };
24629
+ }
24630
+ const value = metadata.enabledGroupIds;
24631
+ if (!Array.isArray(value) || value.length > LIMITS.maxGroups) {
24632
+ return failure("runtime.groups-invalid");
24633
+ }
24634
+ const known = new Set(groupIds);
24635
+ const enabled = /* @__PURE__ */ new Set();
24636
+ for (let index = 0; index < value.length; index += 1) {
24637
+ if (!Object.hasOwn(value, index)) return failure("runtime.groups-invalid");
24638
+ const id2 = value[index];
24639
+ if (typeof id2 !== "string" || !ID_PATTERN2.test(id2) || !known.has(id2) || enabled.has(id2)) {
24640
+ return failure("runtime.groups-invalid");
24641
+ }
24642
+ enabled.add(id2);
24643
+ }
24644
+ return { ok: true, value: enabled };
24645
+ }
23908
24646
  function normalizeRuntimePreset(value) {
23909
24647
  const snapshot2 = snapshotOwnData3(value);
23910
24648
  if (!snapshot2.valid || snapshot2.value === null || typeof snapshot2.value !== "object" || Array.isArray(snapshot2.value)) {
@@ -23991,18 +24729,41 @@ function normalizeRuntimePreset(value) {
23991
24729
  });
23992
24730
  return { ok: true, value: normalized };
23993
24731
  }
23994
- function base64(bytes) {
23995
- return Buffer.from(bytes).toString("base64");
24732
+ function matchersFromOperations(operations) {
24733
+ return operations.map((operation) => ({
24734
+ kind: "matcher",
24735
+ groupId: operation.groupId,
24736
+ ruleId: operation.ruleId,
24737
+ name: operation.name,
24738
+ redactSensitiveInLogs: operation.redactSensitiveInLogs,
24739
+ matcher: operation.matcher
24740
+ }));
23996
24741
  }
23997
- async function delay(ms) {
23998
- if (ms <= 0) return;
23999
- await new Promise((resolve22) => setTimeout(resolve22, ms));
24742
+ function mocksFromOperations(operations, enabledGroupIds) {
24743
+ const mocks = [];
24744
+ for (const operation of operations) {
24745
+ if (operation.kind !== "mock") continue;
24746
+ if (enabledGroupIds !== void 0 && !enabledGroupIds.has(operation.groupId)) {
24747
+ continue;
24748
+ }
24749
+ const mock = operation.mock;
24750
+ mocks.push({
24751
+ ruleId: operation.ruleId,
24752
+ status: mock.status,
24753
+ ...mock.headers === void 0 ? {} : { headers: mock.headers },
24754
+ ...mock.delayMs === void 0 ? {} : { delayMs: mock.delayMs },
24755
+ ...mock.body === void 0 ? {} : { body: mock.body },
24756
+ ...mock.file === void 0 ? {} : { file: mock.file }
24757
+ });
24758
+ }
24759
+ return mocks;
24000
24760
  }
24001
24761
  function createNativeRuntimeController(options) {
24002
24762
  let preset = options.preset;
24003
- const fileRoot = options.fileRoot;
24004
- const mockPort = options.mockPort;
24763
+ let fileRoot = options.fileRoot;
24005
24764
  const clock = options.clock ?? (() => Date.now());
24765
+ const directoryPicker = options.directoryPicker ?? pickDirectory;
24766
+ const mockFileErrors = /* @__PURE__ */ new Map();
24006
24767
  let state = "idle";
24007
24768
  let activation;
24008
24769
  let capability;
@@ -24028,8 +24789,8 @@ function createNativeRuntimeController(options) {
24028
24789
  if (!reader) return aiConfigInForce;
24029
24790
  let next = null;
24030
24791
  try {
24031
- const read = await reader();
24032
- next = isUsableAIProviderConfig(read) ? read : null;
24792
+ const read2 = await reader();
24793
+ next = isUsableAIProviderConfig(read2) ? read2 : null;
24033
24794
  } catch {
24034
24795
  next = null;
24035
24796
  }
@@ -24107,6 +24868,7 @@ function createNativeRuntimeController(options) {
24107
24868
  }
24108
24869
  const startedAt = clock();
24109
24870
  capability = createCapabilityState(preset, startedAt);
24871
+ mockFileErrors.clear();
24110
24872
  mockTokens.clear();
24111
24873
  for (const mock of preset.mocks ?? []) {
24112
24874
  mockTokens.set(mintToken(), mock);
@@ -24136,6 +24898,7 @@ function createNativeRuntimeController(options) {
24136
24898
  return { state: "stopped" };
24137
24899
  }
24138
24900
  state = "stopping";
24901
+ mockFileErrors.clear();
24139
24902
  if (options.onStop) await options.onStop();
24140
24903
  await stopInterception();
24141
24904
  if (capability) closeCapabilityState(capability);
@@ -24154,15 +24917,35 @@ function createNativeRuntimeController(options) {
24154
24917
  getActivePolicy() {
24155
24918
  return activePolicy;
24156
24919
  },
24157
- async serveMock(token) {
24920
+ async serveMock(token, options2) {
24158
24921
  const mock = mockTokens.get(token);
24159
- if (mock === void 0) return failure("runtime.mock-unknown");
24160
- if (!preset) throw new Error("runtime not started");
24161
- return renderMockResponse({
24922
+ if (mock === void 0 || state !== "running" || preset === void 0) {
24923
+ return failure("runtime.mock-unknown");
24924
+ }
24925
+ const method = options2?.method?.toUpperCase();
24926
+ const matcher = preset.matchers.find(
24927
+ (candidate) => candidate.ruleId === mock.ruleId
24928
+ );
24929
+ if (method !== void 0) {
24930
+ if (matcher === void 0) return failure("runtime.mock-unknown");
24931
+ if (matcher.matcher.method !== void 0 && matcher.matcher.method !== method) {
24932
+ return failure("runtime.unsupported-method");
24933
+ }
24934
+ }
24935
+ const rendered = await renderMockResponse({
24162
24936
  mock,
24163
24937
  fileRoot,
24164
- presetDigest: preset.digest
24938
+ presetDigest: preset.digest,
24939
+ ...method !== void 0 ? { method } : {},
24940
+ ...options2?.signal !== void 0 ? { signal: options2.signal } : {}
24165
24941
  });
24942
+ if (!rendered.ok) {
24943
+ recordMockFileError(mockFileErrors, mock.ruleId, rendered.error.code);
24944
+ return rendered;
24945
+ }
24946
+ if (mock.file !== void 0)
24947
+ clearMockFileError(mockFileErrors, mock.ruleId);
24948
+ return rendered;
24166
24949
  },
24167
24950
  getBootstrapCapability() {
24168
24951
  if (capability === void 0 || capability.consumed || capability.closed) {
@@ -24209,23 +24992,55 @@ function createNativeRuntimeController(options) {
24209
24992
  }
24210
24993
  };
24211
24994
  }
24212
- const matcherOps = [];
24213
- for (const op of compileResult.operations) {
24214
- if ("matcher" in op) {
24215
- matcherOps.push({
24216
- kind: "matcher",
24217
- groupId: op.groupId,
24218
- ruleId: op.ruleId,
24219
- redactSensitiveInLogs: op.redactSensitiveInLogs,
24220
- matcher: op.matcher
24221
- });
24995
+ const metadata = input.metadata;
24996
+ if (metadata === null || typeof metadata !== "object") {
24997
+ return {
24998
+ protocol: "v1",
24999
+ type: "runtime.project.set",
25000
+ ...requestId !== void 0 ? { requestId } : {},
25001
+ timestamp,
25002
+ metadata: { ok: false, error: "runtime.project-missing" }
25003
+ };
25004
+ }
25005
+ const enabledGroups = parseEnabledGroupIds(
25006
+ metadata,
25007
+ schemaResult.data.groups.map((group) => group.id)
25008
+ );
25009
+ if (!enabledGroups.ok) {
25010
+ return {
25011
+ protocol: "v1",
25012
+ type: "runtime.project.set",
25013
+ ...requestId !== void 0 ? { requestId } : {},
25014
+ timestamp,
25015
+ metadata: { ok: false, error: enabledGroups.error.code }
25016
+ };
25017
+ }
25018
+ if (Object.hasOwn(metadata, "fileRoot")) {
25019
+ const resolvedRoot = await resolveConfinedRoot(
25020
+ metadata.fileRoot
25021
+ );
25022
+ if (!resolvedRoot.ok) {
25023
+ return {
25024
+ protocol: "v1",
25025
+ type: "runtime.project.set",
25026
+ ...requestId !== void 0 ? { requestId } : {},
25027
+ timestamp,
25028
+ metadata: { ok: false, error: resolvedRoot.error.code }
25029
+ };
24222
25030
  }
25031
+ fileRoot = resolvedRoot.value;
24223
25032
  }
25033
+ const matcherOps = matchersFromOperations(compileResult.operations);
25034
+ const mocks = mocksFromOperations(
25035
+ compileResult.operations,
25036
+ enabledGroups.value
25037
+ );
24224
25038
  const presetInput = {
24225
25039
  version: 1,
24226
25040
  limits: RUNTIME_LIMITS,
24227
25041
  matchers: matcherOps,
24228
- grants: []
25042
+ grants: [],
25043
+ ...mocks.length > 0 ? { mocks } : {}
24229
25044
  };
24230
25045
  console.error(
24231
25046
  "[rogatio-host] normalizeRuntimePreset input: matchers=",
@@ -24329,6 +25144,88 @@ function createNativeRuntimeController(options) {
24329
25144
  metadata: { ok: true, state: "stopped" }
24330
25145
  };
24331
25146
  }
25147
+ if (input.type === "runtime.pick-directory") {
25148
+ const picked = await directoryPicker();
25149
+ if (!picked.ok) {
25150
+ return {
25151
+ protocol: "v1",
25152
+ type: "runtime.pick-directory",
25153
+ ...requestId !== void 0 ? { requestId } : {},
25154
+ timestamp,
25155
+ metadata: { ok: false, error: picked.code }
25156
+ };
25157
+ }
25158
+ if (picked.path === null) {
25159
+ return {
25160
+ protocol: "v1",
25161
+ type: "runtime.pick-directory",
25162
+ ...requestId !== void 0 ? { requestId } : {},
25163
+ timestamp,
25164
+ metadata: { ok: true, cancelled: true }
25165
+ };
25166
+ }
25167
+ const resolved = await resolveConfinedRoot(picked.path);
25168
+ return {
25169
+ protocol: "v1",
25170
+ type: "runtime.pick-directory",
25171
+ ...requestId !== void 0 ? { requestId } : {},
25172
+ timestamp,
25173
+ metadata: resolved.ok ? { ok: true, path: resolved.value } : { ok: false, error: resolved.error.code }
25174
+ };
25175
+ }
25176
+ if (input.type === "runtime.set-file-root") {
25177
+ if (state !== "running") {
25178
+ return {
25179
+ protocol: "v1",
25180
+ type: "runtime.set-file-root",
25181
+ ...requestId !== void 0 ? { requestId } : {},
25182
+ timestamp,
25183
+ metadata: { ok: false, error: "runtime.not-started" }
25184
+ };
25185
+ }
25186
+ const candidate = input.metadata.path;
25187
+ if (candidate === void 0 || candidate === null || candidate === "") {
25188
+ fileRoot = void 0;
25189
+ mockFileErrors.clear();
25190
+ return {
25191
+ protocol: "v1",
25192
+ type: "runtime.set-file-root",
25193
+ ...requestId !== void 0 ? { requestId } : {},
25194
+ timestamp,
25195
+ metadata: { ok: true, cleared: true }
25196
+ };
25197
+ }
25198
+ const resolved = await resolveConfinedRoot(candidate);
25199
+ if (!resolved.ok) {
25200
+ return {
25201
+ protocol: "v1",
25202
+ type: "runtime.set-file-root",
25203
+ ...requestId !== void 0 ? { requestId } : {},
25204
+ timestamp,
25205
+ metadata: { ok: false, error: resolved.error.code }
25206
+ };
25207
+ }
25208
+ fileRoot = resolved.value;
25209
+ mockFileErrors.clear();
25210
+ return {
25211
+ protocol: "v1",
25212
+ type: "runtime.set-file-root",
25213
+ ...requestId !== void 0 ? { requestId } : {},
25214
+ timestamp,
25215
+ metadata: { ok: true, path: resolved.value }
25216
+ };
25217
+ }
25218
+ if (input.type === "runtime.check-directory") {
25219
+ const candidate = input.metadata.path;
25220
+ const resolved = await resolveConfinedRoot(candidate);
25221
+ return {
25222
+ protocol: "v1",
25223
+ type: "runtime.check-directory",
25224
+ ...requestId !== void 0 ? { requestId } : {},
25225
+ timestamp,
25226
+ metadata: resolved.ok ? { ok: true, path: resolved.value } : { ok: false, error: resolved.error.code }
25227
+ };
25228
+ }
24332
25229
  if (state !== "running" || capability === void 0) {
24333
25230
  throw new Error("runtime not started");
24334
25231
  }
@@ -24427,8 +25324,7 @@ function createNativeRuntimeController(options) {
24427
25324
  const metadata = {
24428
25325
  protocol: "v1",
24429
25326
  presetDigest: preset.digest,
24430
- mocks,
24431
- ...mockPort !== void 0 ? { port: mockPort } : {}
25327
+ mocks
24432
25328
  };
24433
25329
  return {
24434
25330
  protocol: "v1",
@@ -24438,29 +25334,6 @@ function createNativeRuntimeController(options) {
24438
25334
  metadata
24439
25335
  };
24440
25336
  }
24441
- case "mock.request": {
24442
- const meta = input.metadata;
24443
- const mock = mockTokens.get(meta.token);
24444
- if (mock === void 0) {
24445
- const body = JSON.stringify({
24446
- ok: false,
24447
- error: { code: "runtime.mock-unknown" }
24448
- });
24449
- const metadata = {
24450
- status: 404,
24451
- headers: [["Content-Type", "application/json"]],
24452
- mockBody: base64(new TextEncoder().encode(body))
24453
- };
24454
- return {
24455
- protocol: "v1",
24456
- type: "mock.response",
24457
- ...requestId !== void 0 ? { requestId } : {},
24458
- timestamp,
24459
- metadata
24460
- };
24461
- }
24462
- return renderMock(mock, requestId, timestamp);
24463
- }
24464
25337
  case "runtime.status": {
24465
25338
  if (!preset) throw new Error("runtime not started");
24466
25339
  return {
@@ -24468,7 +25341,11 @@ function createNativeRuntimeController(options) {
24468
25341
  type: "runtime.status",
24469
25342
  ...requestId !== void 0 ? { requestId } : {},
24470
25343
  timestamp,
24471
- metadata: { state, presetDigest: preset.digest }
25344
+ metadata: {
25345
+ state,
25346
+ presetDigest: preset.digest,
25347
+ mockFileErrors: listMockFileErrors(mockFileErrors)
25348
+ }
24472
25349
  };
24473
25350
  }
24474
25351
  case "ai.status": {
@@ -24604,49 +25481,127 @@ function createNativeRuntimeController(options) {
24604
25481
  }
24605
25482
  }
24606
25483
  };
24607
- async function renderMock(mock, requestId, timestamp) {
24608
- if (mock.delayMs !== void 0) await delay(mock.delayMs);
24609
- if (!preset) throw new Error("runtime not started");
24610
- const rendered = await renderMockResponse({
24611
- mock,
24612
- fileRoot,
24613
- presetDigest: preset.digest
24614
- });
24615
- if (!rendered.ok) {
24616
- const body = JSON.stringify({
24617
- ok: false,
24618
- error: { code: rendered.error.code }
24619
- });
24620
- const metadata2 = {
24621
- status: 500,
24622
- headers: [["Content-Type", "application/json"]],
24623
- mockBody: base64(new TextEncoder().encode(body))
24624
- };
24625
- return {
24626
- protocol: "v1",
24627
- type: "mock.response",
24628
- ...requestId !== void 0 ? { requestId } : {},
24629
- timestamp,
24630
- metadata: metadata2
24631
- };
25484
+ }
25485
+ var NATIVE_FRAME_MAX_BYTES = 64 * 1024;
25486
+ var NATIVE_POLICY_MAX_BYTES = 256 * 1024;
25487
+ var NATIVE_POLICY_MAX_FRAMES = 32;
25488
+ var NATIVE_POLICY_STAGE_TIMEOUT_MS = 5e3;
25489
+ function encodeNativeFrame(frame) {
25490
+ const json = JSON.stringify(frame);
25491
+ const bytes = new TextEncoder().encode(json);
25492
+ if (bytes.byteLength > NATIVE_FRAME_MAX_BYTES) {
25493
+ throw new Error("Frame exceeds maximum size");
25494
+ }
25495
+ const result = new Uint8Array(4 + bytes.byteLength);
25496
+ new DataView(result.buffer).setUint32(0, bytes.byteLength, true);
25497
+ result.set(bytes, 4);
25498
+ return result;
25499
+ }
25500
+ function decodeNativeFrame(buffer) {
25501
+ if (buffer.byteLength < 4) {
25502
+ return failure("runtime.native-frame-too-small");
25503
+ }
25504
+ const length = new DataView(buffer.buffer, buffer.byteOffset, 4).getUint32(
25505
+ 0,
25506
+ true
25507
+ );
25508
+ if (length > NATIVE_FRAME_MAX_BYTES) {
25509
+ return failure("runtime.native-frame-too-large");
25510
+ }
25511
+ if (buffer.byteLength !== 4 + length) {
25512
+ return failure("runtime.native-frame-length-mismatch");
25513
+ }
25514
+ try {
25515
+ const json = new TextDecoder("utf-8", { fatal: true }).decode(
25516
+ buffer.slice(4)
25517
+ );
25518
+ const frame = JSON.parse(json);
25519
+ if (frame.protocol !== "v1") {
25520
+ return failure("runtime.native-frame-invalid-protocol");
24632
25521
  }
24633
- const headers = [];
24634
- for (const header of rendered.value.headers) {
24635
- headers.push([header[0], header[1]]);
25522
+ return { ok: true, value: frame };
25523
+ } catch {
25524
+ return failure("runtime.native-frame-invalid-json");
25525
+ }
25526
+ }
25527
+ function createPolicyStageState(totalBytes, partCount, expectedDigest) {
25528
+ return {
25529
+ totalBytes,
25530
+ partCount,
25531
+ receivedParts: /* @__PURE__ */ new Map(),
25532
+ expectedDigest,
25533
+ startTime: Date.now()
25534
+ };
25535
+ }
25536
+ function addPolicyPart(state, index, data) {
25537
+ if (Date.now() - state.startTime > NATIVE_POLICY_STAGE_TIMEOUT_MS) {
25538
+ return failure("runtime.request-body-policy-stage-timeout");
25539
+ }
25540
+ if (index < 0 || index >= state.partCount) {
25541
+ return failure("runtime.request-body-policy-invalid-part-index");
25542
+ }
25543
+ if (state.receivedParts.has(index)) {
25544
+ return failure("runtime.request-body-policy-duplicate-part");
25545
+ }
25546
+ state.receivedParts.set(index, data);
25547
+ return { ok: true, value: void 0 };
25548
+ }
25549
+ function commitPolicyStage(state) {
25550
+ if (state.receivedParts.size !== state.partCount) {
25551
+ return failure("runtime.request-body-policy-incomplete");
25552
+ }
25553
+ let totalReceived = 0;
25554
+ for (let i = 0; i < state.partCount; i += 1) {
25555
+ const part = state.receivedParts.get(i);
25556
+ if (!part) {
25557
+ return failure("runtime.request-body-policy-missing-part");
24636
25558
  }
24637
- const metadata = {
24638
- status: rendered.value.status,
24639
- headers,
24640
- mockBody: base64(rendered.value.bodyBytes)
24641
- };
24642
- return {
24643
- protocol: "v1",
24644
- type: "mock.response",
24645
- ...requestId !== void 0 ? { requestId } : {},
24646
- timestamp,
24647
- metadata
24648
- };
25559
+ totalReceived += part.byteLength;
25560
+ }
25561
+ if (totalReceived !== state.totalBytes) {
25562
+ return failure("runtime.request-body-policy-byte-count-mismatch");
25563
+ }
25564
+ const assembled = new Uint8Array(totalReceived);
25565
+ let offset = 0;
25566
+ for (let i = 0; i < state.partCount; i += 1) {
25567
+ const part = state.receivedParts.get(i);
25568
+ if (!part) {
25569
+ return failure("runtime.request-body-policy-missing-part");
25570
+ }
25571
+ assembled.set(part, offset);
25572
+ offset += part.byteLength;
25573
+ }
25574
+ return { ok: true, value: assembled };
25575
+ }
25576
+ var DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
25577
+ function isProjectStageType(type) {
25578
+ return type === "policy-begin" || type === "policy-part" || type === "policy-commit";
25579
+ }
25580
+ function beginProjectStage(frame) {
25581
+ const { totalBytes, partCount, policyDigest } = frame;
25582
+ if (typeof totalBytes !== "number" || !Number.isSafeInteger(totalBytes) || totalBytes < 1 || totalBytes > NATIVE_POLICY_MAX_BYTES || typeof partCount !== "number" || !Number.isSafeInteger(partCount) || partCount < 1 || partCount > NATIVE_POLICY_MAX_FRAMES || typeof policyDigest !== "string" || !DIGEST_PATTERN.test(policyDigest)) {
25583
+ return failure("runtime.project-stage-invalid");
25584
+ }
25585
+ return {
25586
+ ok: true,
25587
+ value: createPolicyStageState(totalBytes, partCount, policyDigest)
25588
+ };
25589
+ }
25590
+ function acceptProjectPart(state, frame) {
25591
+ if (typeof frame.index !== "number" || !Number.isSafeInteger(frame.index) || typeof frame.data !== "string") {
25592
+ return failure("runtime.project-stage-invalid");
25593
+ }
25594
+ const bytes = Buffer.from(frame.data, "base64");
25595
+ return addPolicyPart(state, frame.index, bytes);
25596
+ }
25597
+ function finishProjectStage(state) {
25598
+ const assembled = commitPolicyStage(state);
25599
+ if (!assembled.ok) return assembled;
25600
+ const digest = `sha256:${createHash4("sha256").update(assembled.value).digest("hex")}`;
25601
+ if (digest !== state.expectedDigest) {
25602
+ return failure("runtime.project-stage-invalid");
24649
25603
  }
25604
+ return assembled;
24650
25605
  }
24651
25606
  var import_reflect_metadata = __toESM(require_Reflect(), 1);
24652
25607
  var import_x509 = __toESM(require_x509_cjs(), 1);
@@ -24714,7 +25669,7 @@ function generateNativeMessagingManifest(hostPath, name, allowedOrigins, install
24714
25669
  "host path must be an absolute path"
24715
25670
  );
24716
25671
  }
24717
- const rel = relative3(installRoot, hostPath);
25672
+ const rel = relative2(installRoot, hostPath);
24718
25673
  if (rel === "" || rel.startsWith("..") || isAbsolute3(rel)) {
24719
25674
  throw new TrustError(
24720
25675
  "trust.invalid-host-path",
@@ -24779,7 +25734,7 @@ async function writeFileAtomic(path2, data) {
24779
25734
  }
24780
25735
  async function existsFile(path2) {
24781
25736
  try {
24782
- return (await stat2(path2)).isFile();
25737
+ return (await stat(path2)).isFile();
24783
25738
  } catch {
24784
25739
  return false;
24785
25740
  }
@@ -24904,7 +25859,7 @@ function createRequestBodyTrustController(options = {}) {
24904
25859
  await writeFileAtomic(caCertFile, certPem);
24905
25860
  }
24906
25861
  if (caTrustInstaller && !installerCalled) {
24907
- await caTrustInstaller(await readFile3(caCertFile, "utf8"));
25862
+ await caTrustInstaller(await readFile2(caCertFile, "utf8"));
24908
25863
  installerCalled = true;
24909
25864
  }
24910
25865
  }
@@ -24946,7 +25901,7 @@ function createRequestBodyTrustController(options = {}) {
24946
25901
  async function status() {
24947
25902
  let installed = false;
24948
25903
  try {
24949
- const raw = await readFile3(manifestPath(), "utf8");
25904
+ const raw = await readFile2(manifestPath(), "utf8");
24950
25905
  installed = isWellFormedManifest(JSON.parse(raw));
24951
25906
  } catch {
24952
25907
  installed = false;
@@ -24968,14 +25923,14 @@ function createRequestBodyTrustController(options = {}) {
24968
25923
  let binaryExecutable = false;
24969
25924
  let allowedOrigins = [];
24970
25925
  try {
24971
- const raw = await readFile3(manifestPath(), "utf8");
25926
+ const raw = await readFile2(manifestPath(), "utf8");
24972
25927
  manifestExists = true;
24973
25928
  const parsed = JSON.parse(raw);
24974
25929
  if (isWellFormedManifest(parsed)) {
24975
25930
  manifestValid = true;
24976
25931
  allowedOrigins = [...parsed.allowed_origins];
24977
25932
  try {
24978
- const fileStat = await stat2(parsed.path);
25933
+ const fileStat = await stat(parsed.path);
24979
25934
  binaryExists = fileStat.isFile();
24980
25935
  binaryExecutable = (fileStat.mode & 73) !== 0;
24981
25936
  if (!binaryExists) reasons.push("binary-not-found");
@@ -25008,6 +25963,17 @@ function createRequestBodyTrustController(options = {}) {
25008
25963
  }
25009
25964
  return { install, uninstall, status, verify };
25010
25965
  }
25966
+ function stageAck(type, requestId, ok, error) {
25967
+ return encodeNativeFrame({
25968
+ protocol: "v1",
25969
+ type,
25970
+ ...requestId !== void 0 ? { requestId } : {},
25971
+ data: JSON.stringify({
25972
+ ok,
25973
+ ...error !== void 0 ? { error } : {}
25974
+ })
25975
+ });
25976
+ }
25011
25977
  function encodeEnvelopeFrame(envelope) {
25012
25978
  const json = new TextEncoder().encode(serializeEnvelope(envelope));
25013
25979
  const out = new Uint8Array(4 + json.byteLength);
@@ -25028,41 +25994,6 @@ function decodeEnvelopeFrame(buffer) {
25028
25994
  );
25029
25995
  return parseEnvelope(json);
25030
25996
  }
25031
- function startMockFaucet(port, controller) {
25032
- const server = createServer2((req, res) => {
25033
- void (async () => {
25034
- try {
25035
- const url = new URL(req.url ?? "", "http://localhost");
25036
- const match = /^\/mock\/([a-f0-9]+)$/.exec(url.pathname);
25037
- if (!match || req.method !== "GET") {
25038
- res.writeHead(404);
25039
- res.end();
25040
- return;
25041
- }
25042
- const result = await controller.serveMock(match[1]);
25043
- if (!result.ok) {
25044
- res.writeHead(404);
25045
- res.end();
25046
- return;
25047
- }
25048
- const { status, headers, bodyBytes } = result.value;
25049
- res.writeHead(
25050
- status,
25051
- Object.fromEntries(
25052
- headers.map((h) => [h[0], h[1]])
25053
- )
25054
- );
25055
- res.end(Buffer2.from(bodyBytes));
25056
- } catch {
25057
- if (!res.headersSent) res.writeHead(500);
25058
- res.end();
25059
- }
25060
- })();
25061
- });
25062
- return new Promise(
25063
- (resolve22) => server.listen(port, () => resolve22(server))
25064
- );
25065
- }
25066
25997
  function defaultTrustRoot() {
25067
25998
  return defaultTrustInstallRoot(process.platform);
25068
25999
  }
@@ -25070,12 +26001,10 @@ function createNativeHost(options) {
25070
26001
  const controller = createNativeRuntimeController({
25071
26002
  preset: options.preset,
25072
26003
  fileRoot: options.fileRoot,
25073
- ...options.mockPort !== void 0 ? { mockPort: options.mockPort } : {},
25074
26004
  ...options.aiProviderConfig !== void 0 ? { aiProviderConfig: options.aiProviderConfig } : {},
25075
26005
  ...options.aiConfigReader !== void 0 ? { aiConfigReader: options.aiConfigReader } : {},
25076
26006
  ...options.clock ? { clock: options.clock } : {}
25077
26007
  });
25078
- let faucet = null;
25079
26008
  let interceptProxy = null;
25080
26009
  let outboundWrite = null;
25081
26010
  let hostRequestCounter = 0;
@@ -25089,12 +26018,12 @@ function createNativeHost(options) {
25089
26018
  timestamp: Date.now(),
25090
26019
  metadata
25091
26020
  };
25092
- return new Promise((resolve22, reject) => {
26021
+ return new Promise((resolve10, reject) => {
25093
26022
  if (!outboundWrite) {
25094
26023
  reject(new Error("host stdout not attached"));
25095
26024
  return;
25096
26025
  }
25097
- pendingHost.set(requestId, { resolve: resolve22, reject });
26026
+ pendingHost.set(requestId, { resolve: resolve10, reject });
25098
26027
  try {
25099
26028
  outboundWrite(encodeEnvelopeFrame(envelope));
25100
26029
  } catch (error) {
@@ -25116,7 +26045,7 @@ function createNativeHost(options) {
25116
26045
  const platformAdapter = {
25117
26046
  platform: process.platform,
25118
26047
  detect() {
25119
- const trusted = existsSync(caKeyFile) && existsSync(caCertFile);
26048
+ const trusted = existsSync2(caKeyFile) && existsSync2(caCertFile);
25120
26049
  return {
25121
26050
  supported: true,
25122
26051
  reasons: [],
@@ -25128,7 +26057,7 @@ function createNativeHost(options) {
25128
26057
  };
25129
26058
  },
25130
26059
  async provisionOrVerifyCa() {
25131
- return existsSync(caKeyFile) && existsSync(caCertFile);
26060
+ return existsSync2(caKeyFile) && existsSync2(caCertFile);
25132
26061
  },
25133
26062
  async installPac(script) {
25134
26063
  const response = await sendHostRequest("runtime.pac.install", {
@@ -25156,7 +26085,11 @@ function createNativeHost(options) {
25156
26085
  project: policy.project,
25157
26086
  operations: policy.operations,
25158
26087
  presetDigest: policy.presetDigest
25159
- } : null
26088
+ } : null,
26089
+ serveMock: (request) => controller.serveMock(request.token, {
26090
+ method: request.method,
26091
+ signal: request.signal
26092
+ })
25160
26093
  });
25161
26094
  return interceptProxy.endpoint;
25162
26095
  },
@@ -25189,20 +26122,93 @@ function createNativeHost(options) {
25189
26122
  await platformProvider.stop();
25190
26123
  }
25191
26124
  });
26125
+ let projectStage;
26126
+ async function handleProjectStage(frame) {
26127
+ const requestId = frame.requestId;
26128
+ if (frame.type === "policy-begin") {
26129
+ const begun = beginProjectStage(frame);
26130
+ if (!begun.ok) {
26131
+ projectStage = void 0;
26132
+ return stageAck(
26133
+ "policy-begin",
26134
+ requestId,
26135
+ false,
26136
+ begun.error.code
26137
+ );
26138
+ }
26139
+ projectStage = begun.value;
26140
+ return stageAck("policy-begin", requestId, true);
26141
+ }
26142
+ if (frame.type === "policy-part") {
26143
+ if (projectStage === void 0) {
26144
+ return stageAck(
26145
+ "policy-part",
26146
+ requestId,
26147
+ false,
26148
+ "runtime.project-stage-invalid"
26149
+ );
26150
+ }
26151
+ const accepted = acceptProjectPart(projectStage, frame);
26152
+ if (!accepted.ok) {
26153
+ projectStage = void 0;
26154
+ return stageAck(
26155
+ "policy-part",
26156
+ requestId,
26157
+ false,
26158
+ accepted.error.code
26159
+ );
26160
+ }
26161
+ return stageAck("policy-part", requestId, true);
26162
+ }
26163
+ if (projectStage === void 0) {
26164
+ return stageAck(
26165
+ "policy-commit",
26166
+ requestId,
26167
+ false,
26168
+ "runtime.project-stage-invalid"
26169
+ );
26170
+ }
26171
+ const finished = finishProjectStage(projectStage);
26172
+ projectStage = void 0;
26173
+ if (!finished.ok) {
26174
+ return stageAck(
26175
+ "policy-commit",
26176
+ requestId,
26177
+ false,
26178
+ finished.error.code
26179
+ );
26180
+ }
26181
+ try {
26182
+ const json = new TextDecoder("utf-8", { fatal: true }).decode(
26183
+ finished.value
26184
+ );
26185
+ const envelope = parseEnvelope(json);
26186
+ if (envelope.type !== "runtime.project.set") {
26187
+ return stageAck(
26188
+ "policy-commit",
26189
+ requestId,
26190
+ false,
26191
+ "runtime.project-stage-invalid"
26192
+ );
26193
+ }
26194
+ const response = await controller.handleEnvelope(envelope);
26195
+ return encodeEnvelopeFrame(response);
26196
+ } catch {
26197
+ return stageAck(
26198
+ "policy-commit",
26199
+ requestId,
26200
+ false,
26201
+ "runtime.project-stage-invalid"
26202
+ );
26203
+ }
26204
+ }
25192
26205
  const handle = {
25193
26206
  controller,
25194
- mockPort: options.mockPort ?? null,
25195
26207
  async start() {
25196
26208
  await controller.start();
25197
- if (options.mockPort !== void 0) {
25198
- faucet = await startMockFaucet(options.mockPort, controller);
25199
- }
25200
26209
  },
25201
26210
  async stop() {
25202
- if (faucet) {
25203
- await new Promise((resolve22) => faucet?.close(() => resolve22()));
25204
- faucet = null;
25205
- }
26211
+ projectStage = void 0;
25206
26212
  await controller.stop();
25207
26213
  clearSession();
25208
26214
  if (interceptProxy) {
@@ -25211,6 +26217,10 @@ function createNativeHost(options) {
25211
26217
  }
25212
26218
  },
25213
26219
  async processFrame(frame) {
26220
+ const native = decodeNativeFrame(frame);
26221
+ if (native.ok && isProjectStageType(native.value.type)) {
26222
+ return handleProjectStage(native.value);
26223
+ }
25214
26224
  let envelope;
25215
26225
  try {
25216
26226
  envelope = decodeEnvelopeFrame(frame);
@@ -25348,16 +26358,14 @@ async function runNativeHost(options) {
25348
26358
  buffer = Buffer2.concat([buffer, buf]);
25349
26359
  pump();
25350
26360
  });
25351
- await new Promise((resolve22) => {
26361
+ await new Promise((resolve10) => {
25352
26362
  stdin.on("end", () => {
25353
- void commandChain.finally(() => host.stop()).then(() => resolve22());
26363
+ void commandChain.finally(() => host.stop()).then(() => resolve10());
25354
26364
  });
25355
26365
  if (options.onReady) options.onReady();
25356
26366
  pump();
25357
26367
  });
25358
26368
  }
25359
- var NATIVE_FRAME_MAX_BYTES = 64 * 1024;
25360
- var NATIVE_POLICY_MAX_BYTES = 256 * 1024;
25361
26369
  var PRIVATE_RANGES = [
25362
26370
  { start: ipToInt("10.0.0.0"), end: ipToInt("10.255.255.255") },
25363
26371
  { start: ipToInt("172.16.0.0"), end: ipToInt("172.31.255.255") },
@@ -25394,12 +26402,12 @@ var darwinAdapter = {
25394
26402
  reasons.push("tooling-missing");
25395
26403
  }
25396
26404
  try {
25397
- accessSync(manifestDir, constants2.W_OK);
26405
+ accessSync(manifestDir, constants3.W_OK);
25398
26406
  } catch {
25399
26407
  reasons.push("manifest-dir-unwritable");
25400
26408
  }
25401
26409
  try {
25402
- accessSync(caDir, constants2.W_OK);
26410
+ accessSync(caDir, constants3.W_OK);
25403
26411
  } catch {
25404
26412
  reasons.push("keychain-unwritable");
25405
26413
  }
@@ -25422,8 +26430,8 @@ var darwinAdapter = {
25422
26430
  ]);
25423
26431
  }
25424
26432
  const result = await new Promise(
25425
- (resolve22) => {
25426
- const child = spawn("security", [
26433
+ (resolve10) => {
26434
+ const child = spawn2("security", [
25427
26435
  "add-trusted-cert",
25428
26436
  "-d",
25429
26437
  "-r",
@@ -25436,8 +26444,8 @@ var darwinAdapter = {
25436
26444
  child.stderr?.on("data", (data) => {
25437
26445
  stderr += data.toString();
25438
26446
  });
25439
- child.on("close", (code) => resolve22({ code: code ?? 1, stderr }));
25440
- child.on("error", (err) => resolve22({ code: 1, stderr: err.message }));
26447
+ child.on("close", (code) => resolve10({ code: code ?? 1, stderr }));
26448
+ child.on("error", (err) => resolve10({ code: 1, stderr: err.message }));
25441
26449
  }
25442
26450
  );
25443
26451
  try {
@@ -25456,8 +26464,8 @@ var darwinAdapter = {
25456
26464
  async caTrustRemover() {
25457
26465
  const keychain = this.defaultCaInstallPath();
25458
26466
  const result = await new Promise(
25459
- (resolve22) => {
25460
- const child = spawn("security", [
26467
+ (resolve10) => {
26468
+ const child = spawn2("security", [
25461
26469
  "delete-certificate",
25462
26470
  "-c",
25463
26471
  "CN=Rogatio Request-Body CA",
@@ -25467,8 +26475,8 @@ var darwinAdapter = {
25467
26475
  child.stderr?.on("data", (data) => {
25468
26476
  stderr += data.toString();
25469
26477
  });
25470
- child.on("close", (code) => resolve22({ code: code ?? 1, stderr }));
25471
- child.on("error", (err) => resolve22({ code: 1, stderr: err.message }));
26478
+ child.on("close", (code) => resolve10({ code: code ?? 1, stderr }));
26479
+ child.on("error", (err) => resolve10({ code: 1, stderr: err.message }));
25472
26480
  }
25473
26481
  );
25474
26482
  if (result.code !== 0) {
@@ -25505,16 +26513,16 @@ var linuxAdapter = {
25505
26513
  reasons.push("tooling-missing");
25506
26514
  }
25507
26515
  try {
25508
- accessSync2(manifestDir, constants3.W_OK);
26516
+ accessSync2(manifestDir, constants4.W_OK);
25509
26517
  } catch {
25510
26518
  reasons.push("manifest-dir-unwritable");
25511
26519
  }
25512
26520
  try {
25513
- accessSync2(CA_DIR, constants3.W_OK);
26521
+ accessSync2(CA_DIR, constants4.W_OK);
25514
26522
  } catch (err) {
25515
26523
  if (err?.code === "ENOENT") {
25516
26524
  try {
25517
- accessSync2(dirname4(CA_DIR), constants3.W_OK);
26525
+ accessSync2(dirname4(CA_DIR), constants4.W_OK);
25518
26526
  } catch {
25519
26527
  if (!hasSudo()) {
25520
26528
  reasons.push("elevation-required");
@@ -25541,8 +26549,8 @@ var linuxAdapter = {
25541
26549
  ]);
25542
26550
  }
25543
26551
  const certResult = await new Promise(
25544
- (resolve22) => {
25545
- const child = spawn2("sudo", [
26552
+ (resolve10) => {
26553
+ const child = spawn3("sudo", [
25546
26554
  "sh",
25547
26555
  "-c",
25548
26556
  `cat > "${CERT_PATH}" << 'EOF'
@@ -25553,8 +26561,8 @@ EOF`
25553
26561
  child.stderr?.on("data", (data) => {
25554
26562
  stderr += data.toString();
25555
26563
  });
25556
- child.on("close", (code) => resolve22({ code: code ?? 1, stderr }));
25557
- child.on("error", (err) => resolve22({ code: 1, stderr: err.message }));
26564
+ child.on("close", (code) => resolve10({ code: code ?? 1, stderr }));
26565
+ child.on("error", (err) => resolve10({ code: 1, stderr: err.message }));
25558
26566
  }
25559
26567
  );
25560
26568
  if (certResult.code !== 0) {
@@ -25566,14 +26574,14 @@ EOF`
25566
26574
  throw new TrustError("trust.internal", reason, [reason]);
25567
26575
  }
25568
26576
  const result = await new Promise(
25569
- (resolve22) => {
25570
- const child = spawn2("sudo", ["update-ca-certificates"]);
26577
+ (resolve10) => {
26578
+ const child = spawn3("sudo", ["update-ca-certificates"]);
25571
26579
  let stderr = "";
25572
26580
  child.stderr?.on("data", (data) => {
25573
26581
  stderr += data.toString();
25574
26582
  });
25575
- child.on("close", (code) => resolve22({ code: code ?? 1, stderr }));
25576
- child.on("error", (err) => resolve22({ code: 1, stderr: err.message }));
26583
+ child.on("close", (code) => resolve10({ code: code ?? 1, stderr }));
26584
+ child.on("error", (err) => resolve10({ code: 1, stderr: err.message }));
25577
26585
  }
25578
26586
  );
25579
26587
  if (result.code !== 0) {
@@ -25632,7 +26640,7 @@ var win32Adapter = {
25632
26640
  reasons.push("tooling-missing");
25633
26641
  }
25634
26642
  try {
25635
- accessSync3(manifestDir, constants4.W_OK);
26643
+ accessSync3(manifestDir, constants5.W_OK);
25636
26644
  } catch {
25637
26645
  reasons.push("manifest-dir-unwritable");
25638
26646
  }
@@ -25654,14 +26662,14 @@ var win32Adapter = {
25654
26662
  ]);
25655
26663
  }
25656
26664
  const result = await new Promise(
25657
- (resolve22) => {
25658
- const child = spawn3("certutil", ["-addstore", "-f", "Root", tmpPath]);
26665
+ (resolve10) => {
26666
+ const child = spawn4("certutil", ["-addstore", "-f", "Root", tmpPath]);
25659
26667
  let stderr = "";
25660
26668
  child.stderr?.on("data", (data) => {
25661
26669
  stderr += data.toString();
25662
26670
  });
25663
- child.on("close", (code) => resolve22({ code: code ?? 1, stderr }));
25664
- child.on("error", (err) => resolve22({ code: 1, stderr: err.message }));
26671
+ child.on("close", (code) => resolve10({ code: code ?? 1, stderr }));
26672
+ child.on("error", (err) => resolve10({ code: 1, stderr: err.message }));
25665
26673
  }
25666
26674
  );
25667
26675
  try {
@@ -25679,8 +26687,8 @@ var win32Adapter = {
25679
26687
  },
25680
26688
  async caTrustRemover() {
25681
26689
  const result = await new Promise(
25682
- (resolve22) => {
25683
- const child = spawn3("certutil", [
26690
+ (resolve10) => {
26691
+ const child = spawn4("certutil", [
25684
26692
  "-delstore",
25685
26693
  "Root",
25686
26694
  "CN=Rogatio Request-Body CA"
@@ -25689,8 +26697,8 @@ var win32Adapter = {
25689
26697
  child.stderr?.on("data", (data) => {
25690
26698
  stderr += data.toString();
25691
26699
  });
25692
- child.on("close", (code) => resolve22({ code: code ?? 1, stderr }));
25693
- child.on("error", (err) => resolve22({ code: 1, stderr: err.message }));
26700
+ child.on("close", (code) => resolve10({ code: code ?? 1, stderr }));
26701
+ child.on("error", (err) => resolve10({ code: 1, stderr: err.message }));
25694
26702
  }
25695
26703
  );
25696
26704
  if (result.code !== 0) {
@@ -25786,8 +26794,10 @@ Options:
25786
26794
  --extension-id Extension ID for the native messaging manifest. Release users
25787
26795
  never need this. It is only for development (a local unpacked
25788
26796
  build without the release key) and for forks.
25789
- --root <dir> Root for confined runtime file access (host only; default: project directory)
25790
- --mock-port <n> Bind the loopback mock-response faucet to this port (host only, 1-65535)
26797
+ --root <dir> Root for confined runtime file access (host only). Default: the
26798
+ saved device-local root for that project file, otherwise the
26799
+ project directory. Required when the host reads a stdin
26800
+ project that contains a file mock.
25791
26801
  --help, -h Show this help
25792
26802
 
25793
26803
  Exit codes:
@@ -26103,7 +27113,7 @@ var HttpServerError = class extends Error {
26103
27113
  this.code = code;
26104
27114
  }
26105
27115
  };
26106
- function createServer3(handler, options = {}) {
27116
+ function createServer2(handler, options = {}) {
26107
27117
  const server = createHttpServer(handler);
26108
27118
  const preferredPort = options.port;
26109
27119
  let port = null;
@@ -26175,7 +27185,7 @@ function createServer3(handler, options = {}) {
26175
27185
  // packages/cli/src/server/routes.ts
26176
27186
  import { randomBytes as randomBytes3 } from "node:crypto";
26177
27187
  import { readFile as readFile4 } from "node:fs/promises";
26178
- import { resolve as resolve2 } from "node:path";
27188
+ import { dirname as dirname6, isAbsolute as isAbsolute5, resolve as resolve2 } from "node:path";
26179
27189
 
26180
27190
  // packages/dry-run/dist/node/index.js
26181
27191
  function parseTestUrl(input) {
@@ -26470,6 +27480,18 @@ function dryRunProject(operations, cases, options) {
26470
27480
  }
26471
27481
  };
26472
27482
  }
27483
+ function mockPreviewSummary(mock) {
27484
+ const parts = [];
27485
+ parts.push(mock.body !== void 0 ? "inline body" : "file-backed body");
27486
+ const headerCount = mock.headers?.length ?? 0;
27487
+ if (headerCount > 0) {
27488
+ parts.push(headerCount === 1 ? "1 header" : `${headerCount} headers`);
27489
+ }
27490
+ if (mock.delayMs !== void 0) {
27491
+ parts.push(`${mock.delayMs} ms delay`);
27492
+ }
27493
+ return `Mock ${mock.status} (${parts.join(", ")})`;
27494
+ }
26473
27495
  function captureSubject(matcher, url) {
26474
27496
  if (matcher.source.key === "url") return url;
26475
27497
  try {
@@ -26517,9 +27539,82 @@ function previewRuleAction(operation, url) {
26517
27539
  summary: expand(operation.responseBody.body)
26518
27540
  };
26519
27541
  }
27542
+ if (operation.kind === "mock") {
27543
+ return {
27544
+ kind: "mock",
27545
+ summary: mockPreviewSummary(operation.mock)
27546
+ };
27547
+ }
26520
27548
  return null;
26521
27549
  }
26522
27550
 
27551
+ // packages/cli/src/mock-root-config.ts
27552
+ import { mkdir as mkdir3, readFile as readFile3, realpath as realpath3, rename as rename2, writeFile as writeFile3 } from "node:fs/promises";
27553
+ import { homedir } from "node:os";
27554
+ import { dirname as dirname5, isAbsolute as isAbsolute4, join as join6, resolve } from "node:path";
27555
+ function mockRootConfigPath(env = process.env) {
27556
+ const base = env.ROGATIO_CONFIG_DIR ?? join6(homedir(), ".config", "rogatio");
27557
+ return join6(base, "mock-roots.json");
27558
+ }
27559
+ async function canonicalProjectPath(filePath) {
27560
+ const absolute = resolve(filePath);
27561
+ try {
27562
+ return await realpath3(absolute);
27563
+ } catch {
27564
+ return absolute;
27565
+ }
27566
+ }
27567
+ async function readMap(env) {
27568
+ let raw;
27569
+ try {
27570
+ raw = await readFile3(mockRootConfigPath(env), "utf8");
27571
+ } catch {
27572
+ return {};
27573
+ }
27574
+ let parsed;
27575
+ try {
27576
+ parsed = JSON.parse(raw);
27577
+ } catch {
27578
+ return {};
27579
+ }
27580
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
27581
+ return {};
27582
+ }
27583
+ const projects = parsed.projects;
27584
+ if (typeof projects !== "object" || projects === null || Array.isArray(projects)) {
27585
+ return {};
27586
+ }
27587
+ const map = {};
27588
+ for (const key of Object.keys(projects)) {
27589
+ const value = projects[key];
27590
+ if (typeof value === "string" && isAbsolute4(value)) map[key] = value;
27591
+ }
27592
+ return map;
27593
+ }
27594
+ async function writeMap(map, env) {
27595
+ const path2 = mockRootConfigPath(env);
27596
+ await mkdir3(dirname5(path2), { recursive: true });
27597
+ const temporary = `${path2}.${process.pid}.tmp`;
27598
+ await writeFile3(
27599
+ temporary,
27600
+ `${JSON.stringify({ projects: map }, null, 2)}
27601
+ `,
27602
+ "utf8"
27603
+ );
27604
+ await rename2(temporary, path2);
27605
+ }
27606
+ async function readSavedMockRoot(filePath, env = process.env) {
27607
+ const key = await canonicalProjectPath(filePath);
27608
+ return (await readMap(env))[key];
27609
+ }
27610
+ async function writeSavedMockRoot(filePath, root, env = process.env) {
27611
+ const key = await canonicalProjectPath(filePath);
27612
+ const map = await readMap(env);
27613
+ if (root === null || root.length === 0) delete map[key];
27614
+ else map[key] = root;
27615
+ await writeMap(map, env);
27616
+ }
27617
+
26523
27618
  // packages/cli/src/server/routes.ts
26524
27619
  function generateCsrfToken() {
26525
27620
  return randomBytes3(16).toString("hex");
@@ -26758,6 +27853,60 @@ function createRoutes(context) {
26758
27853
  res.end(JSON.stringify({ diagnostics }));
26759
27854
  return;
26760
27855
  }
27856
+ if (pathname === "/api/mock-root" && method === "GET") {
27857
+ const saved = await readSavedMockRoot(context.filePath);
27858
+ res.writeHead(200, { "Content-Type": "application/json" });
27859
+ res.end(
27860
+ JSON.stringify({
27861
+ root: saved ?? dirname6(context.filePath),
27862
+ saved: saved !== void 0
27863
+ })
27864
+ );
27865
+ return;
27866
+ }
27867
+ if (pathname === "/api/mock-root" && method === "POST") {
27868
+ if (!validateCsrf(req, context.csrfToken)) {
27869
+ res.writeHead(403, { "Content-Type": "application/json" });
27870
+ res.end(
27871
+ JSON.stringify({
27872
+ code: "csrf-invalid",
27873
+ message: "Invalid CSRF token"
27874
+ })
27875
+ );
27876
+ return;
27877
+ }
27878
+ let body;
27879
+ try {
27880
+ body = JSON.parse(await getRequestBody(req));
27881
+ } catch (error) {
27882
+ const failure2 = bodyErrorResponse(error);
27883
+ res.writeHead(failure2.status, { "Content-Type": "application/json" });
27884
+ res.end(JSON.stringify(failure2.body));
27885
+ return;
27886
+ }
27887
+ const root = typeof body === "object" && body !== null && "root" in body ? body.root : void 0;
27888
+ if (root !== null && (typeof root !== "string" || root !== "" && !isAbsolute5(root))) {
27889
+ res.writeHead(400, { "Content-Type": "application/json" });
27890
+ res.end(
27891
+ JSON.stringify({
27892
+ code: "invalid-root",
27893
+ message: "Mock file root must be an absolute path or null"
27894
+ })
27895
+ );
27896
+ return;
27897
+ }
27898
+ await writeSavedMockRoot(context.filePath, root);
27899
+ const saved = await readSavedMockRoot(context.filePath);
27900
+ res.writeHead(200, { "Content-Type": "application/json" });
27901
+ res.end(
27902
+ JSON.stringify({
27903
+ ok: true,
27904
+ root: saved ?? dirname6(context.filePath),
27905
+ saved: saved !== void 0
27906
+ })
27907
+ );
27908
+ return;
27909
+ }
26761
27910
  if (pathname === "/api/save" && method === "POST") {
26762
27911
  if (!validateCsrf(req, context.csrfToken)) {
26763
27912
  res.writeHead(403, { "Content-Type": "application/json" });
@@ -27119,12 +28268,12 @@ function createRoutes(context) {
27119
28268
  }
27120
28269
 
27121
28270
  // packages/cli/src/utils/asset-paths.ts
27122
- import { dirname as dirname5, resolve as resolve3 } from "node:path";
28271
+ import { dirname as dirname7, resolve as resolve3 } from "node:path";
27123
28272
  import { fileURLToPath } from "node:url";
27124
- function isDistBuild(here = dirname5(fileURLToPath(import.meta.url))) {
28273
+ function isDistBuild(here = dirname7(fileURLToPath(import.meta.url))) {
27125
28274
  return here.includes("/dist/") || here.includes("\\dist\\");
27126
28275
  }
27127
- function editorAssetPaths(here = dirname5(fileURLToPath(import.meta.url))) {
28276
+ function editorAssetPaths(here = dirname7(fileURLToPath(import.meta.url))) {
27128
28277
  const editorRoot = isDistBuild(here) ? resolve3(here, "..", "editor") : resolve3(here, "..", "..", "..", "editor", "dist", "browser");
27129
28278
  return {
27130
28279
  bundle: resolve3(editorRoot, "index.js"),
@@ -27134,7 +28283,7 @@ function editorAssetPaths(here = dirname5(fileURLToPath(import.meta.url))) {
27134
28283
  }
27135
28284
 
27136
28285
  // packages/cli/src/utils/browser.ts
27137
- import { spawn as spawn4 } from "node:child_process";
28286
+ import { spawn as spawn5 } from "node:child_process";
27138
28287
  var BrowserLaunchError = class extends Error {
27139
28288
  platform;
27140
28289
  constructor(platform, message) {
@@ -27170,7 +28319,7 @@ async function launchBrowser(url) {
27170
28319
  );
27171
28320
  }
27172
28321
  return new Promise((resolve10) => {
27173
- const child = spawn4(command, args, {
28322
+ const child = spawn5(command, args, {
27174
28323
  detached: true,
27175
28324
  stdio: "ignore"
27176
28325
  });
@@ -27192,15 +28341,15 @@ async function launchBrowser(url) {
27192
28341
  import { randomBytes as randomBytes4 } from "node:crypto";
27193
28342
  import {
27194
28343
  access,
27195
- mkdir as mkdir3,
28344
+ mkdir as mkdir4,
27196
28345
  readdir,
27197
28346
  readFile as readFile5,
27198
- rename as rename2,
27199
- stat as stat3,
28347
+ rename as rename3,
28348
+ stat as stat2,
27200
28349
  unlink as unlink2,
27201
- writeFile as writeFile3
28350
+ writeFile as writeFile4
27202
28351
  } from "node:fs/promises";
27203
- import { basename as basename2, dirname as dirname6, join as join6 } from "node:path";
28352
+ import { basename as basename2, dirname as dirname8, join as join7 } from "node:path";
27204
28353
  var ProjectStorageError = class extends Error {
27205
28354
  code;
27206
28355
  id;
@@ -27398,14 +28547,14 @@ async function readDocument(id2) {
27398
28547
  }
27399
28548
  async function isDirectoryPath(id2) {
27400
28549
  try {
27401
- return (await stat3(id2)).isDirectory();
28550
+ return (await stat2(id2)).isDirectory();
27402
28551
  } catch {
27403
28552
  return false;
27404
28553
  }
27405
28554
  }
27406
28555
  async function writeDocument(id2, data) {
27407
- const tempName = `.${basename2(dirname6(id2))}.${randomBytes4(8).toString("hex")}.tmp`;
27408
- const tempPath = join6(dirname6(id2), tempName);
28556
+ const tempName = `.${basename2(dirname8(id2))}.${randomBytes4(8).toString("hex")}.tmp`;
28557
+ const tempPath = join7(dirname8(id2), tempName);
27409
28558
  try {
27410
28559
  if (await isDirectoryPath(id2)) {
27411
28560
  throw new ProjectFileError(
@@ -27414,9 +28563,9 @@ async function writeDocument(id2, data) {
27414
28563
  "Target path is a directory"
27415
28564
  );
27416
28565
  }
27417
- await mkdir3(dirname6(id2), { recursive: true });
27418
- await writeFile3(tempPath, JSON.stringify(data, null, 2), "utf-8");
27419
- await rename2(tempPath, id2);
28566
+ await mkdir4(dirname8(id2), { recursive: true });
28567
+ await writeFile4(tempPath, JSON.stringify(data, null, 2), "utf-8");
28568
+ await rename3(tempPath, id2);
27420
28569
  } catch (e) {
27421
28570
  if (e instanceof ProjectFileError) throw e;
27422
28571
  try {
@@ -27464,7 +28613,7 @@ function createJsonFileProjectStorage() {
27464
28613
  if (entry.isDirectory()) continue;
27465
28614
  const named = isRogatioProjectFilename(entry.name);
27466
28615
  if (!named && !isJsonFilename(entry.name)) continue;
27467
- const id2 = join6(scope, entry.name);
28616
+ const id2 = join7(scope, entry.name);
27468
28617
  if (!named && !await isDiscoverableProjectFile(id2)) continue;
27469
28618
  let name = "";
27470
28619
  try {
@@ -27597,10 +28746,10 @@ async function editCommand(args, options = {}) {
27597
28746
  filePath = resolve4(process.cwd(), ".rogatio.json");
27598
28747
  }
27599
28748
  try {
27600
- const stat4 = await import("node:fs/promises").then(
28749
+ const stat3 = await import("node:fs/promises").then(
27601
28750
  (fs2) => fs2.stat(filePath)
27602
28751
  );
27603
- if (stat4.isDirectory()) {
28752
+ if (stat3.isDirectory()) {
27604
28753
  console.error("Error: Path is a directory");
27605
28754
  return { exitCode: Promise.resolve(2), shutdown: () => {
27606
28755
  } };
@@ -27665,7 +28814,7 @@ async function editCommand(args, options = {}) {
27665
28814
  };
27666
28815
  let server;
27667
28816
  try {
27668
- server = createServer3(
28817
+ server = createServer2(
27669
28818
  createRoutes(context),
27670
28819
  port !== void 0 ? { port } : {}
27671
28820
  );
@@ -27951,8 +29100,30 @@ function generateEditorHtml(apiBase, csrfToken, filePath, aiConfigured) {
27951
29100
  onCancel: () => {
27952
29101
  cancel();
27953
29102
  },
29103
+ projectActions: [
29104
+ { command: 'set-mock-root', label: 'Set mock file root' },
29105
+ { command: 'clear-mock-root', label: 'Clear mock file root' },
29106
+ ],
27954
29107
  ${aiAssistHandler}
27955
29108
  });
29109
+ document.addEventListener('click', (event) => {
29110
+ const target = event.target;
29111
+ if (!(target instanceof Element)) return;
29112
+ const command = target.closest('[data-command]')?.getAttribute('data-command');
29113
+ if (command !== 'set-mock-root' && command !== 'clear-mock-root') return;
29114
+ const root = command === 'clear-mock-root'
29115
+ ? null
29116
+ : window.prompt('Mock file root');
29117
+ if (command === 'set-mock-root' && root === null) return;
29118
+ void fetch(apiBase + '/api/mock-root', {
29119
+ method: 'POST',
29120
+ headers: {
29121
+ 'Content-Type': 'application/json',
29122
+ 'X-CSRF-Token': csrfToken,
29123
+ },
29124
+ body: JSON.stringify({ root }),
29125
+ });
29126
+ });
27956
29127
  void editor;
27957
29128
  } catch (error) {
27958
29129
  const parts = [];
@@ -28064,16 +29235,16 @@ function readFilters(source) {
28064
29235
  };
28065
29236
  }
28066
29237
  if (Object.hasOwn(entry, "resourceType")) {
28067
- const read = readResourceTypes(entry.resourceType);
28068
- if (!read.ok) return read;
28069
- resourceTypes = intersect(resourceTypes, read.values);
28070
- changes.push(...read.changes);
29238
+ const read2 = readResourceTypes(entry.resourceType);
29239
+ if (!read2.ok) return read2;
29240
+ resourceTypes = intersect(resourceTypes, read2.values);
29241
+ changes.push(...read2.changes);
28071
29242
  }
28072
29243
  if (Object.hasOwn(entry, "requestMethod")) {
28073
- const read = readMethods(entry.requestMethod);
28074
- if (!read.ok) return read;
28075
- methods = intersect(methods, read.values);
28076
- changes.push(...read.changes);
29244
+ const read2 = readMethods(entry.requestMethod);
29245
+ if (!read2.ok) return read2;
29246
+ methods = intersect(methods, read2.values);
29247
+ changes.push(...read2.changes);
28077
29248
  }
28078
29249
  }
28079
29250
  if (resourceTypes !== void 0 && resourceTypes.length === 0) {
@@ -30019,20 +31190,8 @@ function errorMessage(error) {
30019
31190
  }
30020
31191
 
30021
31192
  // packages/cli/src/commands/runtime.ts
30022
- import { chmod, mkdir as mkdir4, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
30023
- import { dirname as dirname7, join as join7, resolve as resolve6 } from "node:path";
30024
- function toMatcherOperations(operations) {
30025
- return operations.map(
30026
- ({ groupId, ruleId, name, matcher, redactSensitiveInLogs }) => ({
30027
- kind: "matcher",
30028
- groupId,
30029
- ruleId,
30030
- name,
30031
- matcher,
30032
- redactSensitiveInLogs
30033
- })
30034
- );
30035
- }
31193
+ import { chmod, mkdir as mkdir5, rm as rm2, writeFile as writeFile5 } from "node:fs/promises";
31194
+ import { dirname as dirname9, join as join8, resolve as resolve6 } from "node:path";
30036
31195
  function generateHostWrapper(rogatioCommand, logPath) {
30037
31196
  const parts = rogatioCommand.split(" ");
30038
31197
  const nodeBin = parts[0] ?? "node";
@@ -30046,13 +31205,13 @@ exec "${nodeBin}" "${cliScript}" runtime host 2>>"${logPath}"
30046
31205
  `;
30047
31206
  }
30048
31207
  async function writeHostWrapper(hostPath, installRoot) {
30049
- await mkdir4(installRoot, { recursive: true });
30050
- const logPath = join7(installRoot, "runtime-host.log");
31208
+ await mkdir5(installRoot, { recursive: true });
31209
+ const logPath = join8(installRoot, "runtime-host.log");
30051
31210
  const script = generateHostWrapper(
30052
31211
  `${process.execPath} ${process.argv[1] ?? "rogatio"}`,
30053
31212
  logPath
30054
31213
  );
30055
- await writeFile4(hostPath, script, "utf8");
31214
+ await writeFile5(hostPath, script, "utf8");
30056
31215
  await chmod(hostPath, 493);
30057
31216
  }
30058
31217
  function makeTrustController() {
@@ -30062,7 +31221,7 @@ function makeTrustController() {
30062
31221
  return createRequestBodyTrustController({
30063
31222
  platform,
30064
31223
  installRoot,
30065
- hostPath: join7(installRoot, "runtime-host"),
31224
+ hostPath: join8(installRoot, "runtime-host"),
30066
31225
  hostName: "com.rogatio.runtime",
30067
31226
  allowedOrigins: [],
30068
31227
  manifestDir: adapter.defaultManifestDir(),
@@ -30131,7 +31290,7 @@ async function trustRuntimeCommand(args) {
30131
31290
  }
30132
31291
  const controller = makeTrustController();
30133
31292
  const installRoot = defaultTrustInstallRoot(process.platform);
30134
- const hostPath = join7(installRoot, "runtime-host");
31293
+ const hostPath = join8(installRoot, "runtime-host");
30135
31294
  switch (subcommand) {
30136
31295
  case "install": {
30137
31296
  const installResult = await controller.install(extensionId);
@@ -30159,7 +31318,7 @@ Note: reload the extension at chrome://extensions for the native host to be dete
30159
31318
  } catch {
30160
31319
  }
30161
31320
  try {
30162
- await rm2(join7(installRoot, "runtime-host.log"), { force: true });
31321
+ await rm2(join8(installRoot, "runtime-host.log"), { force: true });
30163
31322
  } catch {
30164
31323
  }
30165
31324
  return reportTrust(
@@ -30229,7 +31388,6 @@ allowed_origins includes chrome-extension://${extensionId}/`
30229
31388
  }
30230
31389
  async function runtimeHostCommand(args, options = {}) {
30231
31390
  let root;
30232
- let mockPort;
30233
31391
  const positional = [];
30234
31392
  let argumentError;
30235
31393
  for (let index = 0; index < args.length; index += 1) {
@@ -30238,15 +31396,6 @@ async function runtimeHostCommand(args, options = {}) {
30238
31396
  root = resolve6(args[++index]);
30239
31397
  } else if (arg === "--root") {
30240
31398
  argumentError = "--root requires a value";
30241
- } else if (arg === "--mock-port" && index + 1 < args.length) {
30242
- const parsed = Number(args[++index]);
30243
- if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
30244
- argumentError = "--mock-port must be an integer port in 1..65535";
30245
- } else {
30246
- mockPort = parsed;
30247
- }
30248
- } else if (arg === "--mock-port") {
30249
- argumentError = "--mock-port requires a value";
30250
31399
  } else if (arg === "-" || !arg.startsWith("-")) {
30251
31400
  positional.push(arg);
30252
31401
  } else {
@@ -30262,7 +31411,6 @@ async function runtimeHostCommand(args, options = {}) {
30262
31411
  const aiProviderConfig2 = await readProviderConfig();
30263
31412
  await runNativeHost({
30264
31413
  fileRoot: root,
30265
- ...mockPort !== void 0 ? { mockPort } : {},
30266
31414
  ...aiProviderConfig2 !== null ? { aiProviderConfig: aiProviderConfig2 } : {},
30267
31415
  aiConfigReader: readProviderConfig,
30268
31416
  onReady: () => console.error(
@@ -30276,12 +31424,18 @@ async function runtimeHostCommand(args, options = {}) {
30276
31424
  const storage = options.storage ?? createJsonFileProjectStorage();
30277
31425
  try {
30278
31426
  if (inputPath === "-") {
30279
- const chunks = [];
30280
- for await (const chunk of process.stdin) {
30281
- chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
30282
- }
30283
31427
  filePath = "<stdin>";
30284
- projectData = JSON.parse(chunks.join(""));
31428
+ if (options.stdinInput !== void 0) {
31429
+ projectData = JSON.parse(options.stdinInput);
31430
+ } else {
31431
+ const chunks = [];
31432
+ for await (const chunk of process.stdin) {
31433
+ chunks.push(
31434
+ typeof chunk === "string" ? chunk : chunk.toString("utf8")
31435
+ );
31436
+ }
31437
+ projectData = JSON.parse(chunks.join(""));
31438
+ }
30285
31439
  } else {
30286
31440
  filePath = resolve6(inputPath);
30287
31441
  projectData = await storage.get(filePath);
@@ -30309,12 +31463,22 @@ async function runtimeHostCommand(args, options = {}) {
30309
31463
  }
30310
31464
  return 1;
30311
31465
  }
30312
- const rootDir = root ?? (inputPath === "-" ? process.cwd() : dirname7(filePath));
31466
+ const fileMock = compileResult.operations.some(
31467
+ (operation) => operation.kind === "mock" && operation.mock.file !== void 0
31468
+ );
31469
+ if (inputPath === "-" && fileMock && root === void 0) {
31470
+ console.error("Error: runtime.root-required");
31471
+ return 2;
31472
+ }
31473
+ const savedRoot = root === void 0 && inputPath !== "-" ? await readSavedMockRoot(filePath) : void 0;
31474
+ const rootDir = root ?? savedRoot ?? (inputPath === "-" ? void 0 : dirname9(filePath));
31475
+ const mocks = mocksFromOperations(compileResult.operations, void 0);
30313
31476
  const normalized = normalizeRuntimePreset({
30314
31477
  version: 1,
30315
31478
  limits: RUNTIME_LIMITS,
30316
- matchers: toMatcherOperations(compileResult.operations),
30317
- grants: []
31479
+ matchers: matchersFromOperations(compileResult.operations),
31480
+ grants: [],
31481
+ ...mocks.length > 0 ? { mocks } : {}
30318
31482
  });
30319
31483
  if (!normalized.ok) {
30320
31484
  console.error("Error: Failed to build runtime preset");
@@ -30323,8 +31487,7 @@ async function runtimeHostCommand(args, options = {}) {
30323
31487
  const aiProviderConfig = await readProviderConfig();
30324
31488
  await runNativeHost({
30325
31489
  preset: normalized.value,
30326
- fileRoot: rootDir,
30327
- ...mockPort !== void 0 ? { mockPort } : {},
31490
+ ...rootDir !== void 0 ? { fileRoot: rootDir } : {},
30328
31491
  ...aiProviderConfig !== null ? { aiProviderConfig } : {},
30329
31492
  aiConfigReader: readProviderConfig,
30330
31493
  onReady: () => console.error("rogatio runtime-host active")
@@ -30754,7 +31917,7 @@ async function verifyCommand(args, stdinInput, captureOutput = false, options =
30754
31917
  }
30755
31918
 
30756
31919
  // packages/cli/src/index.ts
30757
- var __dirname = dirname8(fileURLToPath2(import.meta.url));
31920
+ var __dirname = dirname10(fileURLToPath2(import.meta.url));
30758
31921
  var packageJsonPath = resolve9(
30759
31922
  __dirname,
30760
31923
  isDistBuild(__dirname) ? "../../package.json" : "../package.json"