@testsmith/api-spector 0.3.8 → 0.4.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/chunks/{handle-BCnNIZZr.js → handle-Coax8Nz1.js} +1 -0
- package/out/main/chunks/{import-DUKaMYGO.js → import-Bilo697Z.js} +2 -2
- package/out/main/chunks/{request-collection-CBuPjBwa.js → request-collection-CJoXpvOw.js} +20 -0
- package/out/main/chunks/{request-exec-AQD6cEgT.js → request-exec-C0DnGKdd.js} +1 -1
- package/out/main/chunks/{snapshots-NGkGQNZ4.js → snapshots-CatB4zLh.js} +1 -1
- package/out/main/chunks/{soap-handler-h0dWIjOK.js → soap-handler-B1Huudj7.js} +2 -2
- package/out/main/contract.js +3 -3
- package/out/main/index.js +72 -9
- package/out/main/runner.js +5 -5
- package/out/main/wsdl.js +3 -3
- package/out/preload/index.js +2 -0
- package/out/renderer/assets/{index-BK58GuOI.js → index-BtkRge9R.js} +125 -45
- package/out/renderer/index.html +1 -1
- package/package.json +1 -1
|
@@ -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-B1Huudj7.js");
|
|
5
|
+
require("./handle-Coax8Nz1.js");
|
|
6
6
|
require("https");
|
|
7
7
|
require("http");
|
|
8
8
|
require("@xmldom/xmldom");
|
|
@@ -85,6 +85,25 @@ function getAllApplicableHooks(folderId, collection) {
|
|
|
85
85
|
}
|
|
86
86
|
return { beforeAll, before, after, afterAll };
|
|
87
87
|
}
|
|
88
|
+
function authIsConfigured(auth) {
|
|
89
|
+
if (!auth || auth.type === "none") return false;
|
|
90
|
+
const set = (s) => !!(s && s.trim());
|
|
91
|
+
switch (auth.type) {
|
|
92
|
+
case "bearer":
|
|
93
|
+
return set(auth.token) || set(auth.tokenSecretRef);
|
|
94
|
+
case "basic":
|
|
95
|
+
case "digest":
|
|
96
|
+
return set(auth.username) || set(auth.password) || set(auth.passwordSecretRef);
|
|
97
|
+
case "ntlm":
|
|
98
|
+
return set(auth.username) || set(auth.password) || set(auth.passwordSecretRef);
|
|
99
|
+
case "apikey":
|
|
100
|
+
return set(auth.apiKeyValue) || set(auth.apiKeySecretRef);
|
|
101
|
+
case "oauth2":
|
|
102
|
+
return set(auth.oauth2TokenUrl) || set(auth.oauth2ClientId) || set(auth.oauth2CachedToken);
|
|
103
|
+
default:
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
88
107
|
function resolveInheritedAuthAndHeaders(requestId, collection) {
|
|
89
108
|
let inheritedAuth = collection.auth && collection.auth.type !== "none" ? collection.auth : null;
|
|
90
109
|
let inheritedHeaders = collection.headers?.filter((h) => h.enabled && h.key) ?? [];
|
|
@@ -113,6 +132,7 @@ function buildRunPlan(collection, folderId, filterTags) {
|
|
|
113
132
|
);
|
|
114
133
|
}
|
|
115
134
|
}
|
|
135
|
+
exports.authIsConfigured = authIsConfigured;
|
|
116
136
|
exports.buildRunPlan = buildRunPlan;
|
|
117
137
|
exports.getAllApplicableHooks = getAllApplicableHooks;
|
|
118
138
|
exports.resolveInheritedAuthAndHeaders = resolveInheritedAuthAndHeaders;
|
|
@@ -23,7 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
const undici = require("undici");
|
|
25
25
|
const promises = require("fs/promises");
|
|
26
|
-
const handle = require("./handle-
|
|
26
|
+
const handle = require("./handle-Coax8Nz1.js");
|
|
27
27
|
const crypto = require("crypto");
|
|
28
28
|
const path = require("path");
|
|
29
29
|
const dayjs = require("dayjs");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const undici = require("undici");
|
|
3
|
-
const requestExec = require("./request-exec-
|
|
3
|
+
const requestExec = require("./request-exec-C0DnGKdd.js");
|
|
4
4
|
const promises = require("fs/promises");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const Ajv = require("ajv");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const handle = require("./handle-
|
|
2
|
+
const handle = require("./handle-Coax8Nz1.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-Bilo697Z.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
|
@@ -24,14 +24,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
const promises = require("fs/promises");
|
|
26
26
|
const path = require("path");
|
|
27
|
-
const snapshots = require("./chunks/snapshots-
|
|
27
|
+
const snapshots = require("./chunks/snapshots-CatB4zLh.js");
|
|
28
28
|
const crypto = require("crypto");
|
|
29
29
|
const undici = require("undici");
|
|
30
30
|
const os = require("os");
|
|
31
31
|
const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
|
|
32
32
|
const environments = require("./chunks/environments-iM3SUM-4.js");
|
|
33
|
-
require("./chunks/request-exec-
|
|
34
|
-
require("./chunks/handle-
|
|
33
|
+
require("./chunks/request-exec-C0DnGKdd.js");
|
|
34
|
+
require("./chunks/handle-Coax8Nz1.js");
|
|
35
35
|
require("dayjs");
|
|
36
36
|
require("vm");
|
|
37
37
|
require("tv4");
|
package/out/main/index.js
CHANGED
|
@@ -22,24 +22,24 @@ 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-Coax8Nz1.js");
|
|
26
26
|
const path = require("path");
|
|
27
27
|
const fs = require("fs");
|
|
28
28
|
const promises = require("fs/promises");
|
|
29
|
-
const
|
|
29
|
+
const crypto = require("crypto");
|
|
30
|
+
const requestExec = require("./chunks/request-exec-C0DnGKdd.js");
|
|
30
31
|
const ipcValidate = require("./chunks/ipc-validate-k6KI8adf.js");
|
|
31
32
|
const uuid = require("uuid");
|
|
32
33
|
const jsYaml = require("js-yaml");
|
|
33
34
|
const undici = require("undici");
|
|
34
35
|
const JSZip = require("jszip");
|
|
35
|
-
const requestCollection = require("./chunks/request-collection-
|
|
36
|
+
const requestCollection = require("./chunks/request-collection-CJoXpvOw.js");
|
|
36
37
|
const mockServer = require("./chunks/mock-server-DSLR2ulH.js");
|
|
37
38
|
const http = require("http");
|
|
38
39
|
const WebSocket = require("ws");
|
|
39
|
-
const soapHandler = require("./chunks/soap-handler-
|
|
40
|
+
const soapHandler = require("./chunks/soap-handler-B1Huudj7.js");
|
|
40
41
|
const os = require("os");
|
|
41
|
-
const
|
|
42
|
-
const snapshots = require("./chunks/snapshots-NGkGQNZ4.js");
|
|
42
|
+
const snapshots = require("./chunks/snapshots-CatB4zLh.js");
|
|
43
43
|
const simpleGit = require("simple-git");
|
|
44
44
|
const recorder = require("./chunks/recorder-0Ij921El.js");
|
|
45
45
|
require("dayjs");
|
|
@@ -224,12 +224,27 @@ function registerFileHandlers(ipc) {
|
|
|
224
224
|
});
|
|
225
225
|
if (result.canceled || !result.filePaths[0]) return null;
|
|
226
226
|
const wsPath = result.filePaths[0];
|
|
227
|
+
const raw = await promises.readFile(wsPath, "utf8");
|
|
228
|
+
const parsed = JSON.parse(raw);
|
|
229
|
+
if (looksLikeCollection(parsed)) {
|
|
230
|
+
const { response } = await electron.dialog.showMessageBox({
|
|
231
|
+
type: "question",
|
|
232
|
+
buttons: ["Create Workspace", "Cancel"],
|
|
233
|
+
defaultId: 0,
|
|
234
|
+
cancelId: 1,
|
|
235
|
+
message: "This is a collection, not a workspace.",
|
|
236
|
+
detail: `API Spector opens workspaces, which contain collections.
|
|
237
|
+
|
|
238
|
+
Create a workspace in this folder and add "${path.basename(wsPath)}" to it? The collection is moved into a collections/ folder and this file becomes the workspace.`
|
|
239
|
+
});
|
|
240
|
+
if (response !== 0) return null;
|
|
241
|
+
return wrapCollectionAsWorkspace(wsPath, parsed);
|
|
242
|
+
}
|
|
227
243
|
workspaceDir = path.dirname(wsPath);
|
|
228
244
|
workspaceFile = wsPath;
|
|
229
245
|
await requestExec.loadGlobals(workspaceDir);
|
|
230
246
|
await saveLastWorkspacePath(wsPath);
|
|
231
|
-
|
|
232
|
-
return { workspace: JSON.parse(raw), workspacePath: wsPath };
|
|
247
|
+
return { workspace: parsed, workspacePath: wsPath };
|
|
233
248
|
});
|
|
234
249
|
handle.handleIpc(ipc, handle.IPC.file.newWorkspace, async () => {
|
|
235
250
|
const startDir = dialogStartDir();
|
|
@@ -390,6 +405,30 @@ async function tryOpenWorkspaceInDir(dir) {
|
|
|
390
405
|
function getWorkspaceDir() {
|
|
391
406
|
return workspaceDir;
|
|
392
407
|
}
|
|
408
|
+
function looksLikeCollection(obj) {
|
|
409
|
+
if (!obj || typeof obj !== "object") return false;
|
|
410
|
+
const o = obj;
|
|
411
|
+
return !Array.isArray(o["collections"]) && (!!o["rootFolder"] || !!o["requests"]);
|
|
412
|
+
}
|
|
413
|
+
async function wrapCollectionAsWorkspace(collFilePath, collection) {
|
|
414
|
+
const dir = path.dirname(collFilePath);
|
|
415
|
+
const base = path.basename(collFilePath).replace(/\.(spector|json)$/i, "");
|
|
416
|
+
const safe = base.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "collection";
|
|
417
|
+
const collRel = `collections/${safe}.json`;
|
|
418
|
+
if (!collection.id) collection.id = crypto.randomUUID();
|
|
419
|
+
await promises.mkdir(path.join(dir, "collections"), { recursive: true });
|
|
420
|
+
await atomicWrite(path.join(dir, collRel), JSON.stringify(collection, null, 2));
|
|
421
|
+
const ws = { version: "1.0", collections: [collRel], environments: [], activeEnvironmentId: null };
|
|
422
|
+
await ensureGitignore(dir);
|
|
423
|
+
await ensureVscodeFileAssociation(dir);
|
|
424
|
+
await ensureReadme(dir, path.basename(collFilePath));
|
|
425
|
+
await atomicWrite(collFilePath, JSON.stringify(ws, null, 2));
|
|
426
|
+
workspaceDir = dir;
|
|
427
|
+
workspaceFile = collFilePath;
|
|
428
|
+
await requestExec.loadGlobals(dir);
|
|
429
|
+
await saveLastWorkspacePath(collFilePath);
|
|
430
|
+
return { workspace: ws, workspacePath: collFilePath };
|
|
431
|
+
}
|
|
393
432
|
function asObject(value) {
|
|
394
433
|
return value && typeof value === "object" ? value : null;
|
|
395
434
|
}
|
|
@@ -1593,6 +1632,30 @@ function registerImportHandlers(ipc) {
|
|
|
1593
1632
|
if (result.canceled || !result.filePaths[0]) return null;
|
|
1594
1633
|
return importHttpFile(result.filePaths[0]);
|
|
1595
1634
|
});
|
|
1635
|
+
handle.handleIpc(ipc, handle.IPC.import.spector, async () => {
|
|
1636
|
+
const result = await electron.dialog.showOpenDialog({
|
|
1637
|
+
title: "Import API Spector Collection",
|
|
1638
|
+
filters: [{ name: "API Spector Collection", extensions: ["json", "spector"] }],
|
|
1639
|
+
properties: ["openFile"]
|
|
1640
|
+
});
|
|
1641
|
+
if (result.canceled || !result.filePaths[0]) return null;
|
|
1642
|
+
const raw = await promises.readFile(result.filePaths[0], "utf8");
|
|
1643
|
+
let parsed;
|
|
1644
|
+
try {
|
|
1645
|
+
parsed = JSON.parse(raw);
|
|
1646
|
+
} catch {
|
|
1647
|
+
throw new Error("Not a valid JSON file.");
|
|
1648
|
+
}
|
|
1649
|
+
if (Array.isArray(parsed["collections"])) {
|
|
1650
|
+
throw new Error("This is a workspace file, not a collection. Open it with File > Open Workspace instead.");
|
|
1651
|
+
}
|
|
1652
|
+
if (!parsed["rootFolder"] || !parsed["requests"]) {
|
|
1653
|
+
throw new Error("This does not look like an API Spector collection (no rootFolder / requests).");
|
|
1654
|
+
}
|
|
1655
|
+
const collection = parsed;
|
|
1656
|
+
collection.id = crypto.randomUUID();
|
|
1657
|
+
return collection;
|
|
1658
|
+
});
|
|
1596
1659
|
handle.handleIpc(ipc, handle.IPC.import.openapiSchemas, async () => {
|
|
1597
1660
|
const result = await electron.dialog.showOpenDialog({
|
|
1598
1661
|
title: "Load OpenAPI spec for schema sync",
|
|
@@ -1719,7 +1782,7 @@ function javaClass(name) {
|
|
|
1719
1782
|
return name.replace(/[^\w\s]/g, " ").split(/\s+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
1720
1783
|
}
|
|
1721
1784
|
function resolveEffectiveAuth(req, inherited) {
|
|
1722
|
-
return req.auth
|
|
1785
|
+
return requestCollection.authIsConfigured(req.auth) ? req.auth : inherited.auth ?? req.auth;
|
|
1723
1786
|
}
|
|
1724
1787
|
function mergeHeaders(req, inherited) {
|
|
1725
1788
|
return [...inherited.headers.filter((h) => h.enabled && h.key), ...req.headers.filter((h) => h.enabled && h.key)];
|
package/out/main/runner.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
const promises = require("fs/promises");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const requestExec = require("./chunks/request-exec-
|
|
6
|
-
const requestCollection = require("./chunks/request-collection-
|
|
5
|
+
const requestExec = require("./chunks/request-exec-C0DnGKdd.js");
|
|
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-CDQY1erJ.js");
|
|
9
9
|
require("undici");
|
|
10
|
-
require("./chunks/handle-
|
|
10
|
+
require("./chunks/handle-Coax8Nz1.js");
|
|
11
11
|
require("crypto");
|
|
12
12
|
require("dayjs");
|
|
13
13
|
require("vm");
|
|
@@ -396,7 +396,7 @@ async function main() {
|
|
|
396
396
|
} else if (!envName && workspace.settings?.defaultEnvironment && !env) {
|
|
397
397
|
console.warn(cliCommon.color(`Warning: default environment "${workspace.settings.defaultEnvironment}" not found. Running without environment.`, cliCommon.C.yellow));
|
|
398
398
|
}
|
|
399
|
-
const version = `v${"0.
|
|
399
|
+
const version = `v${"0.4.0"}`;
|
|
400
400
|
console.log("");
|
|
401
401
|
console.log(cliCommon.color(" API Test Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
|
|
402
402
|
console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
|
|
@@ -448,7 +448,7 @@ async function main() {
|
|
|
448
448
|
for (const item of items) {
|
|
449
449
|
const req = item.request;
|
|
450
450
|
const inherited = requestCollection.resolveInheritedAuthAndHeaders(req.id, col);
|
|
451
|
-
if (req.auth
|
|
451
|
+
if (!requestCollection.authIsConfigured(req.auth) && inherited.auth && inherited.auth.type !== "none") {
|
|
452
452
|
req.auth = inherited.auth;
|
|
453
453
|
}
|
|
454
454
|
const inheritedHeaders = inherited.headers.filter((h) => h.enabled && h.key);
|
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-B1Huudj7.js");
|
|
8
|
+
const _import = require("./chunks/import-Bilo697Z.js");
|
|
9
9
|
const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
|
|
10
|
-
require("./chunks/handle-
|
|
10
|
+
require("./chunks/handle-Coax8Nz1.js");
|
|
11
11
|
require("@xmldom/xmldom");
|
|
12
12
|
require("uuid");
|
|
13
13
|
function fetchUrl(url) {
|
package/out/preload/index.js
CHANGED
|
@@ -55,6 +55,7 @@ const IPC = {
|
|
|
55
55
|
insomnia: "import:insomnia",
|
|
56
56
|
bruno: "import:bruno",
|
|
57
57
|
http: "import:http",
|
|
58
|
+
spector: "import:spector",
|
|
58
59
|
openapiSchemas: "import:openapi-schemas",
|
|
59
60
|
openapiSchemasUrl: "import:openapi-schemas-url"
|
|
60
61
|
},
|
|
@@ -198,6 +199,7 @@ const api = {
|
|
|
198
199
|
importInsomnia: () => electron.ipcRenderer.invoke(IPC.import.insomnia),
|
|
199
200
|
importBruno: () => electron.ipcRenderer.invoke(IPC.import.bruno),
|
|
200
201
|
importHttpFile: () => electron.ipcRenderer.invoke(IPC.import.http),
|
|
202
|
+
importSpectorCollection: () => electron.ipcRenderer.invoke(IPC.import.spector),
|
|
201
203
|
/** Extract response-body schemas from an OpenAPI spec file (no full import). */
|
|
202
204
|
extractOpenApiSchemas: () => electron.ipcRenderer.invoke(IPC.import.openapiSchemas),
|
|
203
205
|
/** Extract response-body schemas from an OpenAPI spec URL (no full import). */
|
|
@@ -14350,14 +14350,14 @@ function useWorkspaceLoader() {
|
|
|
14350
14350
|
} else {
|
|
14351
14351
|
useStore.getState().setHistory([]);
|
|
14352
14352
|
}
|
|
14353
|
-
for (const colPath of ws2.collections) {
|
|
14353
|
+
for (const colPath of ws2.collections ?? []) {
|
|
14354
14354
|
try {
|
|
14355
14355
|
const col = await electron$r.loadCollection(colPath);
|
|
14356
14356
|
loadCollection(colPath, col);
|
|
14357
14357
|
} catch {
|
|
14358
14358
|
}
|
|
14359
14359
|
}
|
|
14360
|
-
for (const envPath of ws2.environments) {
|
|
14360
|
+
for (const envPath of ws2.environments ?? []) {
|
|
14361
14361
|
try {
|
|
14362
14362
|
const env = await electron$r.loadEnvironment(envPath);
|
|
14363
14363
|
loadEnvironment(envPath, env);
|
|
@@ -14388,7 +14388,7 @@ function useWorkspaceLoader() {
|
|
|
14388
14388
|
for (const { relPath, snapshot } of snapshots) loadContractSnapshot(relPath, snapshot);
|
|
14389
14389
|
} catch {
|
|
14390
14390
|
}
|
|
14391
|
-
if (ws2.collections.length > 0) {
|
|
14391
|
+
if ((ws2.collections ?? []).length > 0) {
|
|
14392
14392
|
try {
|
|
14393
14393
|
const firstCol = await electron$r.loadCollection(ws2.collections[0]);
|
|
14394
14394
|
setActiveCollection(firstCol.id);
|
|
@@ -35468,7 +35468,11 @@ function AuthEditor({
|
|
|
35468
35468
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35469
35469
|
"button",
|
|
35470
35470
|
{
|
|
35471
|
-
onClick: () =>
|
|
35471
|
+
onClick: () => {
|
|
35472
|
+
const ref2 = auth.passwordSecretRef ?? "NTLM_PASSWORD";
|
|
35473
|
+
onChange({ passwordSecretRef: ref2 });
|
|
35474
|
+
void secrets.saveSecret(ref2);
|
|
35475
|
+
},
|
|
35472
35476
|
className: "px-2 py-1 bg-blue-700 hover:bg-blue-600 rounded transition-colors",
|
|
35473
35477
|
children: secrets.saved ? "✓" : "Save"
|
|
35474
35478
|
}
|
|
@@ -35581,7 +35585,11 @@ function AuthEditor({
|
|
|
35581
35585
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35582
35586
|
"button",
|
|
35583
35587
|
{
|
|
35584
|
-
onClick: () =>
|
|
35588
|
+
onClick: () => {
|
|
35589
|
+
const ref2 = auth.apiKeySecretRef ?? "API_KEY";
|
|
35590
|
+
onChange({ apiKeySecretRef: ref2 });
|
|
35591
|
+
void secrets.saveSecret(ref2);
|
|
35592
|
+
},
|
|
35585
35593
|
className: "px-2 py-1 bg-blue-700 hover:bg-blue-600 rounded transition-colors",
|
|
35586
35594
|
children: secrets.saved ? "✓" : "Save"
|
|
35587
35595
|
}
|
|
@@ -35640,55 +35648,88 @@ function BasicCredentialsFields({
|
|
|
35640
35648
|
label,
|
|
35641
35649
|
note
|
|
35642
35650
|
}) {
|
|
35651
|
+
const [keychainOpen, setKeychainOpen] = reactExports.useState(!!auth.passwordSecretRef);
|
|
35643
35652
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
35644
35653
|
label && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500 text-[10px] uppercase tracking-wide", children: label }),
|
|
35645
35654
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
|
|
35646
35655
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
|
|
35647
35656
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-surface-400", children: "Username" }),
|
|
35648
35657
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35649
|
-
|
|
35658
|
+
VarInput,
|
|
35650
35659
|
{
|
|
35651
35660
|
value: auth.username ?? "",
|
|
35652
|
-
onChange: (
|
|
35661
|
+
onChange: (v) => setAuth({ username: v }),
|
|
35662
|
+
placeholder: "username",
|
|
35653
35663
|
className: "mt-1 w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500"
|
|
35654
35664
|
}
|
|
35655
35665
|
)
|
|
35656
35666
|
] }),
|
|
35657
35667
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
|
|
35658
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
35659
|
-
|
|
35660
|
-
|
|
35661
|
-
|
|
35662
|
-
|
|
35663
|
-
|
|
35664
|
-
|
|
35665
|
-
|
|
35666
|
-
|
|
35667
|
-
|
|
35668
|
-
|
|
35669
|
-
|
|
35670
|
-
|
|
35671
|
-
"
|
|
35672
|
-
|
|
35673
|
-
|
|
35674
|
-
|
|
35675
|
-
children: saved ? "✓" : "Save"
|
|
35676
|
-
}
|
|
35677
|
-
)
|
|
35678
|
-
] })
|
|
35668
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-surface-400", children: [
|
|
35669
|
+
"Password",
|
|
35670
|
+
" ",
|
|
35671
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 text-[10px]", children: [
|
|
35672
|
+
"- supports ",
|
|
35673
|
+
"{{variables}}"
|
|
35674
|
+
] })
|
|
35675
|
+
] }),
|
|
35676
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35677
|
+
VarInput,
|
|
35678
|
+
{
|
|
35679
|
+
value: auth.password ?? "",
|
|
35680
|
+
onChange: (v) => setAuth({ password: v }),
|
|
35681
|
+
placeholder: "password or API token",
|
|
35682
|
+
className: "mt-1 w-full bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500 font-mono"
|
|
35683
|
+
}
|
|
35684
|
+
)
|
|
35679
35685
|
] })
|
|
35680
35686
|
] }),
|
|
35681
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
35682
|
-
"
|
|
35683
|
-
|
|
35684
|
-
|
|
35685
|
-
"
|
|
35686
|
-
|
|
35687
|
-
|
|
35688
|
-
|
|
35689
|
-
|
|
35690
|
-
|
|
35691
|
-
|
|
35687
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
35688
|
+
"button",
|
|
35689
|
+
{
|
|
35690
|
+
onClick: () => setKeychainOpen((o) => !o),
|
|
35691
|
+
className: "text-[10px] text-surface-500 hover:text-surface-300 text-left transition-colors w-fit",
|
|
35692
|
+
children: [
|
|
35693
|
+
keychainOpen ? "▾" : "▸",
|
|
35694
|
+
" Store password in OS keychain instead"
|
|
35695
|
+
]
|
|
35696
|
+
}
|
|
35697
|
+
),
|
|
35698
|
+
keychainOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 pl-3 border-l border-surface-800", children: [
|
|
35699
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500 text-[10px]", children: "Encrypt the password in your OS keychain rather than saving it with the request. Leave the Password field above empty to use the keychain value." }),
|
|
35700
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
|
|
35701
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35702
|
+
"input",
|
|
35703
|
+
{
|
|
35704
|
+
type: "password",
|
|
35705
|
+
value: secretValue,
|
|
35706
|
+
onChange: (e) => setSecretValue(e.target.value),
|
|
35707
|
+
placeholder: auth.passwordSecretRef ? `Stored as "${auth.passwordSecretRef}"` : "Password",
|
|
35708
|
+
className: "flex-1 bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500 font-mono"
|
|
35709
|
+
}
|
|
35710
|
+
),
|
|
35711
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35712
|
+
"input",
|
|
35713
|
+
{
|
|
35714
|
+
value: auth.passwordSecretRef ?? "API_PASSWORD",
|
|
35715
|
+
onChange: (e) => setAuth({ passwordSecretRef: e.target.value }),
|
|
35716
|
+
placeholder: "Keychain key",
|
|
35717
|
+
className: "w-32 bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500"
|
|
35718
|
+
}
|
|
35719
|
+
),
|
|
35720
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35721
|
+
"button",
|
|
35722
|
+
{
|
|
35723
|
+
onClick: () => {
|
|
35724
|
+
const ref2 = auth.passwordSecretRef ?? "API_PASSWORD";
|
|
35725
|
+
setAuth({ passwordSecretRef: ref2 });
|
|
35726
|
+
void saveSecret(ref2);
|
|
35727
|
+
},
|
|
35728
|
+
className: "px-2 py-1 bg-blue-700 hover:bg-blue-600 rounded transition-colors",
|
|
35729
|
+
children: saved ? "✓" : "Save"
|
|
35730
|
+
}
|
|
35731
|
+
)
|
|
35732
|
+
] })
|
|
35692
35733
|
] }),
|
|
35693
35734
|
note && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-600 text-[10px]", children: note })
|
|
35694
35735
|
] });
|
|
@@ -35758,7 +35799,11 @@ function BearerPanel({
|
|
|
35758
35799
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
35759
35800
|
"button",
|
|
35760
35801
|
{
|
|
35761
|
-
onClick: () =>
|
|
35802
|
+
onClick: () => {
|
|
35803
|
+
const ref2 = auth.tokenSecretRef ?? "API_TOKEN";
|
|
35804
|
+
setAuth({ tokenSecretRef: ref2 });
|
|
35805
|
+
void saveSecret(ref2);
|
|
35806
|
+
},
|
|
35762
35807
|
className: "px-2 py-1 bg-blue-700 hover:bg-blue-600 rounded transition-colors",
|
|
35763
35808
|
children: saved ? "✓" : "Save"
|
|
35764
35809
|
}
|
|
@@ -36531,6 +36576,10 @@ function DotsBtn({ items: items2 }) {
|
|
|
36531
36576
|
menu && /* @__PURE__ */ jsxRuntimeExports.jsx(ContextMenu, { items: items2, x: menu.x, y: menu.y, onClose: () => setMenu(null) })
|
|
36532
36577
|
] });
|
|
36533
36578
|
}
|
|
36579
|
+
function requestPath(url) {
|
|
36580
|
+
const trimmed = url.trim().replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/i, "");
|
|
36581
|
+
return trimmed || url.trim();
|
|
36582
|
+
}
|
|
36534
36583
|
const HOOK_LABELS = {
|
|
36535
36584
|
beforeAll: "Before All",
|
|
36536
36585
|
before: "Before",
|
|
@@ -36557,6 +36606,7 @@ function RequestRow({
|
|
|
36557
36606
|
folderId,
|
|
36558
36607
|
reqIndex,
|
|
36559
36608
|
name: name2,
|
|
36609
|
+
url,
|
|
36560
36610
|
method,
|
|
36561
36611
|
protocol,
|
|
36562
36612
|
authType,
|
|
@@ -36658,6 +36708,10 @@ function RequestRow({
|
|
|
36658
36708
|
}
|
|
36659
36709
|
)
|
|
36660
36710
|
] }),
|
|
36711
|
+
(() => {
|
|
36712
|
+
const path = requestPath(url);
|
|
36713
|
+
return path && path !== name2 ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[10px] text-surface-500 font-mono truncate leading-tight", title: url, children: path }) : null;
|
|
36714
|
+
})(),
|
|
36661
36715
|
(tags2.length > 0 || addingTag) && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
36662
36716
|
TagChips,
|
|
36663
36717
|
{
|
|
@@ -37241,6 +37295,7 @@ function FolderContents({
|
|
|
37241
37295
|
folderId: folder.id,
|
|
37242
37296
|
reqIndex,
|
|
37243
37297
|
name: req.name,
|
|
37298
|
+
url: req.url,
|
|
37244
37299
|
method: req.method,
|
|
37245
37300
|
protocol: req.protocol,
|
|
37246
37301
|
authType: req.auth.type,
|
|
@@ -37358,6 +37413,25 @@ function getHooksForRequest(requestId, collection) {
|
|
|
37358
37413
|
}
|
|
37359
37414
|
return { before, after: afterReversed.reverse() };
|
|
37360
37415
|
}
|
|
37416
|
+
function authIsConfigured(auth) {
|
|
37417
|
+
if (!auth || auth.type === "none") return false;
|
|
37418
|
+
const set2 = (s) => !!(s && s.trim());
|
|
37419
|
+
switch (auth.type) {
|
|
37420
|
+
case "bearer":
|
|
37421
|
+
return set2(auth.token) || set2(auth.tokenSecretRef);
|
|
37422
|
+
case "basic":
|
|
37423
|
+
case "digest":
|
|
37424
|
+
return set2(auth.username) || set2(auth.password) || set2(auth.passwordSecretRef);
|
|
37425
|
+
case "ntlm":
|
|
37426
|
+
return set2(auth.username) || set2(auth.password) || set2(auth.passwordSecretRef);
|
|
37427
|
+
case "apikey":
|
|
37428
|
+
return set2(auth.apiKeyValue) || set2(auth.apiKeySecretRef);
|
|
37429
|
+
case "oauth2":
|
|
37430
|
+
return set2(auth.oauth2TokenUrl) || set2(auth.oauth2ClientId) || set2(auth.oauth2CachedToken);
|
|
37431
|
+
default:
|
|
37432
|
+
return true;
|
|
37433
|
+
}
|
|
37434
|
+
}
|
|
37361
37435
|
function resolveInheritedAuthAndHeaders(requestId, collection) {
|
|
37362
37436
|
let inheritedAuth = collection.auth && collection.auth.type !== "none" ? collection.auth : null;
|
|
37363
37437
|
let inheritedHeaders = collection.headers?.filter((h) => h.enabled && h.key) ?? [];
|
|
@@ -55886,7 +55960,11 @@ function AuthTab({ request, onChange }) {
|
|
|
55886
55960
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
55887
55961
|
"button",
|
|
55888
55962
|
{
|
|
55889
|
-
onClick: () =>
|
|
55963
|
+
onClick: () => {
|
|
55964
|
+
const ref2 = auth.oauth2ClientSecretRef ?? "OAUTH2_CLIENT_SECRET";
|
|
55965
|
+
setAuth({ oauth2ClientSecretRef: ref2 });
|
|
55966
|
+
void saveSecret(ref2);
|
|
55967
|
+
},
|
|
55890
55968
|
className: "px-2 py-1 bg-blue-700 hover:bg-blue-600 rounded transition-colors",
|
|
55891
55969
|
children: saved ? "✓" : "Save"
|
|
55892
55970
|
}
|
|
@@ -63829,7 +63907,7 @@ function RequestBuilder({ request }) {
|
|
|
63829
63907
|
};
|
|
63830
63908
|
let liveGlobals = { ...globals };
|
|
63831
63909
|
const inherited = useStore.getState().getInheritedAuthAndHeaders(request.id);
|
|
63832
|
-
const mergedAuth = request.auth
|
|
63910
|
+
const mergedAuth = authIsConfigured(request.auth) ? request.auth : inherited.auth ?? request.auth;
|
|
63833
63911
|
const mergedHeaders = [
|
|
63834
63912
|
...inherited.headers.filter((h) => h.enabled),
|
|
63835
63913
|
...request.headers
|
|
@@ -67233,7 +67311,8 @@ const OPTIONS = [
|
|
|
67233
67311
|
{ id: "openapi", label: "OpenAPI", description: "JSON or YAML (v3.x)", supportsUrl: true },
|
|
67234
67312
|
{ id: "insomnia", label: "Insomnia", description: "Export v4 JSON" },
|
|
67235
67313
|
{ id: "bruno", label: "Bruno", description: "bruno.json collection file" },
|
|
67236
|
-
{ id: "http", label: "HTTP file", description: ".http / .rest (REST Client)" }
|
|
67314
|
+
{ id: "http", label: "HTTP file", description: ".http / .rest (REST Client)" },
|
|
67315
|
+
{ id: "spector", label: "API Spector", description: "An existing .spector / .json collection" }
|
|
67237
67316
|
];
|
|
67238
67317
|
function listEndpoints(col) {
|
|
67239
67318
|
const result = [];
|
|
@@ -67304,6 +67383,7 @@ function ImportModal({ onImport, onClose }) {
|
|
|
67304
67383
|
if (opt2.id === "insomnia") col = await electron$9.importInsomnia();
|
|
67305
67384
|
if (opt2.id === "bruno") col = await electron$9.importBruno();
|
|
67306
67385
|
if (opt2.id === "http") col = await electron$9.importHttpFile();
|
|
67386
|
+
if (opt2.id === "spector") col = await electron$9.importSpectorCollection();
|
|
67307
67387
|
if (!col) {
|
|
67308
67388
|
setLoading(false);
|
|
67309
67389
|
return;
|
|
@@ -68506,7 +68586,7 @@ function RunnerModal() {
|
|
|
68506
68586
|
items2 = items2.map((item) => {
|
|
68507
68587
|
const inherited = resolveInheritedAuthAndHeaders(item.request.id, col);
|
|
68508
68588
|
const req = JSON.parse(JSON.stringify(item.request));
|
|
68509
|
-
if (req.auth
|
|
68589
|
+
if (!authIsConfigured(req.auth) && inherited.auth && inherited.auth.type !== "none") {
|
|
68510
68590
|
req.auth = inherited.auth;
|
|
68511
68591
|
}
|
|
68512
68592
|
const inheritedHeaders = inherited.headers.filter((h) => h.enabled && h.key);
|
|
@@ -72054,7 +72134,7 @@ function App() {
|
|
|
72054
72134
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
|
|
72055
72135
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
|
|
72056
72136
|
"v",
|
|
72057
|
-
"0.
|
|
72137
|
+
"0.4.0"
|
|
72058
72138
|
] }),
|
|
72059
72139
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", children: "·" }),
|
|
72060
72140
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
package/out/renderer/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta charset="UTF-8" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>API Spector</title>
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-BtkRge9R.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="./assets/index-B6kFQEbI.css">
|
|
10
10
|
</head>
|
|
11
11
|
|