@testsmith/api-spector 0.3.5 → 0.3.6

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-BtRMtQJg.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-DbNHbA8x.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-CqXVreNa.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-rV5tPwAd.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",
@@ -102,7 +102,7 @@ function readmeContents(workspaceFileName) {
102
102
  `# API tests`,
103
103
  ``,
104
104
  `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.`,
105
+ `Everything here is plain JSON - diff it, commit it, review it like any other code.`,
106
106
  ``,
107
107
  `## Layout`,
108
108
  ``,
@@ -144,7 +144,7 @@ function readmeContents(workspaceFileName) {
144
144
  `## A note on secrets`,
145
145
  ``,
146
146
  `Secret values (passwords, OAuth client secrets, API keys) are stored in your`,
147
- `OS keychain **not** in this folder. Environment files only reference the`,
147
+ `OS keychain - **not** in this folder. Environment files only reference the`,
148
148
  `keychain entry by name, so it's safe to commit them.`,
149
149
  ``
150
150
  ].join("\n");
@@ -223,7 +223,7 @@ function registerFileHandlers(ipc) {
223
223
  const wsPath = result.filePaths[0];
224
224
  workspaceDir = path.dirname(wsPath);
225
225
  workspaceFile = wsPath;
226
- await requestCollection.loadGlobals(workspaceDir);
226
+ await requestExec.loadGlobals(workspaceDir);
227
227
  await saveLastWorkspacePath(wsPath);
228
228
  const raw = await promises.readFile(wsPath, "utf8");
229
229
  return { workspace: JSON.parse(raw), workspacePath: wsPath };
@@ -239,7 +239,7 @@ function registerFileHandlers(ipc) {
239
239
  if (result.canceled || !result.filePath) return null;
240
240
  workspaceDir = path.dirname(result.filePath);
241
241
  workspaceFile = result.filePath;
242
- await requestCollection.loadGlobals(workspaceDir);
242
+ await requestExec.loadGlobals(workspaceDir);
243
243
  await promises.mkdir(path.join(workspaceDir, "collections"), { recursive: true });
244
244
  await promises.mkdir(path.join(workspaceDir, "environments"), { recursive: true });
245
245
  await ensureGitignore(workspaceDir);
@@ -317,10 +317,10 @@ function registerFileHandlers(ipc) {
317
317
  await promises.writeFile(result.filePath, content, "utf8");
318
318
  return true;
319
319
  });
320
- handle.handleIpc(ipc, handle.IPC.globals.get, () => requestCollection.getGlobals());
320
+ handle.handleIpc(ipc, handle.IPC.globals.get, () => requestExec.getGlobals());
321
321
  handle.handleIpc(ipc, handle.IPC.globals.set, async (_e, patch) => {
322
- requestCollection.setGlobals(patch);
323
- await requestCollection.persistGlobals();
322
+ requestExec.setGlobals(patch);
323
+ await requestExec.persistGlobals();
324
324
  });
325
325
  handle.handleIpc(ipc, handle.IPC.file.closeWorkspace, async () => {
326
326
  workspaceDir = null;
@@ -342,7 +342,7 @@ function registerFileHandlers(ipc) {
342
342
  const workspace = JSON.parse(raw);
343
343
  workspaceDir = path.dirname(wsPath);
344
344
  workspaceFile = wsPath;
345
- await requestCollection.loadGlobals(workspaceDir);
345
+ await requestExec.loadGlobals(workspaceDir);
346
346
  return { workspace, workspacePath: wsPath };
347
347
  } catch {
348
348
  return null;
@@ -364,7 +364,7 @@ async function tryOpenWorkspaceInDir(dir) {
364
364
  const workspace = JSON.parse(raw);
365
365
  workspaceDir = dir;
366
366
  workspaceFile = wsPath;
367
- await requestCollection.loadGlobals(workspaceDir);
367
+ await requestExec.loadGlobals(workspaceDir);
368
368
  return { workspace, workspacePath: wsPath };
369
369
  } catch {
370
370
  return null;
@@ -383,7 +383,7 @@ function readStringField(obj, key) {
383
383
  function safeProxySummary(proxy) {
384
384
  if (!proxy?.url?.trim()) return "off";
385
385
  try {
386
- const normalized = requestCollection.buildProxyUri({ url: proxy.url });
386
+ const normalized = requestExec.buildProxyUri({ url: proxy.url });
387
387
  const parsed = new URL(normalized);
388
388
  const host = parsed.port ? `${parsed.hostname}:${parsed.port}` : parsed.hostname;
389
389
  const auth = proxy.auth ? "yes" : "no";
@@ -438,11 +438,11 @@ function registerRequestHandler(ipc) {
438
438
  tls,
439
439
  piiMaskPatterns = []
440
440
  } = payload;
441
- requestCollection.applyRequestDefaults(req);
441
+ requestExec.applyRequestDefaults(req);
442
442
  const start = Date.now();
443
- const liveGlobals = requestCollection.getGlobals();
443
+ const liveGlobals = requestExec.getGlobals();
444
444
  const mergedGlobals = { ...payloadGlobals, ...liveGlobals };
445
- const envVars = await authBuilder.buildEnvVars(environment);
445
+ const envVars = await requestExec.buildEnvVars(environment);
446
446
  let localVars = {};
447
447
  const decryptionWarnings = [];
448
448
  if (environment) {
@@ -456,14 +456,14 @@ function registerRequestHandler(ipc) {
456
456
  }
457
457
  }
458
458
  }
459
- const dynamicVars = await authBuilder.buildDynamicVars();
460
- let vars = authBuilder.mergeVars(envVars, collectionVars, mergedGlobals, localVars, dynamicVars);
459
+ const dynamicVars = await requestExec.buildDynamicVars();
460
+ let vars = requestExec.mergeVars(envVars, collectionVars, mergedGlobals, localVars, dynamicVars);
461
461
  let preScriptMeta = { consoleOutput: [] };
462
462
  let updatedCollectionVars = { ...collectionVars };
463
463
  let updatedEnvVars = { ...envVars };
464
464
  let updatedGlobals = { ...mergedGlobals };
465
465
  if (req.preRequestScript?.trim()) {
466
- const result = await requestCollection.runScript(authBuilder.interpolate(req.preRequestScript, vars), {
466
+ const result = await requestExec.runScript(requestExec.interpolate(req.preRequestScript, vars), {
467
467
  envVars: { ...envVars },
468
468
  collectionVars: { ...collectionVars },
469
469
  globals: { ...mergedGlobals },
@@ -474,14 +474,14 @@ function registerRequestHandler(ipc) {
474
474
  updatedEnvVars = result.updatedEnvVars;
475
475
  updatedCollectionVars = result.updatedCollectionVars;
476
476
  updatedGlobals = result.updatedGlobals;
477
- requestCollection.patchGlobals(result.updatedGlobals);
478
- await requestCollection.persistGlobals();
479
- vars = authBuilder.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
477
+ requestExec.patchGlobals(result.updatedGlobals);
478
+ await requestExec.persistGlobals();
479
+ vars = requestExec.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
480
480
  }
481
481
  let response;
482
482
  let scriptResponse;
483
483
  let sentRequest = { method: req.method, url: "", headers: {} };
484
- const resolvedUrl = authBuilder.buildUrl(req.url, req.params, vars);
484
+ const resolvedUrl = requestExec.buildUrl(req.url, req.params, vars);
485
485
  const secretValues = /* @__PURE__ */ new Set();
486
486
  if (environment) {
487
487
  for (const v of environment.variables) {
@@ -512,8 +512,8 @@ function registerRequestHandler(ipc) {
512
512
  };
513
513
  }
514
514
  try {
515
- const dispatcher = await requestCollection.buildDispatcher(proxy, tls);
516
- const exchange = await requestCollection.performHttpExchange({
515
+ const dispatcher = await requestExec.buildDispatcher(proxy, tls);
516
+ const exchange = await requestExec.performHttpExchange({
517
517
  req,
518
518
  vars,
519
519
  resolvedUrl,
@@ -524,8 +524,8 @@ function registerRequestHandler(ipc) {
524
524
  sentRequest = sent;
525
525
  }
526
526
  });
527
- const maskedBody = requestCollection.maskPii(exchange.responseBody, piiMaskPatterns);
528
- const maskedHeaders = requestCollection.maskHeaders(exchange.rawHeaders, piiMaskPatterns);
527
+ const maskedBody = requestExec.maskPii(exchange.responseBody, piiMaskPatterns);
528
+ const maskedHeaders = requestExec.maskHeaders(exchange.rawHeaders, piiMaskPatterns);
529
529
  const bodySize = Buffer.byteLength(exchange.responseBody, "utf8");
530
530
  response = {
531
531
  status: exchange.status,
@@ -563,12 +563,12 @@ function registerRequestHandler(ipc) {
563
563
  };
564
564
  scriptResponse = response;
565
565
  }
566
- const schemaTestResults = !response.error ? requestCollection.buildSchemaTestResults(req.schema, scriptResponse.body) : [];
566
+ const schemaTestResults = !response.error ? requestExec.buildSchemaTestResults(req.schema, scriptResponse.body) : [];
567
567
  let postTestResults = [];
568
568
  let postConsole = [];
569
569
  let postError;
570
570
  if (req.postRequestScript?.trim() && !response.error) {
571
- const result = await requestCollection.runScript(authBuilder.interpolate(req.postRequestScript, vars), {
571
+ const result = await requestExec.runScript(requestExec.interpolate(req.postRequestScript, vars), {
572
572
  envVars: { ...updatedEnvVars },
573
573
  collectionVars: { ...updatedCollectionVars },
574
574
  globals: { ...updatedGlobals },
@@ -584,15 +584,15 @@ function registerRequestHandler(ipc) {
584
584
  updatedCollectionVars = result.updatedCollectionVars;
585
585
  updatedGlobals = result.updatedGlobals;
586
586
  localVars = result.updatedLocalVars;
587
- requestCollection.patchGlobals(result.updatedGlobals);
588
- await requestCollection.persistGlobals();
587
+ requestExec.patchGlobals(result.updatedGlobals);
588
+ await requestExec.persistGlobals();
589
589
  }
590
590
  const combinedTestResults = [...schemaTestResults, ...postTestResults];
591
591
  if (!response.error && response.status >= 400 && combinedTestResults.length === 0) {
592
592
  combinedTestResults.push({
593
593
  name: `HTTP status ${response.status} ${response.statusText}`.trim(),
594
594
  passed: false,
595
- error: `Request returned ${response.status} no assertion was defined to verify the status code.`
595
+ error: `Request returned ${response.status} - no assertion was defined to verify the status code.`
596
596
  });
597
597
  }
598
598
  const scriptResult = {
@@ -610,9 +610,9 @@ function registerRequestHandler(ipc) {
610
610
  });
611
611
  handle.handleIpc(ipc, handle.IPC.script.runHook, async (_e, payload) => {
612
612
  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();
613
+ const result = await requestExec.runScript(script, { envVars, collectionVars, globals, localVars: {} });
614
+ requestExec.patchGlobals(result.updatedGlobals);
615
+ await requestExec.persistGlobals();
616
616
  return {
617
617
  updatedEnvVars: result.updatedEnvVars,
618
618
  updatedCollectionVars: result.updatedCollectionVars,
@@ -1781,14 +1781,14 @@ function buildNameMap(root, requests) {
1781
1781
  function buildVariablesFile(environment) {
1782
1782
  const lines = ["*** Variables ***"];
1783
1783
  if (!environment) {
1784
- lines.push("# No environment add your variables here");
1784
+ lines.push("# No environment - add your variables here");
1785
1785
  lines.push("${BASE_URL} http://localhost:8080");
1786
1786
  return lines.join("\n") + "\n";
1787
1787
  }
1788
1788
  for (const v of environment.variables) {
1789
1789
  if (!v.enabled) continue;
1790
1790
  if (v.secret) {
1791
- lines.push(`# ${envVar(v.key)} stored in OS keychain, never hardcoded`);
1791
+ lines.push(`# ${envVar(v.key)} - stored in OS keychain, never hardcoded`);
1792
1792
  } else {
1793
1793
  lines.push(`${robotVar(v.key)} ${v.value}`);
1794
1794
  }
@@ -1824,9 +1824,9 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
1824
1824
  const url = interpolate(req.url, varMap);
1825
1825
  const method = req.method.charAt(0) + req.method.slice(1).toLowerCase();
1826
1826
  lines.push(kwName);
1827
- lines.push(` [Documentation] Hook: ${req.hookType} ${req.name}`);
1827
+ lines.push(` [Documentation] Hook: ${req.hookType} - ${req.name}`);
1828
1828
  if (!ROBOT_REQUESTS_METHODS.includes(req.method)) {
1829
- lines.push(` Log ${req.method} is not supported by robotframework-requests hook skipped WARN`);
1829
+ lines.push(` Log ${req.method} is not supported by robotframework-requests - hook skipped WARN`);
1830
1830
  lines.push(` RETURN \${None}`);
1831
1831
  lines.push("");
1832
1832
  continue;
@@ -1867,7 +1867,7 @@ function buildKeywordsFile(collection, varMap, nameMap, hookExtractedVars) {
1867
1867
  lines.push(kwName);
1868
1868
  lines.push(` [Documentation] ${req.description || req.name}`);
1869
1869
  if (!ROBOT_REQUESTS_METHODS.includes(req.method)) {
1870
- lines.push(` Log ${req.method} is not supported by robotframework-requests request skipped WARN`);
1870
+ lines.push(` Log ${req.method} is not supported by robotframework-requests - request skipped WARN`);
1871
1871
  lines.push(` RETURN \${None}`);
1872
1872
  lines.push("");
1873
1873
  continue;
@@ -2015,7 +2015,7 @@ function buildTestSuite(collection, environment, nameMap) {
2015
2015
  }
2016
2016
  function buildReadme$6(collectionName, filePaths) {
2017
2017
  const tree = renderTree(filePaths);
2018
- return `# ${collectionName} API Tests (Robot Framework)
2018
+ return `# ${collectionName} - API Tests (Robot Framework)
2019
2019
 
2020
2020
  ## Project structure
2021
2021
 
@@ -2024,7 +2024,7 @@ ${tree}
2024
2024
  \`\`\`
2025
2025
 
2026
2026
  > Secrets are read from OS environment variables (e.g. \`%{API_TOKEN}\`).
2027
- > Never hardcode secrets export them in your shell or CI environment.
2027
+ > Never hardcode secrets - export them in your shell or CI environment.
2028
2028
 
2029
2029
  ## Setup
2030
2030
 
@@ -2309,7 +2309,7 @@ function buildPackageJson$3(collectionName) {
2309
2309
  }
2310
2310
  function buildReadme$5(collectionName, filePaths) {
2311
2311
  const tree = renderTree([...filePaths, ".env.local"]);
2312
- return `# ${collectionName} API Tests (Playwright TypeScript)
2312
+ return `# ${collectionName} - API Tests (Playwright TypeScript)
2313
2313
 
2314
2314
  ## Project structure
2315
2315
 
@@ -2317,7 +2317,7 @@ function buildReadme$5(collectionName, filePaths) {
2317
2317
  ${tree}
2318
2318
  \`\`\`
2319
2319
 
2320
- > \`.env.local\` is git-ignored fill in your secrets before running.
2320
+ > \`.env.local\` is git-ignored - fill in your secrets before running.
2321
2321
 
2322
2322
  ## Setup
2323
2323
 
@@ -2582,7 +2582,7 @@ function buildPackageJson$2(collectionName) {
2582
2582
  }
2583
2583
  function buildReadme$4(collectionName, filePaths) {
2584
2584
  const tree = renderTree([...filePaths, ".env.local"]);
2585
- return `# ${collectionName} API Tests (Playwright JavaScript)
2585
+ return `# ${collectionName} - API Tests (Playwright JavaScript)
2586
2586
 
2587
2587
  ## Project structure
2588
2588
 
@@ -2590,7 +2590,7 @@ function buildReadme$4(collectionName, filePaths) {
2590
2590
  ${tree}
2591
2591
  \`\`\`
2592
2592
 
2593
- > \`.env.local\` is git-ignored fill in your secrets before running.
2593
+ > \`.env.local\` is git-ignored - fill in your secrets before running.
2594
2594
 
2595
2595
  ## Setup
2596
2596
 
@@ -2842,7 +2842,7 @@ function buildTsConfig() {
2842
2842
  }
2843
2843
  function buildReadme$3(collectionName, filePaths) {
2844
2844
  const tree = renderTree([...filePaths, ".env.local"]);
2845
- return `# ${collectionName} API Tests (Supertest + Jest TypeScript)
2845
+ return `# ${collectionName} - API Tests (Supertest + Jest TypeScript)
2846
2846
 
2847
2847
  ## Project structure
2848
2848
 
@@ -2850,7 +2850,7 @@ function buildReadme$3(collectionName, filePaths) {
2850
2850
  ${tree}
2851
2851
  \`\`\`
2852
2852
 
2853
- > \`.env.local\` is git-ignored fill in your secrets before running.
2853
+ > \`.env.local\` is git-ignored - fill in your secrets before running.
2854
2854
 
2855
2855
  ## Setup
2856
2856
 
@@ -3088,7 +3088,7 @@ function buildPackageJson(collectionName) {
3088
3088
  }
3089
3089
  function buildReadme$2(collectionName, filePaths) {
3090
3090
  const tree = renderTree([...filePaths, ".env.local"]);
3091
- return `# ${collectionName} API Tests (Supertest + Jest JavaScript)
3091
+ return `# ${collectionName} - API Tests (Supertest + Jest JavaScript)
3092
3092
 
3093
3093
  ## Project structure
3094
3094
 
@@ -3096,7 +3096,7 @@ function buildReadme$2(collectionName, filePaths) {
3096
3096
  ${tree}
3097
3097
  \`\`\`
3098
3098
 
3099
- > \`.env.local\` is git-ignored fill in your secrets before running.
3099
+ > \`.env.local\` is git-ignored - fill in your secrets before running.
3100
3100
 
3101
3101
  ## Setup
3102
3102
 
@@ -3427,7 +3427,7 @@ ${methods.join("\n\n")}
3427
3427
  }
3428
3428
  function buildReadme$1(collectionName, filePaths) {
3429
3429
  const tree = renderTree(filePaths);
3430
- return `# ${collectionName} API Tests (REST Assured + JUnit 5)
3430
+ return `# ${collectionName} - API Tests (REST Assured + JUnit 5)
3431
3431
 
3432
3432
  ## Project structure
3433
3433
 
@@ -3827,10 +3827,10 @@ ${scenarios.join("\n\n")}
3827
3827
  }
3828
3828
  function buildReadme(collectionName, filePaths) {
3829
3829
  const tree = renderTree(filePaths);
3830
- return `# ${collectionName} API Tests (Karate + JUnit 5)
3830
+ return `# ${collectionName} - API Tests (Karate + JUnit 5)
3831
3831
 
3832
3832
  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.
3833
+ (no glue code) - see https://docs.karatelabs.io for the full reference.
3834
3834
 
3835
3835
  ## Project structure
3836
3836
 
@@ -4035,17 +4035,17 @@ const sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
4035
4035
  function registerRunnerHandler(ipc) {
4036
4036
  handle.handleIpc(ipc, handle.IPC.runner.start, async (event, payload) => {
4037
4037
  const { items, environment, globals: payloadGlobals, proxy, tls, piiMaskPatterns = [], requestDelay = 0 } = payload;
4038
- const envVars = await authBuilder.buildEnvVars(environment);
4039
- const liveGlobals = requestCollection.getGlobals();
4038
+ const envVars = await requestExec.buildEnvVars(environment);
4039
+ const liveGlobals = requestExec.getGlobals();
4040
4040
  const globals = { ...payloadGlobals, ...liveGlobals };
4041
- const dispatcher = await requestCollection.buildDispatcher(proxy, tls);
4041
+ const dispatcher = await requestExec.buildDispatcher(proxy, tls);
4042
4042
  const summary = { total: items.length, passed: 0, failed: 0, errors: 0, skipped: 0, durationMs: 0 };
4043
4043
  const totalStart = Date.now();
4044
4044
  let runEnvVars = { ...envVars };
4045
4045
  let runCollectionVars = {};
4046
4046
  let runGlobals = { ...globals };
4047
4047
  let runLocalVars = {};
4048
- const skipTracker = new requestCollection.HookSkipTracker();
4048
+ const skipTracker = new requestExec.HookSkipTracker();
4049
4049
  for (const item of items) {
4050
4050
  const skipReason = skipTracker.shouldSkip(item);
4051
4051
  if (skipReason) {
@@ -4075,7 +4075,7 @@ function registerRunnerHandler(ipc) {
4075
4075
  scopePath: item.scopePath
4076
4076
  };
4077
4077
  event.sender.send(handle.IPC.runner.progress, { requestId: item.request.id, ...runningUpdate });
4078
- const { result, updatedEnvVars, updatedCollectionVars, updatedGlobals, updatedLocalVars } = await requestCollection.executeRunnerRequest({
4078
+ const { result, updatedEnvVars, updatedCollectionVars, updatedGlobals, updatedLocalVars } = await requestExec.executeRunnerRequest({
4079
4079
  req: item.request,
4080
4080
  collectionVars: { ...item.collectionVars, ...runCollectionVars },
4081
4081
  envVars: runEnvVars,
@@ -4142,14 +4142,14 @@ function registerOAuth2Handlers(ipc) {
4142
4142
  handle.handleIpc(ipc, handle.IPC.oauth2.startFlow, async (_e, auth, vars) => {
4143
4143
  const port = auth.oauth2RedirectPort ?? 9876;
4144
4144
  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);
4145
+ const authUrl = requestExec.interpolate(auth.oauth2AuthUrl ?? "", vars);
4146
+ const tokenUrl = requestExec.interpolate(auth.oauth2TokenUrl ?? "", vars);
4147
+ const clientId = requestExec.interpolate(auth.oauth2ClientId ?? "", vars);
4148
4148
  let clientSecret = auth.oauth2ClientSecret ?? "";
4149
4149
  if (!clientSecret && auth.oauth2ClientSecretRef) {
4150
- clientSecret = await authBuilder.getSecret(auth.oauth2ClientSecretRef) ?? "";
4150
+ clientSecret = await requestExec.getSecret(auth.oauth2ClientSecretRef) ?? "";
4151
4151
  }
4152
- clientSecret = authBuilder.interpolate(clientSecret, vars);
4152
+ clientSecret = requestExec.interpolate(clientSecret, vars);
4153
4153
  if (!authUrl) throw new Error("OAuth 2.0: authUrl is required for authorization_code flow.");
4154
4154
  if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for authorization_code flow.");
4155
4155
  if (!clientId) throw new Error("OAuth 2.0: clientId is required.");
@@ -4224,13 +4224,13 @@ function registerOAuth2Handlers(ipc) {
4224
4224
  };
4225
4225
  });
4226
4226
  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);
4227
+ const tokenUrl = requestExec.interpolate(auth.oauth2TokenUrl ?? "", vars);
4228
+ const clientId = requestExec.interpolate(auth.oauth2ClientId ?? "", vars);
4229
4229
  let clientSecret = auth.oauth2ClientSecret ?? "";
4230
4230
  if (!clientSecret && auth.oauth2ClientSecretRef) {
4231
- clientSecret = await authBuilder.getSecret(auth.oauth2ClientSecretRef) ?? "";
4231
+ clientSecret = await requestExec.getSecret(auth.oauth2ClientSecretRef) ?? "";
4232
4232
  }
4233
- clientSecret = authBuilder.interpolate(clientSecret, vars);
4233
+ clientSecret = requestExec.interpolate(clientSecret, vars);
4234
4234
  if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for refresh.");
4235
4235
  const { fetch: nodeFetch } = await import("undici");
4236
4236
  const params = new URLSearchParams();
@@ -4638,7 +4638,7 @@ function inferSchemaFromJson(json) {
4638
4638
  }
4639
4639
  async function resolveSnapshotSpec(relPath) {
4640
4640
  const dir = getWorkspaceDir();
4641
- if (!dir) throw new Error("No workspace open cannot resolve snapshot.");
4641
+ if (!dir) throw new Error("No workspace open - cannot resolve snapshot.");
4642
4642
  const snap = await snapshots.loadSnapshot(dir, relPath);
4643
4643
  const tmp = path.join(os.tmpdir(), `api-spector-${crypto.randomUUID()}.${snap.format === "yaml" ? "yaml" : "json"}`);
4644
4644
  await promises.writeFile(tmp, snap.spec, "utf8");
@@ -4658,7 +4658,7 @@ function registerContractHandlers(ipc) {
4658
4658
  case "consumer":
4659
4659
  return snapshots.runConsumerContracts(requests, envVars, collectionVars);
4660
4660
  case "provider":
4661
- return snapshots.runProviderVerification(requests, envVars, specUrl, specPath, requestBaseUrl);
4661
+ return snapshots.runProviderVerification(requests, envVars, collectionVars, specUrl, specPath, requestBaseUrl);
4662
4662
  case "provider-live":
4663
4663
  return snapshots.runLiveProviderVerification(requests, envVars, collectionVars, providerBaseUrl, stateHandlerUrl);
4664
4664
  case "bidirectional":
@@ -4681,7 +4681,7 @@ function registerContractHandlers(ipc) {
4681
4681
  });
4682
4682
  handle.handleIpc(ipc, handle.IPC.contract.captureSnapshot, async (_e, opts) => {
4683
4683
  const dir = getWorkspaceDir();
4684
- if (!dir) throw new Error("No workspace open cannot capture snapshot.");
4684
+ if (!dir) throw new Error("No workspace open - cannot capture snapshot.");
4685
4685
  const snapshot = await snapshots.captureSnapshot(dir, opts);
4686
4686
  const relPath = snapshots.relPathOf(snapshot);
4687
4687
  if (!relPath) throw new Error("Snapshot created but relPath was not attached.");
@@ -4702,6 +4702,24 @@ function registerContractHandlers(ipc) {
4702
4702
  if (!dir) return;
4703
4703
  await snapshots.deleteSnapshot(dir, relPath);
4704
4704
  });
4705
+ handle.handleIpc(ipc, handle.IPC.contract.fuzz, async (_e, payload) => {
4706
+ let { specUrl, specPath } = payload;
4707
+ if (payload.specSnapshotRelPath) {
4708
+ const resolved = await resolveSnapshotSpec(payload.specSnapshotRelPath);
4709
+ specPath = resolved.specPath;
4710
+ specUrl = void 0;
4711
+ }
4712
+ return snapshots.runFuzz({ ...payload, specUrl, specPath });
4713
+ });
4714
+ handle.handleIpc(ipc, handle.IPC.contract.recordResult, async (_e, opts) => {
4715
+ const dir = getWorkspaceDir();
4716
+ if (!dir) throw new Error("No workspace open — cannot record result.");
4717
+ const pacticipant = opts.pacticipant?.trim();
4718
+ const version = opts.version?.trim();
4719
+ if (!pacticipant || !version) throw new Error("Pacticipant and version are required.");
4720
+ const file = await snapshots.recordResult(dir, pacticipant, version, opts.report, (/* @__PURE__ */ new Date()).toISOString());
4721
+ return { file };
4722
+ });
4705
4723
  }
4706
4724
  function git() {
4707
4725
  const dir = getWorkspaceDir();
@@ -4983,10 +5001,10 @@ function createWindow() {
4983
5001
  }
4984
5002
  electron.app.whenReady().then(async () => {
4985
5003
  if (process.platform !== "darwin") electron.Menu.setApplicationMenu(null);
4986
- await authBuilder.initSecretStore(electron.app.getPath("userData"));
5004
+ await requestExec.initSecretStore(electron.app.getPath("userData"));
4987
5005
  registerFileHandlers(electron.ipcMain);
4988
5006
  registerRequestHandler(electron.ipcMain);
4989
- authBuilder.registerSecretHandlers(electron.ipcMain);
5007
+ requestExec.registerSecretHandlers(electron.ipcMain);
4990
5008
  registerImportHandlers(electron.ipcMain);
4991
5009
  registerGenerateHandlers(electron.ipcMain);
4992
5010
  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");
@@ -2,18 +2,19 @@
2
2
  "use strict";
3
3
  const promises = require("fs/promises");
4
4
  const path = require("path");
5
- const authBuilder = require("./chunks/auth-builder-CUs9yzOF.js");
6
- const requestCollection = require("./chunks/request-collection-8TOVNXE0.js");
5
+ const requestExec = require("./chunks/request-exec-DbNHbA8x.js");
6
+ const requestCollection = require("./chunks/request-collection-CBuPjBwa.js");
7
+ const environments = require("./chunks/environments-iM3SUM-4.js");
7
8
  const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
9
+ require("undici");
10
+ require("./chunks/handle-BtRMtQJg.js");
8
11
  require("crypto");
9
- require("http");
10
- require("./chunks/handle-C0IQL-Vl.js");
11
12
  require("dayjs");
12
13
  require("vm");
13
- require("undici");
14
14
  require("tv4");
15
15
  require("jsonpath-plus");
16
16
  require("@xmldom/xmldom");
17
+ require("http");
17
18
  require("ajv");
18
19
  function buildJsonReport(results, summary, meta = {}) {
19
20
  return JSON.stringify({
@@ -349,7 +350,7 @@ function printResult(r, verbose) {
349
350
  if (r.testResults?.length) {
350
351
  for (const t of r.testResults) {
351
352
  const ti = t.passed ? cliCommon.color(" ✓", cliCommon.C.green) : cliCommon.color(" ✗", cliCommon.C.red);
352
- console.log(`${ti} ${t.name}${t.error ? cliCommon.color(` ${t.error}`, cliCommon.C.red) : ""}`);
353
+ console.log(`${ti} ${t.name}${t.error ? cliCommon.color(` - ${t.error}`, cliCommon.C.red) : ""}`);
353
354
  }
354
355
  }
355
356
  if (r.error) console.log(cliCommon.color(` Error: ${r.error}`, cliCommon.C.red));
@@ -384,23 +385,25 @@ async function main() {
384
385
  console.error(cliCommon.color(`Error: could not read workspace file: ${wsPath}`, cliCommon.C.red));
385
386
  process.exit(1);
386
387
  }
387
- await requestCollection.loadGlobals(wsDir);
388
+ await requestExec.loadGlobals(wsDir);
388
389
  const collections = await cliCommon.loadCollections(workspace, wsDir, {
389
390
  onError: (relPath) => console.error(cliCommon.color(` [warn] Could not load collection: ${relPath}`, cliCommon.C.yellow))
390
391
  });
391
- const environments = await cliCommon.loadEnvironments(workspace, wsDir);
392
- const env = envName ? environments.find((e) => e.name.toLowerCase() === envName.toLowerCase()) ?? null : null;
392
+ const environments$1 = await cliCommon.loadEnvironments(workspace, wsDir);
393
+ const env = environments.selectEnvironment(workspace, environments$1, envName);
393
394
  if (envName && !env) {
394
395
  console.warn(cliCommon.color(`Warning: environment "${envName}" not found. Running without environment.`, cliCommon.C.yellow));
396
+ } else if (!envName && workspace.settings?.defaultEnvironment && !env) {
397
+ console.warn(cliCommon.color(`Warning: default environment "${workspace.settings.defaultEnvironment}" not found. Running without environment.`, cliCommon.C.yellow));
395
398
  }
396
- const version = `v${"0.3.5"}`;
399
+ const version = `v${"0.3.6"}`;
397
400
  console.log("");
398
401
  console.log(cliCommon.color(" API Test Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
399
402
  console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
400
403
  console.log(cliCommon.color(` Environment: ${env?.name ?? "(none)"}`, cliCommon.C.gray));
401
404
  if (filterTags.length) console.log(cliCommon.color(` Tags: ${filterTags.join(", ")}`, cliCommon.C.gray));
402
405
  console.log("");
403
- const envVarsSnapshot = await authBuilder.buildEnvVars(env);
406
+ const envVarsSnapshot = await requestExec.buildEnvVars(env);
404
407
  const secretValuesToMask = (env?.variables ?? []).filter((v) => v.secret && v.enabled).map((v) => envVarsSnapshot[v.key]).filter((v) => typeof v === "string" && v.length > 0);
405
408
  function redact(s) {
406
409
  let out = s;
@@ -414,14 +417,14 @@ async function main() {
414
417
  ...r,
415
418
  sentRequest: r.sentRequest ? {
416
419
  headers: Object.fromEntries(
417
- Object.entries(requestCollection.maskHeaders(r.sentRequest.headers, piiPatterns)).map(([k, v]) => [k, redact(v)])
420
+ Object.entries(requestExec.maskHeaders(r.sentRequest.headers, piiPatterns)).map(([k, v]) => [k, redact(v)])
418
421
  ),
419
- body: r.sentRequest.body != null ? redact(requestCollection.maskPii(r.sentRequest.body, piiPatterns)) : void 0
422
+ body: r.sentRequest.body != null ? redact(requestExec.maskPii(r.sentRequest.body, piiPatterns)) : void 0
420
423
  } : void 0,
421
424
  receivedResponse: r.receivedResponse ? {
422
425
  ...r.receivedResponse,
423
- headers: requestCollection.maskHeaders(r.receivedResponse.headers, piiPatterns),
424
- body: redact(requestCollection.maskPii(r.receivedResponse.body, piiPatterns))
426
+ headers: requestExec.maskHeaders(r.receivedResponse.headers, piiPatterns),
427
+ body: redact(requestExec.maskPii(r.receivedResponse.body, piiPatterns))
425
428
  } : void 0
426
429
  };
427
430
  }
@@ -435,8 +438,8 @@ async function main() {
435
438
  const items = requestCollection.buildRunPlan(col, null, filterTags);
436
439
  if (items.length === 0) continue;
437
440
  if (!firstColName) firstColName = col.name;
438
- let runEnvVars = await authBuilder.buildEnvVars(env);
439
- let runGlobals = requestCollection.getGlobals();
441
+ let runEnvVars = await requestExec.buildEnvVars(env);
442
+ let runGlobals = requestExec.getGlobals();
440
443
  let runCollectionVars = { ...col.collectionVariables ?? {} };
441
444
  let runLocalVars = {};
442
445
  console.log(cliCommon.color(` ┌ ${col.name}`, cliCommon.C.bold, cliCommon.C.white));
@@ -455,8 +458,8 @@ async function main() {
455
458
  }
456
459
  let bailed = false;
457
460
  let lastPrintedScope = null;
458
- const skipTracker = new requestCollection.HookSkipTracker();
459
- const dispatcher = await requestCollection.buildDispatcher(void 0, effectiveTls);
461
+ const skipTracker = new requestExec.HookSkipTracker();
462
+ const dispatcher = await requestExec.buildDispatcher(void 0, effectiveTls);
460
463
  const onScriptOutput = (phase, lines, error) => {
461
464
  if (verbose && lines.length) lines.forEach((l) => console.log(cliCommon.color(` [${phase}] ${l}`, cliCommon.C.gray)));
462
465
  if (phase === "pre" && error) console.error(cliCommon.color(` [pre-script error] ${error}`, cliCommon.C.red));
@@ -479,7 +482,7 @@ async function main() {
479
482
  scopePath: item.scopePath
480
483
  };
481
484
  } else {
482
- const out = await requestCollection.executeRunnerRequest({
485
+ const out = await requestExec.executeRunnerRequest({
483
486
  req: item.request,
484
487
  collectionVars: { ...item.collectionVars, ...runCollectionVars },
485
488
  envVars: runEnvVars,