@testsmith/api-spector 0.5.2 → 0.5.4
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/chunks/{handle-b6bp3fb0.js → handle-BFaeFA0A.js} +4 -0
- package/out/main/chunks/{import-BeY7gjWL.js → import-BBr8q5eT.js} +2 -2
- package/out/main/chunks/{request-exec-D_xyyt3_.js → request-exec-Dg-EHFX0.js} +2 -1
- package/out/main/chunks/{snapshots-BJiZNuRN.js → snapshots-CN8g7lqA.js} +1 -1
- package/out/main/chunks/{soap-handler-BYkE6MyE.js → soap-handler-DCCE344x.js} +2 -2
- package/out/main/contract.js +3 -3
- package/out/main/index.js +306 -86
- package/out/main/lib.js +2 -2
- package/out/main/runner.js +3 -3
- package/out/main/wsdl.js +3 -3
- package/out/preload/index.js +11 -0
- package/out/renderer/assets/{GraphQLEditor-CMyLKaaQ.js → GraphQLEditor-B5bSe2ot.js} +29 -32
- package/out/renderer/assets/{SoapEditor-COISS5Ms.js → SoapEditor-B-4NwGjp.js} +25 -20
- package/out/renderer/assets/{index-DQQavtNF.css → index-B4ozo7gn.css} +1 -1
- package/out/renderer/assets/{index-C7kyUDAy.js → index-DUMcaGlL.js} +12153 -3659
- package/out/renderer/index.html +2 -2
- package/package.json +9 -1
|
@@ -4,6 +4,10 @@ const IPC = {
|
|
|
4
4
|
file: {
|
|
5
5
|
openWorkspace: "file:openWorkspace",
|
|
6
6
|
openWorkspacePath: "file:openWorkspacePath",
|
|
7
|
+
openFromGit: "file:openFromGit",
|
|
8
|
+
/** Event (main→renderer): a `spector://open-from-git?url=…` deep link was
|
|
9
|
+
* received; the renderer runs the same open-from-Git flow as the button. */
|
|
10
|
+
openFromGitDeepLink: "file:openFromGitDeepLink",
|
|
7
11
|
getLastWorkspace: "file:getLastWorkspace",
|
|
8
12
|
getRecentWorkspaces: "file:getRecentWorkspaces",
|
|
9
13
|
saveWorkspace: "file:saveWorkspace",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const uuid = require("uuid");
|
|
4
|
-
const soapHandler = require("./soap-handler-
|
|
5
|
-
require("./handle-
|
|
4
|
+
const soapHandler = require("./soap-handler-DCCE344x.js");
|
|
5
|
+
require("./handle-BFaeFA0A.js");
|
|
6
6
|
require("https");
|
|
7
7
|
require("http");
|
|
8
8
|
require("@xmldom/xmldom");
|
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
const undici = require("undici");
|
|
25
25
|
const promises = require("fs/promises");
|
|
26
26
|
const nodeTls = require("tls");
|
|
27
|
-
const handle = require("./handle-
|
|
27
|
+
const handle = require("./handle-BFaeFA0A.js");
|
|
28
28
|
const fs = require("node:fs");
|
|
29
29
|
const os = require("node:os");
|
|
30
30
|
const path$1 = require("node:path");
|
|
@@ -2252,6 +2252,7 @@ exports.buildSchemaTestResults = buildSchemaTestResults;
|
|
|
2252
2252
|
exports.buildUrl = buildUrl;
|
|
2253
2253
|
exports.deriveRunStatus = deriveRunStatus;
|
|
2254
2254
|
exports.executeRunnerRequest = executeRunnerRequest;
|
|
2255
|
+
exports.fetchOAuth2Token = fetchOAuth2Token;
|
|
2255
2256
|
exports.getGlobals = getGlobals;
|
|
2256
2257
|
exports.getSecret = getSecret;
|
|
2257
2258
|
exports.hasSecretScheme = hasSecretScheme;
|
|
@@ -3,7 +3,7 @@ const promises = require("fs/promises");
|
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const crypto = require("crypto");
|
|
5
5
|
const undici = require("undici");
|
|
6
|
-
const requestExec = require("./request-exec-
|
|
6
|
+
const requestExec = require("./request-exec-Dg-EHFX0.js");
|
|
7
7
|
const Ajv = require("ajv");
|
|
8
8
|
const jsYaml = require("js-yaml");
|
|
9
9
|
require("http");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const handle = require("./handle-
|
|
2
|
+
const handle = require("./handle-BFaeFA0A.js");
|
|
3
3
|
const https = require("https");
|
|
4
4
|
const http = require("http");
|
|
5
5
|
const xmldom = require("@xmldom/xmldom");
|
|
@@ -315,7 +315,7 @@ function registerSoapHandlers(ipc) {
|
|
|
315
315
|
handle.handleIpc(ipc, handle.IPC.wsdl.import, async (_event, opts) => {
|
|
316
316
|
const { validateWsdlImport } = await Promise.resolve().then(() => require("./ipc-validate-k6KI8adf.js"));
|
|
317
317
|
validateWsdlImport(opts);
|
|
318
|
-
const { importWsdl } = await Promise.resolve().then(() => require("./import-
|
|
318
|
+
const { importWsdl } = await Promise.resolve().then(() => require("./import-BBr8q5eT.js"));
|
|
319
319
|
const wsdlText = opts.xml ?? (opts.url ? await fetchUrl(opts.url) : "");
|
|
320
320
|
if (!wsdlText) throw new Error("wsdl:import requires either `url` or `xml`");
|
|
321
321
|
return importWsdl(wsdlText, { name: opts.name, existingMockPorts: opts.existingMockPorts });
|
package/out/main/contract.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
const promises = require("fs/promises");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const snapshots = require("./chunks/snapshots-
|
|
5
|
+
const snapshots = require("./chunks/snapshots-CN8g7lqA.js");
|
|
6
6
|
const crypto = require("crypto");
|
|
7
7
|
const undici = require("undici");
|
|
8
8
|
const os = require("os");
|
|
@@ -10,9 +10,9 @@ const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
|
|
|
10
10
|
const child_process = require("child_process");
|
|
11
11
|
const jsYaml = require("js-yaml");
|
|
12
12
|
const environments = require("./chunks/environments-iM3SUM-4.js");
|
|
13
|
-
require("./chunks/request-exec-
|
|
13
|
+
require("./chunks/request-exec-Dg-EHFX0.js");
|
|
14
14
|
require("tls");
|
|
15
|
-
require("./chunks/handle-
|
|
15
|
+
require("./chunks/handle-BFaeFA0A.js");
|
|
16
16
|
require("node:fs");
|
|
17
17
|
require("node:os");
|
|
18
18
|
require("node:path");
|
package/out/main/index.js
CHANGED
|
@@ -22,17 +22,17 @@ 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-BFaeFA0A.js");
|
|
26
26
|
const path = require("path");
|
|
27
27
|
const fs$1 = require("fs");
|
|
28
28
|
const promises = require("fs/promises");
|
|
29
29
|
const crypto = require("crypto");
|
|
30
|
-
const
|
|
30
|
+
const JSZip = require("jszip");
|
|
31
|
+
const requestExec = require("./chunks/request-exec-Dg-EHFX0.js");
|
|
31
32
|
const ipcValidate = require("./chunks/ipc-validate-k6KI8adf.js");
|
|
32
33
|
const uuid = require("uuid");
|
|
33
34
|
const jsYaml = require("js-yaml");
|
|
34
35
|
const undici = require("undici");
|
|
35
|
-
const JSZip = require("jszip");
|
|
36
36
|
const requestCollection = require("./chunks/request-collection-CJoXpvOw.js");
|
|
37
37
|
const mockServer = require("./chunks/mock-server-BxiXhP1m.js");
|
|
38
38
|
const http = require("http");
|
|
@@ -42,10 +42,10 @@ const os = require("node:os");
|
|
|
42
42
|
const path$1 = require("node:path");
|
|
43
43
|
const grpc = require("@grpc/grpc-js");
|
|
44
44
|
const protoLoader = require("@grpc/proto-loader");
|
|
45
|
-
const soapHandler = require("./chunks/soap-handler-
|
|
45
|
+
const soapHandler = require("./chunks/soap-handler-DCCE344x.js");
|
|
46
46
|
const promises$1 = require("node:fs/promises");
|
|
47
47
|
const os$1 = require("os");
|
|
48
|
-
const snapshots = require("./chunks/snapshots-
|
|
48
|
+
const snapshots = require("./chunks/snapshots-CN8g7lqA.js");
|
|
49
49
|
const simpleGit = require("simple-git");
|
|
50
50
|
const recorder = require("./chunks/recorder-0Ij921El.js");
|
|
51
51
|
require("tls");
|
|
@@ -461,6 +461,40 @@ Create a workspace in this folder and add "${path.basename(wsPath)}" to it? The
|
|
|
461
461
|
requestExec.setSecretsConfig(parsed.settings?.secrets);
|
|
462
462
|
return { workspace: parsed, workspacePath: wsPath };
|
|
463
463
|
});
|
|
464
|
+
handle.handleIpc(ipc, handle.IPC.file.openFromGit, async (_e, repoUrl) => {
|
|
465
|
+
const archiveUrl = await resolveArchiveUrl(repoUrl);
|
|
466
|
+
if (!archiveUrl) {
|
|
467
|
+
throw new Error("unsupported URL — use a public GitHub repo URL or a direct .zip link");
|
|
468
|
+
}
|
|
469
|
+
const pick = await electron.dialog.showOpenDialog({
|
|
470
|
+
title: "Choose a folder to clone the repository into",
|
|
471
|
+
properties: ["openDirectory", "createDirectory"],
|
|
472
|
+
buttonLabel: "Clone here"
|
|
473
|
+
});
|
|
474
|
+
if (pick.canceled || pick.filePaths.length === 0) return null;
|
|
475
|
+
const parent = pick.filePaths[0];
|
|
476
|
+
const repoName = (repoUrl.match(/github\.com\/[^/]+\/([^/#?]+)/i)?.[1] ?? "repository").replace(/\.git$/i, "").replace(/[^a-zA-Z0-9._-]+/g, "-") || "repository";
|
|
477
|
+
const destDir = path.join(parent, repoName);
|
|
478
|
+
try {
|
|
479
|
+
const existing = await promises.readdir(destDir);
|
|
480
|
+
if (existing.length > 0) {
|
|
481
|
+
throw new Error(`"${repoName}" already exists in that folder and is not empty`);
|
|
482
|
+
}
|
|
483
|
+
} catch (err) {
|
|
484
|
+
if (err instanceof Error && err.message.includes("already exists")) throw err;
|
|
485
|
+
}
|
|
486
|
+
await promises.mkdir(destDir, { recursive: true });
|
|
487
|
+
const buf = await downloadArchive(archiveUrl);
|
|
488
|
+
await extractZip(buf, destDir);
|
|
489
|
+
const wsPath = await findWorkspaceSpector(destDir);
|
|
490
|
+
const parsed = JSON.parse(await promises.readFile(wsPath, "utf8"));
|
|
491
|
+
workspaceDir = path.dirname(wsPath);
|
|
492
|
+
workspaceFile = wsPath;
|
|
493
|
+
await requestExec.loadGlobals(workspaceDir);
|
|
494
|
+
await saveLastWorkspacePath(wsPath);
|
|
495
|
+
requestExec.setSecretsConfig(parsed.settings?.secrets);
|
|
496
|
+
return { workspace: parsed, workspacePath: wsPath };
|
|
497
|
+
});
|
|
464
498
|
}
|
|
465
499
|
async function tryOpenWorkspaceInDir(dir) {
|
|
466
500
|
let entries;
|
|
@@ -492,6 +526,103 @@ function looksLikeCollection(obj) {
|
|
|
492
526
|
const o = obj;
|
|
493
527
|
return !Array.isArray(o["collections"]) && (!!o["rootFolder"] || !!o["requests"]);
|
|
494
528
|
}
|
|
529
|
+
const MAX_ARCHIVE_BYTES = 100 * 1024 * 1024;
|
|
530
|
+
async function resolveArchiveUrl(input) {
|
|
531
|
+
let url;
|
|
532
|
+
try {
|
|
533
|
+
url = new URL(input.trim());
|
|
534
|
+
} catch {
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
if (url.protocol !== "https:") return null;
|
|
538
|
+
if (url.pathname.toLowerCase().endsWith(".zip")) return url.toString();
|
|
539
|
+
if (url.hostname === "github.com" || url.hostname === "www.github.com") {
|
|
540
|
+
const parts = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
|
|
541
|
+
if (parts.length < 2) return null;
|
|
542
|
+
const owner = parts[0];
|
|
543
|
+
const repo = parts[1].replace(/\.git$/i, "");
|
|
544
|
+
let branch = null;
|
|
545
|
+
if (parts[2] === "tree" && parts[3]) branch = parts.slice(3).join("/");
|
|
546
|
+
if (!branch) {
|
|
547
|
+
try {
|
|
548
|
+
const resp = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
|
549
|
+
headers: { "User-Agent": "API-Spector", Accept: "application/vnd.github+json" }
|
|
550
|
+
});
|
|
551
|
+
if (!resp.ok) return null;
|
|
552
|
+
const meta = await resp.json();
|
|
553
|
+
branch = meta.default_branch ?? "main";
|
|
554
|
+
} catch {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return `https://codeload.github.com/${owner}/${repo}/zip/refs/heads/${branch}`;
|
|
559
|
+
}
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
async function downloadArchive(archiveUrl) {
|
|
563
|
+
const resp = await fetch(archiveUrl, { headers: { "User-Agent": "API-Spector" } });
|
|
564
|
+
if (!resp.ok) throw new Error(`download failed (HTTP ${resp.status})`);
|
|
565
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
566
|
+
if (buf.byteLength > MAX_ARCHIVE_BYTES) throw new Error("archive is too large");
|
|
567
|
+
return buf;
|
|
568
|
+
}
|
|
569
|
+
async function extractZip(buf, destDir) {
|
|
570
|
+
const zip = await JSZip.loadAsync(buf);
|
|
571
|
+
const paths = Object.keys(zip.files);
|
|
572
|
+
let root = null;
|
|
573
|
+
for (const p of paths) {
|
|
574
|
+
const top = p.split("/")[0];
|
|
575
|
+
if (root === null) root = top;
|
|
576
|
+
else if (root !== top) {
|
|
577
|
+
root = null;
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const prefix = root ? `${root}/` : "";
|
|
582
|
+
for (const p of paths) {
|
|
583
|
+
const entry = zip.files[p];
|
|
584
|
+
const rel = prefix && p.startsWith(prefix) ? p.slice(prefix.length) : p;
|
|
585
|
+
if (!rel) continue;
|
|
586
|
+
const outPath = path.resolve(destDir, rel);
|
|
587
|
+
if (outPath !== destDir && !outPath.startsWith(destDir + path.sep)) {
|
|
588
|
+
throw new Error("archive contains an unsafe path");
|
|
589
|
+
}
|
|
590
|
+
if (entry.dir) {
|
|
591
|
+
await promises.mkdir(outPath, { recursive: true });
|
|
592
|
+
} else {
|
|
593
|
+
await promises.mkdir(path.dirname(outPath), { recursive: true });
|
|
594
|
+
await promises.writeFile(outPath, Buffer.from(await entry.async("nodebuffer")));
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
async function findWorkspaceSpector(dir) {
|
|
599
|
+
const found = [];
|
|
600
|
+
async function walk(d) {
|
|
601
|
+
let entries;
|
|
602
|
+
try {
|
|
603
|
+
entries = await promises.readdir(d, { withFileTypes: true });
|
|
604
|
+
} catch {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
for (const e of entries) {
|
|
608
|
+
const full = path.join(d, e.name);
|
|
609
|
+
if (e.isDirectory()) {
|
|
610
|
+
if (e.name === "node_modules" || e.name === ".git") continue;
|
|
611
|
+
await walk(full);
|
|
612
|
+
} else if (e.isFile() && e.name.endsWith(".spector")) {
|
|
613
|
+
try {
|
|
614
|
+
const parsed = JSON.parse(await promises.readFile(full, "utf8"));
|
|
615
|
+
if (!looksLikeCollection(parsed)) found.push(full);
|
|
616
|
+
} catch {
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
await walk(dir);
|
|
622
|
+
if (found.length === 0) throw new Error("no workspace (.spector) file found in the repository");
|
|
623
|
+
if (found.length > 1) throw new Error("multiple workspace files found — open the folder and pick one");
|
|
624
|
+
return found[0];
|
|
625
|
+
}
|
|
495
626
|
async function wrapCollectionAsWorkspace(collFilePath, collection) {
|
|
496
627
|
const dir = path.dirname(collFilePath);
|
|
497
628
|
const base = path.basename(collFilePath).replace(/\.(spector|json)$/i, "");
|
|
@@ -4550,90 +4681,108 @@ function registerMockHandlers(ipc) {
|
|
|
4550
4681
|
return JSON.parse(raw);
|
|
4551
4682
|
});
|
|
4552
4683
|
}
|
|
4553
|
-
function
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
const
|
|
4576
|
-
const
|
|
4577
|
-
|
|
4578
|
-
if (!reqUrl.pathname.startsWith("/callback")) {
|
|
4579
|
-
res.writeHead(404);
|
|
4580
|
-
res.end();
|
|
4581
|
-
return;
|
|
4582
|
-
}
|
|
4583
|
-
const returnedState = reqUrl.searchParams.get("state");
|
|
4584
|
-
const returnedCode = reqUrl.searchParams.get("code");
|
|
4585
|
-
const error = reqUrl.searchParams.get("error");
|
|
4684
|
+
async function resolveClientSecret(auth, vars) {
|
|
4685
|
+
let secret = auth.oauth2ClientSecret ?? "";
|
|
4686
|
+
if (!secret && auth.oauth2ClientSecretRef) {
|
|
4687
|
+
secret = await requestExec.getSecret(auth.oauth2ClientSecretRef) ?? "";
|
|
4688
|
+
}
|
|
4689
|
+
return requestExec.interpolate(secret, vars);
|
|
4690
|
+
}
|
|
4691
|
+
async function browserFlow(auth, vars, flow) {
|
|
4692
|
+
const port = auth.oauth2RedirectPort ?? 9876;
|
|
4693
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
4694
|
+
const authUrl = requestExec.interpolate(auth.oauth2AuthUrl ?? "", vars);
|
|
4695
|
+
const clientId = requestExec.interpolate(auth.oauth2ClientId ?? "", vars);
|
|
4696
|
+
if (!authUrl) throw new Error(`OAuth 2.0: authUrl is required for the ${flow} flow.`);
|
|
4697
|
+
if (!clientId) throw new Error("OAuth 2.0: clientId is required.");
|
|
4698
|
+
const state = Math.random().toString(36).slice(2);
|
|
4699
|
+
const authUrlFull = new URL(authUrl);
|
|
4700
|
+
authUrlFull.searchParams.set("response_type", flow === "implicit" ? "token" : "code");
|
|
4701
|
+
authUrlFull.searchParams.set("client_id", clientId);
|
|
4702
|
+
authUrlFull.searchParams.set("redirect_uri", redirectUri);
|
|
4703
|
+
authUrlFull.searchParams.set("state", state);
|
|
4704
|
+
if (auth.oauth2Scopes) authUrlFull.searchParams.set("scope", auth.oauth2Scopes);
|
|
4705
|
+
const captured = await new Promise((resolve2, reject) => {
|
|
4706
|
+
const server = http.createServer((req, res) => {
|
|
4707
|
+
const reqUrl = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
4708
|
+
if (flow === "implicit" && reqUrl.pathname === "/callback" && !reqUrl.searchParams.get("access_token") && !reqUrl.searchParams.get("error")) {
|
|
4586
4709
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
4587
|
-
res.end(
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
}
|
|
4601
|
-
|
|
4602
|
-
}
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
}
|
|
4607
|
-
|
|
4608
|
-
server.close();
|
|
4609
|
-
reject(new Error("OAuth 2.0 authorization timed out (5 min)."));
|
|
4610
|
-
}, 5 * 60 * 1e3);
|
|
4710
|
+
res.end('<!doctype html><html><body><p>Completing sign-in… you may close this tab.</p><script>location.replace("/callback/capture?" + (location.hash ? location.hash.substring(1) : ""));<\/script></body></html>');
|
|
4711
|
+
return;
|
|
4712
|
+
}
|
|
4713
|
+
if (reqUrl.pathname !== "/callback" && reqUrl.pathname !== "/callback/capture") {
|
|
4714
|
+
res.writeHead(404);
|
|
4715
|
+
res.end();
|
|
4716
|
+
return;
|
|
4717
|
+
}
|
|
4718
|
+
const params2 = Object.fromEntries(reqUrl.searchParams.entries());
|
|
4719
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
4720
|
+
res.end("<!doctype html><html><body><p>Authorization complete. You may close this tab.</p></body></html>");
|
|
4721
|
+
server.close();
|
|
4722
|
+
if (params2["error"]) {
|
|
4723
|
+
reject(new Error(`OAuth 2.0 authorization error: ${params2["error_description"] ?? params2["error"]}`));
|
|
4724
|
+
return;
|
|
4725
|
+
}
|
|
4726
|
+
if (params2["state"] !== state) {
|
|
4727
|
+
reject(new Error("OAuth 2.0: state mismatch."));
|
|
4728
|
+
return;
|
|
4729
|
+
}
|
|
4730
|
+
resolve2(params2);
|
|
4611
4731
|
});
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
params.set("code", code);
|
|
4616
|
-
params.set("redirect_uri", redirectUri);
|
|
4617
|
-
params.set("client_id", clientId);
|
|
4618
|
-
if (clientSecret) params.set("client_secret", clientSecret);
|
|
4619
|
-
const resp = await nodeFetch(tokenUrl, {
|
|
4620
|
-
method: "POST",
|
|
4621
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
4622
|
-
body: params.toString()
|
|
4732
|
+
server.on("error", reject);
|
|
4733
|
+
server.listen(port, "127.0.0.1", () => {
|
|
4734
|
+
electron.shell.openExternal(authUrlFull.toString()).catch(reject);
|
|
4623
4735
|
});
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4736
|
+
setTimeout(() => {
|
|
4737
|
+
server.close();
|
|
4738
|
+
reject(new Error("OAuth 2.0 authorization timed out (5 min)."));
|
|
4739
|
+
}, 5 * 60 * 1e3);
|
|
4740
|
+
});
|
|
4741
|
+
if (flow === "implicit") {
|
|
4742
|
+
const accessToken2 = captured["access_token"] ?? "";
|
|
4743
|
+
if (!accessToken2) throw new Error("OAuth 2.0: no access_token in implicit callback.");
|
|
4744
|
+
const expiresIn2 = Number(captured["expires_in"] ?? 3600);
|
|
4745
|
+
return { accessToken: accessToken2, expiresAt: Date.now() + expiresIn2 * 1e3 };
|
|
4746
|
+
}
|
|
4747
|
+
const code = captured["code"] ?? "";
|
|
4748
|
+
if (!code) throw new Error("OAuth 2.0: no code in callback.");
|
|
4749
|
+
const tokenUrl = requestExec.interpolate(auth.oauth2TokenUrl ?? "", vars);
|
|
4750
|
+
if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for the authorization_code flow.");
|
|
4751
|
+
const clientSecret = await resolveClientSecret(auth, vars);
|
|
4752
|
+
const { fetch: nodeFetch } = await import("undici");
|
|
4753
|
+
const params = new URLSearchParams();
|
|
4754
|
+
params.set("grant_type", "authorization_code");
|
|
4755
|
+
params.set("code", code);
|
|
4756
|
+
params.set("redirect_uri", redirectUri);
|
|
4757
|
+
params.set("client_id", clientId);
|
|
4758
|
+
if (clientSecret) params.set("client_secret", clientSecret);
|
|
4759
|
+
const resp = await nodeFetch(tokenUrl, {
|
|
4760
|
+
method: "POST",
|
|
4761
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
4762
|
+
body: params.toString()
|
|
4763
|
+
});
|
|
4764
|
+
if (!resp.ok) {
|
|
4765
|
+
const body = await resp.text();
|
|
4766
|
+
throw new Error(`OAuth 2.0 token exchange failed (${resp.status}): ${body}`);
|
|
4767
|
+
}
|
|
4768
|
+
const json = await resp.json();
|
|
4769
|
+
const accessToken = String(json["access_token"] ?? "");
|
|
4770
|
+
if (!accessToken) throw new Error("OAuth 2.0: token response missing access_token.");
|
|
4771
|
+
const expiresIn = Number(json["expires_in"] ?? 3600);
|
|
4772
|
+
return {
|
|
4773
|
+
accessToken,
|
|
4774
|
+
expiresAt: Date.now() + expiresIn * 1e3,
|
|
4775
|
+
refreshToken: json["refresh_token"] ? String(json["refresh_token"]) : void 0
|
|
4776
|
+
};
|
|
4777
|
+
}
|
|
4778
|
+
function registerOAuth2Handlers(ipc) {
|
|
4779
|
+
handle.handleIpc(ipc, handle.IPC.oauth2.startFlow, async (_e, auth, vars) => {
|
|
4780
|
+
if (auth.type !== "oauth2") throw new Error("OAuth 2.0: auth is not an oauth2 config.");
|
|
4781
|
+
const flow = auth.oauth2Flow ?? "client_credentials";
|
|
4782
|
+
if (flow === "authorization_code" || flow === "implicit") {
|
|
4783
|
+
return browserFlow(auth, vars, flow);
|
|
4627
4784
|
}
|
|
4628
|
-
|
|
4629
|
-
const accessToken = String(json["access_token"] ?? "");
|
|
4630
|
-
if (!accessToken) throw new Error("OAuth 2.0: token response missing access_token.");
|
|
4631
|
-
const expiresIn = Number(json["expires_in"] ?? 3600);
|
|
4632
|
-
return {
|
|
4633
|
-
accessToken,
|
|
4634
|
-
expiresAt: Date.now() + expiresIn * 1e3,
|
|
4635
|
-
refreshToken: json["refresh_token"] ? String(json["refresh_token"]) : void 0
|
|
4636
|
-
};
|
|
4785
|
+
return requestExec.fetchOAuth2Token(auth, vars);
|
|
4637
4786
|
});
|
|
4638
4787
|
handle.handleIpc(ipc, handle.IPC.oauth2.refreshToken, async (_e, auth, vars, refreshToken) => {
|
|
4639
4788
|
const tokenUrl = requestExec.interpolate(auth.oauth2TokenUrl ?? "", vars);
|
|
@@ -5792,7 +5941,7 @@ function isNewer(a, b) {
|
|
|
5792
5941
|
return a2 > b2;
|
|
5793
5942
|
}
|
|
5794
5943
|
async function checkForUpdate() {
|
|
5795
|
-
const current = "0.5.
|
|
5944
|
+
const current = "0.5.4";
|
|
5796
5945
|
try {
|
|
5797
5946
|
const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(4e3) });
|
|
5798
5947
|
if (!res.ok) return null;
|
|
@@ -5813,6 +5962,47 @@ if (process.env.ELECTRON_NO_SANDBOX === "1") {
|
|
|
5813
5962
|
electron.app.commandLine.appendSwitch("no-sandbox");
|
|
5814
5963
|
electron.app.commandLine.appendSwitch("disable-features", "RendererCodeIntegrity");
|
|
5815
5964
|
}
|
|
5965
|
+
const PROTOCOL = "spector";
|
|
5966
|
+
let pendingRepoUrl = null;
|
|
5967
|
+
function deepLinkFromArgv(argv) {
|
|
5968
|
+
return argv.find((a) => a.startsWith(`${PROTOCOL}://`)) ?? null;
|
|
5969
|
+
}
|
|
5970
|
+
function parseOpenFromGit(deepLink) {
|
|
5971
|
+
let u;
|
|
5972
|
+
try {
|
|
5973
|
+
u = new URL(deepLink);
|
|
5974
|
+
} catch {
|
|
5975
|
+
return null;
|
|
5976
|
+
}
|
|
5977
|
+
if (u.protocol !== `${PROTOCOL}:`) return null;
|
|
5978
|
+
const action = u.hostname || u.pathname.replace(/^\/+/, "").split("/")[0];
|
|
5979
|
+
if (action !== "open-from-git") return null;
|
|
5980
|
+
const repo = u.searchParams.get("url");
|
|
5981
|
+
return repo && repo.trim() ? repo.trim() : null;
|
|
5982
|
+
}
|
|
5983
|
+
function handleDeepLink(deepLink) {
|
|
5984
|
+
const repo = parseOpenFromGit(deepLink);
|
|
5985
|
+
if (!repo) return;
|
|
5986
|
+
const win = electron.BrowserWindow.getAllWindows()[0];
|
|
5987
|
+
if (!win) {
|
|
5988
|
+
pendingRepoUrl = repo;
|
|
5989
|
+
if (electron.app.isReady()) createWindow();
|
|
5990
|
+
return;
|
|
5991
|
+
}
|
|
5992
|
+
if (win.isMinimized()) win.restore();
|
|
5993
|
+
win.focus();
|
|
5994
|
+
if (win.webContents.isLoading()) {
|
|
5995
|
+
pendingRepoUrl = repo;
|
|
5996
|
+
} else {
|
|
5997
|
+
win.webContents.send(handle.IPC.file.openFromGitDeepLink, repo);
|
|
5998
|
+
}
|
|
5999
|
+
}
|
|
6000
|
+
function flushDeepLink(win) {
|
|
6001
|
+
if (!pendingRepoUrl) return;
|
|
6002
|
+
const repo = pendingRepoUrl;
|
|
6003
|
+
pendingRepoUrl = null;
|
|
6004
|
+
win.webContents.send(handle.IPC.file.openFromGitDeepLink, repo);
|
|
6005
|
+
}
|
|
5816
6006
|
function createSplashWindow() {
|
|
5817
6007
|
const splash = new electron.BrowserWindow({
|
|
5818
6008
|
width: 420,
|
|
@@ -5885,6 +6075,7 @@ function createWindow() {
|
|
|
5885
6075
|
setTimeout(() => {
|
|
5886
6076
|
splash.close();
|
|
5887
6077
|
win.show();
|
|
6078
|
+
flushDeepLink(win);
|
|
5888
6079
|
}, remaining);
|
|
5889
6080
|
});
|
|
5890
6081
|
if (process.platform === "win32") {
|
|
@@ -5898,6 +6089,31 @@ function createWindow() {
|
|
|
5898
6089
|
if (devToolsShortcut) win.webContents.toggleDevTools();
|
|
5899
6090
|
});
|
|
5900
6091
|
}
|
|
6092
|
+
if (process.defaultApp && process.argv.length >= 2) {
|
|
6093
|
+
electron.app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, [path.resolve(process.argv[1])]);
|
|
6094
|
+
} else {
|
|
6095
|
+
electron.app.setAsDefaultProtocolClient(PROTOCOL);
|
|
6096
|
+
}
|
|
6097
|
+
const gotLock = electron.app.requestSingleInstanceLock();
|
|
6098
|
+
if (!gotLock) {
|
|
6099
|
+
electron.app.quit();
|
|
6100
|
+
} else {
|
|
6101
|
+
electron.app.on("second-instance", (_e, argv) => {
|
|
6102
|
+
const url = deepLinkFromArgv(argv);
|
|
6103
|
+
if (url) handleDeepLink(url);
|
|
6104
|
+
else {
|
|
6105
|
+
const win = electron.BrowserWindow.getAllWindows()[0];
|
|
6106
|
+
if (win) {
|
|
6107
|
+
if (win.isMinimized()) win.restore();
|
|
6108
|
+
win.focus();
|
|
6109
|
+
}
|
|
6110
|
+
}
|
|
6111
|
+
});
|
|
6112
|
+
}
|
|
6113
|
+
electron.app.on("open-url", (event, url) => {
|
|
6114
|
+
event.preventDefault();
|
|
6115
|
+
handleDeepLink(url);
|
|
6116
|
+
});
|
|
5901
6117
|
electron.app.whenReady().then(async () => {
|
|
5902
6118
|
if (process.platform !== "darwin") electron.Menu.setApplicationMenu(null);
|
|
5903
6119
|
await requestExec.initSecretStore(electron.app.getPath("userData"));
|
|
@@ -5922,6 +6138,10 @@ electron.app.whenReady().then(async () => {
|
|
|
5922
6138
|
handle.handleIpc(electron.ipcMain, handle.IPC.shell.openExternal, (_e, url) => electron.shell.openExternal(url));
|
|
5923
6139
|
handle.handleIpc(electron.ipcMain, handle.IPC.app.checkUpdate, () => checkForUpdate());
|
|
5924
6140
|
createWindow();
|
|
6141
|
+
if (process.platform !== "darwin") {
|
|
6142
|
+
const url = deepLinkFromArgv(process.argv);
|
|
6143
|
+
if (url) handleDeepLink(url);
|
|
6144
|
+
}
|
|
5925
6145
|
electron.app.on("activate", () => {
|
|
5926
6146
|
if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
5927
6147
|
});
|
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-Dg-EHFX0.js");
|
|
5
5
|
require("http");
|
|
6
6
|
require("crypto");
|
|
7
7
|
require("vm");
|
|
@@ -10,7 +10,7 @@ require("@xmldom/xmldom");
|
|
|
10
10
|
require("undici");
|
|
11
11
|
require("fs/promises");
|
|
12
12
|
require("tls");
|
|
13
|
-
require("./chunks/handle-
|
|
13
|
+
require("./chunks/handle-BFaeFA0A.js");
|
|
14
14
|
require("node:fs");
|
|
15
15
|
require("node:os");
|
|
16
16
|
require("node:path");
|
package/out/main/runner.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
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-Dg-EHFX0.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-BFaeFA0A.js");
|
|
12
12
|
require("node:fs");
|
|
13
13
|
require("node:os");
|
|
14
14
|
require("node:path");
|
|
@@ -402,7 +402,7 @@ async function main() {
|
|
|
402
402
|
} else if (!envName && workspace.settings?.defaultEnvironment && !env) {
|
|
403
403
|
console.warn(cliCommon.color(`Warning: default environment "${workspace.settings.defaultEnvironment}" not found. Running without environment.`, cliCommon.C.yellow));
|
|
404
404
|
}
|
|
405
|
-
const version = `v${"0.5.
|
|
405
|
+
const version = `v${"0.5.4"}`;
|
|
406
406
|
console.log("");
|
|
407
407
|
console.log(cliCommon.color(" API Test Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
|
|
408
408
|
console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
|
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-DCCE344x.js");
|
|
8
|
+
const _import = require("./chunks/import-BBr8q5eT.js");
|
|
9
9
|
const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
|
|
10
|
-
require("./chunks/handle-
|
|
10
|
+
require("./chunks/handle-BFaeFA0A.js");
|
|
11
11
|
require("@xmldom/xmldom");
|
|
12
12
|
require("uuid");
|
|
13
13
|
require("crypto");
|
package/out/preload/index.js
CHANGED
|
@@ -5,6 +5,10 @@ const IPC = {
|
|
|
5
5
|
file: {
|
|
6
6
|
openWorkspace: "file:openWorkspace",
|
|
7
7
|
openWorkspacePath: "file:openWorkspacePath",
|
|
8
|
+
openFromGit: "file:openFromGit",
|
|
9
|
+
/** Event (main→renderer): a `spector://open-from-git?url=…` deep link was
|
|
10
|
+
* received; the renderer runs the same open-from-Git flow as the button. */
|
|
11
|
+
openFromGitDeepLink: "file:openFromGitDeepLink",
|
|
8
12
|
getLastWorkspace: "file:getLastWorkspace",
|
|
9
13
|
getRecentWorkspaces: "file:getRecentWorkspaces",
|
|
10
14
|
saveWorkspace: "file:saveWorkspace",
|
|
@@ -215,6 +219,13 @@ const api = {
|
|
|
215
219
|
getLastWorkspace: () => electron.ipcRenderer.invoke(IPC.file.getLastWorkspace),
|
|
216
220
|
getRecentWorkspaces: () => electron.ipcRenderer.invoke(IPC.file.getRecentWorkspaces),
|
|
217
221
|
openWorkspacePath: (path) => electron.ipcRenderer.invoke(IPC.file.openWorkspacePath, path),
|
|
222
|
+
openFromGit: (repoUrl) => electron.ipcRenderer.invoke(IPC.file.openFromGit, repoUrl),
|
|
223
|
+
onOpenFromGit: (cb) => {
|
|
224
|
+
electron.ipcRenderer.on(IPC.file.openFromGitDeepLink, (_e, repoUrl) => cb(repoUrl));
|
|
225
|
+
},
|
|
226
|
+
offOpenFromGit: () => {
|
|
227
|
+
electron.ipcRenderer.removeAllListeners(IPC.file.openFromGitDeepLink);
|
|
228
|
+
},
|
|
218
229
|
saveWorkspace: (ws) => electron.ipcRenderer.invoke(IPC.file.saveWorkspace, ws),
|
|
219
230
|
newWorkspace: () => electron.ipcRenderer.invoke(IPC.file.newWorkspace),
|
|
220
231
|
closeWorkspace: () => electron.ipcRenderer.invoke(IPC.file.closeWorkspace),
|