@testsmith/api-spector 0.3.5 → 0.3.7

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/out/main/index.js CHANGED
@@ -22,28 +22,28 @@ 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-C0IQL-Vl.js");
25
+ const handle = require("./chunks/handle-BCnNIZZr.js");
26
26
  const path = require("path");
27
27
  const fs = require("fs");
28
28
  const promises = require("fs/promises");
29
- const requestCollection = require("./chunks/request-collection-8TOVNXE0.js");
30
- const authBuilder = require("./chunks/auth-builder-CUs9yzOF.js");
29
+ const requestExec = require("./chunks/request-exec-AQD6cEgT.js");
31
30
  const ipcValidate = require("./chunks/ipc-validate-k6KI8adf.js");
32
31
  const uuid = require("uuid");
33
32
  const jsYaml = require("js-yaml");
34
33
  const undici = require("undici");
35
34
  const JSZip = require("jszip");
35
+ const requestCollection = require("./chunks/request-collection-CBuPjBwa.js");
36
36
  const mockServer = require("./chunks/mock-server-DSLR2ulH.js");
37
37
  const http = require("http");
38
38
  const WebSocket = require("ws");
39
- const soapHandler = require("./chunks/soap-handler-B9x_YCtj.js");
39
+ const soapHandler = require("./chunks/soap-handler-h0dWIjOK.js");
40
40
  const os = require("os");
41
41
  const crypto = require("crypto");
42
- const snapshots = require("./chunks/snapshots-UFd3XgSS.js");
42
+ const snapshots = require("./chunks/snapshots-NGkGQNZ4.js");
43
43
  const simpleGit = require("simple-git");
44
- const recorder = require("./chunks/recorder-DFxJgn9c.js");
45
- require("vm");
44
+ const recorder = require("./chunks/recorder-0Ij921El.js");
46
45
  require("dayjs");
46
+ require("vm");
47
47
  require("tv4");
48
48
  require("jsonpath-plus");
49
49
  require("@xmldom/xmldom");
@@ -67,7 +67,7 @@ function atomicWrite(path2, data) {
67
67
  return promises.writeFile(path2, data, "utf8");
68
68
  }
69
69
  const SPECTOR_GITIGNORE = [
70
- "# API Spector — never commit secrets",
70
+ "# API Spector - never commit secrets",
71
71
  "*.secrets",
72
72
  ".env",
73
73
  ".env.local",
@@ -91,6 +91,9 @@ const SPECTOR_GITIGNORE = [
91
91
  "results.html",
92
92
  "coverage/",
93
93
  "",
94
+ "# Local session history (opt-in persistence)",
95
+ "history.json",
96
+ "",
94
97
  "# OS / editor",
95
98
  ".DS_Store",
96
99
  "Thumbs.db",
@@ -102,7 +105,7 @@ function readmeContents(workspaceFileName) {
102
105
  `# API tests`,
103
106
  ``,
104
107
  `This folder is an [API Spector](https://github.com/testsmith-io/api-spector) workspace.`,
105
- `Everything here is plain JSON — diff it, commit it, review it like any other code.`,
108
+ `Everything here is plain JSON - diff it, commit it, review it like any other code.`,
106
109
  ``,
107
110
  `## Layout`,
108
111
  ``,
@@ -144,7 +147,7 @@ function readmeContents(workspaceFileName) {
144
147
  `## A note on secrets`,
145
148
  ``,
146
149
  `Secret values (passwords, OAuth client secrets, API keys) are stored in your`,
147
- `OS keychain — **not** in this folder. Environment files only reference the`,
150
+ `OS keychain - **not** in this folder. Environment files only reference the`,
148
151
  `keychain entry by name, so it's safe to commit them.`,
149
152
  ``
150
153
  ].join("\n");
@@ -223,7 +226,7 @@ function registerFileHandlers(ipc) {
223
226
  const wsPath = result.filePaths[0];
224
227
  workspaceDir = path.dirname(wsPath);
225
228
  workspaceFile = wsPath;
226
- await requestCollection.loadGlobals(workspaceDir);
229
+ await requestExec.loadGlobals(workspaceDir);
227
230
  await saveLastWorkspacePath(wsPath);
228
231
  const raw = await promises.readFile(wsPath, "utf8");
229
232
  return { workspace: JSON.parse(raw), workspacePath: wsPath };
@@ -239,7 +242,7 @@ function registerFileHandlers(ipc) {
239
242
  if (result.canceled || !result.filePath) return null;
240
243
  workspaceDir = path.dirname(result.filePath);
241
244
  workspaceFile = result.filePath;
242
- await requestCollection.loadGlobals(workspaceDir);
245
+ await requestExec.loadGlobals(workspaceDir);
243
246
  await promises.mkdir(path.join(workspaceDir, "collections"), { recursive: true });
244
247
  await promises.mkdir(path.join(workspaceDir, "environments"), { recursive: true });
245
248
  await ensureGitignore(workspaceDir);
@@ -294,6 +297,20 @@ function registerFileHandlers(ipc) {
294
297
  if (err.code !== "ENOENT") throw err;
295
298
  }
296
299
  });
300
+ handle.handleIpc(ipc, handle.IPC.file.loadHistory, async () => {
301
+ if (!workspaceDir) return [];
302
+ try {
303
+ const raw = await promises.readFile(path.join(workspaceDir, "history.json"), "utf8");
304
+ const parsed = JSON.parse(raw);
305
+ return Array.isArray(parsed) ? parsed : [];
306
+ } catch {
307
+ return [];
308
+ }
309
+ });
310
+ handle.handleIpc(ipc, handle.IPC.file.saveHistory, async (_e, entries) => {
311
+ if (!workspaceDir) return;
312
+ await promises.writeFile(path.join(workspaceDir, "history.json"), JSON.stringify(entries, null, 2), "utf8");
313
+ });
297
314
  handle.handleIpc(ipc, handle.IPC.dialog.pickDir, async () => {
298
315
  const result = await electron.dialog.showOpenDialog({
299
316
  title: "Select Output Directory",
@@ -317,10 +334,10 @@ function registerFileHandlers(ipc) {
317
334
  await promises.writeFile(result.filePath, content, "utf8");
318
335
  return true;
319
336
  });
320
- handle.handleIpc(ipc, handle.IPC.globals.get, () => requestCollection.getGlobals());
337
+ handle.handleIpc(ipc, handle.IPC.globals.get, () => requestExec.getGlobals());
321
338
  handle.handleIpc(ipc, handle.IPC.globals.set, async (_e, patch) => {
322
- requestCollection.setGlobals(patch);
323
- await requestCollection.persistGlobals();
339
+ requestExec.setGlobals(patch);
340
+ await requestExec.persistGlobals();
324
341
  });
325
342
  handle.handleIpc(ipc, handle.IPC.file.closeWorkspace, async () => {
326
343
  workspaceDir = null;
@@ -342,7 +359,7 @@ function registerFileHandlers(ipc) {
342
359
  const workspace = JSON.parse(raw);
343
360
  workspaceDir = path.dirname(wsPath);
344
361
  workspaceFile = wsPath;
345
- await requestCollection.loadGlobals(workspaceDir);
362
+ await requestExec.loadGlobals(workspaceDir);
346
363
  return { workspace, workspacePath: wsPath };
347
364
  } catch {
348
365
  return null;
@@ -364,7 +381,7 @@ async function tryOpenWorkspaceInDir(dir) {
364
381
  const workspace = JSON.parse(raw);
365
382
  workspaceDir = dir;
366
383
  workspaceFile = wsPath;
367
- await requestCollection.loadGlobals(workspaceDir);
384
+ await requestExec.loadGlobals(workspaceDir);
368
385
  return { workspace, workspacePath: wsPath };
369
386
  } catch {
370
387
  return null;
@@ -383,7 +400,7 @@ function readStringField(obj, key) {
383
400
  function safeProxySummary(proxy) {
384
401
  if (!proxy?.url?.trim()) return "off";
385
402
  try {
386
- const normalized = requestCollection.buildProxyUri({ url: proxy.url });
403
+ const normalized = requestExec.buildProxyUri({ url: proxy.url });
387
404
  const parsed = new URL(normalized);
388
405
  const host = parsed.port ? `${parsed.hostname}:${parsed.port}` : parsed.hostname;
389
406
  const auth = proxy.auth ? "yes" : "no";
@@ -438,11 +455,11 @@ function registerRequestHandler(ipc) {
438
455
  tls,
439
456
  piiMaskPatterns = []
440
457
  } = payload;
441
- requestCollection.applyRequestDefaults(req);
458
+ requestExec.applyRequestDefaults(req);
442
459
  const start = Date.now();
443
- const liveGlobals = requestCollection.getGlobals();
460
+ const liveGlobals = requestExec.getGlobals();
444
461
  const mergedGlobals = { ...payloadGlobals, ...liveGlobals };
445
- const envVars = await authBuilder.buildEnvVars(environment);
462
+ const envVars = await requestExec.buildEnvVars(environment);
446
463
  let localVars = {};
447
464
  const decryptionWarnings = [];
448
465
  if (environment) {
@@ -456,14 +473,14 @@ function registerRequestHandler(ipc) {
456
473
  }
457
474
  }
458
475
  }
459
- const dynamicVars = await authBuilder.buildDynamicVars();
460
- let vars = authBuilder.mergeVars(envVars, collectionVars, mergedGlobals, localVars, dynamicVars);
476
+ const dynamicVars = await requestExec.buildDynamicVars();
477
+ let vars = requestExec.mergeVars(envVars, collectionVars, mergedGlobals, localVars, dynamicVars);
461
478
  let preScriptMeta = { consoleOutput: [] };
462
479
  let updatedCollectionVars = { ...collectionVars };
463
480
  let updatedEnvVars = { ...envVars };
464
481
  let updatedGlobals = { ...mergedGlobals };
465
482
  if (req.preRequestScript?.trim()) {
466
- const result = await requestCollection.runScript(authBuilder.interpolate(req.preRequestScript, vars), {
483
+ const result = await requestExec.runScript(requestExec.interpolate(req.preRequestScript, vars), {
467
484
  envVars: { ...envVars },
468
485
  collectionVars: { ...collectionVars },
469
486
  globals: { ...mergedGlobals },
@@ -474,14 +491,14 @@ function registerRequestHandler(ipc) {
474
491
  updatedEnvVars = result.updatedEnvVars;
475
492
  updatedCollectionVars = result.updatedCollectionVars;
476
493
  updatedGlobals = result.updatedGlobals;
477
- requestCollection.patchGlobals(result.updatedGlobals);
478
- await requestCollection.persistGlobals();
479
- vars = authBuilder.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
494
+ requestExec.patchGlobals(result.updatedGlobals);
495
+ await requestExec.persistGlobals();
496
+ vars = requestExec.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
480
497
  }
481
498
  let response;
482
499
  let scriptResponse;
483
500
  let sentRequest = { method: req.method, url: "", headers: {} };
484
- const resolvedUrl = authBuilder.buildUrl(req.url, req.params, vars);
501
+ const resolvedUrl = requestExec.buildUrl(req.url, req.params, vars);
485
502
  const secretValues = /* @__PURE__ */ new Set();
486
503
  if (environment) {
487
504
  for (const v of environment.variables) {
@@ -512,8 +529,8 @@ function registerRequestHandler(ipc) {
512
529
  };
513
530
  }
514
531
  try {
515
- const dispatcher = await requestCollection.buildDispatcher(proxy, tls);
516
- const exchange = await requestCollection.performHttpExchange({
532
+ const dispatcher = await requestExec.buildDispatcher(proxy, tls);
533
+ const exchange = await requestExec.performHttpExchange({
517
534
  req,
518
535
  vars,
519
536
  resolvedUrl,
@@ -524,8 +541,8 @@ function registerRequestHandler(ipc) {
524
541
  sentRequest = sent;
525
542
  }
526
543
  });
527
- const maskedBody = requestCollection.maskPii(exchange.responseBody, piiMaskPatterns);
528
- const maskedHeaders = requestCollection.maskHeaders(exchange.rawHeaders, piiMaskPatterns);
544
+ const maskedBody = requestExec.maskPii(exchange.responseBody, piiMaskPatterns);
545
+ const maskedHeaders = requestExec.maskHeaders(exchange.rawHeaders, piiMaskPatterns);
529
546
  const bodySize = Buffer.byteLength(exchange.responseBody, "utf8");
530
547
  response = {
531
548
  status: exchange.status,
@@ -563,12 +580,12 @@ function registerRequestHandler(ipc) {
563
580
  };
564
581
  scriptResponse = response;
565
582
  }
566
- const schemaTestResults = !response.error ? requestCollection.buildSchemaTestResults(req.schema, scriptResponse.body) : [];
583
+ const schemaTestResults = !response.error ? requestExec.buildSchemaTestResults(req.schema, scriptResponse.body) : [];
567
584
  let postTestResults = [];
568
585
  let postConsole = [];
569
586
  let postError;
570
587
  if (req.postRequestScript?.trim() && !response.error) {
571
- const result = await requestCollection.runScript(authBuilder.interpolate(req.postRequestScript, vars), {
588
+ const result = await requestExec.runScript(requestExec.interpolate(req.postRequestScript, vars), {
572
589
  envVars: { ...updatedEnvVars },
573
590
  collectionVars: { ...updatedCollectionVars },
574
591
  globals: { ...updatedGlobals },
@@ -584,15 +601,15 @@ function registerRequestHandler(ipc) {
584
601
  updatedCollectionVars = result.updatedCollectionVars;
585
602
  updatedGlobals = result.updatedGlobals;
586
603
  localVars = result.updatedLocalVars;
587
- requestCollection.patchGlobals(result.updatedGlobals);
588
- await requestCollection.persistGlobals();
604
+ requestExec.patchGlobals(result.updatedGlobals);
605
+ await requestExec.persistGlobals();
589
606
  }
590
607
  const combinedTestResults = [...schemaTestResults, ...postTestResults];
591
608
  if (!response.error && response.status >= 400 && combinedTestResults.length === 0) {
592
609
  combinedTestResults.push({
593
610
  name: `HTTP status ${response.status} ${response.statusText}`.trim(),
594
611
  passed: false,
595
- error: `Request returned ${response.status} — no assertion was defined to verify the status code.`
612
+ error: `Request returned ${response.status} - no assertion was defined to verify the status code.`
596
613
  });
597
614
  }
598
615
  const scriptResult = {
@@ -610,9 +627,9 @@ function registerRequestHandler(ipc) {
610
627
  });
611
628
  handle.handleIpc(ipc, handle.IPC.script.runHook, async (_e, payload) => {
612
629
  const { script, envVars, collectionVars, globals } = payload;
613
- const result = await requestCollection.runScript(script, { envVars, collectionVars, globals, localVars: {} });
614
- requestCollection.patchGlobals(result.updatedGlobals);
615
- await requestCollection.persistGlobals();
630
+ const result = await requestExec.runScript(script, { envVars, collectionVars, globals, localVars: {} });
631
+ requestExec.patchGlobals(result.updatedGlobals);
632
+ await requestExec.persistGlobals();
616
633
  return {
617
634
  updatedEnvVars: result.updatedEnvVars,
618
635
  updatedCollectionVars: result.updatedCollectionVars,
@@ -1781,14 +1798,14 @@ function buildNameMap(root, requests) {
1781
1798
  function buildVariablesFile(environment) {
1782
1799
  const lines = ["*** Variables ***"];
1783
1800
  if (!environment) {
1784
- lines.push("# No environment — add your variables here");
1801
+ lines.push("# No environment - add your variables here");
1785
1802
  lines.push("${BASE_URL} http://localhost:8080");
1786
1803
  return lines.join("\n") + "\n";
1787
1804
  }
1788
1805
  for (const v of environment.variables) {
1789
1806
  if (!v.enabled) continue;
1790
1807
  if (v.secret) {
1791
- lines.push(`# ${envVar(v.key)} — stored in OS keychain, never hardcoded`);
1808
+ lines.push(`# ${envVar(v.key)} - stored in OS keychain, never hardcoded`);
1792
1809
  } else {
1793
1810
  lines.push(`${robotVar(v.key)} ${v.value}`);
1794
1811
  }
@@ -1824,9 +1841,9 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
1824
1841
  const url = interpolate(req.url, varMap);
1825
1842
  const method = req.method.charAt(0) + req.method.slice(1).toLowerCase();
1826
1843
  lines.push(kwName);
1827
- lines.push(` [Documentation] Hook: ${req.hookType} — ${req.name}`);
1844
+ lines.push(` [Documentation] Hook: ${req.hookType} - ${req.name}`);
1828
1845
  if (!ROBOT_REQUESTS_METHODS.includes(req.method)) {
1829
- lines.push(` Log ${req.method} is not supported by robotframework-requests — hook skipped WARN`);
1846
+ lines.push(` Log ${req.method} is not supported by robotframework-requests - hook skipped WARN`);
1830
1847
  lines.push(` RETURN \${None}`);
1831
1848
  lines.push("");
1832
1849
  continue;
@@ -1867,7 +1884,7 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
1867
1884
  lines.push(kwName);
1868
1885
  lines.push(` [Documentation] ${req.description || req.name}`);
1869
1886
  if (!ROBOT_REQUESTS_METHODS.includes(req.method)) {
1870
- lines.push(` Log ${req.method} is not supported by robotframework-requests — request skipped WARN`);
1887
+ lines.push(` Log ${req.method} is not supported by robotframework-requests - request skipped WARN`);
1871
1888
  lines.push(` RETURN \${None}`);
1872
1889
  lines.push("");
1873
1890
  continue;
@@ -2015,7 +2032,7 @@ function buildTestSuite(collection, environment, nameMap) {
2015
2032
  }
2016
2033
  function buildReadme$6(collectionName, filePaths) {
2017
2034
  const tree = renderTree(filePaths);
2018
- return `# ${collectionName} — API Tests (Robot Framework)
2035
+ return `# ${collectionName} - API Tests (Robot Framework)
2019
2036
 
2020
2037
  ## Project structure
2021
2038
 
@@ -2024,7 +2041,7 @@ ${tree}
2024
2041
  \`\`\`
2025
2042
 
2026
2043
  > Secrets are read from OS environment variables (e.g. \`%{API_TOKEN}\`).
2027
- > Never hardcode secrets — export them in your shell or CI environment.
2044
+ > Never hardcode secrets - export them in your shell or CI environment.
2028
2045
 
2029
2046
  ## Setup
2030
2047
 
@@ -2309,7 +2326,7 @@ function buildPackageJson$3(collectionName) {
2309
2326
  }
2310
2327
  function buildReadme$5(collectionName, filePaths) {
2311
2328
  const tree = renderTree([...filePaths, ".env.local"]);
2312
- return `# ${collectionName} — API Tests (Playwright TypeScript)
2329
+ return `# ${collectionName} - API Tests (Playwright TypeScript)
2313
2330
 
2314
2331
  ## Project structure
2315
2332
 
@@ -2317,7 +2334,7 @@ function buildReadme$5(collectionName, filePaths) {
2317
2334
  ${tree}
2318
2335
  \`\`\`
2319
2336
 
2320
- > \`.env.local\` is git-ignored — fill in your secrets before running.
2337
+ > \`.env.local\` is git-ignored - fill in your secrets before running.
2321
2338
 
2322
2339
  ## Setup
2323
2340
 
@@ -2582,7 +2599,7 @@ function buildPackageJson$2(collectionName) {
2582
2599
  }
2583
2600
  function buildReadme$4(collectionName, filePaths) {
2584
2601
  const tree = renderTree([...filePaths, ".env.local"]);
2585
- return `# ${collectionName} — API Tests (Playwright JavaScript)
2602
+ return `# ${collectionName} - API Tests (Playwright JavaScript)
2586
2603
 
2587
2604
  ## Project structure
2588
2605
 
@@ -2590,7 +2607,7 @@ function buildReadme$4(collectionName, filePaths) {
2590
2607
  ${tree}
2591
2608
  \`\`\`
2592
2609
 
2593
- > \`.env.local\` is git-ignored — fill in your secrets before running.
2610
+ > \`.env.local\` is git-ignored - fill in your secrets before running.
2594
2611
 
2595
2612
  ## Setup
2596
2613
 
@@ -2842,7 +2859,7 @@ function buildTsConfig() {
2842
2859
  }
2843
2860
  function buildReadme$3(collectionName, filePaths) {
2844
2861
  const tree = renderTree([...filePaths, ".env.local"]);
2845
- return `# ${collectionName} — API Tests (Supertest + Jest TypeScript)
2862
+ return `# ${collectionName} - API Tests (Supertest + Jest TypeScript)
2846
2863
 
2847
2864
  ## Project structure
2848
2865
 
@@ -2850,7 +2867,7 @@ function buildReadme$3(collectionName, filePaths) {
2850
2867
  ${tree}
2851
2868
  \`\`\`
2852
2869
 
2853
- > \`.env.local\` is git-ignored — fill in your secrets before running.
2870
+ > \`.env.local\` is git-ignored - fill in your secrets before running.
2854
2871
 
2855
2872
  ## Setup
2856
2873
 
@@ -3088,7 +3105,7 @@ function buildPackageJson(collectionName) {
3088
3105
  }
3089
3106
  function buildReadme$2(collectionName, filePaths) {
3090
3107
  const tree = renderTree([...filePaths, ".env.local"]);
3091
- return `# ${collectionName} — API Tests (Supertest + Jest JavaScript)
3108
+ return `# ${collectionName} - API Tests (Supertest + Jest JavaScript)
3092
3109
 
3093
3110
  ## Project structure
3094
3111
 
@@ -3096,7 +3113,7 @@ function buildReadme$2(collectionName, filePaths) {
3096
3113
  ${tree}
3097
3114
  \`\`\`
3098
3115
 
3099
- > \`.env.local\` is git-ignored — fill in your secrets before running.
3116
+ > \`.env.local\` is git-ignored - fill in your secrets before running.
3100
3117
 
3101
3118
  ## Setup
3102
3119
 
@@ -3427,7 +3444,7 @@ ${methods.join("\n\n")}
3427
3444
  }
3428
3445
  function buildReadme$1(collectionName, filePaths) {
3429
3446
  const tree = renderTree(filePaths);
3430
- return `# ${collectionName} — API Tests (REST Assured + JUnit 5)
3447
+ return `# ${collectionName} - API Tests (REST Assured + JUnit 5)
3431
3448
 
3432
3449
  ## Project structure
3433
3450
 
@@ -3827,10 +3844,10 @@ ${scenarios.join("\n\n")}
3827
3844
  }
3828
3845
  function buildReadme(collectionName, filePaths) {
3829
3846
  const tree = renderTree(filePaths);
3830
- return `# ${collectionName} — API Tests (Karate + JUnit 5)
3847
+ return `# ${collectionName} - API Tests (Karate + JUnit 5)
3831
3848
 
3832
3849
  Karate is a BDD-flavoured API test framework that uses Gherkin feature files
3833
- (no glue code) — see https://docs.karatelabs.io for the full reference.
3850
+ (no glue code) - see https://docs.karatelabs.io for the full reference.
3834
3851
 
3835
3852
  ## Project structure
3836
3853
 
@@ -4035,17 +4052,17 @@ const sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
4035
4052
  function registerRunnerHandler(ipc) {
4036
4053
  handle.handleIpc(ipc, handle.IPC.runner.start, async (event, payload) => {
4037
4054
  const { items, environment, globals: payloadGlobals, proxy, tls, piiMaskPatterns = [], requestDelay = 0 } = payload;
4038
- const envVars = await authBuilder.buildEnvVars(environment);
4039
- const liveGlobals = requestCollection.getGlobals();
4055
+ const envVars = await requestExec.buildEnvVars(environment);
4056
+ const liveGlobals = requestExec.getGlobals();
4040
4057
  const globals = { ...payloadGlobals, ...liveGlobals };
4041
- const dispatcher = await requestCollection.buildDispatcher(proxy, tls);
4058
+ const dispatcher = await requestExec.buildDispatcher(proxy, tls);
4042
4059
  const summary = { total: items.length, passed: 0, failed: 0, errors: 0, skipped: 0, durationMs: 0 };
4043
4060
  const totalStart = Date.now();
4044
4061
  let runEnvVars = { ...envVars };
4045
4062
  let runCollectionVars = {};
4046
4063
  let runGlobals = { ...globals };
4047
4064
  let runLocalVars = {};
4048
- const skipTracker = new requestCollection.HookSkipTracker();
4065
+ const skipTracker = new requestExec.HookSkipTracker();
4049
4066
  for (const item of items) {
4050
4067
  const skipReason = skipTracker.shouldSkip(item);
4051
4068
  if (skipReason) {
@@ -4075,7 +4092,7 @@ function registerRunnerHandler(ipc) {
4075
4092
  scopePath: item.scopePath
4076
4093
  };
4077
4094
  event.sender.send(handle.IPC.runner.progress, { requestId: item.request.id, ...runningUpdate });
4078
- const { result, updatedEnvVars, updatedCollectionVars, updatedGlobals, updatedLocalVars } = await requestCollection.executeRunnerRequest({
4095
+ const { result, updatedEnvVars, updatedCollectionVars, updatedGlobals, updatedLocalVars } = await requestExec.executeRunnerRequest({
4079
4096
  req: item.request,
4080
4097
  collectionVars: { ...item.collectionVars, ...runCollectionVars },
4081
4098
  envVars: runEnvVars,
@@ -4142,14 +4159,14 @@ function registerOAuth2Handlers(ipc) {
4142
4159
  handle.handleIpc(ipc, handle.IPC.oauth2.startFlow, async (_e, auth, vars) => {
4143
4160
  const port = auth.oauth2RedirectPort ?? 9876;
4144
4161
  const redirectUri = `http://localhost:${port}/callback`;
4145
- const authUrl = authBuilder.interpolate(auth.oauth2AuthUrl ?? "", vars);
4146
- const tokenUrl = authBuilder.interpolate(auth.oauth2TokenUrl ?? "", vars);
4147
- const clientId = authBuilder.interpolate(auth.oauth2ClientId ?? "", vars);
4162
+ const authUrl = requestExec.interpolate(auth.oauth2AuthUrl ?? "", vars);
4163
+ const tokenUrl = requestExec.interpolate(auth.oauth2TokenUrl ?? "", vars);
4164
+ const clientId = requestExec.interpolate(auth.oauth2ClientId ?? "", vars);
4148
4165
  let clientSecret = auth.oauth2ClientSecret ?? "";
4149
4166
  if (!clientSecret && auth.oauth2ClientSecretRef) {
4150
- clientSecret = await authBuilder.getSecret(auth.oauth2ClientSecretRef) ?? "";
4167
+ clientSecret = await requestExec.getSecret(auth.oauth2ClientSecretRef) ?? "";
4151
4168
  }
4152
- clientSecret = authBuilder.interpolate(clientSecret, vars);
4169
+ clientSecret = requestExec.interpolate(clientSecret, vars);
4153
4170
  if (!authUrl) throw new Error("OAuth 2.0: authUrl is required for authorization_code flow.");
4154
4171
  if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for authorization_code flow.");
4155
4172
  if (!clientId) throw new Error("OAuth 2.0: clientId is required.");
@@ -4224,13 +4241,13 @@ function registerOAuth2Handlers(ipc) {
4224
4241
  };
4225
4242
  });
4226
4243
  handle.handleIpc(ipc, handle.IPC.oauth2.refreshToken, async (_e, auth, vars, refreshToken) => {
4227
- const tokenUrl = authBuilder.interpolate(auth.oauth2TokenUrl ?? "", vars);
4228
- const clientId = authBuilder.interpolate(auth.oauth2ClientId ?? "", vars);
4244
+ const tokenUrl = requestExec.interpolate(auth.oauth2TokenUrl ?? "", vars);
4245
+ const clientId = requestExec.interpolate(auth.oauth2ClientId ?? "", vars);
4229
4246
  let clientSecret = auth.oauth2ClientSecret ?? "";
4230
4247
  if (!clientSecret && auth.oauth2ClientSecretRef) {
4231
- clientSecret = await authBuilder.getSecret(auth.oauth2ClientSecretRef) ?? "";
4248
+ clientSecret = await requestExec.getSecret(auth.oauth2ClientSecretRef) ?? "";
4232
4249
  }
4233
- clientSecret = authBuilder.interpolate(clientSecret, vars);
4250
+ clientSecret = requestExec.interpolate(clientSecret, vars);
4234
4251
  if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for refresh.");
4235
4252
  const { fetch: nodeFetch } = await import("undici");
4236
4253
  const params = new URLSearchParams();
@@ -4638,7 +4655,7 @@ function inferSchemaFromJson(json) {
4638
4655
  }
4639
4656
  async function resolveSnapshotSpec(relPath) {
4640
4657
  const dir = getWorkspaceDir();
4641
- if (!dir) throw new Error("No workspace open — cannot resolve snapshot.");
4658
+ if (!dir) throw new Error("No workspace open - cannot resolve snapshot.");
4642
4659
  const snap = await snapshots.loadSnapshot(dir, relPath);
4643
4660
  const tmp = path.join(os.tmpdir(), `api-spector-${crypto.randomUUID()}.${snap.format === "yaml" ? "yaml" : "json"}`);
4644
4661
  await promises.writeFile(tmp, snap.spec, "utf8");
@@ -4658,7 +4675,7 @@ function registerContractHandlers(ipc) {
4658
4675
  case "consumer":
4659
4676
  return snapshots.runConsumerContracts(requests, envVars, collectionVars);
4660
4677
  case "provider":
4661
- return snapshots.runProviderVerification(requests, envVars, specUrl, specPath, requestBaseUrl);
4678
+ return snapshots.runProviderVerification(requests, envVars, collectionVars, specUrl, specPath, requestBaseUrl);
4662
4679
  case "provider-live":
4663
4680
  return snapshots.runLiveProviderVerification(requests, envVars, collectionVars, providerBaseUrl, stateHandlerUrl);
4664
4681
  case "bidirectional":
@@ -4681,7 +4698,7 @@ function registerContractHandlers(ipc) {
4681
4698
  });
4682
4699
  handle.handleIpc(ipc, handle.IPC.contract.captureSnapshot, async (_e, opts) => {
4683
4700
  const dir = getWorkspaceDir();
4684
- if (!dir) throw new Error("No workspace open — cannot capture snapshot.");
4701
+ if (!dir) throw new Error("No workspace open - cannot capture snapshot.");
4685
4702
  const snapshot = await snapshots.captureSnapshot(dir, opts);
4686
4703
  const relPath = snapshots.relPathOf(snapshot);
4687
4704
  if (!relPath) throw new Error("Snapshot created but relPath was not attached.");
@@ -4702,6 +4719,24 @@ function registerContractHandlers(ipc) {
4702
4719
  if (!dir) return;
4703
4720
  await snapshots.deleteSnapshot(dir, relPath);
4704
4721
  });
4722
+ handle.handleIpc(ipc, handle.IPC.contract.fuzz, async (_e, payload) => {
4723
+ let { specUrl, specPath } = payload;
4724
+ if (payload.specSnapshotRelPath) {
4725
+ const resolved = await resolveSnapshotSpec(payload.specSnapshotRelPath);
4726
+ specPath = resolved.specPath;
4727
+ specUrl = void 0;
4728
+ }
4729
+ return snapshots.runFuzz({ ...payload, specUrl, specPath });
4730
+ });
4731
+ handle.handleIpc(ipc, handle.IPC.contract.recordResult, async (_e, opts) => {
4732
+ const dir = getWorkspaceDir();
4733
+ if (!dir) throw new Error("No workspace open — cannot record result.");
4734
+ const pacticipant = opts.pacticipant?.trim();
4735
+ const version = opts.version?.trim();
4736
+ if (!pacticipant || !version) throw new Error("Pacticipant and version are required.");
4737
+ const file = await snapshots.recordResult(dir, pacticipant, version, opts.report, (/* @__PURE__ */ new Date()).toISOString());
4738
+ return { file };
4739
+ });
4705
4740
  }
4706
4741
  function git() {
4707
4742
  const dir = getWorkspaceDir();
@@ -4983,10 +5018,10 @@ function createWindow() {
4983
5018
  }
4984
5019
  electron.app.whenReady().then(async () => {
4985
5020
  if (process.platform !== "darwin") electron.Menu.setApplicationMenu(null);
4986
- await authBuilder.initSecretStore(electron.app.getPath("userData"));
5021
+ await requestExec.initSecretStore(electron.app.getPath("userData"));
4987
5022
  registerFileHandlers(electron.ipcMain);
4988
5023
  registerRequestHandler(electron.ipcMain);
4989
- authBuilder.registerSecretHandlers(electron.ipcMain);
5024
+ requestExec.registerSecretHandlers(electron.ipcMain);
4990
5025
  registerImportHandlers(electron.ipcMain);
4991
5026
  registerGenerateHandlers(electron.ipcMain);
4992
5027
  registerRunnerHandler(electron.ipcMain);
@@ -2,7 +2,7 @@
2
2
  "use strict";
3
3
  const promises = require("fs/promises");
4
4
  const path = require("path");
5
- const recorder = require("./chunks/recorder-DFxJgn9c.js");
5
+ const recorder = require("./chunks/recorder-0Ij921El.js");
6
6
  const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
7
7
  require("http");
8
8
  require("crypto");
@@ -39,7 +39,7 @@ async function main() {
39
39
  });
40
40
  await recorder.startRecorder({ upstream, port, maskHeaders: extraMask, ignoreHeaders: extraIgnore });
41
41
  console.log("");
42
- console.log(cliCommon.color(" API Spector — Record Proxy", cliCommon.C.bold, cliCommon.C.white));
42
+ console.log(cliCommon.color(" API Spector - Record Proxy", cliCommon.C.bold, cliCommon.C.white));
43
43
  console.log(cliCommon.color(` Upstream: ${upstream}`, cliCommon.C.gray));
44
44
  console.log(cliCommon.color(` Listening: http://localhost:${port}`, cliCommon.C.cyan));
45
45
  console.log(cliCommon.color(` Output: ${outputDir}`, cliCommon.C.gray));
@@ -56,7 +56,7 @@ async function main() {
56
56
  const slug = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
57
57
  const sessionPath = path.join(outputDir, `session-${slug}.recording.json`);
58
58
  await promises.writeFile(sessionPath, JSON.stringify(session, null, 2), "utf8");
59
- const mockName = `Recorded — ${new URL(upstream).hostname} ${slug}`;
59
+ const mockName = `Recorded - ${new URL(upstream).hostname} ${slug}`;
60
60
  const mockServer = recorder.entriesToMockServer(session.entries, upstream, mockName, port);
61
61
  const mockPath = path.join(outputDir, `session-${slug}.mock.json`);
62
62
  await promises.writeFile(mockPath, JSON.stringify(mockServer, null, 2), "utf8");