@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.
package/README.md CHANGED
@@ -42,7 +42,7 @@ rogatio <command> [options]
42
42
  | `rogatio test [path] [url...]` | Run offline dry-run tests. Any filename is accepted; the default is `.rogatio.json` in the current directory. |
43
43
  | `rogatio ai <setup\|ls\|show\|delete\|test>` | AI provider configuration. `setup` interactive; `ls` list; `show` redacted; `delete` remove; `test` connection. |
44
44
  | `rogatio runtime <install\|uninstall\|verify>` | Register the native-messaging host with `install` and trust the device-local CA in one **transactional** call; it needs elevated privileges (Linux `sudo`, macOS keychain authorization, Windows Administrator) and rolls back with exit `1` and `trust unsupported: <reasons>` when a capability or elevation is missing. Release users never pass `--extension-id`. That flag is only for development (a local unpacked build without the release key) and for forks. `uninstall` removes the host manifest, the device-local CA files, and the CA trust installation (idempotent). `verify` reports whether manifest, `runtime-host` wrapper, allowed origins, and CA trust are all valid, and fails when `allowed_origins` omits the release ID. Release users never pass `--extension-id` on verify either. |
45
- | `rogatio runtime host [path]` | Run the consolidated native-messaging host for the project (pair/authorize/body transforms over stdio). Normally launched by the browser; run manually only for debugging. `--root <dir>` overrides the confined file root; `--mock-port <n>` binds the loopback mock-response faucet. |
45
+ | `rogatio runtime host [path]` | Run the consolidated native-messaging host for the project (pair/authorize/body transforms and mock responses over stdio). Normally launched by the browser; run manually only for debugging. `--root <dir>` overrides the confined mock file root. When omitted, a saved device-local root for that project file is used, otherwise the project directory. A stdin project that contains a file mock requires `--root`. |
46
46
 
47
47
  Global options: `--help, -h` and `--version, -v`. Run `rogatio <command> --help`
48
48
  for command-specific usage.
@@ -375,6 +375,35 @@
375
375
  font-weight: 600;
376
376
  font-size: 0.8125rem;
377
377
  }
378
+ .rogatio-editor [data-mock-file-root] {
379
+ display: grid;
380
+ gap: 0.45rem;
381
+ min-width: 0;
382
+ }
383
+ .rogatio-editor [data-mock-file-root] > span {
384
+ font-weight: 600;
385
+ }
386
+ .rogatio-editor [data-mock-file-root-hint] {
387
+ color: var(--rogatio-muted);
388
+ font-size: 0.8125rem;
389
+ }
390
+ .rogatio-editor [data-mock-file-root-value] {
391
+ font-family: var(--rogatio-font-mono);
392
+ font-size: 0.8125rem;
393
+ }
394
+ .rogatio-editor [data-mock-file-root] p {
395
+ margin: 0;
396
+ }
397
+ .rogatio-editor [data-mock-file-root-actions] {
398
+ display: flex;
399
+ flex-wrap: wrap;
400
+ gap: 0.5rem;
401
+ }
402
+ .rogatio-editor [data-mock-file-root-error] {
403
+ color: var(--rogatio-danger);
404
+ font-weight: 600;
405
+ font-size: 0.8125rem;
406
+ }
378
407
  .rogatio-editor [data-editor-origin-row] {
379
408
  display: grid;
380
409
  grid-template-columns: minmax(0, 1fr) auto;
@@ -909,16 +909,260 @@ function createHeaderRuleType() {
909
909
  };
910
910
  }
911
911
 
912
+ // packages/editor/src/rule-types/mock.ts
913
+ function isRecord(value) {
914
+ return typeof value === "object" && value !== null && !Array.isArray(value);
915
+ }
916
+ function asMockAction(value) {
917
+ if (!isRecord(value) || typeof value.status !== "number") return void 0;
918
+ const action = value;
919
+ if (action.headers !== void 0 && !Array.isArray(action.headers)) {
920
+ return void 0;
921
+ }
922
+ return action;
923
+ }
924
+ function compact(action) {
925
+ const next = {};
926
+ for (const [key, value] of Object.entries(action)) {
927
+ if (value !== void 0) next[key] = value;
928
+ }
929
+ return next;
930
+ }
931
+ function createMockRuleType() {
932
+ return {
933
+ id: "mock",
934
+ label: "Mock response",
935
+ actionField: "mock",
936
+ matches(rule) {
937
+ const type = rule.type;
938
+ if (type === "mock") return true;
939
+ if (type !== void 0) return false;
940
+ return asMockAction(rule.mock) !== void 0;
941
+ },
942
+ // Validation is owned by the host-supplied `validate` adapter (schema and
943
+ // semantic rules, including the 204/205/304 status ban). This extension adds
944
+ // no editor-local policy; mount() registers controls at the `/mock/...`
945
+ // paths so host diagnostics land on the matching fields.
946
+ validate() {
947
+ return [];
948
+ },
949
+ mount(context) {
950
+ const { document: document2, container } = context;
951
+ const getCurrent = () => asMockAction(context.getField("mock"));
952
+ const setAction = (next) => {
953
+ context.setField("mock", compact(next));
954
+ };
955
+ const render = () => {
956
+ const current = getCurrent();
957
+ container.replaceChildren();
958
+ if (current === void 0) return;
959
+ const statusLabel = document2.createElement("label");
960
+ statusLabel.textContent = "Status";
961
+ const statusInput = document2.createElement("input");
962
+ statusInput.type = "number";
963
+ statusInput.min = String(LIMITS.minMockStatus);
964
+ statusInput.max = String(LIMITS.maxMockStatus);
965
+ statusInput.step = "1";
966
+ statusInput.value = String(current.status);
967
+ statusInput.dataset.editorField = "true";
968
+ statusInput.addEventListener("input", () => {
969
+ const c = getCurrent();
970
+ if (c === void 0) return;
971
+ setAction({ ...c, status: Number(statusInput.value) });
972
+ });
973
+ context.registerControl("/mock/status", statusInput);
974
+ statusLabel.append(statusInput);
975
+ const delayLabel = document2.createElement("label");
976
+ delayLabel.textContent = "Delay (ms)";
977
+ const delayInput = document2.createElement("input");
978
+ delayInput.type = "number";
979
+ delayInput.min = "0";
980
+ delayInput.max = String(LIMITS.maxMockDelayMs);
981
+ delayInput.step = "1";
982
+ delayInput.value = current.delayMs === void 0 ? "" : String(current.delayMs);
983
+ delayInput.dataset.editorField = "true";
984
+ delayInput.addEventListener("input", () => {
985
+ const c = getCurrent();
986
+ if (c === void 0) return;
987
+ const raw = delayInput.value;
988
+ const next = raw === "" ? { ...c, delayMs: void 0 } : { ...c, delayMs: Number(raw) };
989
+ setAction(next);
990
+ });
991
+ context.registerControl("/mock/delayMs", delayInput);
992
+ delayLabel.append(delayInput);
993
+ const sourceLabel = document2.createElement("label");
994
+ sourceLabel.textContent = "Body source";
995
+ const sourceSelect = document2.createElement("select");
996
+ sourceSelect.dataset.editorField = "true";
997
+ const bodyOption = document2.createElement("option");
998
+ bodyOption.value = "body";
999
+ bodyOption.textContent = "Inline body";
1000
+ const fileOption = document2.createElement("option");
1001
+ fileOption.value = "file";
1002
+ fileOption.textContent = "File snapshot";
1003
+ sourceSelect.append(bodyOption, fileOption);
1004
+ sourceSelect.value = typeof current.file === "string" ? "file" : "body";
1005
+ context.registerControl("/mock", sourceSelect);
1006
+ sourceLabel.append(sourceSelect);
1007
+ const bodyArea = document2.createElement("textarea");
1008
+ bodyArea.value = typeof current.body === "string" ? current.body : "";
1009
+ bodyArea.dataset.editorField = "true";
1010
+ bodyArea.setAttribute("aria-label", "Body");
1011
+ bodyArea.addEventListener("input", () => {
1012
+ const c = getCurrent();
1013
+ if (c === void 0) return;
1014
+ setAction({ ...c, body: bodyArea.value, file: void 0 });
1015
+ });
1016
+ context.registerControl("/mock/body", bodyArea);
1017
+ const fileLabel = document2.createElement("label");
1018
+ fileLabel.textContent = "File path";
1019
+ const fileInput = document2.createElement("input");
1020
+ fileInput.type = "text";
1021
+ fileInput.value = typeof current.file === "string" ? current.file : "";
1022
+ fileInput.dataset.editorField = "true";
1023
+ fileInput.addEventListener("input", () => {
1024
+ const c = getCurrent();
1025
+ if (c === void 0) return;
1026
+ setAction({ ...c, body: void 0, file: fileInput.value });
1027
+ });
1028
+ context.registerControl("/mock/file", fileInput);
1029
+ fileLabel.append(fileInput);
1030
+ const syncSource = () => {
1031
+ const useFile = sourceSelect.value === "file";
1032
+ bodyArea.hidden = useFile;
1033
+ fileLabel.hidden = !useFile;
1034
+ };
1035
+ sourceSelect.addEventListener("change", () => {
1036
+ const c = getCurrent();
1037
+ if (c === void 0) return;
1038
+ if (sourceSelect.value === "file") {
1039
+ setAction({
1040
+ ...c,
1041
+ body: void 0,
1042
+ file: typeof c.file === "string" ? c.file : ""
1043
+ });
1044
+ } else {
1045
+ setAction({
1046
+ ...c,
1047
+ file: void 0,
1048
+ body: typeof c.body === "string" ? c.body : ""
1049
+ });
1050
+ }
1051
+ syncSource();
1052
+ });
1053
+ syncSource();
1054
+ const headersFieldset = document2.createElement("fieldset");
1055
+ const headersLegend = document2.createElement("legend");
1056
+ headersLegend.textContent = "Response headers";
1057
+ headersFieldset.append(headersLegend);
1058
+ const renderHeaders = () => {
1059
+ const c = getCurrent();
1060
+ if (c === void 0) return;
1061
+ headersFieldset.querySelectorAll("[data-mock-header-row]").forEach((row) => {
1062
+ row.remove();
1063
+ });
1064
+ headersFieldset.querySelectorAll("[data-mock-add-header]").forEach((button) => {
1065
+ button.remove();
1066
+ });
1067
+ const headers = c.headers ?? [];
1068
+ headers.forEach((header, index) => {
1069
+ const row = document2.createElement("div");
1070
+ row.dataset.mockHeaderRow = String(index);
1071
+ const nameLabel = document2.createElement("label");
1072
+ nameLabel.textContent = "Name";
1073
+ const nameInput = document2.createElement("input");
1074
+ nameInput.type = "text";
1075
+ nameInput.value = header.name;
1076
+ nameInput.dataset.editorField = "true";
1077
+ nameInput.addEventListener("input", () => {
1078
+ const cc = getCurrent();
1079
+ if (cc === void 0) return;
1080
+ const next = (cc.headers ?? []).map(
1081
+ (entry, itemIndex) => itemIndex === index ? { ...entry, name: nameInput.value } : entry
1082
+ );
1083
+ setAction({ ...cc, headers: next });
1084
+ });
1085
+ context.registerControl(`/mock/headers/${index}/name`, nameInput);
1086
+ nameLabel.append(nameInput);
1087
+ const valueLabel = document2.createElement("label");
1088
+ valueLabel.textContent = "Value";
1089
+ const valueInput = document2.createElement("input");
1090
+ valueInput.type = "text";
1091
+ valueInput.value = header.value;
1092
+ valueInput.dataset.editorField = "true";
1093
+ valueInput.addEventListener("input", () => {
1094
+ const cc = getCurrent();
1095
+ if (cc === void 0) return;
1096
+ const next = (cc.headers ?? []).map(
1097
+ (entry, itemIndex) => itemIndex === index ? { ...entry, value: valueInput.value } : entry
1098
+ );
1099
+ setAction({ ...cc, headers: next });
1100
+ });
1101
+ context.registerControl(`/mock/headers/${index}/value`, valueInput);
1102
+ valueLabel.append(valueInput);
1103
+ const remove = document2.createElement("button");
1104
+ remove.type = "button";
1105
+ remove.textContent = "Remove";
1106
+ remove.dataset.editorField = "true";
1107
+ remove.addEventListener("click", () => {
1108
+ const cc = getCurrent();
1109
+ if (cc === void 0) return;
1110
+ const next = (cc.headers ?? []).filter(
1111
+ (_, itemIndex) => itemIndex !== index
1112
+ );
1113
+ setAction({
1114
+ ...cc,
1115
+ headers: next.length > 0 ? next : void 0
1116
+ });
1117
+ });
1118
+ row.append(nameLabel, valueLabel, remove);
1119
+ headersFieldset.append(row);
1120
+ });
1121
+ const add = document2.createElement("button");
1122
+ add.type = "button";
1123
+ add.textContent = "Add header";
1124
+ add.dataset.editorField = "true";
1125
+ add.dataset.mockAddHeader = "true";
1126
+ add.addEventListener("click", () => {
1127
+ const cc = getCurrent();
1128
+ if (cc === void 0) return;
1129
+ setAction({
1130
+ ...cc,
1131
+ headers: [...cc.headers ?? [], { name: "", value: "" }]
1132
+ });
1133
+ });
1134
+ headersFieldset.append(add);
1135
+ };
1136
+ renderHeaders();
1137
+ container.append(
1138
+ statusLabel,
1139
+ delayLabel,
1140
+ sourceLabel,
1141
+ bodyArea,
1142
+ fileLabel,
1143
+ headersFieldset
1144
+ );
1145
+ };
1146
+ render();
1147
+ return { destroy() {
1148
+ } };
1149
+ },
1150
+ defaultAction() {
1151
+ return { status: 200, body: "" };
1152
+ }
1153
+ };
1154
+ }
1155
+
912
1156
  // packages/editor/src/rule-types/query.ts
913
1157
  var MAX_QUERY_NAME_LENGTH = 256;
914
1158
  var MAX_QUERY_VALUE_LENGTH = 2048;
915
1159
  var MAX_QUERY_PARAMS = 64;
916
1160
  var QUERY_OPERATIONS = Object.freeze(["set", "remove"]);
917
- function isRecord(value) {
1161
+ function isRecord2(value) {
918
1162
  return typeof value === "object" && value !== null && !Array.isArray(value);
919
1163
  }
920
1164
  function asQueryAction(value) {
921
- if (!isRecord(value)) return void 0;
1165
+ if (!isRecord2(value)) return void 0;
922
1166
  if (value.type !== "query") return void 0;
923
1167
  const params = value.params;
924
1168
  if (!Array.isArray(params)) return void 0;
@@ -1193,11 +1437,11 @@ function createRedirectRuleType() {
1193
1437
  }
1194
1438
 
1195
1439
  // packages/editor/src/rule-types/request-body.ts
1196
- function isRecord2(value) {
1440
+ function isRecord3(value) {
1197
1441
  return value !== null && typeof value === "object" && !Array.isArray(value);
1198
1442
  }
1199
1443
  function requestBodyOf(value) {
1200
- if (!isRecord2(value) || !("mode" in value)) return void 0;
1444
+ if (!isRecord3(value) || !("mode" in value)) return void 0;
1201
1445
  return value;
1202
1446
  }
1203
1447
  function stable2(values) {
@@ -1389,11 +1633,11 @@ function createRequestBodyRuleType() {
1389
1633
  }
1390
1634
 
1391
1635
  // packages/editor/src/rule-types/response-body.ts
1392
- function isRecord3(value) {
1636
+ function isRecord4(value) {
1393
1637
  return value !== null && typeof value === "object" && !Array.isArray(value);
1394
1638
  }
1395
1639
  function responseBodyOf(value) {
1396
- if (!isRecord3(value)) return void 0;
1640
+ if (!isRecord4(value)) return void 0;
1397
1641
  if (value.mode === "replace") {
1398
1642
  return { mode: "replace", body: value.body };
1399
1643
  }
@@ -1462,7 +1706,7 @@ function createResponseBodyRuleType() {
1462
1706
  },
1463
1707
  validate(rule, rulePath) {
1464
1708
  const raw = rule.responseBody;
1465
- if (isRecord3(raw) && "mode" in raw && raw.mode !== "replace" && raw.mode !== "regex") {
1709
+ if (isRecord4(raw) && "mode" in raw && raw.mode !== "replace" && raw.mode !== "regex") {
1466
1710
  return stable3([
1467
1711
  {
1468
1712
  code: "editor.response-body-mode",
@@ -1474,7 +1718,7 @@ function createResponseBodyRuleType() {
1474
1718
  }
1475
1719
  const action = responseBodyOf(raw);
1476
1720
  if (action === void 0) {
1477
- if (isRecord3(raw) && raw.mode === "regex") {
1721
+ if (isRecord4(raw) && raw.mode === "regex") {
1478
1722
  return stable3(validateRegexReplacements([], rulePath));
1479
1723
  }
1480
1724
  return [];
@@ -1612,7 +1856,7 @@ function createResponseBodyRuleType() {
1612
1856
  } else {
1613
1857
  const stored = context.getField("responseBody");
1614
1858
  const writeRegex = (replacements) => {
1615
- if (isRecord3(stored) && !("mode" in stored) && Array.isArray(stored.replacements)) {
1859
+ if (isRecord4(stored) && !("mode" in stored) && Array.isArray(stored.replacements)) {
1616
1860
  context.setField("responseBody", { replacements });
1617
1861
  } else {
1618
1862
  context.setField("responseBody", {
@@ -1640,7 +1884,8 @@ var builtInRuleTypes = Object.freeze([
1640
1884
  createRedirectRuleType(),
1641
1885
  queryRuleType,
1642
1886
  createResponseBodyRuleType(),
1643
- createRequestBodyRuleType()
1887
+ createRequestBodyRuleType(),
1888
+ createMockRuleType()
1644
1889
  ]);
1645
1890
 
1646
1891
  // packages/editor/src/types.ts
@@ -1765,7 +2010,8 @@ var RULE_TYPE_LABELS = {
1765
2010
  query: "Query parameters",
1766
2011
  header: "Header",
1767
2012
  "response-body": "Response body",
1768
- "request-body": "Request body"
2013
+ "request-body": "Request body",
2014
+ mock: "Mock response"
1769
2015
  };
1770
2016
  var COMMON_RULE_FIELDS = /* @__PURE__ */ new Set([
1771
2017
  "id",
@@ -1790,7 +2036,7 @@ function normalizeProjectActions(value) {
1790
2036
  for (let index = 0; index < value.length; index += 1) {
1791
2037
  if (!Object.hasOwn(value, index)) continue;
1792
2038
  const item = value[index];
1793
- if (!isRecord4(item)) continue;
2039
+ if (!isRecord5(item)) continue;
1794
2040
  const command = item.command;
1795
2041
  const label = item.label;
1796
2042
  if (typeof command !== "string" || command.length === 0) continue;
@@ -1808,7 +2054,7 @@ function ruleCountLabel(count) {
1808
2054
  if (count === 1) return "1 rule";
1809
2055
  return `${count} rules`;
1810
2056
  }
1811
- function isRecord4(value) {
2057
+ function isRecord5(value) {
1812
2058
  return value !== null && typeof value === "object" && !Array.isArray(value);
1813
2059
  }
1814
2060
  function snapshotOwnData(value, ancestors = /* @__PURE__ */ new WeakSet()) {
@@ -1887,11 +2133,11 @@ function freezeSnapshot(value, seen = /* @__PURE__ */ new WeakSet()) {
1887
2133
  return value;
1888
2134
  }
1889
2135
  function asDraftProject(value) {
1890
- if (!isRecord4(value) || !Array.isArray(value.groups)) return void 0;
2136
+ if (!isRecord5(value) || !Array.isArray(value.groups)) return void 0;
1891
2137
  for (const group of value.groups) {
1892
- if (!isRecord4(group) || !Array.isArray(group.rules)) return void 0;
2138
+ if (!isRecord5(group) || !Array.isArray(group.rules)) return void 0;
1893
2139
  for (const rule of group.rules) {
1894
- if (!isRecord4(rule)) return void 0;
2140
+ if (!isRecord5(rule)) return void 0;
1895
2141
  }
1896
2142
  }
1897
2143
  return value;
@@ -1922,7 +2168,7 @@ function valueAtPath(root, path) {
1922
2168
  if (index === void 0 || !Object.hasOwn(current, index))
1923
2169
  return void 0;
1924
2170
  current = current[index];
1925
- } else if (isRecord4(current) && Object.hasOwn(current, segment)) {
2171
+ } else if (isRecord5(current) && Object.hasOwn(current, segment)) {
1926
2172
  current = current[segment];
1927
2173
  } else {
1928
2174
  return void 0;
@@ -1934,6 +2180,7 @@ var MATCHER_CREATABLE_FIELDS = ["description", "method", "type"];
1934
2180
  var ACTION_PAYLOAD_FIELDS = [
1935
2181
  "redirect",
1936
2182
  "action",
2183
+ "mock",
1937
2184
  "requestBody",
1938
2185
  "responseBody",
1939
2186
  "headerDirection",
@@ -1958,7 +2205,7 @@ function setValueAtPath(root, path, value) {
1958
2205
  return false;
1959
2206
  }
1960
2207
  current = current[childIndex];
1961
- } else if (isRecord4(current) && Object.hasOwn(current, segment)) {
2208
+ } else if (isRecord5(current) && Object.hasOwn(current, segment)) {
1962
2209
  current = current[segment];
1963
2210
  } else {
1964
2211
  return false;
@@ -1972,7 +2219,7 @@ function setValueAtPath(root, path, value) {
1972
2219
  current[index] = value;
1973
2220
  return true;
1974
2221
  }
1975
- if (!isRecord4(current)) return false;
2222
+ if (!isRecord5(current)) return false;
1976
2223
  if (!Object.hasOwn(current, finalSegment) && !CREATABLE_RULE_FIELDS.has(finalSegment)) {
1977
2224
  return false;
1978
2225
  }
@@ -1991,7 +2238,7 @@ function deleteValueAtPath(root, path) {
1991
2238
  const parentPath = pointer(...segments.slice(0, -1));
1992
2239
  const parent = valueAtPath(root, parentPath);
1993
2240
  const key = segments[segments.length - 1];
1994
- if (!isRecord4(parent) || !Object.hasOwn(parent, key)) return false;
2241
+ if (!isRecord5(parent) || !Object.hasOwn(parent, key)) return false;
1995
2242
  return delete parent[key];
1996
2243
  }
1997
2244
  function safeText(value, fallback = "") {
@@ -2027,7 +2274,7 @@ function normalizeDiagnostics(value) {
2027
2274
  }
2028
2275
  const diagnostics = [];
2029
2276
  for (const item of value) {
2030
- if (!isRecord4(item)) continue;
2277
+ if (!isRecord5(item)) continue;
2031
2278
  const code = item.code;
2032
2279
  const path = item.path;
2033
2280
  const message = item.message;
@@ -2041,7 +2288,7 @@ function isValidExtensionName(name) {
2041
2288
  return name.length > 0 && !COMMON_RULE_FIELDS.has(name) && !FORBIDDEN_EXTENSION_FIELDS.has(name) && !hasControl(name);
2042
2289
  }
2043
2290
  function clearActionFields(rule, keep, keepFields) {
2044
- if (!isRecord4(rule)) return false;
2291
+ if (!isRecord5(rule)) return false;
2045
2292
  let changed = false;
2046
2293
  for (const field of ACTION_PAYLOAD_FIELDS) {
2047
2294
  if (field === keep || keepFields?.has(field)) continue;
@@ -2057,9 +2304,9 @@ function extensionFieldParent(rule, name) {
2057
2304
  if (segments.length === 0) return void 0;
2058
2305
  let current = rule;
2059
2306
  for (let index = 0; index < segments.length - 1; index += 1) {
2060
- if (!isRecord4(current)) return void 0;
2307
+ if (!isRecord5(current)) return void 0;
2061
2308
  const next = current[segments[index]];
2062
- if (!isRecord4(next)) {
2309
+ if (!isRecord5(next)) {
2063
2310
  const created = {};
2064
2311
  Object.defineProperty(current, segments[index], {
2065
2312
  configurable: true,
@@ -2072,7 +2319,7 @@ function extensionFieldParent(rule, name) {
2072
2319
  current = next;
2073
2320
  }
2074
2321
  }
2075
- if (!isRecord4(current)) return void 0;
2322
+ if (!isRecord5(current)) return void 0;
2076
2323
  return { parent: current, key: segments[segments.length - 1] };
2077
2324
  }
2078
2325
  function toSearchText(value) {
@@ -2129,9 +2376,9 @@ function exactUrlFromSource(source) {
2129
2376
  return decoded;
2130
2377
  }
2131
2378
  function isDryRunResult(value) {
2132
- if (!isRecord4(value)) return false;
2379
+ if (!isRecord5(value)) return false;
2133
2380
  const summary = value.summary;
2134
- return Array.isArray(value.results) && Array.isArray(value.errors) && isRecord4(summary) && typeof summary.matchedUrlCount === "number" && typeof summary.urlCount === "number";
2381
+ return Array.isArray(value.results) && Array.isArray(value.errors) && isRecord5(summary) && typeof summary.matchedUrlCount === "number" && typeof summary.urlCount === "number";
2135
2382
  }
2136
2383
  function previewSentence(preview) {
2137
2384
  if (!preview || preview.summary.length === 0) return null;
@@ -2146,6 +2393,8 @@ function previewSentence(preview) {
2146
2393
  return `The request body would be ${preview.summary}.`;
2147
2394
  case "response-body":
2148
2395
  return `The response body would be ${preview.summary}.`;
2396
+ case "mock":
2397
+ return preview.summary;
2149
2398
  default:
2150
2399
  return null;
2151
2400
  }
@@ -2194,7 +2443,7 @@ function applyRuleProposalAction(rule, ruleProposal) {
2194
2443
  }
2195
2444
  case "header": {
2196
2445
  rule.type = "header";
2197
- if (!isRecord4(action)) return;
2446
+ if (!isRecord5(action)) return;
2198
2447
  const direction = ownString(action, "headerDirection", "direction");
2199
2448
  const operation = ownString(action, "headerOperation", "operation");
2200
2449
  const name = ownString(action, "headerName", "name");
@@ -3504,7 +3753,7 @@ var EditorControllerImpl = class {
3504
3753
  return;
3505
3754
  }
3506
3755
  const diagnostics = normalizeDiagnostics(
3507
- isRecord4(outcome) ? outcome.diagnostics : void 0
3756
+ isRecord5(outcome) ? outcome.diagnostics : void 0
3508
3757
  );
3509
3758
  this.showDryRunDiagnostics(diagnostics);
3510
3759
  } catch (e) {
@@ -4166,6 +4415,7 @@ var EditorControllerImpl = class {
4166
4415
  "/description",
4167
4416
  description
4168
4417
  );
4418
+ this.options.mountProjectDetails?.(fieldGrid);
4169
4419
  fields.append(fieldGrid);
4170
4420
  this.renderProjectActions(fields);
4171
4421
  this.form.append(fields);
@@ -5565,10 +5815,10 @@ var EditorControllerImpl = class {
5565
5815
  }
5566
5816
  };
5567
5817
  function isSaveSuccess(value) {
5568
- return isRecord4(value) && value.ok === true;
5818
+ return isRecord5(value) && value.ok === true;
5569
5819
  }
5570
5820
  function saveFailureDiagnostic(value) {
5571
- if (!isRecord4(value)) {
5821
+ if (!isRecord5(value)) {
5572
5822
  return diagnostic(
5573
5823
  "editor.save-failed",
5574
5824
  "",
@@ -5631,6 +5881,7 @@ export {
5631
5881
  builtInRuleTypes,
5632
5882
  createEditor,
5633
5883
  createHeaderRuleType,
5884
+ createMockRuleType,
5634
5885
  createRedirectRuleType,
5635
5886
  createRequestBodyRuleType,
5636
5887
  createResponseBodyRuleType,