@testsmith/api-spector 0.5.1 → 0.5.3

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.
@@ -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-BYkE6MyE.js");
5
- require("./handle-b6bp3fb0.js");
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-b6bp3fb0.js");
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");
@@ -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-D_xyyt3_.js");
6
+ const requestExec = require("./request-exec-igCwP-QK.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-b6bp3fb0.js");
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-BeY7gjWL.js"));
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 });
@@ -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-BJiZNuRN.js");
5
+ const snapshots = require("./chunks/snapshots-DWvEZ64W.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-D_xyyt3_.js");
13
+ require("./chunks/request-exec-igCwP-QK.js");
14
14
  require("tls");
15
- require("./chunks/handle-b6bp3fb0.js");
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-b6bp3fb0.js");
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 requestExec = require("./chunks/request-exec-D_xyyt3_.js");
30
+ const JSZip = require("jszip");
31
+ const requestExec = require("./chunks/request-exec-igCwP-QK.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-BYkE6MyE.js");
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-BJiZNuRN.js");
48
+ const snapshots = require("./chunks/snapshots-DWvEZ64W.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, "");
@@ -5792,7 +5923,7 @@ function isNewer(a, b) {
5792
5923
  return a2 > b2;
5793
5924
  }
5794
5925
  async function checkForUpdate() {
5795
- const current = "0.5.1";
5926
+ const current = "0.5.3";
5796
5927
  try {
5797
5928
  const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(4e3) });
5798
5929
  if (!res.ok) return null;
@@ -5813,6 +5944,47 @@ if (process.env.ELECTRON_NO_SANDBOX === "1") {
5813
5944
  electron.app.commandLine.appendSwitch("no-sandbox");
5814
5945
  electron.app.commandLine.appendSwitch("disable-features", "RendererCodeIntegrity");
5815
5946
  }
5947
+ const PROTOCOL = "spector";
5948
+ let pendingRepoUrl = null;
5949
+ function deepLinkFromArgv(argv) {
5950
+ return argv.find((a) => a.startsWith(`${PROTOCOL}://`)) ?? null;
5951
+ }
5952
+ function parseOpenFromGit(deepLink) {
5953
+ let u;
5954
+ try {
5955
+ u = new URL(deepLink);
5956
+ } catch {
5957
+ return null;
5958
+ }
5959
+ if (u.protocol !== `${PROTOCOL}:`) return null;
5960
+ const action = u.hostname || u.pathname.replace(/^\/+/, "").split("/")[0];
5961
+ if (action !== "open-from-git") return null;
5962
+ const repo = u.searchParams.get("url");
5963
+ return repo && repo.trim() ? repo.trim() : null;
5964
+ }
5965
+ function handleDeepLink(deepLink) {
5966
+ const repo = parseOpenFromGit(deepLink);
5967
+ if (!repo) return;
5968
+ const win = electron.BrowserWindow.getAllWindows()[0];
5969
+ if (!win) {
5970
+ pendingRepoUrl = repo;
5971
+ if (electron.app.isReady()) createWindow();
5972
+ return;
5973
+ }
5974
+ if (win.isMinimized()) win.restore();
5975
+ win.focus();
5976
+ if (win.webContents.isLoading()) {
5977
+ pendingRepoUrl = repo;
5978
+ } else {
5979
+ win.webContents.send(handle.IPC.file.openFromGitDeepLink, repo);
5980
+ }
5981
+ }
5982
+ function flushDeepLink(win) {
5983
+ if (!pendingRepoUrl) return;
5984
+ const repo = pendingRepoUrl;
5985
+ pendingRepoUrl = null;
5986
+ win.webContents.send(handle.IPC.file.openFromGitDeepLink, repo);
5987
+ }
5816
5988
  function createSplashWindow() {
5817
5989
  const splash = new electron.BrowserWindow({
5818
5990
  width: 420,
@@ -5851,7 +6023,9 @@ function loadAppIcon() {
5851
6023
  }
5852
6024
  return void 0;
5853
6025
  }
6026
+ const MIN_SPLASH_MS = 350;
5854
6027
  function createWindow() {
6028
+ const startedAt = Date.now();
5855
6029
  const splash = createSplashWindow();
5856
6030
  const appIcon = loadAppIcon();
5857
6031
  if (appIcon && process.platform === "darwin" && electron.app.dock) {
@@ -5879,10 +6053,12 @@ function createWindow() {
5879
6053
  win.loadFile(path.join(__dirname, "../renderer/index.html"));
5880
6054
  }
5881
6055
  win.webContents.once("did-finish-load", () => {
6056
+ const remaining = Math.max(0, MIN_SPLASH_MS - (Date.now() - startedAt));
5882
6057
  setTimeout(() => {
5883
6058
  splash.close();
5884
6059
  win.show();
5885
- }, 1200);
6060
+ flushDeepLink(win);
6061
+ }, remaining);
5886
6062
  });
5887
6063
  if (process.platform === "win32") {
5888
6064
  const version = electron.app.getVersion();
@@ -5895,6 +6071,31 @@ function createWindow() {
5895
6071
  if (devToolsShortcut) win.webContents.toggleDevTools();
5896
6072
  });
5897
6073
  }
6074
+ if (process.defaultApp && process.argv.length >= 2) {
6075
+ electron.app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, [path.resolve(process.argv[1])]);
6076
+ } else {
6077
+ electron.app.setAsDefaultProtocolClient(PROTOCOL);
6078
+ }
6079
+ const gotLock = electron.app.requestSingleInstanceLock();
6080
+ if (!gotLock) {
6081
+ electron.app.quit();
6082
+ } else {
6083
+ electron.app.on("second-instance", (_e, argv) => {
6084
+ const url = deepLinkFromArgv(argv);
6085
+ if (url) handleDeepLink(url);
6086
+ else {
6087
+ const win = electron.BrowserWindow.getAllWindows()[0];
6088
+ if (win) {
6089
+ if (win.isMinimized()) win.restore();
6090
+ win.focus();
6091
+ }
6092
+ }
6093
+ });
6094
+ }
6095
+ electron.app.on("open-url", (event, url) => {
6096
+ event.preventDefault();
6097
+ handleDeepLink(url);
6098
+ });
5898
6099
  electron.app.whenReady().then(async () => {
5899
6100
  if (process.platform !== "darwin") electron.Menu.setApplicationMenu(null);
5900
6101
  await requestExec.initSecretStore(electron.app.getPath("userData"));
@@ -5919,6 +6120,10 @@ electron.app.whenReady().then(async () => {
5919
6120
  handle.handleIpc(electron.ipcMain, handle.IPC.shell.openExternal, (_e, url) => electron.shell.openExternal(url));
5920
6121
  handle.handleIpc(electron.ipcMain, handle.IPC.app.checkUpdate, () => checkForUpdate());
5921
6122
  createWindow();
6123
+ if (process.platform !== "darwin") {
6124
+ const url = deepLinkFromArgv(process.argv);
6125
+ if (url) handleDeepLink(url);
6126
+ }
5922
6127
  electron.app.on("activate", () => {
5923
6128
  if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
5924
6129
  });
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-D_xyyt3_.js");
4
+ const requestExec = require("./chunks/request-exec-igCwP-QK.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-b6bp3fb0.js");
13
+ require("./chunks/handle-BFaeFA0A.js");
14
14
  require("node:fs");
15
15
  require("node:os");
16
16
  require("node:path");
@@ -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-D_xyyt3_.js");
5
+ const requestExec = require("./chunks/request-exec-igCwP-QK.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-b6bp3fb0.js");
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.1"}`;
405
+ const version = `v${"0.5.3"}`;
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-BYkE6MyE.js");
8
- const _import = require("./chunks/import-BeY7gjWL.js");
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-b6bp3fb0.js");
10
+ require("./chunks/handle-BFaeFA0A.js");
11
11
  require("@xmldom/xmldom");
12
12
  require("uuid");
13
13
  require("crypto");
@@ -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),