@testsmith/api-spector 0.6.1 → 0.6.2

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.
Files changed (30) hide show
  1. package/out/main/agents.js +2 -1
  2. package/out/main/chunks/{snapshots-Y33M1SEy.js → broker-client-WEymdqAK.js} +119 -10
  3. package/out/main/chunks/{cli-common-BKYgsbGB.js → cli-common-vASoDfQH.js} +3 -2
  4. package/out/main/chunks/data-files-Br2h3Dt2.js +85 -0
  5. package/out/main/chunks/escape-BwPM4Tf6.js +9 -0
  6. package/out/main/chunks/{handle-BFaeFA0A.js → handle-CL-nWtYA.js} +11 -2
  7. package/out/main/chunks/{import-D9LH0isB.js → import-DdXhO56X.js} +2 -2
  8. package/out/main/chunks/{request-collection-BQIo1765.js → request-collection-Hqh3I3rs.js} +75 -15
  9. package/out/main/chunks/{request-exec-5_3279w1.js → request-exec-DAFA6Fap.js} +19 -1
  10. package/out/main/chunks/{soap-handler-CWn3o6Py.js → soap-handler-BAPCALkN.js} +2 -2
  11. package/out/main/compare.js +2 -1
  12. package/out/main/contract.js +50 -147
  13. package/out/main/coverage.js +2 -1
  14. package/out/main/generate-tests.js +2 -1
  15. package/out/main/index.js +131 -36
  16. package/out/main/lib.js +2 -2
  17. package/out/main/mock.js +2 -1
  18. package/out/main/record.js +2 -1
  19. package/out/main/runner.js +80 -12
  20. package/out/main/wsdl.js +5 -4
  21. package/out/preload/index.js +21 -3
  22. package/out/renderer/assets/{GraphQLEditor-8wU1UeBL.js → GraphQLEditor-DtppmZJO.js} +3 -1
  23. package/out/renderer/assets/{SoapEditor-DQwLLqCX.js → SoapEditor-BwSm12wH.js} +4 -2
  24. package/out/renderer/assets/ajv-2MAg6kGF.js +6153 -0
  25. package/out/renderer/assets/codemirror-F6m3Hm1M.js +26552 -0
  26. package/out/renderer/assets/index-B3mtLdU3.css +2 -0
  27. package/out/renderer/assets/{index-CdxTj7i6.js → index-D6-zmXen.js} +17491 -49305
  28. package/out/renderer/index.html +4 -2
  29. package/package.json +1 -1
  30. package/out/renderer/assets/index-CkHujZU3.css +0 -2
package/out/main/index.js CHANGED
@@ -22,18 +22,19 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  mod
23
23
  ));
24
24
  const electron = require("electron");
25
- const handle = require("./chunks/handle-BFaeFA0A.js");
25
+ const handle = require("./chunks/handle-CL-nWtYA.js");
26
26
  const path = require("path");
27
27
  const fs$1 = require("fs");
28
28
  const promises = require("fs/promises");
29
29
  const crypto = require("crypto");
30
30
  const JSZip = require("jszip");
31
- const requestExec = require("./chunks/request-exec-5_3279w1.js");
31
+ const dataFiles = require("./chunks/data-files-Br2h3Dt2.js");
32
+ const requestExec = require("./chunks/request-exec-DAFA6Fap.js");
32
33
  const ipcValidate = require("./chunks/ipc-validate-sqUFqRmE.js");
33
34
  const uuid = require("uuid");
34
35
  const jsYaml = require("js-yaml");
35
36
  const undici = require("undici");
36
- const requestCollection = require("./chunks/request-collection-BQIo1765.js");
37
+ const requestCollection = require("./chunks/request-collection-Hqh3I3rs.js");
37
38
  const mockServer = require("./chunks/mock-server-BxiXhP1m.js");
38
39
  const http = require("http");
39
40
  const WebSocket = require("ws");
@@ -42,10 +43,11 @@ const os = require("node:os");
42
43
  const path$1 = require("node:path");
43
44
  const grpc = require("@grpc/grpc-js");
44
45
  const protoLoader = require("@grpc/proto-loader");
45
- const soapHandler = require("./chunks/soap-handler-CWn3o6Py.js");
46
+ const soapHandler = require("./chunks/soap-handler-BAPCALkN.js");
47
+ const escape = require("./chunks/escape-BwPM4Tf6.js");
46
48
  const promises$1 = require("node:fs/promises");
47
49
  const os$1 = require("os");
48
- const snapshots = require("./chunks/snapshots-Y33M1SEy.js");
50
+ const brokerClient = require("./chunks/broker-client-WEymdqAK.js");
49
51
  const simpleGit = require("simple-git");
50
52
  const recorder = require("./chunks/recorder-0Ij921El.js");
51
53
  require("tls");
@@ -332,14 +334,16 @@ Create a workspace in this folder and add "${path.basename(wsPath)}" to it? The
332
334
  });
333
335
  handle.handleIpc(ipc, handle.IPC.file.loadCollection, async (_e, relPath) => {
334
336
  if (!workspaceDir) throw new Error("No workspace open");
335
- const raw = await promises.readFile(path.resolve(workspaceDir, relPath), "utf8");
336
- return JSON.parse(raw);
337
+ const fullPath = path.resolve(workspaceDir, relPath);
338
+ const raw = await promises.readFile(fullPath, "utf8");
339
+ return dataFiles.inlineDataSets(JSON.parse(raw), path.dirname(fullPath));
337
340
  });
338
341
  handle.handleIpc(ipc, handle.IPC.file.saveCollection, async (_e, relPath, col) => {
339
342
  if (!workspaceDir) throw new Error("No workspace open");
340
343
  const fullPath = path.resolve(workspaceDir, relPath);
341
344
  await promises.mkdir(path.dirname(fullPath), { recursive: true });
342
- await atomicWrite(fullPath, JSON.stringify(col, null, 2));
345
+ const toWrite = await dataFiles.externalizeDataSets(col, path.dirname(fullPath));
346
+ await atomicWrite(fullPath, JSON.stringify(toWrite, null, 2));
343
347
  });
344
348
  handle.handleIpc(ipc, handle.IPC.file.loadEnvironment, async (_e, relPath) => {
345
349
  if (!workspaceDir) throw new Error("No workspace open");
@@ -942,6 +946,77 @@ function registerRequestHandler(ipc) {
942
946
  };
943
947
  });
944
948
  }
949
+ const AI_OPENAI_TOKEN_REF = "ai:openai:token";
950
+ const DEFAULT_AI_MODEL = "gpt-4o-mini";
951
+ const OPENAI_URL = "https://api.openai.com/v1/chat/completions";
952
+ function systemPrompt(level) {
953
+ return [
954
+ "You write clear, concise API documentation in GitHub-flavored Markdown.",
955
+ `You are documenting an API ${level}.`,
956
+ "Return ONLY markdown — no preamble, no code fences around the whole answer.",
957
+ "Use short paragraphs and headings. Describe purpose, key parameters, request/response shape,",
958
+ "auth if relevant, and any gotchas. Do not invent endpoints or fields that are not in the context.",
959
+ "Keep it practical for a developer who will call this API."
960
+ ].join(" ");
961
+ }
962
+ function registerAiHandlers(ipc) {
963
+ handle.handleIpc(ipc, handle.IPC.ai.generateDocs, async (_e, input) => {
964
+ const token = await requestExec.getSecret(AI_OPENAI_TOKEN_REF);
965
+ if (!token) {
966
+ throw new Error("No OpenAI API key configured. Add one in Settings → AI.");
967
+ }
968
+ const model = input.model?.trim() || DEFAULT_AI_MODEL;
969
+ const userParts = [
970
+ `Name: ${input.name}`,
971
+ "",
972
+ input.context
973
+ ];
974
+ if (input.existing?.trim()) {
975
+ userParts.push("", "Existing documentation to refine and expand (keep what is still accurate):", input.existing.trim());
976
+ }
977
+ const controller = new AbortController();
978
+ const timeout = setTimeout(() => controller.abort(), 6e4);
979
+ try {
980
+ const res = await fetch(OPENAI_URL, {
981
+ method: "POST",
982
+ headers: {
983
+ "content-type": "application/json",
984
+ authorization: `Bearer ${token}`
985
+ },
986
+ body: JSON.stringify({
987
+ model,
988
+ temperature: 0.3,
989
+ messages: [
990
+ { role: "system", content: systemPrompt(input.level) },
991
+ { role: "user", content: userParts.join("\n") }
992
+ ]
993
+ }),
994
+ signal: controller.signal,
995
+ // @ts-expect-error undici accepts a dispatcher for proxy/TLS support
996
+ dispatcher: await requestExec.buildDispatcher(void 0, void 0)
997
+ });
998
+ if (!res.ok) {
999
+ let detail = `HTTP ${res.status}`;
1000
+ try {
1001
+ const body = await res.json();
1002
+ if (body?.error?.message) detail = body.error.message;
1003
+ } catch {
1004
+ }
1005
+ if (res.status === 401) detail = "OpenAI rejected the API key (401). Check the key in Settings → AI.";
1006
+ throw new Error(`Doc generation failed: ${detail}`);
1007
+ }
1008
+ const data = await res.json();
1009
+ const content = data.choices?.[0]?.message?.content?.trim();
1010
+ if (!content) throw new Error("The model returned an empty response.");
1011
+ return content;
1012
+ } catch (e) {
1013
+ if (e instanceof Error && e.name === "AbortError") throw new Error("Doc generation timed out after 60s.");
1014
+ throw e;
1015
+ } finally {
1016
+ clearTimeout(timeout);
1017
+ }
1018
+ });
1019
+ }
945
1020
  const POSTMAN_RULES = [
946
1021
  // Variables — environment
947
1022
  [/\bpm\.environment\.get\(/g, "sp.environment.get("],
@@ -1433,6 +1508,10 @@ function buildCollection(spec) {
1433
1508
  const globalSecurity = spec.security ?? [];
1434
1509
  const requests = {};
1435
1510
  const foldersByTag = {};
1511
+ const tagDescriptions = {};
1512
+ for (const tg of spec.tags ?? []) {
1513
+ if (tg?.name && tg.description) tagDescriptions[tg.name] = tg.description;
1514
+ }
1436
1515
  for (const [pathStr, pathItem] of Object.entries(spec.paths ?? {})) {
1437
1516
  const resolved = resolve(spec, pathItem);
1438
1517
  const pathLevelParams = resolved.parameters ?? [];
@@ -1462,14 +1541,14 @@ function buildCollection(spec) {
1462
1541
  params,
1463
1542
  auth: buildAuth(security, securitySchemes),
1464
1543
  body,
1465
- description: operation.description ?? "",
1544
+ description: operation.description || operation.summary || "",
1466
1545
  meta: { tags },
1467
1546
  ...responseSchema ? { schema: responseSchema } : {},
1468
1547
  ...examples ? { examples } : {}
1469
1548
  };
1470
1549
  requests[req.id] = req;
1471
1550
  if (!foldersByTag[tag]) {
1472
- foldersByTag[tag] = { id: uuid.v4(), name: tag, description: "", folders: [], requestIds: [] };
1551
+ foldersByTag[tag] = { id: uuid.v4(), name: tag, description: tagDescriptions[tag] ?? "", folders: [], requestIds: [] };
1473
1552
  }
1474
1553
  foldersByTag[tag].requestIds.push(req.id);
1475
1554
  }
@@ -1744,7 +1823,8 @@ function parseBruFile(content, fileName) {
1744
1823
  params,
1745
1824
  auth,
1746
1825
  body,
1747
- description: "",
1826
+ // Bruno keeps request documentation in a `docs { ... }` block (markdown).
1827
+ description: extractBlock(content, "docs")?.trim() ?? "",
1748
1828
  preRequestScript: extractBlock(content, "script:pre-request") ? translateScript(extractBlock(content, "script:pre-request").trim(), "bruno") || void 0 : void 0,
1749
1829
  postRequestScript: extractBlock(content, "script:post-response") ? translateScript(extractBlock(content, "script:post-response").trim(), "bruno") || void 0 : void 0,
1750
1830
  meta: { seq: meta["seq"] !== void 0 ? Number(meta["seq"]) : 0 }
@@ -4631,7 +4711,9 @@ function registerRunnerHandler(ipc) {
4631
4711
  dispatcher,
4632
4712
  piiMaskPatterns,
4633
4713
  proxy,
4634
- tls
4714
+ tls,
4715
+ expectStatus: item.expectStatus,
4716
+ owaspTag: item.owaspTag
4635
4717
  });
4636
4718
  runEnvVars = updatedEnvVars;
4637
4719
  runCollectionVars = updatedCollectionVars;
@@ -5251,9 +5333,7 @@ function generateMarkdown(payload) {
5251
5333
  }
5252
5334
  return lines.join("\n");
5253
5335
  }
5254
- function escHtml(s) {
5255
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
5256
- }
5336
+ const escHtml = escape.escapeHtml;
5257
5337
  const METHOD_COLORS = {
5258
5338
  GET: "#34d399",
5259
5339
  POST: "#60a5fa",
@@ -5443,7 +5523,7 @@ function inferSchemaFromJson(json) {
5443
5523
  async function resolveSnapshotSpec(relPath) {
5444
5524
  const dir = getWorkspaceDir();
5445
5525
  if (!dir) throw new Error("No workspace open - cannot resolve snapshot.");
5446
- const snap = await snapshots.loadSnapshot(dir, relPath);
5526
+ const snap = await brokerClient.loadSnapshot(dir, relPath);
5447
5527
  const tmp = path.join(os$1.tmpdir(), `api-spector-${crypto.randomUUID()}.${snap.format === "yaml" ? "yaml" : "json"}`);
5448
5528
  await promises.writeFile(tmp, snap.spec, "utf8");
5449
5529
  return { specPath: tmp };
@@ -5455,7 +5535,7 @@ function registerContractHandlers(ipc) {
5455
5535
  let requests = payload.requests;
5456
5536
  let { specUrl, specPath } = payload;
5457
5537
  if (mode !== "provider") {
5458
- const designReqs = await snapshots.loadDesignContractRequests(
5538
+ const designReqs = await brokerClient.loadDesignContractRequests(
5459
5539
  { designContracts: payload.designContracts },
5460
5540
  getWorkspaceDir() ?? void 0
5461
5541
  );
@@ -5476,13 +5556,13 @@ function registerContractHandlers(ipc) {
5476
5556
  }
5477
5557
  switch (mode) {
5478
5558
  case "consumer":
5479
- return snapshots.runConsumerContracts(requests, envVars, collectionVars, providerBaseUrl);
5559
+ return brokerClient.runConsumerContracts(requests, envVars, collectionVars, providerBaseUrl);
5480
5560
  case "provider":
5481
- return snapshots.runProviderVerification(requests, envVars, collectionVars, specUrl, specPath, requestBaseUrl);
5561
+ return brokerClient.runProviderVerification(requests, envVars, collectionVars, specUrl, specPath, requestBaseUrl);
5482
5562
  case "provider-live":
5483
- return snapshots.runLiveProviderVerification(requests, envVars, collectionVars, providerBaseUrl, stateHandlerUrl);
5563
+ return brokerClient.runLiveProviderVerification(requests, envVars, collectionVars, providerBaseUrl, stateHandlerUrl);
5484
5564
  case "bidirectional":
5485
- return snapshots.runBidirectional(requests, envVars, collectionVars, specUrl, specPath, requestBaseUrl);
5565
+ return brokerClient.runBidirectional(requests, envVars, collectionVars, specUrl, specPath, requestBaseUrl);
5486
5566
  }
5487
5567
  });
5488
5568
  handle.handleIpc(ipc, handle.IPC.contract.inferSchema, (_e, jsonBody) => {
@@ -5496,7 +5576,7 @@ function registerContractHandlers(ipc) {
5496
5576
  filters: [{ name: "HTML", extensions: ["html"] }]
5497
5577
  });
5498
5578
  if (canceled || !filePath) return false;
5499
- await promises.writeFile(filePath, snapshots.reportToHtml(report, { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...meta }), "utf8");
5579
+ await promises.writeFile(filePath, brokerClient.reportToHtml(report, { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...meta }), "utf8");
5500
5580
  return true;
5501
5581
  });
5502
5582
  handle.handleIpc(ipc, handle.IPC.contract.exportDesignPact, async (_e, contract) => {
@@ -5506,31 +5586,31 @@ function registerContractHandlers(ipc) {
5506
5586
  const relPath = path.join("pacts", `${safe(contract.consumer)}-${safe(contract.provider)}.pact.json`);
5507
5587
  const fullPath = path.join(dir, relPath);
5508
5588
  await promises.mkdir(path.join(dir, "pacts"), { recursive: true });
5509
- await promises.writeFile(fullPath, JSON.stringify(snapshots.designContractToPact(contract), null, 2), "utf8");
5589
+ await promises.writeFile(fullPath, JSON.stringify(brokerClient.designContractToPact(contract), null, 2), "utf8");
5510
5590
  return relPath;
5511
5591
  });
5512
5592
  handle.handleIpc(ipc, handle.IPC.contract.captureSnapshot, async (_e, opts) => {
5513
5593
  const dir = getWorkspaceDir();
5514
5594
  if (!dir) throw new Error("No workspace open - cannot capture snapshot.");
5515
- const snapshot = await snapshots.captureSnapshot(dir, opts);
5516
- const relPath = snapshots.relPathOf(snapshot);
5595
+ const snapshot = await brokerClient.captureSnapshot(dir, opts);
5596
+ const relPath = brokerClient.relPathOf(snapshot);
5517
5597
  if (!relPath) throw new Error("Snapshot created but relPath was not attached.");
5518
5598
  return { relPath, snapshot };
5519
5599
  });
5520
5600
  handle.handleIpc(ipc, handle.IPC.contract.listSnapshots, async (_e, registered = []) => {
5521
5601
  const dir = getWorkspaceDir();
5522
5602
  if (!dir) return [];
5523
- return snapshots.listSnapshots(dir, registered);
5603
+ return brokerClient.listSnapshots(dir, registered);
5524
5604
  });
5525
5605
  handle.handleIpc(ipc, handle.IPC.contract.loadSnapshot, async (_e, relPath) => {
5526
5606
  const dir = getWorkspaceDir();
5527
5607
  if (!dir) throw new Error("No workspace open.");
5528
- return snapshots.loadSnapshot(dir, relPath);
5608
+ return brokerClient.loadSnapshot(dir, relPath);
5529
5609
  });
5530
5610
  handle.handleIpc(ipc, handle.IPC.contract.deleteSnapshot, async (_e, relPath) => {
5531
5611
  const dir = getWorkspaceDir();
5532
5612
  if (!dir) return;
5533
- await snapshots.deleteSnapshot(dir, relPath);
5613
+ await brokerClient.deleteSnapshot(dir, relPath);
5534
5614
  });
5535
5615
  handle.handleIpc(ipc, handle.IPC.contract.fuzz, async (_e, payload) => {
5536
5616
  let { specUrl, specPath } = payload;
@@ -5539,7 +5619,7 @@ function registerContractHandlers(ipc) {
5539
5619
  specPath = resolved.specPath;
5540
5620
  specUrl = void 0;
5541
5621
  }
5542
- return snapshots.runFuzz({ ...payload, specUrl, specPath });
5622
+ return brokerClient.runFuzz({ ...payload, specUrl, specPath });
5543
5623
  });
5544
5624
  handle.handleIpc(ipc, handle.IPC.contract.recordResult, async (_e, opts) => {
5545
5625
  const dir = getWorkspaceDir();
@@ -5547,7 +5627,7 @@ function registerContractHandlers(ipc) {
5547
5627
  const pacticipant = opts.pacticipant?.trim();
5548
5628
  const version = opts.version?.trim();
5549
5629
  if (!pacticipant || !version) throw new Error("Pacticipant and version are required.");
5550
- const file = await snapshots.recordResult(dir, pacticipant, version, opts.report, (/* @__PURE__ */ new Date()).toISOString());
5630
+ const file = await brokerClient.recordResult(dir, pacticipant, version, opts.report, (/* @__PURE__ */ new Date()).toISOString());
5551
5631
  return { file };
5552
5632
  });
5553
5633
  }
@@ -5598,6 +5678,17 @@ function registerGitHandlers(ipc) {
5598
5678
  handle.handleIpc(ipc, handle.IPC.git.markResolved, async (_e, filePath) => {
5599
5679
  await git().add([filePath]);
5600
5680
  });
5681
+ handle.handleIpc(ipc, handle.IPC.git.readConflict, async (_e, filePath) => {
5682
+ const dir = getWorkspaceDir();
5683
+ if (!dir) throw new Error("No workspace open");
5684
+ return promises.readFile(path.join(dir, filePath), "utf8");
5685
+ });
5686
+ handle.handleIpc(ipc, handle.IPC.git.writeResolved, async (_e, filePath, content) => {
5687
+ const dir = getWorkspaceDir();
5688
+ if (!dir) throw new Error("No workspace open");
5689
+ await promises.writeFile(path.join(dir, filePath), content, "utf8");
5690
+ await git().add([filePath]);
5691
+ });
5601
5692
  handle.handleIpc(ipc, handle.IPC.git.diff, async (_e, filePath) => {
5602
5693
  if (filePath) return git().diff(["--", filePath]);
5603
5694
  return git().diff();
@@ -5683,8 +5774,11 @@ function registerGitHandlers(ipc) {
5683
5774
  handle.handleIpc(ipc, handle.IPC.git.deleteBranch, async (_e, name, force = false) => {
5684
5775
  await git().deleteLocalBranch(name, force);
5685
5776
  });
5686
- handle.handleIpc(ipc, handle.IPC.git.pull, async () => {
5687
- await git().pull();
5777
+ handle.handleIpc(ipc, handle.IPC.git.fetch, async () => {
5778
+ await git().fetch(["--prune"]);
5779
+ });
5780
+ handle.handleIpc(ipc, handle.IPC.git.pull, async (_e, rebase = false) => {
5781
+ await git().raw(["pull", ...rebase ? ["--rebase"] : []]);
5688
5782
  });
5689
5783
  handle.handleIpc(ipc, handle.IPC.git.push, async (_e, setUpstream) => {
5690
5784
  if (setUpstream) {
@@ -5744,7 +5838,7 @@ function registerRecordHandlers(ipc, getWebContents) {
5744
5838
  });
5745
5839
  }
5746
5840
  const CLOUD_TOKEN_REF = "cloud:token";
5747
- const CLOUD_ENDPOINT = (process.env["API_SPECTOR_CLOUD_ENDPOINT"] || "https://api-spector.dev").replace(/\/+$/, "");
5841
+ const CLOUD_ENDPOINT = brokerClient.resolveCloudEndpoint();
5748
5842
  async function cloudFetch(path2, method, body) {
5749
5843
  const token = await requestExec.getSecret(CLOUD_TOKEN_REF);
5750
5844
  if (!token) throw new Error("No cloud API token set. Add one in Settings → Cloud.");
@@ -5871,7 +5965,7 @@ function registerCloudHandlers(ipc) {
5871
5965
  return cloudFetch("/api/mocks/" + encodeURIComponent(slugify(name)), "GET");
5872
5966
  });
5873
5967
  handle.handleIpc(ipc, handle.IPC.cloud.pushPact, async (_e, input) => {
5874
- const pact = snapshots.exportPact(input.consumer, input.provider, input.requests ?? []);
5968
+ const pact = brokerClient.exportPact(input.consumer, input.provider, input.requests ?? []);
5875
5969
  return cloudFetch("/api/contracts", "PUT", {
5876
5970
  consumer: input.consumer,
5877
5971
  consumerVersion: input.consumerVersion,
@@ -5880,7 +5974,7 @@ function registerCloudHandlers(ipc) {
5880
5974
  });
5881
5975
  });
5882
5976
  handle.handleIpc(ipc, handle.IPC.cloud.pushDesignContract, async (_e, input) => {
5883
- const pact = snapshots.designContractToPact(input.contract);
5977
+ const pact = brokerClient.designContractToPact(input.contract);
5884
5978
  return cloudFetch("/api/contracts", "PUT", {
5885
5979
  consumer: input.contract.consumer,
5886
5980
  consumerVersion: input.consumerVersion,
@@ -5945,7 +6039,7 @@ function isNewer(a, b) {
5945
6039
  return a2 > b2;
5946
6040
  }
5947
6041
  async function checkForUpdate() {
5948
- const current = "0.6.1";
6042
+ const current = "0.6.2";
5949
6043
  try {
5950
6044
  const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(4e3) });
5951
6045
  if (!res.ok) return null;
@@ -6124,6 +6218,7 @@ electron.app.whenReady().then(async () => {
6124
6218
  registerFileHandlers(electron.ipcMain);
6125
6219
  registerRequestHandler(electron.ipcMain);
6126
6220
  requestExec.registerSecretHandlers(electron.ipcMain);
6221
+ registerAiHandlers(electron.ipcMain);
6127
6222
  registerImportHandlers(electron.ipcMain);
6128
6223
  registerGenerateHandlers(electron.ipcMain);
6129
6224
  registerRunnerHandler(electron.ipcMain);
package/out/main/lib.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const mockServer = require("./chunks/mock-server-BxiXhP1m.js");
4
- const requestExec = require("./chunks/request-exec-5_3279w1.js");
4
+ const requestExec = require("./chunks/request-exec-DAFA6Fap.js");
5
5
  require("http");
6
6
  require("crypto");
7
7
  require("vm");
@@ -10,7 +10,7 @@ require("@xmldom/xmldom");
10
10
  require("undici");
11
11
  require("fs/promises");
12
12
  require("tls");
13
- require("./chunks/handle-BFaeFA0A.js");
13
+ require("./chunks/handle-CL-nWtYA.js");
14
14
  require("node:fs");
15
15
  require("node:os");
16
16
  require("node:path");
package/out/main/mock.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  const mockServer = require("./chunks/mock-server-BxiXhP1m.js");
4
- const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
4
+ const cliCommon = require("./chunks/cli-common-vASoDfQH.js");
5
5
  require("http");
6
6
  require("crypto");
7
7
  require("vm");
@@ -9,6 +9,7 @@ require("dayjs");
9
9
  require("@xmldom/xmldom");
10
10
  require("fs/promises");
11
11
  require("path");
12
+ require("./chunks/data-files-Br2h3Dt2.js");
12
13
  async function main() {
13
14
  const args = cliCommon.parseArgs(process.argv.slice(2), ["name"]);
14
15
  if (args.help) {
@@ -3,10 +3,11 @@
3
3
  const promises = require("fs/promises");
4
4
  const path = require("path");
5
5
  const recorder = require("./chunks/recorder-0Ij921El.js");
6
- const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
6
+ const cliCommon = require("./chunks/cli-common-vASoDfQH.js");
7
7
  require("http");
8
8
  require("crypto");
9
9
  require("undici");
10
+ require("./chunks/data-files-Br2h3Dt2.js");
10
11
  function methodBadge(method) {
11
12
  const m = method.toUpperCase();
12
13
  const c = m === "GET" ? cliCommon.C.green : m === "POST" ? cliCommon.C.cyan : m === "PUT" || m === "PATCH" ? cliCommon.C.yellow : m === "DELETE" ? cliCommon.C.red : cliCommon.C.white;
@@ -2,13 +2,14 @@
2
2
  "use strict";
3
3
  const promises = require("fs/promises");
4
4
  const path = require("path");
5
- const requestExec = require("./chunks/request-exec-5_3279w1.js");
6
- const requestCollection = require("./chunks/request-collection-BQIo1765.js");
5
+ const requestExec = require("./chunks/request-exec-DAFA6Fap.js");
6
+ const escape = require("./chunks/escape-BwPM4Tf6.js");
7
+ const requestCollection = require("./chunks/request-collection-Hqh3I3rs.js");
7
8
  const environments = require("./chunks/environments-iM3SUM-4.js");
8
- const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
9
+ const cliCommon = require("./chunks/cli-common-vASoDfQH.js");
9
10
  require("undici");
10
11
  require("tls");
11
- require("./chunks/handle-BFaeFA0A.js");
12
+ require("./chunks/handle-CL-nWtYA.js");
12
13
  require("node:fs");
13
14
  require("node:os");
14
15
  require("node:path");
@@ -22,6 +23,7 @@ require("@xmldom/xmldom");
22
23
  require("http");
23
24
  require("ajv");
24
25
  require("uuid");
26
+ require("./chunks/data-files-Br2h3Dt2.js");
25
27
  function buildJsonReport(results, summary, meta = {}) {
26
28
  return JSON.stringify({
27
29
  timestamp: meta.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
@@ -50,7 +52,7 @@ function buildJsonReport(results, summary, meta = {}) {
50
52
  }, null, 2);
51
53
  }
52
54
  function buildHtmlReport(results, summary, meta = {}) {
53
- const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
55
+ const esc = escape.escapeHtml;
54
56
  function prettyJson(s) {
55
57
  try {
56
58
  return esc(JSON.stringify(JSON.parse(s), null, 2));
@@ -295,7 +297,7 @@ function buildHtmlReport(results, summary, meta = {}) {
295
297
  `;
296
298
  }
297
299
  function buildJUnitReport(results, summary, meta = {}) {
298
- const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
300
+ const esc = escape.escapeHtml;
299
301
  const suiteName = esc(meta.collection ?? "API Tests");
300
302
  const totalSec = (summary.durationMs / 1e3).toFixed(3);
301
303
  const ts = meta.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
@@ -345,6 +347,70 @@ ${failures.join("\n")}
345
347
  ];
346
348
  return lines.join("\n") + "\n";
347
349
  }
350
+ const OWASP_BY_TAG = {
351
+ BOLA: { id: "API1:2023", name: "Broken Object Level Authorization", ref: "https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/" },
352
+ AUTH: { id: "API2:2023", name: "Broken Authentication", ref: "https://owasp.org/API-Security/editions/2023/en/0xa2-broken-authentication/" },
353
+ "MASS-ASSIGN": { id: "API3:2023", name: "Broken Object Property Level Authorization", ref: "https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/" },
354
+ RESOURCE: { id: "API4:2023", name: "Unrestricted Resource Consumption", ref: "https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/" },
355
+ "FUNC-AUTH": { id: "API5:2023", name: "Broken Function Level Authorization", ref: "https://owasp.org/API-Security/editions/2023/en/0xa5-broken-function-level-authorization/" },
356
+ "BIZ-FLOW": { id: "API6:2023", name: "Unrestricted Access to Sensitive Business Flows", ref: "https://owasp.org/API-Security/editions/2023/en/0xa6-unrestricted-access-to-sensitive-business-flows/" },
357
+ SSRF: { id: "API7:2023", name: "Server Side Request Forgery", ref: "https://owasp.org/API-Security/editions/2023/en/0xa7-server-side-request-forgery/" },
358
+ CONFIG: { id: "API8:2023", name: "Security Misconfiguration", ref: "https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/" },
359
+ INVENTORY: { id: "API9:2023", name: "Improper Inventory Management", ref: "https://owasp.org/API-Security/editions/2023/en/0xa9-improper-inventory-management/" },
360
+ INJECTION: { id: "API10:2023", name: "Unsafe Consumption of APIs", ref: "https://owasp.org/API-Security/editions/2023/en/0xaa-unsafe-consumption-of-apis/" }
361
+ };
362
+ function owaspRuleForName(name) {
363
+ const m = /^\s*\[([A-Za-z-]+)\]/.exec(name ?? "");
364
+ return m ? OWASP_BY_TAG[m[1].toUpperCase()] ?? null : null;
365
+ }
366
+ function buildSarifReport(results, _summary, meta = {}) {
367
+ const rules = /* @__PURE__ */ new Map();
368
+ const addRule = (id, name, helpUri) => {
369
+ if (!rules.has(id)) rules.set(id, { id, name, ...helpUri ? { helpUri } : {} });
370
+ };
371
+ const sarifResults = [];
372
+ const emit = (rule, fallbackId, fallbackName, r, message) => {
373
+ const id = rule ? rule.id : fallbackId;
374
+ const name = rule ? rule.name : fallbackName;
375
+ addRule(id, name, rule?.ref);
376
+ sarifResults.push({
377
+ ruleId: id,
378
+ level: "error",
379
+ message: { text: `${r.method} ${r.resolvedUrl}${r.iterationLabel ? ` #${r.iterationLabel}` : ""} — ${message}` },
380
+ locations: [{ physicalLocation: { artifactLocation: { uri: r.resolvedUrl || r.name } } }]
381
+ });
382
+ };
383
+ for (const r of results) {
384
+ if (r.status === "error" && r.error) emit(owaspRuleForName(r.name), "request-error", "Request error", r, r.error);
385
+ if (r.preScriptError) emit(null, "pre-script-error", "Pre-request script error", r, r.preScriptError);
386
+ if (r.postScriptError) emit(null, "post-script-error", "Post-request script error", r, r.postScriptError);
387
+ for (const t of r.testResults ?? []) {
388
+ if (t.passed) continue;
389
+ const rule = owaspRuleForName(t.name) ?? owaspRuleForName(r.name);
390
+ emit(rule, "assertion-failed", "Assertion failed", r, `${t.name}${t.error ? `: ${t.error}` : ""}`);
391
+ }
392
+ }
393
+ return JSON.stringify({
394
+ version: "2.1.0",
395
+ $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
396
+ runs: [{
397
+ tool: {
398
+ driver: {
399
+ name: "API Spector",
400
+ informationUri: "https://github.com/testsmith-io/api-spector",
401
+ rules: [...rules.values()].map((r) => ({
402
+ id: r.id,
403
+ name: r.name,
404
+ shortDescription: { text: r.name },
405
+ ...r.helpUri ? { helpUri: r.helpUri } : {}
406
+ }))
407
+ }
408
+ },
409
+ ...meta.timestamp ? { invocations: [{ executionSuccessful: sarifResults.length === 0, endTimeUtc: meta.timestamp }] } : {},
410
+ results: sarifResults
411
+ }]
412
+ }, null, 2);
413
+ }
348
414
  function printResult(r, verbose) {
349
415
  const icon = r.status === "passed" ? cliCommon.color("✓", cliCommon.C.green, cliCommon.C.bold) : r.status === "failed" ? cliCommon.color("✗", cliCommon.C.red, cliCommon.C.bold) : r.status === "skipped" ? cliCommon.color("○", cliCommon.C.gray, cliCommon.C.bold) : cliCommon.color("⚠", cliCommon.C.yellow, cliCommon.C.bold);
350
416
  const http = r.httpStatus ? cliCommon.color(` ${r.httpStatus}`, r.httpStatus < 400 ? cliCommon.C.green : cliCommon.C.red) : "";
@@ -365,7 +431,7 @@ async function main() {
365
431
  const args = cliCommon.parseArgs(process.argv.slice(2));
366
432
  if (args.help) {
367
433
  console.log(
368
- "\nUsage:\n api-spector run --workspace <path> [--environment <name>] [--tags <a,b>]\n [--collection <name>] [--output <path>] [--format json|junit]\n [--verbose] [--bail]\n"
434
+ "\nUsage:\n api-spector run --workspace <path> [--environment <name>] [--tags <a,b>]\n [--collection <name>] [--output <path>] [--format json|junit|html|sarif]\n [--verbose] [--bail]\n"
369
435
  );
370
436
  process.exit(0);
371
437
  }
@@ -380,9 +446,9 @@ async function main() {
380
446
  const verbose = Boolean(args.verbose);
381
447
  const bail = Boolean(args.bail);
382
448
  const outputPath = args.output;
383
- const inferredFormat = outputPath ? path.extname(outputPath).toLowerCase() === ".xml" ? "junit" : path.extname(outputPath).toLowerCase() === ".html" ? "html" : "json" : "json";
449
+ const inferredFormat = outputPath ? path.extname(outputPath).toLowerCase() === ".xml" ? "junit" : path.extname(outputPath).toLowerCase() === ".html" ? "html" : path.extname(outputPath).toLowerCase() === ".sarif" ? "sarif" : "json" : "json";
384
450
  const explicitFormat = args.format?.toLowerCase();
385
- const outputFormat = explicitFormat === "junit" || explicitFormat === "html" ? explicitFormat : inferredFormat;
451
+ const outputFormat = explicitFormat === "junit" || explicitFormat === "html" || explicitFormat === "sarif" ? explicitFormat : inferredFormat;
386
452
  let workspace, wsDir;
387
453
  try {
388
454
  ;
@@ -403,7 +469,7 @@ async function main() {
403
469
  } else if (!envName && workspace.settings?.defaultEnvironment && !env) {
404
470
  console.warn(cliCommon.color(`Warning: default environment "${workspace.settings.defaultEnvironment}" not found. Running without environment.`, cliCommon.C.yellow));
405
471
  }
406
- const version = `v${"0.6.1"}`;
472
+ const version = `v${"0.6.2"}`;
407
473
  console.log("");
408
474
  console.log(cliCommon.color(" API Test Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
409
475
  console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
@@ -516,7 +582,9 @@ async function main() {
516
582
  dispatcher,
517
583
  piiMaskPatterns: piiPatterns,
518
584
  tls: effectiveTls,
519
- onScriptOutput
585
+ onScriptOutput,
586
+ expectStatus: item.expectStatus,
587
+ owaspTag: item.owaspTag
520
588
  });
521
589
  result = out.result;
522
590
  runEnvVars = out.updatedEnvVars;
@@ -562,7 +630,7 @@ async function main() {
562
630
  if (outputPath) {
563
631
  const meta = { workspace: wsPath, environment: env?.name ?? null, collection: firstColName, timestamp };
564
632
  const maskedResults = allResults.map(maskResult);
565
- const report = outputFormat === "junit" ? buildJUnitReport(maskedResults, summary, meta) : outputFormat === "html" ? buildHtmlReport(maskedResults, summary, meta) : buildJsonReport(maskedResults, summary, meta);
633
+ const report = outputFormat === "junit" ? buildJUnitReport(maskedResults, summary, meta) : outputFormat === "html" ? buildHtmlReport(maskedResults, summary, meta) : outputFormat === "sarif" ? buildSarifReport(maskedResults, summary, meta) : buildJsonReport(maskedResults, summary, meta);
566
634
  await promises.writeFile(path.resolve(outputPath), report, "utf8");
567
635
  console.log(cliCommon.color(` Report written: ${outputPath} (${outputFormat})
568
636
  `, cliCommon.C.gray));
package/out/main/wsdl.js CHANGED
@@ -4,12 +4,13 @@ const promises = require("fs/promises");
4
4
  const path = require("path");
5
5
  const https = require("https");
6
6
  const http = require("http");
7
- const soapHandler = require("./chunks/soap-handler-CWn3o6Py.js");
8
- const _import = require("./chunks/import-D9LH0isB.js");
9
- const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
10
- require("./chunks/handle-BFaeFA0A.js");
7
+ const soapHandler = require("./chunks/soap-handler-BAPCALkN.js");
8
+ const _import = require("./chunks/import-DdXhO56X.js");
9
+ const cliCommon = require("./chunks/cli-common-vASoDfQH.js");
10
+ require("./chunks/handle-CL-nWtYA.js");
11
11
  require("@xmldom/xmldom");
12
12
  require("uuid");
13
+ require("./chunks/data-files-Br2h3Dt2.js");
13
14
  require("crypto");
14
15
  function fetchUrl(url) {
15
16
  return new Promise((resolveP, rejectP) => {