@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/bin/cli.js +6 -0
- package/out/main/chunks/coverage-C54mZEh_.js +146 -0
- package/out/main/chunks/{handle-rimXXdJH.js → handle-b6bp3fb0.js} +22 -0
- package/out/main/chunks/{import-DcenB5Q_.js → import-BeY7gjWL.js} +2 -2
- package/out/main/chunks/{request-exec-BH-M3KqZ.js → request-exec-D_xyyt3_.js} +544 -9
- package/out/main/chunks/{snapshots-CtYxkSLz.js → snapshots-BJiZNuRN.js} +23 -21
- package/out/main/chunks/{soap-handler-BXx842MN.js → soap-handler-BYkE6MyE.js} +2 -2
- package/out/main/compare.js +218 -0
- package/out/main/contract.js +73 -149
- package/out/main/coverage.js +161 -0
- package/out/main/generate-tests.js +317 -0
- package/out/main/index.js +294 -18
- package/out/main/lib.js +11 -2
- package/out/main/runner.js +9 -4
- package/out/main/wsdl.js +3 -3
- package/out/preload/index.js +40 -0
- package/out/renderer/assets/index-Cgk7l8OV.css +2 -0
- package/out/renderer/assets/{index-CEKgq6t0.js → index-Cj7BRfK2.js} +1614 -194
- package/out/renderer/index.html +2 -2
- package/package.json +3 -1
- package/readme.md +20 -0
- package/out/renderer/assets/index-B_xyaTDo.css +0 -2
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-
|
|
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-
|
|
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
|
|
41
|
-
const os = require("os");
|
|
42
|
-
const
|
|
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$
|
|
206
|
+
const path$12 = path.join(workspaceDir2, "README.md");
|
|
179
207
|
try {
|
|
180
|
-
await promises.readFile(path$
|
|
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$
|
|
215
|
+
await atomicWrite(path$12, readmeContents(workspaceFileName));
|
|
188
216
|
}
|
|
189
217
|
}
|
|
190
218
|
async function ensureGitignore(workspaceDir2) {
|
|
191
|
-
const path$
|
|
219
|
+
const path$12 = path.join(workspaceDir2, ".gitignore");
|
|
192
220
|
try {
|
|
193
|
-
await promises.readFile(path$
|
|
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$
|
|
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.
|
|
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-
|
|
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-
|
|
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;
|
package/out/main/runner.js
CHANGED
|
@@ -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-
|
|
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-
|
|
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.
|
|
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-
|
|
8
|
-
const _import = require("./chunks/import-
|
|
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-
|
|
10
|
+
require("./chunks/handle-b6bp3fb0.js");
|
|
11
11
|
require("@xmldom/xmldom");
|
|
12
12
|
require("uuid");
|
|
13
13
|
require("crypto");
|
package/out/preload/index.js
CHANGED
|
@@ -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),
|