@testsmith/api-spector 0.4.8 → 0.5.0

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,12 +22,12 @@ 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-rimXXdJH.js");
25
+ const handle = require("./chunks/handle-b6bp3fb0.js");
26
26
  const path = require("path");
27
- const fs = require("fs");
27
+ const fs$1 = require("fs");
28
28
  const promises = require("fs/promises");
29
29
  const crypto = require("crypto");
30
- const requestExec = require("./chunks/request-exec-BH-M3KqZ.js");
30
+ const requestExec = require("./chunks/request-exec-D_xyyt3_.js");
31
31
  const ipcValidate = require("./chunks/ipc-validate-k6KI8adf.js");
32
32
  const uuid = require("uuid");
33
33
  const jsYaml = require("js-yaml");
@@ -37,12 +37,19 @@ const requestCollection = require("./chunks/request-collection-CJoXpvOw.js");
37
37
  const mockServer = require("./chunks/mock-server-BxiXhP1m.js");
38
38
  const http = require("http");
39
39
  const WebSocket = require("ws");
40
- const soapHandler = require("./chunks/soap-handler-BXx842MN.js");
41
- const os = require("os");
42
- const snapshots = require("./chunks/snapshots-CtYxkSLz.js");
40
+ const fs = require("node:fs");
41
+ const os = require("node:os");
42
+ const path$1 = require("node:path");
43
+ const grpc = require("@grpc/grpc-js");
44
+ const protoLoader = require("@grpc/proto-loader");
45
+ const soapHandler = require("./chunks/soap-handler-BYkE6MyE.js");
46
+ const promises$1 = require("node:fs/promises");
47
+ const os$1 = require("os");
48
+ const snapshots = require("./chunks/snapshots-BJiZNuRN.js");
43
49
  const simpleGit = require("simple-git");
44
50
  const recorder = require("./chunks/recorder-0Ij921El.js");
45
51
  require("tls");
52
+ require("node:crypto");
46
53
  require("dayjs");
47
54
  require("vm");
48
55
  require("tv4");
@@ -50,6 +57,27 @@ require("jsonpath-plus");
50
57
  require("@xmldom/xmldom");
51
58
  require("ajv");
52
59
  require("https");
60
+ function _interopNamespaceDefault(e) {
61
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
62
+ if (e) {
63
+ for (const k in e) {
64
+ if (k !== "default") {
65
+ const d = Object.getOwnPropertyDescriptor(e, k);
66
+ Object.defineProperty(n, k, d.get ? d : {
67
+ enumerable: true,
68
+ get: () => e[k]
69
+ });
70
+ }
71
+ }
72
+ }
73
+ n.default = e;
74
+ return Object.freeze(n);
75
+ }
76
+ const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
77
+ const os__namespace = /* @__PURE__ */ _interopNamespaceDefault(os);
78
+ const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path$1);
79
+ const grpc__namespace = /* @__PURE__ */ _interopNamespaceDefault(grpc);
80
+ const protoLoader__namespace = /* @__PURE__ */ _interopNamespaceDefault(protoLoader);
53
81
  const LAST_WS_FILE = path.join(electron.app.getPath("userData"), "last-workspace.json");
54
82
  const RECENTS_FILE = path.join(electron.app.getPath("userData"), "recent-workspaces.json");
55
83
  const MAX_RECENTS = 10;
@@ -175,29 +203,29 @@ function readmeContents(workspaceFileName) {
175
203
  ].join("\n");
176
204
  }
177
205
  async function ensureReadme(workspaceDir2, workspaceFileName) {
178
- const path$1 = path.join(workspaceDir2, "README.md");
206
+ const path$12 = path.join(workspaceDir2, "README.md");
179
207
  try {
180
- await promises.readFile(path$1, "utf8");
208
+ await promises.readFile(path$12, "utf8");
181
209
  return;
182
210
  } catch (err) {
183
211
  if (err.code !== "ENOENT") {
184
212
  console.warn("file-handler: could not check README.md", err);
185
213
  return;
186
214
  }
187
- await atomicWrite(path$1, readmeContents(workspaceFileName));
215
+ await atomicWrite(path$12, readmeContents(workspaceFileName));
188
216
  }
189
217
  }
190
218
  async function ensureGitignore(workspaceDir2) {
191
- const path$1 = path.join(workspaceDir2, ".gitignore");
219
+ const path$12 = path.join(workspaceDir2, ".gitignore");
192
220
  try {
193
- await promises.readFile(path$1, "utf8");
221
+ await promises.readFile(path$12, "utf8");
194
222
  return;
195
223
  } catch (err) {
196
224
  if (err.code !== "ENOENT") {
197
225
  console.warn("file-handler: could not check .gitignore", err);
198
226
  return;
199
227
  }
200
- await atomicWrite(path$1, SPECTOR_GITIGNORE);
228
+ await atomicWrite(path$12, SPECTOR_GITIGNORE);
201
229
  }
202
230
  }
203
231
  async function ensureVscodeFileAssociation(workspaceDir2) {
@@ -266,6 +294,7 @@ Create a workspace in this folder and add "${path.basename(wsPath)}" to it? The
266
294
  workspaceFile = wsPath;
267
295
  await requestExec.loadGlobals(workspaceDir);
268
296
  await saveLastWorkspacePath(wsPath);
297
+ requestExec.setSecretsConfig(parsed.settings?.secrets);
269
298
  return { workspace: parsed, workspacePath: wsPath };
270
299
  });
271
300
  handle.handleIpc(ipc, handle.IPC.file.newWorkspace, async () => {
@@ -297,6 +326,7 @@ Create a workspace in this folder and add "${path.basename(wsPath)}" to it? The
297
326
  });
298
327
  handle.handleIpc(ipc, handle.IPC.file.saveWorkspace, async (_e, ws) => {
299
328
  if (!workspaceFile) return;
329
+ requestExec.setSecretsConfig(ws.settings?.secrets);
300
330
  await atomicWrite(workspaceFile, JSON.stringify(ws, null, 2));
301
331
  if (workspaceDir) await ensureVscodeFileAssociation(workspaceDir);
302
332
  });
@@ -397,6 +427,7 @@ Create a workspace in this folder and add "${path.basename(wsPath)}" to it? The
397
427
  workspaceDir = path.dirname(wsPath);
398
428
  workspaceFile = wsPath;
399
429
  await requestExec.loadGlobals(workspaceDir);
430
+ requestExec.setSecretsConfig(workspace.settings?.secrets);
400
431
  return { workspace, workspacePath: wsPath };
401
432
  } catch {
402
433
  return null;
@@ -427,6 +458,7 @@ Create a workspace in this folder and add "${path.basename(wsPath)}" to it? The
427
458
  workspaceFile = wsPath;
428
459
  await requestExec.loadGlobals(workspaceDir);
429
460
  await saveLastWorkspacePath(wsPath);
461
+ requestExec.setSecretsConfig(parsed.settings?.secrets);
430
462
  return { workspace: parsed, workspacePath: wsPath };
431
463
  });
432
464
  }
@@ -446,6 +478,7 @@ async function tryOpenWorkspaceInDir(dir) {
446
478
  workspaceDir = dir;
447
479
  workspaceFile = wsPath;
448
480
  await requestExec.loadGlobals(workspaceDir);
481
+ requestExec.setSecretsConfig(workspace.settings?.secrets);
449
482
  return { workspace, workspacePath: wsPath };
450
483
  } catch {
451
484
  return null;
@@ -4637,6 +4670,79 @@ function registerOAuth2Handlers(ipc) {
4637
4670
  };
4638
4671
  });
4639
4672
  }
4673
+ const CALLBACK_PORT = 8250;
4674
+ const CALLBACK_PATH = "/oidc/callback";
4675
+ function registerVaultHandlers(ipc) {
4676
+ handle.handleIpc(ipc, handle.IPC.vault.oidcLogin, async (_e, opts) => {
4677
+ const address = (opts.address ?? "").replace(/\/+$/, "");
4678
+ if (!address) throw new Error("Vault OIDC: address is required.");
4679
+ const mount = opts.mount?.trim() || "oidc";
4680
+ const redirectUri = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
4681
+ const clientNonce = Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
4682
+ const { fetch: nodeFetch, Agent } = await import("undici");
4683
+ const dispatcher = opts.skipVerify ? new Agent({ connect: { rejectUnauthorized: false } }) : void 0;
4684
+ const nsHeaders = opts.namespace ? { "X-Vault-Namespace": opts.namespace } : {};
4685
+ const vault = async (path2, init = {}) => {
4686
+ const res = await nodeFetch(`${address}/v1/${path2}`, {
4687
+ ...init,
4688
+ headers: { ...nsHeaders, ...init.headers },
4689
+ ...dispatcher ? { dispatcher } : {}
4690
+ });
4691
+ if (!res.ok) {
4692
+ const body = await res.text().catch(() => "");
4693
+ throw new Error(`Vault ${res.status} on /v1/${path2}: ${body.slice(0, 300)}`);
4694
+ }
4695
+ return res.json();
4696
+ };
4697
+ const authUrlResp = await vault(`auth/${mount}/oidc/auth_url`, {
4698
+ method: "POST",
4699
+ headers: { "content-type": "application/json" },
4700
+ body: JSON.stringify({ redirect_uri: redirectUri, role: opts.role || void 0, client_nonce: clientNonce })
4701
+ });
4702
+ const authUrl = authUrlResp?.data?.auth_url;
4703
+ if (!authUrl) throw new Error("Vault OIDC: no auth_url returned (check the mount, role, and allowed redirect URIs).");
4704
+ const params = await new Promise((resolve2, reject) => {
4705
+ const server = http.createServer((req, res) => {
4706
+ const reqUrl = new URL(req.url ?? "/", `http://localhost:${CALLBACK_PORT}`);
4707
+ if (!reqUrl.pathname.startsWith(CALLBACK_PATH)) {
4708
+ res.writeHead(404);
4709
+ res.end();
4710
+ return;
4711
+ }
4712
+ res.writeHead(200, { "Content-Type": "text/html" });
4713
+ res.end("<html><body><p>Vault sign-in complete. You may close this tab.</p></body></html>");
4714
+ server.close();
4715
+ const error = reqUrl.searchParams.get("error");
4716
+ if (error) {
4717
+ reject(new Error(`Vault OIDC error: ${error}`));
4718
+ return;
4719
+ }
4720
+ resolve2(reqUrl.searchParams);
4721
+ });
4722
+ server.on("error", reject);
4723
+ server.listen(CALLBACK_PORT, "127.0.0.1", () => {
4724
+ electron.shell.openExternal(authUrl).catch(reject);
4725
+ });
4726
+ setTimeout(() => {
4727
+ server.close();
4728
+ reject(new Error("Vault OIDC sign-in timed out (5 min)."));
4729
+ }, 5 * 60 * 1e3);
4730
+ });
4731
+ const code = params.get("code");
4732
+ const state = params.get("state");
4733
+ if (!code || !state) throw new Error("Vault OIDC: callback missing code/state.");
4734
+ const cb = new URLSearchParams({ state, code, client_nonce: clientNonce });
4735
+ const login = await vault(`auth/${mount}/oidc/callback?${cb.toString()}`);
4736
+ const token = login?.auth?.client_token;
4737
+ if (!token) throw new Error("Vault OIDC: callback did not return a client token.");
4738
+ process.env.VAULT_TOKEN = token;
4739
+ return {
4740
+ ok: true,
4741
+ expiresInSeconds: Number(login?.auth?.lease_duration ?? 0),
4742
+ entityId: login?.auth?.entity_id ? String(login.auth.entity_id) : void 0
4743
+ };
4744
+ });
4745
+ }
4640
4746
  const connections = /* @__PURE__ */ new Map();
4641
4747
  function closeAllWsConnections() {
4642
4748
  for (const [, ws] of connections) {
@@ -4693,6 +4799,152 @@ function registerWsHandlers(ipc) {
4693
4799
  }
4694
4800
  });
4695
4801
  }
4802
+ const calls = /* @__PURE__ */ new Map();
4803
+ function closeAllGrpcConnections() {
4804
+ for (const [, c] of calls) {
4805
+ try {
4806
+ c.cancel();
4807
+ } catch {
4808
+ }
4809
+ }
4810
+ calls.clear();
4811
+ }
4812
+ const LOADER_OPTS = {
4813
+ keepCase: true,
4814
+ longs: String,
4815
+ enums: String,
4816
+ defaults: true,
4817
+ oneofs: true
4818
+ };
4819
+ function resolveProtoFile(src) {
4820
+ const importPaths = (src.importPaths ?? []).filter(Boolean);
4821
+ if (src.protoPath) {
4822
+ return { file: src.protoPath, includeDirs: [path__namespace.dirname(src.protoPath), ...importPaths] };
4823
+ }
4824
+ if (src.protoSource && src.protoSource.trim()) {
4825
+ const dir = fs__namespace.mkdtempSync(path__namespace.join(os__namespace.tmpdir(), "apispector-proto-"));
4826
+ const file = path__namespace.join(dir, "main.proto");
4827
+ fs__namespace.writeFileSync(file, src.protoSource, "utf8");
4828
+ return { file, includeDirs: [dir, ...importPaths] };
4829
+ }
4830
+ throw new Error("No proto source: paste a .proto or provide a file path.");
4831
+ }
4832
+ function loadPackage(src) {
4833
+ const { file, includeDirs } = resolveProtoFile(src);
4834
+ return protoLoader__namespace.loadSync(file, { ...LOADER_OPTS, includeDirs });
4835
+ }
4836
+ function enumerateServices(pkgDef) {
4837
+ const services = [];
4838
+ for (const [name, def] of Object.entries(pkgDef)) {
4839
+ const methods = Object.entries(def).filter(([, m]) => m && typeof m === "object" && "requestStream" in m).map(([mName, m]) => {
4840
+ const md = m;
4841
+ return { name: mName, requestStream: !!md.requestStream, responseStream: !!md.responseStream };
4842
+ });
4843
+ if (methods.length) services.push({ name, methods });
4844
+ }
4845
+ return services;
4846
+ }
4847
+ function resolveService(grpcObj, serviceName) {
4848
+ let node = grpcObj;
4849
+ for (const part of serviceName.split(".")) {
4850
+ node = node?.[part];
4851
+ if (!node) throw new Error(`Service "${serviceName}" not found in the proto.`);
4852
+ }
4853
+ if (typeof node !== "function") throw new Error(`"${serviceName}" is not a service.`);
4854
+ return node;
4855
+ }
4856
+ function normalizeTarget(url) {
4857
+ return url.replace(/^[a-z0-9+.-]+:\/\//i, "").replace(/\/+$/, "");
4858
+ }
4859
+ function registerGrpcHandlers(ipc) {
4860
+ handle.handleIpc(ipc, handle.IPC.grpc.loadProto, async (_e, src) => {
4861
+ return { services: enumerateServices(loadPackage(src)) };
4862
+ });
4863
+ handle.handleIpc(ipc, handle.IPC.grpc.invoke, async (event, requestId, opts) => {
4864
+ calls.get(requestId)?.cancel();
4865
+ calls.delete(requestId);
4866
+ const sendStatus = (status, extra = {}) => event.sender.send(handle.IPC.grpc.status, { requestId, status, ...extra });
4867
+ const sendMessage = (data) => {
4868
+ const message = {
4869
+ id: uuid.v4(),
4870
+ direction: "received",
4871
+ data: typeof data === "string" ? data : JSON.stringify(data, null, 2),
4872
+ timestamp: Date.now()
4873
+ };
4874
+ event.sender.send(handle.IPC.grpc.message, { requestId, message });
4875
+ };
4876
+ let pkgDef;
4877
+ let ServiceCtor;
4878
+ let request;
4879
+ try {
4880
+ pkgDef = loadPackage(opts);
4881
+ const grpcObj = grpc__namespace.loadPackageDefinition(pkgDef);
4882
+ ServiceCtor = resolveService(grpcObj, opts.serviceName);
4883
+ request = opts.message.trim() ? JSON.parse(opts.message) : {};
4884
+ } catch (err) {
4885
+ sendStatus("error", { error: err instanceof Error ? err.message : String(err) });
4886
+ return;
4887
+ }
4888
+ const serviceDef = ServiceCtor.service;
4889
+ const methodKey = Object.keys(serviceDef).find((k) => k === opts.methodName) ?? Object.keys(serviceDef).find((k) => k.toLowerCase() === opts.methodName.toLowerCase());
4890
+ if (!methodKey) {
4891
+ sendStatus("error", { error: `Method "${opts.methodName}" not found on ${opts.serviceName}.` });
4892
+ return;
4893
+ }
4894
+ const def = serviceDef[methodKey];
4895
+ if (def.requestStream) {
4896
+ sendStatus("error", { error: "Client-streaming methods are not supported yet." });
4897
+ return;
4898
+ }
4899
+ const creds = opts.plaintext ? grpc__namespace.credentials.createInsecure() : grpc__namespace.credentials.createSsl();
4900
+ const client = new ServiceCtor(normalizeTarget(opts.target), creds);
4901
+ const metadata = new grpc__namespace.Metadata();
4902
+ for (const [k, v] of Object.entries(opts.metadata ?? {})) metadata.set(k, v);
4903
+ const statusName = (code) => Object.keys(grpc__namespace.status).find((k) => grpc__namespace.status[k] === code) ?? String(code);
4904
+ const cleanup = () => {
4905
+ calls.delete(requestId);
4906
+ try {
4907
+ client.close();
4908
+ } catch {
4909
+ }
4910
+ };
4911
+ sendStatus("running");
4912
+ try {
4913
+ const method = client[methodKey].bind(client);
4914
+ if (def.responseStream) {
4915
+ const call = method(request, metadata);
4916
+ calls.set(requestId, { cancel: () => call.cancel() });
4917
+ call.on("data", (d) => sendMessage(d));
4918
+ call.on("error", (e) => {
4919
+ sendStatus("error", { code: e.code, codeName: e.code !== void 0 ? statusName(e.code) : void 0, error: e.details || e.message });
4920
+ cleanup();
4921
+ });
4922
+ call.on("status", (s) => {
4923
+ if (s.code === grpc__namespace.status.OK) sendStatus("completed", { code: s.code, codeName: statusName(s.code) });
4924
+ cleanup();
4925
+ });
4926
+ } else {
4927
+ const call = method(request, metadata, (err, response) => {
4928
+ if (err) {
4929
+ sendStatus("error", { code: err.code, codeName: err.code !== void 0 ? statusName(err.code) : void 0, error: err.details || err.message });
4930
+ } else {
4931
+ sendMessage(response);
4932
+ sendStatus("completed", { code: grpc__namespace.status.OK, codeName: "OK" });
4933
+ }
4934
+ cleanup();
4935
+ });
4936
+ calls.set(requestId, { cancel: () => call.cancel() });
4937
+ }
4938
+ } catch (err) {
4939
+ sendStatus("error", { error: err instanceof Error ? err.message : String(err) });
4940
+ cleanup();
4941
+ }
4942
+ });
4943
+ handle.handleIpc(ipc, handle.IPC.grpc.cancel, async (_e, requestId) => {
4944
+ calls.get(requestId)?.cancel();
4945
+ calls.delete(requestId);
4946
+ });
4947
+ }
4696
4948
  function isJsonContentType(ct) {
4697
4949
  return !!ct && /\bjson\b/i.test(ct);
4698
4950
  }
@@ -4983,6 +5235,26 @@ function registerDocsHandlers(ipc) {
4983
5235
  return generateMarkdown(payload);
4984
5236
  });
4985
5237
  }
5238
+ function registerCoverageHandlers(ipc) {
5239
+ handle.handleIpc(ipc, handle.IPC.coverage.loadSpec, async (_e, source) => {
5240
+ if (source.text && source.text.trim()) {
5241
+ const t = source.text.trim();
5242
+ return t.startsWith("{") ? JSON.parse(t) : jsYaml.load(t);
5243
+ }
5244
+ if (source.url) {
5245
+ const resp = await undici.fetch(source.url);
5246
+ if (!resp.ok) throw new Error(`HTTP ${resp.status} fetching ${source.url}`);
5247
+ const raw = await resp.text();
5248
+ const ct = resp.headers.get("content-type") ?? "";
5249
+ return ct.includes("yaml") || /\.ya?ml$/i.test(source.url) ? jsYaml.load(raw) : JSON.parse(raw);
5250
+ }
5251
+ if (source.path) {
5252
+ const raw = await promises$1.readFile(source.path, "utf8");
5253
+ return /\.ya?ml$/i.test(source.path) ? jsYaml.load(raw) : JSON.parse(raw);
5254
+ }
5255
+ throw new Error("No spec source: provide a file path, URL, or pasted text.");
5256
+ });
5257
+ }
4986
5258
  function inferSchema(data) {
4987
5259
  if (data === null || data === void 0) return { type: "null" };
4988
5260
  if (typeof data === "boolean") return { type: "boolean" };
@@ -5019,7 +5291,7 @@ async function resolveSnapshotSpec(relPath) {
5019
5291
  const dir = getWorkspaceDir();
5020
5292
  if (!dir) throw new Error("No workspace open - cannot resolve snapshot.");
5021
5293
  const snap = await snapshots.loadSnapshot(dir, relPath);
5022
- const tmp = path.join(os.tmpdir(), `api-spector-${crypto.randomUUID()}.${snap.format === "yaml" ? "yaml" : "json"}`);
5294
+ const tmp = path.join(os$1.tmpdir(), `api-spector-${crypto.randomUUID()}.${snap.format === "yaml" ? "yaml" : "json"}`);
5023
5295
  await promises.writeFile(tmp, snap.spec, "utf8");
5024
5296
  return { specPath: tmp };
5025
5297
  }
@@ -5051,7 +5323,7 @@ function registerContractHandlers(ipc) {
5051
5323
  }
5052
5324
  switch (mode) {
5053
5325
  case "consumer":
5054
- return snapshots.runConsumerContracts(requests, envVars, collectionVars);
5326
+ return snapshots.runConsumerContracts(requests, envVars, collectionVars, providerBaseUrl);
5055
5327
  case "provider":
5056
5328
  return snapshots.runProviderVerification(requests, envVars, collectionVars, specUrl, specPath, requestBaseUrl);
5057
5329
  case "provider-live":
@@ -5520,7 +5792,7 @@ function isNewer(a, b) {
5520
5792
  return a2 > b2;
5521
5793
  }
5522
5794
  async function checkForUpdate() {
5523
- const current = "0.4.8";
5795
+ const current = "0.5.0";
5524
5796
  try {
5525
5797
  const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(4e3) });
5526
5798
  if (!res.ok) return null;
@@ -5565,15 +5837,15 @@ function loadAppIcon() {
5565
5837
  path.join(__dirname, "../../build/icon.png")
5566
5838
  ];
5567
5839
  for (const p of pngCandidates) {
5568
- if (fs.existsSync(p)) return electron.nativeImage.createFromPath(p);
5840
+ if (fs$1.existsSync(p)) return electron.nativeImage.createFromPath(p);
5569
5841
  }
5570
5842
  const svgCandidates = [
5571
5843
  path.join(electron.app.getAppPath(), "resources/icon.svg"),
5572
5844
  path.join(__dirname, "../../resources/icon.svg")
5573
5845
  ];
5574
5846
  for (const p of svgCandidates) {
5575
- if (fs.existsSync(p)) {
5576
- const dataUrl = "data:image/svg+xml;base64," + fs.readFileSync(p).toString("base64");
5847
+ if (fs$1.existsSync(p)) {
5848
+ const dataUrl = "data:image/svg+xml;base64," + fs$1.readFileSync(p).toString("base64");
5577
5849
  return electron.nativeImage.createFromDataURL(dataUrl);
5578
5850
  }
5579
5851
  }
@@ -5634,9 +5906,12 @@ electron.app.whenReady().then(async () => {
5634
5906
  registerRunnerHandler(electron.ipcMain);
5635
5907
  registerMockHandlers(electron.ipcMain);
5636
5908
  registerOAuth2Handlers(electron.ipcMain);
5909
+ registerVaultHandlers(electron.ipcMain);
5637
5910
  registerWsHandlers(electron.ipcMain);
5911
+ registerGrpcHandlers(electron.ipcMain);
5638
5912
  soapHandler.registerSoapHandlers(electron.ipcMain);
5639
5913
  registerDocsHandlers(electron.ipcMain);
5914
+ registerCoverageHandlers(electron.ipcMain);
5640
5915
  registerContractHandlers(electron.ipcMain);
5641
5916
  registerGitHandlers(electron.ipcMain);
5642
5917
  registerRecordHandlers(electron.ipcMain, () => electron.BrowserWindow.getAllWindows()[0]?.webContents ?? null);
@@ -5653,5 +5928,6 @@ electron.app.on("window-all-closed", () => {
5653
5928
  });
5654
5929
  electron.app.on("before-quit", async () => {
5655
5930
  closeAllWsConnections();
5931
+ closeAllGrpcConnections();
5656
5932
  await mockServer.stopAll();
5657
5933
  });
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-BH-M3KqZ.js");
4
+ const requestExec = require("./chunks/request-exec-D_xyyt3_.js");
5
5
  require("http");
6
6
  require("crypto");
7
7
  require("vm");
@@ -10,7 +10,11 @@ require("@xmldom/xmldom");
10
10
  require("undici");
11
11
  require("fs/promises");
12
12
  require("tls");
13
- require("./chunks/handle-rimXXdJH.js");
13
+ require("./chunks/handle-b6bp3fb0.js");
14
+ require("node:fs");
15
+ require("node:os");
16
+ require("node:path");
17
+ require("node:crypto");
14
18
  require("path");
15
19
  require("tv4");
16
20
  require("jsonpath-plus");
@@ -31,7 +35,12 @@ exports.buildProtocolFaultTests = requestExec.buildProtocolFaultTests;
31
35
  exports.buildSchemaTestResults = requestExec.buildSchemaTestResults;
32
36
  exports.deriveRunStatus = requestExec.deriveRunStatus;
33
37
  exports.executeRunnerRequest = requestExec.executeRunnerRequest;
38
+ exports.hasSecretScheme = requestExec.hasSecretScheme;
34
39
  exports.maskHeaders = requestExec.maskHeaders;
35
40
  exports.maskPii = requestExec.maskPii;
36
41
  exports.performHttpExchange = requestExec.performHttpExchange;
42
+ exports.registerSecretProvider = requestExec.registerSecretProvider;
43
+ exports.registeredSchemes = requestExec.registeredSchemes;
44
+ exports.resolveExternalSecret = requestExec.resolveExternalSecret;
45
+ exports.setSecretsConfig = requestExec.setSecretsConfig;
37
46
  exports.syntheticHttpFailure = requestExec.syntheticHttpFailure;
@@ -2,13 +2,17 @@
2
2
  "use strict";
3
3
  const promises = require("fs/promises");
4
4
  const path = require("path");
5
- const requestExec = require("./chunks/request-exec-BH-M3KqZ.js");
5
+ const requestExec = require("./chunks/request-exec-D_xyyt3_.js");
6
6
  const requestCollection = require("./chunks/request-collection-CJoXpvOw.js");
7
7
  const environments = require("./chunks/environments-iM3SUM-4.js");
8
8
  const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
9
9
  require("undici");
10
10
  require("tls");
11
- require("./chunks/handle-rimXXdJH.js");
11
+ require("./chunks/handle-b6bp3fb0.js");
12
+ require("node:fs");
13
+ require("node:os");
14
+ require("node:path");
15
+ require("node:crypto");
12
16
  require("crypto");
13
17
  require("dayjs");
14
18
  require("vm");
@@ -387,6 +391,7 @@ async function main() {
387
391
  process.exit(1);
388
392
  }
389
393
  await requestExec.loadGlobals(wsDir);
394
+ requestExec.setSecretsConfig(workspace.settings?.secrets);
390
395
  const collections = await cliCommon.loadCollections(workspace, wsDir, {
391
396
  onError: (relPath) => console.error(cliCommon.color(` [warn] Could not load collection: ${relPath}`, cliCommon.C.yellow))
392
397
  });
@@ -397,7 +402,7 @@ async function main() {
397
402
  } else if (!envName && workspace.settings?.defaultEnvironment && !env) {
398
403
  console.warn(cliCommon.color(`Warning: default environment "${workspace.settings.defaultEnvironment}" not found. Running without environment.`, cliCommon.C.yellow));
399
404
  }
400
- const version = `v${"0.4.8"}`;
405
+ const version = `v${"0.5.0"}`;
401
406
  console.log("");
402
407
  console.log(cliCommon.color(" API Test Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
403
408
  console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
@@ -405,7 +410,7 @@ async function main() {
405
410
  if (filterTags.length) console.log(cliCommon.color(` Tags: ${filterTags.join(", ")}`, cliCommon.C.gray));
406
411
  console.log("");
407
412
  const envVarsSnapshot = await requestExec.buildEnvVars(env);
408
- const secretValuesToMask = (env?.variables ?? []).filter((v) => v.secret && v.enabled).map((v) => envVarsSnapshot[v.key]).filter((v) => typeof v === "string" && v.length > 0);
413
+ const secretValuesToMask = (env?.variables ?? []).filter((v) => (v.secret || v.secretRef) && v.enabled).map((v) => envVarsSnapshot[v.key]).filter((v) => typeof v === "string" && v.length > 0);
409
414
  function redact(s) {
410
415
  let out = s;
411
416
  for (const secret of secretValuesToMask) out = out.split(secret).join("***");
package/out/main/wsdl.js CHANGED
@@ -4,10 +4,10 @@ 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-BXx842MN.js");
8
- const _import = require("./chunks/import-DcenB5Q_.js");
7
+ const soapHandler = require("./chunks/soap-handler-BYkE6MyE.js");
8
+ const _import = require("./chunks/import-BeY7gjWL.js");
9
9
  const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
10
- require("./chunks/handle-rimXXdJH.js");
10
+ require("./chunks/handle-b6bp3fb0.js");
11
11
  require("@xmldom/xmldom");
12
12
  require("uuid");
13
13
  require("crypto");
@@ -76,6 +76,10 @@ const IPC = {
76
76
  startFlow: "oauth2:startFlow",
77
77
  refreshToken: "oauth2:refreshToken"
78
78
  },
79
+ // ─── HashiCorp Vault (interactive OIDC login) ──────────────────────────────
80
+ vault: {
81
+ oidcLogin: "vault:oidcLogin"
82
+ },
79
83
  // ─── Mock servers ──────────────────────────────────────────────────────────
80
84
  mock: {
81
85
  start: "mock:start",
@@ -101,10 +105,28 @@ const IPC = {
101
105
  fetch: "wsdl:fetch",
102
106
  import: "wsdl:import"
103
107
  },
108
+ // ─── gRPC ──────────────────────────────────────────────────────────────────
109
+ grpc: {
110
+ /** Load a .proto (source or path) and enumerate its services/methods. */
111
+ loadProto: "grpc:loadProto",
112
+ /** Invoke a method (unary or server-streaming). Streams back messages. */
113
+ invoke: "grpc:invoke",
114
+ /** Cancel an in-flight call. */
115
+ cancel: "grpc:cancel",
116
+ /** Event: a received message frame. */
117
+ message: "grpc:message",
118
+ /** Event: call status change (running / completed / error, with code). */
119
+ status: "grpc:status"
120
+ },
104
121
  // ─── Docs generation ───────────────────────────────────────────────────────
105
122
  docs: {
106
123
  generate: "docs:generate"
107
124
  },
125
+ // ─── OpenAPI coverage ────────────────────────────────────────────────────────
126
+ coverage: {
127
+ /** Read + parse an OpenAPI spec (file path, URL, or raw text). */
128
+ loadSpec: "coverage:loadSpec"
129
+ },
108
130
  // ─── Contract testing ──────────────────────────────────────────────────────
109
131
  contract: {
110
132
  run: "contract:run",
@@ -242,6 +264,8 @@ const api = {
242
264
  pickOutputDir: () => electron.ipcRenderer.invoke(IPC.dialog.pickDir),
243
265
  saveGeneratedFiles: (files, outputDir) => electron.ipcRenderer.invoke(IPC.generate.save, files, outputDir),
244
266
  saveGeneratedFilesAsZip: (files, collectionName, target) => electron.ipcRenderer.invoke(IPC.generate.saveZip, files, collectionName, target),
267
+ // ─── HashiCorp Vault (interactive OIDC login) ─────────────────────────────
268
+ vaultOidcLogin: (opts) => electron.ipcRenderer.invoke(IPC.vault.oidcLogin, opts),
245
269
  // ─── OAuth 2.0 ────────────────────────────────────────────────────────────
246
270
  oauth2StartFlow: (auth, vars) => electron.ipcRenderer.invoke(IPC.oauth2.startFlow, auth, vars),
247
271
  oauth2RefreshToken: (auth, vars, refreshToken) => electron.ipcRenderer.invoke(IPC.oauth2.refreshToken, auth, vars, refreshToken),
@@ -282,6 +306,20 @@ const api = {
282
306
  electron.ipcRenderer.removeAllListeners(IPC.ws.message);
283
307
  electron.ipcRenderer.removeAllListeners(IPC.ws.status);
284
308
  },
309
+ // ─── gRPC ─────────────────────────────────────────────────────────────────
310
+ grpcLoadProto: (src) => electron.ipcRenderer.invoke(IPC.grpc.loadProto, src),
311
+ grpcInvoke: (requestId, opts) => electron.ipcRenderer.invoke(IPC.grpc.invoke, requestId, opts),
312
+ grpcCancel: (requestId) => electron.ipcRenderer.invoke(IPC.grpc.cancel, requestId),
313
+ onGrpcMessage: (cb) => {
314
+ electron.ipcRenderer.on(IPC.grpc.message, (_e, payload) => cb(payload));
315
+ },
316
+ onGrpcStatus: (cb) => {
317
+ electron.ipcRenderer.on(IPC.grpc.status, (_e, payload) => cb(payload));
318
+ },
319
+ offGrpcEvents: () => {
320
+ electron.ipcRenderer.removeAllListeners(IPC.grpc.message);
321
+ electron.ipcRenderer.removeAllListeners(IPC.grpc.status);
322
+ },
285
323
  // ─── Streamed responses (SSE / NDJSON / chunked) ──────────────────────────
286
324
  onRequestStreamEvent: (cb) => {
287
325
  electron.ipcRenderer.on(IPC.request.streamEvent, (_e, payload) => cb(payload));
@@ -298,6 +336,8 @@ const api = {
298
336
  wsdlImport: (opts) => electron.ipcRenderer.invoke(IPC.wsdl.import, opts),
299
337
  // ─── Docs generation ──────────────────────────────────────────────────────
300
338
  generateDocs: (payload) => electron.ipcRenderer.invoke(IPC.docs.generate, payload),
339
+ // ─── OpenAPI coverage ─────────────────────────────────────────────────────
340
+ coverageLoadSpec: (source) => electron.ipcRenderer.invoke(IPC.coverage.loadSpec, source),
301
341
  // ─── Contract testing ─────────────────────────────────────────────────────
302
342
  runContracts: (payload) => electron.ipcRenderer.invoke(IPC.contract.run, payload),
303
343
  inferContractSchema: (jsonBody) => electron.ipcRenderer.invoke(IPC.contract.inferSchema, jsonBody),