@testsmith/api-spector 0.3.4 → 0.3.6
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 +11 -7
- package/out/main/agents.js +1 -1
- package/out/main/chunks/environments-iM3SUM-4.js +28 -0
- package/out/main/chunks/{handle-C0IQL-Vl.js → handle-BtRMtQJg.js} +3 -1
- package/out/main/chunks/{import-C5Ngq8hk.js → import-CZxL6J96.js} +4 -4
- package/out/main/chunks/{recorder-DFxJgn9c.js → recorder-0Ij921El.js} +1 -1
- package/out/main/chunks/request-collection-CBuPjBwa.js +118 -0
- package/out/main/chunks/{request-collection-8TOVNXE0.js → request-exec-DbNHbA8x.js} +602 -146
- package/out/main/chunks/snapshots-rV5tPwAd.js +1658 -0
- package/out/main/chunks/{soap-handler-B9x_YCtj.js → soap-handler-CqXVreNa.js} +3 -3
- package/out/main/contract.js +508 -91
- package/out/main/index.js +158 -87
- package/out/main/record.js +3 -3
- package/out/main/runner.js +23 -20
- package/out/main/wsdl.js +4 -4
- package/out/preload/index.js +5 -1
- package/out/renderer/assets/index-DWiG0dQo.css +2 -0
- package/out/renderer/assets/{index-BjjOh-E5.js → index-htBErIFG.js} +915 -176
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/readme.md +2 -2
- package/out/main/chunks/auth-builder-CUs9yzOF.js +0 -623
- package/out/main/chunks/snapshots-UFd3XgSS.js +0 -928
- package/out/renderer/assets/index-CjvjKHtF.css +0 -2
|
@@ -13460,10 +13460,12 @@ const createRecorderSlice = (set2) => ({
|
|
|
13460
13460
|
});
|
|
13461
13461
|
const createContractSlice = (set2) => ({
|
|
13462
13462
|
lastContractReport: null,
|
|
13463
|
+
lastContractRunMeta: null,
|
|
13463
13464
|
contractSnapshots: {},
|
|
13464
13465
|
activeContractSnapshotRelPath: null,
|
|
13465
|
-
setLastContractReport: (r) => set2((s) => {
|
|
13466
|
+
setLastContractReport: (r, meta2) => set2((s) => {
|
|
13466
13467
|
s.lastContractReport = r;
|
|
13468
|
+
s.lastContractRunMeta = r ? meta2 ?? null : null;
|
|
13467
13469
|
}),
|
|
13468
13470
|
loadContractSnapshot: (relPath, snapshot) => set2((s) => {
|
|
13469
13471
|
s.contractSnapshots[relPath] = snapshot;
|
|
@@ -14242,7 +14244,7 @@ const useStore = create()(
|
|
|
14242
14244
|
})
|
|
14243
14245
|
}))
|
|
14244
14246
|
);
|
|
14245
|
-
const { electron: electron$
|
|
14247
|
+
const { electron: electron$r } = window;
|
|
14246
14248
|
function useAutoSave() {
|
|
14247
14249
|
const collections = useStore((s) => s.collections);
|
|
14248
14250
|
useStore((s) => s.environments);
|
|
@@ -14258,7 +14260,7 @@ function useAutoSave() {
|
|
|
14258
14260
|
for (const { relPath, data, dirty } of dirtyCollections) {
|
|
14259
14261
|
if (!dirty) continue;
|
|
14260
14262
|
try {
|
|
14261
|
-
await electron$
|
|
14263
|
+
await electron$r.saveCollection(relPath, data);
|
|
14262
14264
|
markCollectionClean(data.id);
|
|
14263
14265
|
} catch (e) {
|
|
14264
14266
|
console.error("Auto-save failed for", relPath, e);
|
|
@@ -14274,7 +14276,7 @@ function useAutoSave() {
|
|
|
14274
14276
|
if (wsTimerRef.current) clearTimeout(wsTimerRef.current);
|
|
14275
14277
|
wsTimerRef.current = setTimeout(async () => {
|
|
14276
14278
|
try {
|
|
14277
|
-
await electron$
|
|
14279
|
+
await electron$r.saveWorkspace(workspace);
|
|
14278
14280
|
} catch {
|
|
14279
14281
|
}
|
|
14280
14282
|
}, 300);
|
|
@@ -14283,7 +14285,7 @@ function useAutoSave() {
|
|
|
14283
14285
|
};
|
|
14284
14286
|
}, [workspace]);
|
|
14285
14287
|
}
|
|
14286
|
-
const { electron: electron$
|
|
14288
|
+
const { electron: electron$q } = window;
|
|
14287
14289
|
function useWorkspaceLoader() {
|
|
14288
14290
|
const loadCollection = useStore((s) => s.loadCollection);
|
|
14289
14291
|
const loadEnvironment = useStore((s) => s.loadEnvironment);
|
|
@@ -14307,33 +14309,45 @@ function useWorkspaceLoader() {
|
|
|
14307
14309
|
if (typeof ws2.settings?.zoom === "number") setZoom(ws2.settings.zoom);
|
|
14308
14310
|
for (const colPath of ws2.collections) {
|
|
14309
14311
|
try {
|
|
14310
|
-
const col = await electron$
|
|
14312
|
+
const col = await electron$q.loadCollection(colPath);
|
|
14311
14313
|
loadCollection(colPath, col);
|
|
14312
14314
|
} catch {
|
|
14313
14315
|
}
|
|
14314
14316
|
}
|
|
14315
14317
|
for (const envPath of ws2.environments) {
|
|
14316
14318
|
try {
|
|
14317
|
-
const env = await electron$
|
|
14319
|
+
const env = await electron$q.loadEnvironment(envPath);
|
|
14318
14320
|
loadEnvironment(envPath, env);
|
|
14319
14321
|
} catch {
|
|
14320
14322
|
}
|
|
14321
14323
|
}
|
|
14324
|
+
{
|
|
14325
|
+
const state = useStore.getState();
|
|
14326
|
+
const activeId = state.activeEnvironmentId;
|
|
14327
|
+
const activeIsValid = activeId !== null && Boolean(state.environments[activeId]);
|
|
14328
|
+
const defaultName = ws2.settings?.defaultEnvironment;
|
|
14329
|
+
if (!activeIsValid && defaultName) {
|
|
14330
|
+
const match = Object.values(state.environments).find(
|
|
14331
|
+
(e) => e.data.name.toLowerCase() === defaultName.toLowerCase()
|
|
14332
|
+
);
|
|
14333
|
+
if (match) state.setActiveEnvironment(match.data.id);
|
|
14334
|
+
}
|
|
14335
|
+
}
|
|
14322
14336
|
for (const relPath of ws2.mocks ?? []) {
|
|
14323
14337
|
try {
|
|
14324
|
-
const mockData = await electron$
|
|
14338
|
+
const mockData = await electron$q.loadMock(relPath);
|
|
14325
14339
|
loadMock(relPath, mockData);
|
|
14326
14340
|
} catch {
|
|
14327
14341
|
}
|
|
14328
14342
|
}
|
|
14329
14343
|
try {
|
|
14330
|
-
const snapshots = await electron$
|
|
14344
|
+
const snapshots = await electron$q.listContractSnapshots(ws2.contracts ?? []);
|
|
14331
14345
|
for (const { relPath, snapshot } of snapshots) loadContractSnapshot(relPath, snapshot);
|
|
14332
14346
|
} catch {
|
|
14333
14347
|
}
|
|
14334
14348
|
if (ws2.collections.length > 0) {
|
|
14335
14349
|
try {
|
|
14336
|
-
const firstCol = await electron$
|
|
14350
|
+
const firstCol = await electron$q.loadCollection(ws2.collections[0]);
|
|
14337
14351
|
setActiveCollection(firstCol.id);
|
|
14338
14352
|
} catch {
|
|
14339
14353
|
}
|
|
@@ -14341,6 +14355,40 @@ function useWorkspaceLoader() {
|
|
|
14341
14355
|
}, [loadCollection, loadEnvironment, loadMock, loadContractSnapshot, setActiveCollection, setTheme, setZoom]);
|
|
14342
14356
|
return { applyWorkspace };
|
|
14343
14357
|
}
|
|
14358
|
+
function resolveEnvironmentChain(env, all) {
|
|
14359
|
+
if (!env.extends) return env;
|
|
14360
|
+
const chain = [env];
|
|
14361
|
+
const seen = /* @__PURE__ */ new Set([env.name]);
|
|
14362
|
+
let parentName = env.extends;
|
|
14363
|
+
while (parentName && !seen.has(parentName)) {
|
|
14364
|
+
const parent = all.find((e) => e.name === parentName);
|
|
14365
|
+
if (!parent) break;
|
|
14366
|
+
chain.push(parent);
|
|
14367
|
+
seen.add(parent.name);
|
|
14368
|
+
parentName = parent.extends;
|
|
14369
|
+
}
|
|
14370
|
+
if (chain.length === 1) return env;
|
|
14371
|
+
const merged = /* @__PURE__ */ new Map();
|
|
14372
|
+
for (const link of [...chain].reverse()) {
|
|
14373
|
+
for (const v of link.variables) merged.set(v.key, v);
|
|
14374
|
+
}
|
|
14375
|
+
return { ...env, variables: [...merged.values()] };
|
|
14376
|
+
}
|
|
14377
|
+
function resolveEnvironmentById(environments, id2) {
|
|
14378
|
+
if (!id2) return null;
|
|
14379
|
+
const env = environments[id2]?.data;
|
|
14380
|
+
if (!env) return null;
|
|
14381
|
+
const all = Object.values(environments).map((e) => e.data);
|
|
14382
|
+
return resolveEnvironmentChain(env, all);
|
|
14383
|
+
}
|
|
14384
|
+
function useActiveEnvironment() {
|
|
14385
|
+
const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
|
|
14386
|
+
const environments = useStore((s) => s.environments);
|
|
14387
|
+
return reactExports.useMemo(
|
|
14388
|
+
() => resolveEnvironmentById(environments, activeEnvironmentId),
|
|
14389
|
+
[environments, activeEnvironmentId]
|
|
14390
|
+
);
|
|
14391
|
+
}
|
|
14344
14392
|
let rangeFrom = [], rangeTo = [];
|
|
14345
14393
|
(() => {
|
|
14346
14394
|
let numbers = "lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o".split(",").map((s) => s ? parseInt(s, 36) : 1);
|
|
@@ -34301,7 +34349,7 @@ const DYNAMIC_VAR_NAMES = [
|
|
|
34301
34349
|
"$randomHexColor"
|
|
34302
34350
|
];
|
|
34303
34351
|
const DYNAMIC_VAR_INFO = {
|
|
34304
|
-
$uuid: "Random UUID v4
|
|
34352
|
+
$uuid: "Random UUID v4 - generated fresh each send",
|
|
34305
34353
|
$timestamp: "Current Unix timestamp in milliseconds",
|
|
34306
34354
|
$isoTimestamp: "Current date/time as ISO 8601 string",
|
|
34307
34355
|
$randomInt: "Random integer between 0 and 1000",
|
|
@@ -34341,7 +34389,7 @@ const SP_RESPONSE_MEMBERS = [
|
|
|
34341
34389
|
{ label: "statusText", type: "property", detail: "string", info: "Status text only" },
|
|
34342
34390
|
{ label: "responseTime", type: "property", detail: "number", info: "Request duration in ms" },
|
|
34343
34391
|
{ label: "responseSize", type: "property", detail: "number", info: "Body size in bytes" },
|
|
34344
|
-
{ label: "headers", type: "property", info: "Response headers
|
|
34392
|
+
{ label: "headers", type: "property", info: "Response headers - use .get(name) or .toObject()" },
|
|
34345
34393
|
{ label: "json", type: "function", detail: "()", info: "Parse body as JSON and return it" },
|
|
34346
34394
|
{ label: "text", type: "function", detail: "()", info: "Return body as a raw string" }
|
|
34347
34395
|
];
|
|
@@ -34727,7 +34775,7 @@ function mockBodyCompletionExtension(pathParamNames = [], varNames = []) {
|
|
|
34727
34775
|
const RESPONSE_MEMBERS = [
|
|
34728
34776
|
{ label: "statusCode", type: "property", detail: "number", info: "HTTP status code to send" },
|
|
34729
34777
|
{ label: "body", type: "property", detail: "string", info: "Response body string (overrides template)" },
|
|
34730
|
-
{ label: "headers", type: "property", detail: "object", info: 'Response headers
|
|
34778
|
+
{ label: "headers", type: "property", detail: "object", info: 'Response headers - modify with response.headers["X-Foo"] = "bar"' }
|
|
34731
34779
|
];
|
|
34732
34780
|
const REQUEST_SCRIPT_MEMBERS = [
|
|
34733
34781
|
{ label: "params", type: "property", info: "URL path params { id, slug, … }" },
|
|
@@ -34785,12 +34833,11 @@ function extractScriptVarNames(script) {
|
|
|
34785
34833
|
return names2;
|
|
34786
34834
|
}
|
|
34787
34835
|
function useVarNames() {
|
|
34788
|
-
const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
|
|
34789
34836
|
const activeCollectionId = useStore((s) => s.activeCollectionId);
|
|
34790
|
-
const environments = useStore((s) => s.environments);
|
|
34791
34837
|
const collections = useStore((s) => s.collections);
|
|
34792
34838
|
const globals = useStore((s) => s.globals);
|
|
34793
34839
|
const sessionVars = useStore((s) => s.sessionVars);
|
|
34840
|
+
const activeEnv = useActiveEnvironment();
|
|
34794
34841
|
return reactExports.useMemo(() => {
|
|
34795
34842
|
const names2 = /* @__PURE__ */ new Set();
|
|
34796
34843
|
Object.keys(globals).forEach((k) => names2.add(k));
|
|
@@ -34805,41 +34852,35 @@ function useVarNames() {
|
|
|
34805
34852
|
}
|
|
34806
34853
|
}
|
|
34807
34854
|
}
|
|
34808
|
-
|
|
34809
|
-
|
|
34810
|
-
envVars.filter((v) => v.enabled && v.key).forEach((v) => names2.add(v.key));
|
|
34811
|
-
}
|
|
34855
|
+
const envVars = activeEnv?.variables ?? [];
|
|
34856
|
+
envVars.filter((v) => v.enabled && v.key).forEach((v) => names2.add(v.key));
|
|
34812
34857
|
return [...DYNAMIC_VAR_NAMES, ...Array.from(names2).sort()];
|
|
34813
34858
|
}, [
|
|
34814
|
-
|
|
34859
|
+
activeEnv,
|
|
34815
34860
|
activeCollectionId,
|
|
34816
|
-
environments,
|
|
34817
34861
|
collections,
|
|
34818
34862
|
globals,
|
|
34819
34863
|
sessionVars
|
|
34820
34864
|
]);
|
|
34821
34865
|
}
|
|
34822
34866
|
function useVarValues() {
|
|
34823
|
-
const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
|
|
34824
34867
|
const activeCollectionId = useStore((s) => s.activeCollectionId);
|
|
34825
|
-
const environments = useStore((s) => s.environments);
|
|
34826
34868
|
const collections = useStore((s) => s.collections);
|
|
34827
34869
|
const globals = useStore((s) => s.globals);
|
|
34870
|
+
const activeEnv = useActiveEnvironment();
|
|
34828
34871
|
const result = { ...globals };
|
|
34829
34872
|
if (activeCollectionId) {
|
|
34830
34873
|
const colVars = collections[activeCollectionId]?.data.collectionVariables ?? {};
|
|
34831
34874
|
Object.assign(result, colVars);
|
|
34832
34875
|
}
|
|
34833
|
-
|
|
34834
|
-
|
|
34835
|
-
|
|
34836
|
-
|
|
34837
|
-
|
|
34838
|
-
|
|
34839
|
-
|
|
34840
|
-
|
|
34841
|
-
result[v.key] = v.value;
|
|
34842
|
-
}
|
|
34876
|
+
for (const v of activeEnv?.variables ?? []) {
|
|
34877
|
+
if (!v.enabled || !v.key) continue;
|
|
34878
|
+
if (v.secret && v.secretEncrypted) {
|
|
34879
|
+
result[v.key] = "••••••••";
|
|
34880
|
+
} else if (v.envRef) {
|
|
34881
|
+
result[v.key] = `$${v.envRef}`;
|
|
34882
|
+
} else {
|
|
34883
|
+
result[v.key] = v.value;
|
|
34843
34884
|
}
|
|
34844
34885
|
}
|
|
34845
34886
|
return result;
|
|
@@ -35181,7 +35222,7 @@ function KVTable({ rows, onChange, keyPlaceholder = "Key", valuePlaceholder = "V
|
|
|
35181
35222
|
{
|
|
35182
35223
|
value: row.paramType ?? "query",
|
|
35183
35224
|
onChange: (e) => update(idx, { paramType: e.target.value }),
|
|
35184
|
-
title: (row.paramType ?? "query") === "path" ? "Path variable
|
|
35225
|
+
title: (row.paramType ?? "query") === "path" ? "Path variable - substituted into the URL via {{name}}" : "Query string parameter - appended as ?key=value",
|
|
35185
35226
|
className: `flex-shrink-0 text-[10px] bg-surface-800 border border-surface-700 rounded px-1.5 py-1 focus:outline-none focus:border-blue-500 font-mono ${(row.paramType ?? "query") === "path" ? "text-violet-400" : "text-surface-400"}`,
|
|
35186
35227
|
children: [
|
|
35187
35228
|
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "query", children: "query" }),
|
|
@@ -35624,7 +35665,7 @@ function BearerPanel({
|
|
|
35624
35665
|
"Token",
|
|
35625
35666
|
" ",
|
|
35626
35667
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 text-[10px]", children: [
|
|
35627
|
-
"
|
|
35668
|
+
"- supports ",
|
|
35628
35669
|
"{{variables}}"
|
|
35629
35670
|
] })
|
|
35630
35671
|
] }),
|
|
@@ -35707,7 +35748,7 @@ function FolderSettingsModal({ collectionId, folder, onClose }) {
|
|
|
35707
35748
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold", children: "Folder settings" }),
|
|
35708
35749
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-600 mt-0.5", children: [
|
|
35709
35750
|
folder.name,
|
|
35710
|
-
"
|
|
35751
|
+
" - auth and headers inherited by all requests in this folder"
|
|
35711
35752
|
] })
|
|
35712
35753
|
] }),
|
|
35713
35754
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-white text-lg leading-none", children: "×" })
|
|
@@ -35800,7 +35841,7 @@ function CollectionSettingsModal({ collection, onClose }) {
|
|
|
35800
35841
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold", children: "Collection settings" }),
|
|
35801
35842
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-600 mt-0.5", children: [
|
|
35802
35843
|
collection.name,
|
|
35803
|
-
"
|
|
35844
|
+
" - auth and headers inherited by all requests in this collection"
|
|
35804
35845
|
] })
|
|
35805
35846
|
] }),
|
|
35806
35847
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-white text-lg leading-none", children: "×" })
|
|
@@ -35907,7 +35948,7 @@ function CollectionSettingsModal({ collection, onClose }) {
|
|
|
35907
35948
|
}
|
|
35908
35949
|
);
|
|
35909
35950
|
}
|
|
35910
|
-
const { electron: electron$
|
|
35951
|
+
const { electron: electron$p } = window;
|
|
35911
35952
|
function normalisePath(url) {
|
|
35912
35953
|
let path = url.replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/, "");
|
|
35913
35954
|
if (!path.startsWith("/")) path = "/" + path;
|
|
@@ -35994,7 +36035,7 @@ function SchemaSyncModal({
|
|
|
35994
36035
|
setLoading(true);
|
|
35995
36036
|
setError(null);
|
|
35996
36037
|
try {
|
|
35997
|
-
const entries = await electron$
|
|
36038
|
+
const entries = await electron$p.extractOpenApiSchemas();
|
|
35998
36039
|
if (!entries) {
|
|
35999
36040
|
setLoading(false);
|
|
36000
36041
|
return;
|
|
@@ -36013,7 +36054,7 @@ function SchemaSyncModal({
|
|
|
36013
36054
|
setLoading(true);
|
|
36014
36055
|
setError(null);
|
|
36015
36056
|
try {
|
|
36016
|
-
const entries = await electron$
|
|
36057
|
+
const entries = await electron$p.extractOpenApiSchemasFromUrl(trimmed);
|
|
36017
36058
|
setSpecEntries(entries);
|
|
36018
36059
|
autoSelectChanged(entries);
|
|
36019
36060
|
} catch (err) {
|
|
@@ -36046,7 +36087,7 @@ function SchemaSyncModal({
|
|
|
36046
36087
|
}
|
|
36047
36088
|
const entry = useStore.getState().collections[collectionId];
|
|
36048
36089
|
if (entry) {
|
|
36049
|
-
await electron$
|
|
36090
|
+
await electron$p.saveCollection(entry.relPath, entry.data);
|
|
36050
36091
|
markCollectionClean(collectionId);
|
|
36051
36092
|
}
|
|
36052
36093
|
onClose();
|
|
@@ -36065,7 +36106,7 @@ function SchemaSyncModal({
|
|
|
36065
36106
|
children: [
|
|
36066
36107
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
36067
36108
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-sm font-semibold text-surface-100", children: [
|
|
36068
|
-
"Sync schemas
|
|
36109
|
+
"Sync schemas - ",
|
|
36069
36110
|
scopeLabel
|
|
36070
36111
|
] }),
|
|
36071
36112
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-500 hover:text-surface-300 text-lg leading-none", children: "×" })
|
|
@@ -36184,7 +36225,7 @@ function SchemaSyncModal({
|
|
|
36184
36225
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold text-surface-100", children: "Sync schemas from OpenAPI" }),
|
|
36185
36226
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-500 hover:text-surface-300 text-lg leading-none", children: "×" })
|
|
36186
36227
|
] }),
|
|
36187
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400", children: "Load an OpenAPI spec to update response schemas on existing requests. Matching uses HTTP method + URL path. Only schemas are touched
|
|
36228
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-400", children: "Load an OpenAPI spec to update response schemas on existing requests. Matching uses HTTP method + URL path. Only schemas are touched - URLs, params, headers, auth, and scripts are preserved." }),
|
|
36188
36229
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
36189
36230
|
"button",
|
|
36190
36231
|
{
|
|
@@ -36260,6 +36301,7 @@ const METHOD_COLORS$1 = {
|
|
|
36260
36301
|
DELETE: "text-red-400",
|
|
36261
36302
|
HEAD: "text-purple-400",
|
|
36262
36303
|
OPTIONS: "text-surface-400",
|
|
36304
|
+
QUERY: "text-fuchsia-400",
|
|
36263
36305
|
ANY: "text-surface-400"
|
|
36264
36306
|
};
|
|
36265
36307
|
function getMethodColor(method) {
|
|
@@ -54697,9 +54739,8 @@ function GraphQLEditor({ request, onChange }) {
|
|
|
54697
54739
|
() => request.body.graphql ?? EMPTY_GQL,
|
|
54698
54740
|
[request.body.graphql]
|
|
54699
54741
|
);
|
|
54700
|
-
const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
|
|
54701
54742
|
const activeCollectionId = useStore((s) => s.activeCollectionId);
|
|
54702
|
-
const envData =
|
|
54743
|
+
const envData = useActiveEnvironment();
|
|
54703
54744
|
const colVarsData = useStore((s) => activeCollectionId ? s.collections[activeCollectionId]?.data.collectionVariables : null);
|
|
54704
54745
|
const globals = useStore((s) => s.globals);
|
|
54705
54746
|
const hookVars = reactExports.useMemo(() => {
|
|
@@ -55302,7 +55343,7 @@ function withContentType(headers, value) {
|
|
|
55302
55343
|
if (idx === -1) return [...headers, next];
|
|
55303
55344
|
return headers.map((h, i) => i === idx ? { ...h, value, enabled: true } : h);
|
|
55304
55345
|
}
|
|
55305
|
-
const { electron: electron$
|
|
55346
|
+
const { electron: electron$o } = window;
|
|
55306
55347
|
function ParamTree({ params, depth = 0 }) {
|
|
55307
55348
|
if (params.length === 0) {
|
|
55308
55349
|
return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 italic", children: "No parameters declared in WSDL." });
|
|
@@ -55328,7 +55369,7 @@ function SoapEditor({ request, onChange }) {
|
|
|
55328
55369
|
let cancelled = false;
|
|
55329
55370
|
(async () => {
|
|
55330
55371
|
try {
|
|
55331
|
-
const result = await electron$
|
|
55372
|
+
const result = await electron$o.wsdlFetch(url);
|
|
55332
55373
|
if (cancelled) return;
|
|
55333
55374
|
setOperations(result.operations);
|
|
55334
55375
|
setEndpoints(result.endpoints);
|
|
@@ -55365,7 +55406,7 @@ function SoapEditor({ request, onChange }) {
|
|
|
55365
55406
|
setFetching(true);
|
|
55366
55407
|
setFetchError(null);
|
|
55367
55408
|
try {
|
|
55368
|
-
const result = await electron$
|
|
55409
|
+
const result = await electron$o.wsdlFetch(soap.wsdlUrl.trim());
|
|
55369
55410
|
setOperations(result.operations);
|
|
55370
55411
|
setEndpoints(result.endpoints);
|
|
55371
55412
|
setTargetNs(result.targetNamespace);
|
|
@@ -55506,7 +55547,7 @@ function SoapEditor({ request, onChange }) {
|
|
|
55506
55547
|
"SOAPAction: ",
|
|
55507
55548
|
soap.soapAction
|
|
55508
55549
|
] }),
|
|
55509
|
-
!soap.operationName && !soap.soapAction && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-surface-500", children: soap.wsdlUrl?.trim() ? "WSDL not loaded
|
|
55550
|
+
!soap.operationName && !soap.soapAction && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-surface-500", children: soap.wsdlUrl?.trim() ? "WSDL not loaded - the saved envelope below is still sent on Send." : "No WSDL - hand-crafted SOAP envelope." })
|
|
55510
55551
|
] }),
|
|
55511
55552
|
soap.wsdlUrl?.trim() && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
55512
55553
|
"button",
|
|
@@ -55536,7 +55577,7 @@ function SoapEditor({ request, onChange }) {
|
|
|
55536
55577
|
/* @__PURE__ */ jsxRuntimeExports.jsx("em", { children: "Fetch WSDL" }),
|
|
55537
55578
|
"."
|
|
55538
55579
|
] }),
|
|
55539
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 max-w-sm", children: "The endpoint, SOAP version, Content-Type header, and per-operation envelope are derived from the WSDL
|
|
55580
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 max-w-sm", children: "The endpoint, SOAP version, Content-Type header, and per-operation envelope are derived from the WSDL - you only pick the operation and fill the parameters." })
|
|
55540
55581
|
] })
|
|
55541
55582
|
] });
|
|
55542
55583
|
}
|
|
@@ -55635,7 +55676,7 @@ function BodyTab({ request, onChange }) {
|
|
|
55635
55676
|
mode === "soap" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SoapEditor, { request, onChange }) })
|
|
55636
55677
|
] });
|
|
55637
55678
|
}
|
|
55638
|
-
const { electron: electron$
|
|
55679
|
+
const { electron: electron$n } = window;
|
|
55639
55680
|
const AUTH_TYPES = ["none", "bearer", "basic", "digest", "ntlm", "apikey", "oauth2"];
|
|
55640
55681
|
function AuthTab({ request, onChange }) {
|
|
55641
55682
|
const auth = request.auth;
|
|
@@ -55649,7 +55690,7 @@ function AuthTab({ request, onChange }) {
|
|
|
55649
55690
|
}
|
|
55650
55691
|
async function saveSecret(ref2) {
|
|
55651
55692
|
if (!secretValue || !ref2) return;
|
|
55652
|
-
await electron$
|
|
55693
|
+
await electron$n.setSecret(ref2, secretValue);
|
|
55653
55694
|
setSaved(true);
|
|
55654
55695
|
setSecretValue("");
|
|
55655
55696
|
setTimeout(() => setSaved(false), 2e3);
|
|
@@ -55661,7 +55702,7 @@ function AuthTab({ request, onChange }) {
|
|
|
55661
55702
|
setOauth2Error("");
|
|
55662
55703
|
try {
|
|
55663
55704
|
const vars = {};
|
|
55664
|
-
const result = await electron$
|
|
55705
|
+
const result = await electron$n.oauth2StartFlow(oauth2Auth, vars);
|
|
55665
55706
|
setAuth({
|
|
55666
55707
|
oauth2CachedToken: result.accessToken,
|
|
55667
55708
|
oauth2TokenExpiry: result.expiresAt
|
|
@@ -55679,7 +55720,7 @@ function AuthTab({ request, onChange }) {
|
|
|
55679
55720
|
setOauth2Status("fetching");
|
|
55680
55721
|
setOauth2Error("");
|
|
55681
55722
|
try {
|
|
55682
|
-
const result = await electron$
|
|
55723
|
+
const result = await electron$n.oauth2RefreshToken(oauth2Auth, {}, oauth2RefreshToken);
|
|
55683
55724
|
setAuth({
|
|
55684
55725
|
oauth2CachedToken: result.accessToken,
|
|
55685
55726
|
oauth2TokenExpiry: result.expiresAt
|
|
@@ -56452,7 +56493,7 @@ const SNIPPET_GROUPS = [
|
|
|
56452
56493
|
]
|
|
56453
56494
|
},
|
|
56454
56495
|
{
|
|
56455
|
-
group: "Variables
|
|
56496
|
+
group: "Variables - Get",
|
|
56456
56497
|
items: [
|
|
56457
56498
|
{
|
|
56458
56499
|
label: "Get variable",
|
|
@@ -56473,7 +56514,7 @@ const SNIPPET_GROUPS = [
|
|
|
56473
56514
|
]
|
|
56474
56515
|
},
|
|
56475
56516
|
{
|
|
56476
|
-
group: "Variables
|
|
56517
|
+
group: "Variables - Set",
|
|
56477
56518
|
items: [
|
|
56478
56519
|
{
|
|
56479
56520
|
label: "Save token from response (use in next requests)",
|
|
@@ -56499,7 +56540,7 @@ sp.collectionVariables.set("token", json.access_token);`
|
|
|
56499
56540
|
]
|
|
56500
56541
|
},
|
|
56501
56542
|
{
|
|
56502
|
-
group: "Variables
|
|
56543
|
+
group: "Variables - Clear",
|
|
56503
56544
|
items: [
|
|
56504
56545
|
{
|
|
56505
56546
|
label: "Clear variable",
|
|
@@ -62942,7 +62983,7 @@ function SchemaTab({ request, onChange }) {
|
|
|
62942
62983
|
)
|
|
62943
62984
|
] })
|
|
62944
62985
|
] }),
|
|
62945
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600", children: "Standalone schema for ad-hoc validation. Independent of the contract
|
|
62986
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600", children: "Standalone schema for ad-hoc validation. Independent of the contract - edits here don't affect it." }),
|
|
62946
62987
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-surface-700 rounded overflow-hidden", children: [
|
|
62947
62988
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end px-2 py-0.5 bg-surface-800/50 border-b border-surface-700", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
62948
62989
|
"button",
|
|
@@ -62973,9 +63014,9 @@ function SchemaTab({ request, onChange }) {
|
|
|
62973
63014
|
)
|
|
62974
63015
|
] }),
|
|
62975
63016
|
error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-red-400 bg-red-950/50 border border-red-800 rounded px-3 py-2", children: error2 }),
|
|
62976
|
-
result && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `rounded border px-3 py-2 text-xs ${result.valid ? "bg-emerald-900/20 border-emerald-700" : "bg-red-900/20 border-red-700"}`, children: result.valid ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-emerald-400 font-semibold", children: "Valid
|
|
63017
|
+
result && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `rounded border px-3 py-2 text-xs ${result.valid ? "bg-emerald-900/20 border-emerald-700" : "bg-red-900/20 border-red-700"}`, children: result.valid ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-emerald-400 font-semibold", children: "Valid: response matches the schema." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
62977
63018
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-red-400 font-semibold", children: [
|
|
62978
|
-
"Invalid
|
|
63019
|
+
"Invalid: ",
|
|
62979
63020
|
result.errors.length,
|
|
62980
63021
|
" error",
|
|
62981
63022
|
result.errors.length !== 1 ? "s" : ""
|
|
@@ -62987,7 +63028,7 @@ function SchemaTab({ request, onChange }) {
|
|
|
62987
63028
|
] }) })
|
|
62988
63029
|
] });
|
|
62989
63030
|
}
|
|
62990
|
-
const { electron: electron$
|
|
63031
|
+
const { electron: electron$m } = window;
|
|
62991
63032
|
const EMPTY = { statusCode: 200, headers: [], bodySchema: "" };
|
|
62992
63033
|
function ContractTab({ request, onChange }) {
|
|
62993
63034
|
const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
|
|
@@ -63001,7 +63042,7 @@ function ContractTab({ request, onChange }) {
|
|
|
63001
63042
|
if (!lastResponse?.body) return;
|
|
63002
63043
|
setInferring(true);
|
|
63003
63044
|
try {
|
|
63004
|
-
const schema = await electron$
|
|
63045
|
+
const schema = await electron$m.inferContractSchema(lastResponse.body);
|
|
63005
63046
|
if (schema) update({ bodySchema: schema });
|
|
63006
63047
|
} finally {
|
|
63007
63048
|
setInferring(false);
|
|
@@ -63022,7 +63063,7 @@ function ContractTab({ request, onChange }) {
|
|
|
63022
63063
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-4 h-full min-h-0", children: [
|
|
63023
63064
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-2 px-3 py-2 rounded-lg text-xs border ${hasContract ? "bg-blue-950/40 border-blue-700 text-blue-300" : "bg-surface-800 border-surface-700 text-surface-500"}`, children: [
|
|
63024
63065
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `w-2 h-2 rounded-full ${hasContract ? "bg-blue-400" : "bg-surface-600"}` }),
|
|
63025
|
-
hasContract ? "Contract defined
|
|
63066
|
+
hasContract ? "Contract defined - will be verified in Contract panel" : "No contract defined yet"
|
|
63026
63067
|
] }),
|
|
63027
63068
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
63028
63069
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1.5", children: "Expected Status Code" }),
|
|
@@ -63127,7 +63168,7 @@ function ContractTab({ request, onChange }) {
|
|
|
63127
63168
|
] })
|
|
63128
63169
|
] });
|
|
63129
63170
|
}
|
|
63130
|
-
const { electron: electron$
|
|
63171
|
+
const { electron: electron$l } = window;
|
|
63131
63172
|
function formatTime$1(ts) {
|
|
63132
63173
|
const d = new Date(ts);
|
|
63133
63174
|
const hh = String(d.getHours()).padStart(2, "0");
|
|
@@ -63147,14 +63188,14 @@ function WebSocketPanel({ request }) {
|
|
|
63147
63188
|
const [sendText, setSendText] = reactExports.useState("");
|
|
63148
63189
|
const logEndRef = reactExports.useRef(null);
|
|
63149
63190
|
reactExports.useEffect(() => {
|
|
63150
|
-
electron$
|
|
63191
|
+
electron$l.onWsMessage(({ requestId, message }) => {
|
|
63151
63192
|
addWsMessage(requestId, message);
|
|
63152
63193
|
});
|
|
63153
|
-
electron$
|
|
63194
|
+
electron$l.onWsStatus(({ requestId, status, error: error2 }) => {
|
|
63154
63195
|
setWsStatus(requestId, status, error2);
|
|
63155
63196
|
});
|
|
63156
63197
|
return () => {
|
|
63157
|
-
electron$
|
|
63198
|
+
electron$l.offWsEvents();
|
|
63158
63199
|
};
|
|
63159
63200
|
}, [addWsMessage, setWsStatus]);
|
|
63160
63201
|
reactExports.useEffect(() => {
|
|
@@ -63167,19 +63208,19 @@ function WebSocketPanel({ request }) {
|
|
|
63167
63208
|
if (h.enabled && h.key) headers[h.key] = h.value;
|
|
63168
63209
|
}
|
|
63169
63210
|
try {
|
|
63170
|
-
await electron$
|
|
63211
|
+
await electron$l.wsConnect(request.id, request.url, headers);
|
|
63171
63212
|
} catch (err) {
|
|
63172
63213
|
setWsStatus(request.id, "error", err instanceof Error ? err.message : String(err));
|
|
63173
63214
|
}
|
|
63174
63215
|
}
|
|
63175
63216
|
async function disconnect() {
|
|
63176
|
-
await electron$
|
|
63217
|
+
await electron$l.wsDisconnect(request.id);
|
|
63177
63218
|
}
|
|
63178
63219
|
async function sendMessage() {
|
|
63179
63220
|
const text = sendText.trim();
|
|
63180
63221
|
if (!text || !isConnected) return;
|
|
63181
63222
|
try {
|
|
63182
|
-
await electron$
|
|
63223
|
+
await electron$l.wsSend(request.id, text);
|
|
63183
63224
|
const msg = {
|
|
63184
63225
|
id: crypto.randomUUID(),
|
|
63185
63226
|
direction: "sent",
|
|
@@ -63282,8 +63323,395 @@ function WebSocketPanel({ request }) {
|
|
|
63282
63323
|
] })
|
|
63283
63324
|
] });
|
|
63284
63325
|
}
|
|
63326
|
+
function useToast(durationMs = 3e3) {
|
|
63327
|
+
const [toast, setToast] = reactExports.useState(null);
|
|
63328
|
+
const timer = reactExports.useRef(null);
|
|
63329
|
+
function show(msg, ok) {
|
|
63330
|
+
if (timer.current) clearTimeout(timer.current);
|
|
63331
|
+
setToast({ msg, ok });
|
|
63332
|
+
timer.current = setTimeout(() => setToast(null), durationMs);
|
|
63333
|
+
}
|
|
63334
|
+
return { toast, show };
|
|
63335
|
+
}
|
|
63336
|
+
function Toast({ toast }) {
|
|
63337
|
+
if (!toast) return null;
|
|
63338
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `mx-3 mb-2 px-2 py-1.5 rounded text-[11px] flex-shrink-0 ${toast.ok ? "bg-emerald-900/50 text-emerald-300 border border-emerald-800/50" : "bg-red-900/50 text-red-300 border border-red-800/50"}`, children: toast.msg });
|
|
63339
|
+
}
|
|
63340
|
+
function statusColor$3(code2) {
|
|
63341
|
+
const d = String(code2)[0];
|
|
63342
|
+
return d === "2" ? "text-emerald-400" : d === "3" ? "text-amber-400" : "text-red-400";
|
|
63343
|
+
}
|
|
63344
|
+
const ORACLE_META = {
|
|
63345
|
+
"never-5xx": { label: "Server crash", badge: "bg-red-900/50 text-red-400", border: "border-red-600" },
|
|
63346
|
+
"accepted-invalid": { label: "Accepted invalid", badge: "bg-amber-900/50 text-amber-400", border: "border-amber-500" },
|
|
63347
|
+
"undocumented-status": { label: "Undocumented status", badge: "bg-blue-900/50 text-blue-400", border: "border-blue-500" },
|
|
63348
|
+
"response-schema": { label: "Response schema", badge: "bg-orange-900/50 text-orange-400", border: "border-orange-500" }
|
|
63349
|
+
};
|
|
63350
|
+
function FindingRow({ finding, onCopy }) {
|
|
63351
|
+
const [open, setOpen] = reactExports.useState(false);
|
|
63352
|
+
const meta2 = ORACLE_META[finding.oracle];
|
|
63353
|
+
const req = finding.request;
|
|
63354
|
+
const sentText = `${req.method} ${req.url}
|
|
63355
|
+
` + Object.entries(req.headers).map(([k, v]) => `${k}: ${v}`).join("\n") + (req.body ? `
|
|
63356
|
+
|
|
63357
|
+
${req.body}` : "");
|
|
63358
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex flex-col gap-1.5 px-4 py-2.5 border-l-2 ${meta2.border} bg-red-950/20 rounded-r`, children: [
|
|
63359
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
63360
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] font-bold px-1.5 py-0.5 rounded uppercase tracking-wide ${meta2.badge}`, children: meta2.label }),
|
|
63361
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs font-mono font-bold ${statusColor$3(finding.status)}`, children: finding.status === 0 ? "ERR" : finding.status }),
|
|
63362
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-500 bg-surface-800 px-1.5 py-0.5 rounded", children: finding.mutation.target }),
|
|
63363
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-400 bg-surface-800 px-1.5 py-0.5 rounded", children: finding.mutation.kind })
|
|
63364
|
+
] }),
|
|
63365
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-200", children: finding.message }),
|
|
63366
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-surface-400", children: finding.mutation.description }),
|
|
63367
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63368
|
+
"button",
|
|
63369
|
+
{
|
|
63370
|
+
onClick: () => setOpen((v) => !v),
|
|
63371
|
+
className: "text-[10px] text-surface-500 hover:text-surface-300 transition-colors self-start",
|
|
63372
|
+
children: open ? "▲ Hide sent request" : "▼ Show sent request"
|
|
63373
|
+
}
|
|
63374
|
+
),
|
|
63375
|
+
open && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2 mt-0.5", children: [
|
|
63376
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-surface-800 bg-surface-900 overflow-hidden", children: [
|
|
63377
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-2.5 py-1.5 border-b border-surface-800", children: [
|
|
63378
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[11px] font-bold font-mono ${getMethodColor(req.method)}`, children: req.method }),
|
|
63379
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] font-mono text-surface-400 truncate flex-1", children: req.url }),
|
|
63380
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63381
|
+
"button",
|
|
63382
|
+
{
|
|
63383
|
+
onClick: () => onCopy(sentText),
|
|
63384
|
+
className: "text-[10px] text-surface-500 hover:text-surface-200 transition-colors shrink-0",
|
|
63385
|
+
title: "Copy the sent request",
|
|
63386
|
+
children: "Copy"
|
|
63387
|
+
}
|
|
63388
|
+
)
|
|
63389
|
+
] }),
|
|
63390
|
+
req.body && /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-[11px] font-mono text-surface-300 px-2.5 py-2 overflow-x-auto whitespace-pre-wrap break-words", children: req.body })
|
|
63391
|
+
] }),
|
|
63392
|
+
finding.responseSample && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-surface-800 bg-surface-900 overflow-hidden", children: [
|
|
63393
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-2.5 py-1.5 border-b border-surface-800 text-[10px] uppercase tracking-wider text-surface-500 font-medium", children: "Response sample" }),
|
|
63394
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-[11px] font-mono text-surface-300 px-2.5 py-2 overflow-x-auto whitespace-pre-wrap break-words", children: finding.responseSample })
|
|
63395
|
+
] })
|
|
63396
|
+
] })
|
|
63397
|
+
] });
|
|
63398
|
+
}
|
|
63399
|
+
function OperationCard({ result, onCopy }) {
|
|
63400
|
+
const [open, setOpen] = reactExports.useState(true);
|
|
63401
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border border-red-800/60 overflow-hidden", children: [
|
|
63402
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
63403
|
+
"button",
|
|
63404
|
+
{
|
|
63405
|
+
onClick: () => setOpen((v) => !v),
|
|
63406
|
+
className: "w-full flex items-center gap-3 px-4 py-3 text-left bg-red-950/30 hover:bg-red-950/50 transition-colors",
|
|
63407
|
+
children: [
|
|
63408
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-bold font-mono w-14 ${getMethodColor(result.method)}`, children: result.method }),
|
|
63409
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 text-sm text-white truncate", children: result.requestName }),
|
|
63410
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden lg:block text-[11px] text-surface-500 font-mono truncate max-w-[260px]", children: result.url }),
|
|
63411
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 text-[10px] bg-red-900/50 text-red-300 rounded px-1.5 py-0.5 font-medium", children: [
|
|
63412
|
+
result.findings.length,
|
|
63413
|
+
" ",
|
|
63414
|
+
result.findings.length === 1 ? "finding" : "findings"
|
|
63415
|
+
] }),
|
|
63416
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 text-[11px] text-surface-500", children: [
|
|
63417
|
+
result.cases,
|
|
63418
|
+
" cases"
|
|
63419
|
+
] }),
|
|
63420
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-surface-600 text-xs ml-1", children: open ? "▲" : "▼" })
|
|
63421
|
+
]
|
|
63422
|
+
}
|
|
63423
|
+
),
|
|
63424
|
+
open && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3 bg-surface-900 border-t border-surface-800 flex flex-col gap-2", children: result.findings.map((f, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(FindingRow, { finding: f, onCopy }, i)) })
|
|
63425
|
+
] });
|
|
63426
|
+
}
|
|
63427
|
+
function statusTone(status, finding) {
|
|
63428
|
+
if (finding) return "text-red-400";
|
|
63429
|
+
if (status === 0) return "text-surface-500";
|
|
63430
|
+
if (status >= 200 && status < 300) return "text-emerald-400";
|
|
63431
|
+
if (status >= 400) return "text-amber-400";
|
|
63432
|
+
return "text-surface-300";
|
|
63433
|
+
}
|
|
63434
|
+
function TraceRow({ trace, onCopy }) {
|
|
63435
|
+
const [open, setOpen] = reactExports.useState(false);
|
|
63436
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-surface-800 first:border-t-0", children: [
|
|
63437
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
63438
|
+
"button",
|
|
63439
|
+
{
|
|
63440
|
+
onClick: () => setOpen((v) => !v),
|
|
63441
|
+
className: "w-full flex items-center gap-3 px-3 py-1.5 text-left hover:bg-surface-800/60 transition-colors font-mono text-[11px]",
|
|
63442
|
+
children: [
|
|
63443
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-surface-600 w-3", children: open ? "▾" : "▸" }),
|
|
63444
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 font-bold w-9 ${statusTone(trace.status, trace.finding)}`, children: trace.status || "ERR" }),
|
|
63445
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-surface-300", children: trace.mutation.target }),
|
|
63446
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500 truncate", children: trace.mutation.kind }),
|
|
63447
|
+
trace.finding && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-auto shrink-0 text-[10px] bg-red-900/50 text-red-300 rounded px-1.5 py-0.5", children: "finding" })
|
|
63448
|
+
]
|
|
63449
|
+
}
|
|
63450
|
+
),
|
|
63451
|
+
open && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 pb-3 pt-1 flex flex-col gap-2 bg-surface-950/40", children: [
|
|
63452
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-surface-400", children: trace.mutation.description }),
|
|
63453
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
63454
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
63455
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Request" }),
|
|
63456
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63457
|
+
"button",
|
|
63458
|
+
{
|
|
63459
|
+
onClick: () => onCopy(`${trace.request.method} ${trace.request.url}
|
|
63460
|
+
|
|
63461
|
+
${trace.request.body ?? ""}`),
|
|
63462
|
+
className: "text-[10px] text-surface-500 hover:text-surface-200 transition-colors",
|
|
63463
|
+
children: "copy"
|
|
63464
|
+
}
|
|
63465
|
+
)
|
|
63466
|
+
] }),
|
|
63467
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] font-mono text-surface-500 break-all", children: [
|
|
63468
|
+
trace.request.method,
|
|
63469
|
+
" ",
|
|
63470
|
+
trace.request.url
|
|
63471
|
+
] }),
|
|
63472
|
+
trace.request.body && /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-[11px] font-mono text-surface-300 bg-surface-900 border border-surface-800 rounded px-2.5 py-2 overflow-x-auto whitespace-pre-wrap break-words", children: trace.request.body })
|
|
63473
|
+
] }),
|
|
63474
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
63475
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: [
|
|
63476
|
+
"Response (",
|
|
63477
|
+
trace.status || "no response",
|
|
63478
|
+
")"
|
|
63479
|
+
] }),
|
|
63480
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-[11px] font-mono text-surface-300 bg-surface-900 border border-surface-800 rounded px-2.5 py-2 overflow-x-auto whitespace-pre-wrap break-words", children: trace.responseSample || "(empty)" })
|
|
63481
|
+
] })
|
|
63482
|
+
] })
|
|
63483
|
+
] });
|
|
63484
|
+
}
|
|
63485
|
+
function TraceCard({ result, onCopy }) {
|
|
63486
|
+
const [open, setOpen] = reactExports.useState(false);
|
|
63487
|
+
if (!result.trace?.length) return null;
|
|
63488
|
+
const findingCount = result.trace.filter((t2) => t2.finding).length;
|
|
63489
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border border-surface-700 overflow-hidden", children: [
|
|
63490
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
63491
|
+
"button",
|
|
63492
|
+
{
|
|
63493
|
+
onClick: () => setOpen((v) => !v),
|
|
63494
|
+
className: "w-full flex items-center gap-3 px-4 py-2.5 text-left bg-surface-800 hover:bg-surface-700/60 transition-colors",
|
|
63495
|
+
children: [
|
|
63496
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-bold font-mono w-14 ${getMethodColor(result.method)}`, children: result.method }),
|
|
63497
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 text-xs text-surface-200 truncate", children: result.requestName }),
|
|
63498
|
+
findingCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[10px] bg-red-900/50 text-red-300 rounded px-1.5 py-0.5", children: findingCount }),
|
|
63499
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 text-[11px] text-surface-500", children: [
|
|
63500
|
+
result.trace.length,
|
|
63501
|
+
" cases sent"
|
|
63502
|
+
] }),
|
|
63503
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-surface-600 text-xs", children: open ? "▲" : "▼" })
|
|
63504
|
+
]
|
|
63505
|
+
}
|
|
63506
|
+
),
|
|
63507
|
+
open && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-surface-900 border-t border-surface-800", children: result.trace.map((t2, i) => /* @__PURE__ */ jsxRuntimeExports.jsx(TraceRow, { trace: t2, onCopy }, i)) })
|
|
63508
|
+
] });
|
|
63509
|
+
}
|
|
63510
|
+
function FuzzResultsPanel({ report, onClear }) {
|
|
63511
|
+
const { toast, show: showToast } = useToast();
|
|
63512
|
+
async function copy(text) {
|
|
63513
|
+
try {
|
|
63514
|
+
await navigator.clipboard.writeText(text);
|
|
63515
|
+
showToast("Sent request copied to clipboard.", true);
|
|
63516
|
+
} catch (e) {
|
|
63517
|
+
showToast(e instanceof Error ? e.message : "Copy failed.", false);
|
|
63518
|
+
}
|
|
63519
|
+
}
|
|
63520
|
+
const withFindings = report.results.filter((r) => r.findings.length > 0);
|
|
63521
|
+
const clean = report.results.filter((r) => r.findings.length === 0);
|
|
63522
|
+
const clean5xx = report.totalFindings === 0;
|
|
63523
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full overflow-hidden", children: [
|
|
63524
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-4 px-6 py-3 border-b flex-shrink-0 ${clean5xx ? "bg-emerald-950/30 border-emerald-800/50" : "bg-red-950/30 border-red-800/50"}`, children: [
|
|
63525
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-base font-bold ${clean5xx ? "text-emerald-400" : "text-red-400"}`, children: clean5xx ? "✓ No findings" : `✗ ${report.totalFindings} finding${report.totalFindings !== 1 ? "s" : ""}` }),
|
|
63526
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-sm text-surface-400", children: [
|
|
63527
|
+
report.totalCases,
|
|
63528
|
+
" case",
|
|
63529
|
+
report.totalCases !== 1 ? "s" : "",
|
|
63530
|
+
" across ",
|
|
63531
|
+
report.results.length,
|
|
63532
|
+
" operation",
|
|
63533
|
+
report.results.length !== 1 ? "s" : ""
|
|
63534
|
+
] }),
|
|
63535
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] bg-surface-800 text-surface-400 px-2 py-0.5 rounded font-mono", children: report.inputSource === "spec" ? "spec" : "request body" }),
|
|
63536
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] bg-surface-800 text-surface-400 px-2 py-0.5 rounded font-mono", children: [
|
|
63537
|
+
"seed ",
|
|
63538
|
+
report.seed
|
|
63539
|
+
] }),
|
|
63540
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-surface-500 ml-auto", children: [
|
|
63541
|
+
report.durationMs,
|
|
63542
|
+
"ms"
|
|
63543
|
+
] }),
|
|
63544
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63545
|
+
"button",
|
|
63546
|
+
{
|
|
63547
|
+
onClick: onClear,
|
|
63548
|
+
className: "text-[11px] text-surface-600 hover:text-surface-300 transition-colors",
|
|
63549
|
+
title: "Clear results",
|
|
63550
|
+
children: "Clear"
|
|
63551
|
+
}
|
|
63552
|
+
)
|
|
63553
|
+
] }),
|
|
63554
|
+
(report.skippedWrites > 0 || report.skippedNoBody > 0) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1 px-6 py-2 border-b border-surface-800 bg-surface-900/60 flex-shrink-0", children: [
|
|
63555
|
+
report.skippedWrites > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-amber-400", children: [
|
|
63556
|
+
report.skippedWrites,
|
|
63557
|
+
" write-method request",
|
|
63558
|
+
report.skippedWrites !== 1 ? "s" : "",
|
|
63559
|
+
" skipped (enable Include write methods)."
|
|
63560
|
+
] }),
|
|
63561
|
+
report.skippedNoBody > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-surface-500", children: [
|
|
63562
|
+
report.skippedNoBody,
|
|
63563
|
+
" request",
|
|
63564
|
+
report.skippedNoBody !== 1 ? "s" : "",
|
|
63565
|
+
" had no body to fuzz."
|
|
63566
|
+
] })
|
|
63567
|
+
] }),
|
|
63568
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }),
|
|
63569
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto min-h-0 p-6", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 max-w-4xl mx-auto", children: [
|
|
63570
|
+
withFindings.map((r) => /* @__PURE__ */ jsxRuntimeExports.jsx(OperationCard, { result: r, onCopy: copy }, r.requestId)),
|
|
63571
|
+
clean.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border border-surface-700 bg-surface-800 px-4 py-3", children: [
|
|
63572
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-emerald-400 font-medium mb-1.5", children: [
|
|
63573
|
+
clean.length,
|
|
63574
|
+
" operation",
|
|
63575
|
+
clean.length !== 1 ? "s" : "",
|
|
63576
|
+
" clean"
|
|
63577
|
+
] }),
|
|
63578
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col gap-0.5", children: clean.map((r) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-[11px]", children: [
|
|
63579
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-bold font-mono w-12 shrink-0 ${getMethodColor(r.method)}`, children: r.method }),
|
|
63580
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-300 truncate", children: r.requestName }),
|
|
63581
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-600 ml-auto shrink-0", children: [
|
|
63582
|
+
r.cases,
|
|
63583
|
+
" cases"
|
|
63584
|
+
] })
|
|
63585
|
+
] }, r.requestId)) })
|
|
63586
|
+
] }),
|
|
63587
|
+
report.results.some((r) => r.trace?.length) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
63588
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium mt-2", children: "All cases sent" }),
|
|
63589
|
+
report.results.filter((r) => r.trace?.length).map((r) => /* @__PURE__ */ jsxRuntimeExports.jsx(TraceCard, { result: r, onCopy: copy }, r.requestId))
|
|
63590
|
+
] }),
|
|
63591
|
+
report.results.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 text-center mt-4", children: "No operations were fuzzed. Check that requests have a body or a matching spec operation." })
|
|
63592
|
+
] }) })
|
|
63593
|
+
] });
|
|
63594
|
+
}
|
|
63595
|
+
const { electron: electron$k } = window;
|
|
63596
|
+
const WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
63597
|
+
function FuzzModal({ request, onClose }) {
|
|
63598
|
+
const environments = useStore((s) => s.environments);
|
|
63599
|
+
const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
|
|
63600
|
+
const activeCollectionId = useStore((s) => s.activeCollectionId);
|
|
63601
|
+
const collections = useStore((s) => s.collections);
|
|
63602
|
+
const snapshots = useStore((s) => s.contractSnapshots);
|
|
63603
|
+
const activeSnapshotRelPath = useStore((s) => s.activeContractSnapshotRelPath);
|
|
63604
|
+
const [cases, setCases] = reactExports.useState(40);
|
|
63605
|
+
const [seed, setSeed] = reactExports.useState(1);
|
|
63606
|
+
const [trace, setTrace] = reactExports.useState(true);
|
|
63607
|
+
const [running, setRunning] = reactExports.useState(false);
|
|
63608
|
+
const [error2, setError] = reactExports.useState(null);
|
|
63609
|
+
const [report, setReport] = reactExports.useState(null);
|
|
63610
|
+
const isWrite = WRITE_METHODS.has(request.method);
|
|
63611
|
+
const snapshotList = Object.entries(snapshots).map(([relPath, s]) => ({ relPath, snapshot: s }));
|
|
63612
|
+
const [snapshotRelPath, setSnapshotRelPath] = reactExports.useState(activeSnapshotRelPath ?? "");
|
|
63613
|
+
async function run() {
|
|
63614
|
+
setRunning(true);
|
|
63615
|
+
setError(null);
|
|
63616
|
+
setReport(null);
|
|
63617
|
+
try {
|
|
63618
|
+
const env = resolveEnvironmentById(environments, activeEnvironmentId);
|
|
63619
|
+
const envVars = env ? Object.fromEntries(env.variables.filter((v) => v.enabled).map((v) => [v.key, v.value])) : {};
|
|
63620
|
+
const collectionVars = activeCollectionId ? collections[activeCollectionId]?.data.collectionVariables ?? {} : {};
|
|
63621
|
+
const result = await electron$k.fuzzContracts({
|
|
63622
|
+
requests: [request],
|
|
63623
|
+
envVars,
|
|
63624
|
+
collectionVars,
|
|
63625
|
+
specSnapshotRelPath: snapshotRelPath || void 0,
|
|
63626
|
+
// No providerBaseUrl: the request is fuzzed against its own URL.
|
|
63627
|
+
casesPerOperation: cases,
|
|
63628
|
+
seed,
|
|
63629
|
+
trace,
|
|
63630
|
+
includeWrites: true
|
|
63631
|
+
// a per-request run is an explicit choice to fuzz this request
|
|
63632
|
+
});
|
|
63633
|
+
setReport(result);
|
|
63634
|
+
} catch (e) {
|
|
63635
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
63636
|
+
} finally {
|
|
63637
|
+
setRunning(false);
|
|
63638
|
+
}
|
|
63639
|
+
}
|
|
63640
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63641
|
+
Modal,
|
|
63642
|
+
{
|
|
63643
|
+
onClose,
|
|
63644
|
+
title: `Fuzz: ${request.name}`,
|
|
63645
|
+
subtitle: `${request.method} ${request.url}`,
|
|
63646
|
+
panelClassName: "bg-surface-900 border border-surface-800 rounded-lg shadow-2xl w-[720px] flex flex-col max-h-[85vh]",
|
|
63647
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col min-h-0 flex-1", children: [
|
|
63648
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-end gap-3 px-4 py-3 border-b border-surface-800 flex-shrink-0 flex-wrap", children: [
|
|
63649
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1", children: [
|
|
63650
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Cases" }),
|
|
63651
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63652
|
+
"input",
|
|
63653
|
+
{
|
|
63654
|
+
type: "number",
|
|
63655
|
+
min: 1,
|
|
63656
|
+
value: cases,
|
|
63657
|
+
onChange: (e) => setCases(Math.max(1, Number(e.target.value))),
|
|
63658
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs w-20 focus:outline-none focus:border-blue-500"
|
|
63659
|
+
}
|
|
63660
|
+
)
|
|
63661
|
+
] }),
|
|
63662
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1", children: [
|
|
63663
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Seed" }),
|
|
63664
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63665
|
+
"input",
|
|
63666
|
+
{
|
|
63667
|
+
type: "number",
|
|
63668
|
+
value: seed,
|
|
63669
|
+
onChange: (e) => setSeed(Number(e.target.value)),
|
|
63670
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs w-20 focus:outline-none focus:border-blue-500"
|
|
63671
|
+
}
|
|
63672
|
+
)
|
|
63673
|
+
] }),
|
|
63674
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1 flex-1 min-w-[180px]", children: [
|
|
63675
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Spec (optional)" }),
|
|
63676
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
63677
|
+
"select",
|
|
63678
|
+
{
|
|
63679
|
+
value: snapshotRelPath,
|
|
63680
|
+
onChange: (e) => setSnapshotRelPath(e.target.value),
|
|
63681
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500",
|
|
63682
|
+
children: [
|
|
63683
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "Request body (no spec)" }),
|
|
63684
|
+
snapshotList.map(({ relPath, snapshot }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: relPath, children: snapshot.name }, relPath))
|
|
63685
|
+
]
|
|
63686
|
+
}
|
|
63687
|
+
)
|
|
63688
|
+
] }),
|
|
63689
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-1.5 text-[11px] text-surface-400 select-none", children: [
|
|
63690
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", checked: trace, onChange: (e) => setTrace(e.target.checked) }),
|
|
63691
|
+
"Record all cases"
|
|
63692
|
+
] }),
|
|
63693
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63694
|
+
"button",
|
|
63695
|
+
{
|
|
63696
|
+
onClick: run,
|
|
63697
|
+
disabled: running || !request.url,
|
|
63698
|
+
className: "px-4 py-1.5 bg-blue-600 hover:bg-blue-500 disabled:bg-surface-800 disabled:text-surface-400 rounded text-sm font-medium transition-colors",
|
|
63699
|
+
children: running ? "Fuzzing..." : "Run fuzz"
|
|
63700
|
+
}
|
|
63701
|
+
)
|
|
63702
|
+
] }),
|
|
63703
|
+
isWrite && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-amber-400 px-4 py-2 border-b border-surface-800 flex-shrink-0", children: [
|
|
63704
|
+
request.method,
|
|
63705
|
+
" sends malformed writes. Point this request at a staging environment or a mock, not production."
|
|
63706
|
+
] }),
|
|
63707
|
+
error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-red-400 px-4 py-2 border-b border-surface-800 flex-shrink-0", children: error2 }),
|
|
63708
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: report ? /* @__PURE__ */ jsxRuntimeExports.jsx(FuzzResultsPanel, { report, onClear: () => setReport(null) }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-center p-8", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: running ? "Sending malformed inputs..." : "Generates malformed variants of this request body and flags responses that crash (5xx) or accept invalid input. Pick a pinned spec for richer inputs, or fuzz the request body as-is." }) }) })
|
|
63709
|
+
] })
|
|
63710
|
+
}
|
|
63711
|
+
);
|
|
63712
|
+
}
|
|
63285
63713
|
const { electron: electron$j } = window;
|
|
63286
|
-
const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
63714
|
+
const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "QUERY"];
|
|
63287
63715
|
const METHOD_COLORS = {
|
|
63288
63716
|
GET: "text-emerald-400",
|
|
63289
63717
|
POST: "text-blue-400",
|
|
@@ -63291,7 +63719,8 @@ const METHOD_COLORS = {
|
|
|
63291
63719
|
PATCH: "text-orange-400",
|
|
63292
63720
|
DELETE: "text-red-400",
|
|
63293
63721
|
HEAD: "text-purple-400",
|
|
63294
|
-
OPTIONS: "text-gray-400"
|
|
63722
|
+
OPTIONS: "text-gray-400",
|
|
63723
|
+
QUERY: "text-fuchsia-400"
|
|
63295
63724
|
};
|
|
63296
63725
|
function deriveHookStatus(r) {
|
|
63297
63726
|
if (r.scriptResult.postScriptError) return "error";
|
|
@@ -63323,6 +63752,7 @@ function RequestBuilder({ request }) {
|
|
|
63323
63752
|
if (activeTabId) setTabRequestTab(activeTabId, t2);
|
|
63324
63753
|
}
|
|
63325
63754
|
const [editingName, setEditingName] = reactExports.useState(false);
|
|
63755
|
+
const [showFuzz, setShowFuzz] = reactExports.useState(false);
|
|
63326
63756
|
const [runHooks, setRunHooks] = reactExports.useState(() => localStorage.getItem("runHooks") !== "false");
|
|
63327
63757
|
function toggleRunHooks() {
|
|
63328
63758
|
setRunHooks((prev) => {
|
|
@@ -63341,7 +63771,7 @@ function RequestBuilder({ request }) {
|
|
|
63341
63771
|
setTabHookResults(activeTabId, null);
|
|
63342
63772
|
const collectedHookResults = [];
|
|
63343
63773
|
try {
|
|
63344
|
-
const activeEnv =
|
|
63774
|
+
const activeEnv = resolveEnvironmentById(environments, activeEnvironmentId);
|
|
63345
63775
|
const sessionVars = useStore.getState().sessionVars;
|
|
63346
63776
|
const tls = collectionTls ? { ...workspaceSettings?.tls, ...collectionTls } : workspaceSettings?.tls;
|
|
63347
63777
|
const basePayload = {
|
|
@@ -63367,7 +63797,10 @@ function RequestBuilder({ request }) {
|
|
|
63367
63797
|
for (const hook of hooks.before) {
|
|
63368
63798
|
const start = Date.now();
|
|
63369
63799
|
try {
|
|
63370
|
-
const hookEnv =
|
|
63800
|
+
const hookEnv = resolveEnvironmentById(
|
|
63801
|
+
useStore.getState().environments,
|
|
63802
|
+
activeEnvironmentId
|
|
63803
|
+
);
|
|
63371
63804
|
const hookSessionVars = useStore.getState().sessionVars;
|
|
63372
63805
|
const r = await electron$j.sendRequest({
|
|
63373
63806
|
...basePayload,
|
|
@@ -63408,7 +63841,10 @@ function RequestBuilder({ request }) {
|
|
|
63408
63841
|
});
|
|
63409
63842
|
}
|
|
63410
63843
|
}
|
|
63411
|
-
const freshEnv =
|
|
63844
|
+
const freshEnv = resolveEnvironmentById(
|
|
63845
|
+
useStore.getState().environments,
|
|
63846
|
+
activeEnvironmentId
|
|
63847
|
+
);
|
|
63412
63848
|
const freshSessionVars = useStore.getState().sessionVars;
|
|
63413
63849
|
const result = await electron$j.sendRequest({
|
|
63414
63850
|
...basePayload,
|
|
@@ -63433,7 +63869,10 @@ function RequestBuilder({ request }) {
|
|
|
63433
63869
|
for (const hook of hooks.after) {
|
|
63434
63870
|
const start = Date.now();
|
|
63435
63871
|
try {
|
|
63436
|
-
const hookEnv =
|
|
63872
|
+
const hookEnv = resolveEnvironmentById(
|
|
63873
|
+
useStore.getState().environments,
|
|
63874
|
+
activeEnvironmentId
|
|
63875
|
+
);
|
|
63437
63876
|
const hookSessionVars = useStore.getState().sessionVars;
|
|
63438
63877
|
const r = await electron$j.sendRequest({
|
|
63439
63878
|
...basePayload,
|
|
@@ -63548,7 +63987,7 @@ function RequestBuilder({ request }) {
|
|
|
63548
63987
|
if (activeTabId) setTabRequestTab(activeTabId, "body");
|
|
63549
63988
|
},
|
|
63550
63989
|
className: `px-2 py-1.5 transition-colors ${isSoap ? "bg-amber-700 text-amber-100" : "text-surface-500 hover:text-white"}`,
|
|
63551
|
-
title: "SOAP
|
|
63990
|
+
title: "SOAP - endpoint and method are derived from the WSDL",
|
|
63552
63991
|
children: "SOAP"
|
|
63553
63992
|
}
|
|
63554
63993
|
)
|
|
@@ -63585,7 +64024,7 @@ function RequestBuilder({ request }) {
|
|
|
63585
64024
|
"button",
|
|
63586
64025
|
{
|
|
63587
64026
|
onClick: toggleRunHooks,
|
|
63588
|
-
title: runHooks ? "Hooks enabled
|
|
64027
|
+
title: runHooks ? "Hooks enabled - click to disable" : "Hooks disabled - click to enable",
|
|
63589
64028
|
className: `px-2 py-1.5 rounded text-xs font-medium transition-colors border ${runHooks ? "border-violet-500 text-violet-400 hover:bg-violet-500/10" : "border-surface-700 text-surface-500 hover:text-surface-300"}`,
|
|
63590
64029
|
children: "hooks"
|
|
63591
64030
|
}
|
|
@@ -63601,19 +64040,32 @@ function RequestBuilder({ request }) {
|
|
|
63601
64040
|
)
|
|
63602
64041
|
] })
|
|
63603
64042
|
] }),
|
|
64043
|
+
showFuzz && /* @__PURE__ */ jsxRuntimeExports.jsx(FuzzModal, { request, onClose: () => setShowFuzz(false) }),
|
|
63604
64044
|
isWs ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(WebSocketPanel, { request }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
63605
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
63606
|
-
|
|
63607
|
-
|
|
63608
|
-
|
|
63609
|
-
|
|
63610
|
-
|
|
63611
|
-
|
|
63612
|
-
|
|
63613
|
-
|
|
63614
|
-
|
|
63615
|
-
|
|
63616
|
-
|
|
64045
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex border-b border-surface-800 px-4 gap-0 flex-shrink-0", children: [
|
|
64046
|
+
tabs.map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
64047
|
+
"button",
|
|
64048
|
+
{
|
|
64049
|
+
onClick: () => setActiveTab(tab.id),
|
|
64050
|
+
className: `px-3 py-1.5 text-xs transition-colors border-b-2 -mb-px ${activeTab === tab.id ? "border-blue-500 text-white" : "border-transparent text-surface-400 hover:text-white"}`,
|
|
64051
|
+
children: [
|
|
64052
|
+
tab.label,
|
|
64053
|
+
tab.count > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-1 text-[10px] bg-surface-600 text-white rounded px-1 font-medium", children: tab.count })
|
|
64054
|
+
]
|
|
64055
|
+
},
|
|
64056
|
+
tab.id
|
|
64057
|
+
)),
|
|
64058
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
64059
|
+
"button",
|
|
64060
|
+
{
|
|
64061
|
+
onClick: () => setShowFuzz(true),
|
|
64062
|
+
disabled: !request.url,
|
|
64063
|
+
title: "Fuzz this request with malformed inputs (opens a dialog; nothing is sent until you confirm)",
|
|
64064
|
+
className: "ml-auto my-1 self-center px-2 py-0.5 rounded text-[11px] border border-surface-700 text-surface-500 hover:text-fuchsia-400 hover:border-fuchsia-500 transition-colors disabled:opacity-40",
|
|
64065
|
+
children: "fuzz"
|
|
64066
|
+
}
|
|
64067
|
+
)
|
|
64068
|
+
] }),
|
|
63617
64069
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 flex-1 overflow-y-auto min-h-0", children: [
|
|
63618
64070
|
activeTab === "params" && /* @__PURE__ */ jsxRuntimeExports.jsx(ParamsTab, { request, onChange: update }),
|
|
63619
64071
|
activeTab === "headers" && /* @__PURE__ */ jsxRuntimeExports.jsx(HeadersTab, { request, onChange: update }),
|
|
@@ -64188,7 +64640,7 @@ function HookResultsPanel({ results }) {
|
|
|
64188
64640
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2.passed ? "✓" : "✗" }),
|
|
64189
64641
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2.name }),
|
|
64190
64642
|
t2.error && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 text-[10px]", children: [
|
|
64191
|
-
"
|
|
64643
|
+
"- ",
|
|
64192
64644
|
t2.error
|
|
64193
64645
|
] })
|
|
64194
64646
|
] }, ti)),
|
|
@@ -64323,7 +64775,7 @@ function SaveAsMockModal({ onClose }) {
|
|
|
64323
64775
|
onChange: (e) => setMethod(e.target.value),
|
|
64324
64776
|
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 font-bold text-[11px] focus:outline-none focus:border-blue-500",
|
|
64325
64777
|
style: { color: "var(--text-primary)" },
|
|
64326
|
-
children: ["ANY", "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].map((m) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: m, children: m }, m))
|
|
64778
|
+
children: ["ANY", "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "QUERY"].map((m) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: m, children: m }, m))
|
|
64327
64779
|
}
|
|
64328
64780
|
),
|
|
64329
64781
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -64591,20 +65043,6 @@ function ConsolePanel({ scriptResult }) {
|
|
|
64591
65043
|
)) })
|
|
64592
65044
|
] });
|
|
64593
65045
|
}
|
|
64594
|
-
function useToast(durationMs = 3e3) {
|
|
64595
|
-
const [toast, setToast] = reactExports.useState(null);
|
|
64596
|
-
const timer = reactExports.useRef(null);
|
|
64597
|
-
function show(msg, ok) {
|
|
64598
|
-
if (timer.current) clearTimeout(timer.current);
|
|
64599
|
-
setToast({ msg, ok });
|
|
64600
|
-
timer.current = setTimeout(() => setToast(null), durationMs);
|
|
64601
|
-
}
|
|
64602
|
-
return { toast, show };
|
|
64603
|
-
}
|
|
64604
|
-
function Toast({ toast }) {
|
|
64605
|
-
if (!toast) return null;
|
|
64606
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `mx-3 mb-2 px-2 py-1.5 rounded text-[11px] flex-shrink-0 ${toast.ok ? "bg-emerald-900/50 text-emerald-300 border border-emerald-800/50" : "bg-red-900/50 text-red-300 border border-red-800/50"}`, children: toast.msg });
|
|
64607
|
-
}
|
|
64608
65046
|
const { electron: electron$h } = window;
|
|
64609
65047
|
function ResponseViewer() {
|
|
64610
65048
|
const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
|
|
@@ -64725,7 +65163,7 @@ function ResponseViewer() {
|
|
|
64725
65163
|
{
|
|
64726
65164
|
onClick: () => setBodyView("tree"),
|
|
64727
65165
|
className: `px-2 py-0.5 text-[10px] transition-colors ${bodyView === "tree" ? "bg-surface-700 text-white" : "text-surface-600 hover:text-white"}`,
|
|
64728
|
-
title: "Interactive tree view
|
|
65166
|
+
title: "Interactive tree view - click values to add assertions",
|
|
64729
65167
|
children: "Tree"
|
|
64730
65168
|
}
|
|
64731
65169
|
),
|
|
@@ -64839,7 +65277,7 @@ function GeneratorPanel() {
|
|
|
64839
65277
|
setSelectedFile(null);
|
|
64840
65278
|
try {
|
|
64841
65279
|
const col = collections[selectedCollectionId]?.data;
|
|
64842
|
-
const env =
|
|
65280
|
+
const env = resolveEnvironmentById(environments, activeEnvironmentId);
|
|
64843
65281
|
const generated = await electron$g.generateCode({ collection: col, environment: env, target });
|
|
64844
65282
|
setFiles(generated);
|
|
64845
65283
|
setSelectedFile(generated[0]?.path ?? null);
|
|
@@ -65132,7 +65570,7 @@ function WelcomeScreen() {
|
|
|
65132
65570
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-surface-400 text-xs max-w-xs", children: [
|
|
65133
65571
|
"A workspace is a ",
|
|
65134
65572
|
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-500", children: ".spector" }),
|
|
65135
|
-
" file. Commit it and your collections to Git
|
|
65573
|
+
" file. Commit it and your collections to Git - secrets are stored in your OS keychain, never on disk."
|
|
65136
65574
|
] })
|
|
65137
65575
|
] });
|
|
65138
65576
|
}
|
|
@@ -65143,11 +65581,11 @@ const EXAMPLES = [
|
|
|
65143
65581
|
code: (pw) => `export API_SPECTOR_MASTER_KEY="${pw || "<your-password>"}"`
|
|
65144
65582
|
},
|
|
65145
65583
|
{
|
|
65146
|
-
label: "Windows
|
|
65584
|
+
label: "Windows - PowerShell profile",
|
|
65147
65585
|
code: (pw) => `$env:API_SPECTOR_MASTER_KEY = "${pw || "<your-password>"}"`
|
|
65148
65586
|
},
|
|
65149
65587
|
{
|
|
65150
|
-
label: "Windows
|
|
65588
|
+
label: "Windows - Command Prompt (permanent)",
|
|
65151
65589
|
code: (pw) => `setx API_SPECTOR_MASTER_KEY "${pw || "<your-password>"}"`
|
|
65152
65590
|
}
|
|
65153
65591
|
];
|
|
@@ -65293,6 +65731,26 @@ function EnvironmentEditor({ onClose }) {
|
|
|
65293
65731
|
const duplicateEnvironment = useStore((s) => s.duplicateEnvironment);
|
|
65294
65732
|
const envList = Object.values(environments);
|
|
65295
65733
|
const env = selectedId ? environments[selectedId]?.data ?? null : null;
|
|
65734
|
+
const allEnvDatas = envList.map((e) => e.data);
|
|
65735
|
+
let inheritSummary = null;
|
|
65736
|
+
if (env?.extends) {
|
|
65737
|
+
const parentNames = [];
|
|
65738
|
+
const seen = /* @__PURE__ */ new Set([env.name]);
|
|
65739
|
+
let parentName = env.extends;
|
|
65740
|
+
while (parentName && !seen.has(parentName)) {
|
|
65741
|
+
const parent = allEnvDatas.find((e) => e.name === parentName);
|
|
65742
|
+
if (!parent) break;
|
|
65743
|
+
parentNames.push(parent.name);
|
|
65744
|
+
seen.add(parent.name);
|
|
65745
|
+
parentName = parent.extends;
|
|
65746
|
+
}
|
|
65747
|
+
if (parentNames.length > 0) {
|
|
65748
|
+
const ownKeys = new Set(env.variables.map((v) => v.key));
|
|
65749
|
+
const resolved = resolveEnvironmentChain(env, allEnvDatas);
|
|
65750
|
+
const inheritedCount = resolved.variables.filter((v) => !ownKeys.has(v.key)).length;
|
|
65751
|
+
inheritSummary = `Inherits ${inheritedCount} variable${inheritedCount === 1 ? "" : "s"} from ${parentNames.join(", ")}`;
|
|
65752
|
+
}
|
|
65753
|
+
}
|
|
65296
65754
|
function handleDelete(id2) {
|
|
65297
65755
|
deleteEnvironment(id2);
|
|
65298
65756
|
const remaining = Object.keys(environments).filter((k) => k !== id2);
|
|
@@ -65478,7 +65936,23 @@ function EnvironmentEditor({ onClose }) {
|
|
|
65478
65936
|
),
|
|
65479
65937
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-white text-lg leading-none", children: "×" })
|
|
65480
65938
|
] }),
|
|
65481
|
-
nameError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-red-400 mt-1", children: nameError })
|
|
65939
|
+
nameError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-red-400 mt-1", children: nameError }),
|
|
65940
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
|
|
65941
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-400 font-medium shrink-0", children: "Extends" }),
|
|
65942
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
65943
|
+
"select",
|
|
65944
|
+
{
|
|
65945
|
+
value: env.extends ?? "",
|
|
65946
|
+
onChange: (e) => updateEnvironment(env.id, { ...env, extends: e.target.value || void 0 }),
|
|
65947
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500",
|
|
65948
|
+
children: [
|
|
65949
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "(none)" }),
|
|
65950
|
+
envList.filter(({ data: other }) => other.id !== env.id).map(({ data: other }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: other.name, children: other.name }, other.id))
|
|
65951
|
+
]
|
|
65952
|
+
}
|
|
65953
|
+
)
|
|
65954
|
+
] }),
|
|
65955
|
+
inheritSummary && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-400 mt-1", children: inheritSummary })
|
|
65482
65956
|
] }),
|
|
65483
65957
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto", children: [
|
|
65484
65958
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-xs", children: [
|
|
@@ -65599,7 +66073,7 @@ function EnvironmentEditor({ onClose }) {
|
|
|
65599
66073
|
"button",
|
|
65600
66074
|
{
|
|
65601
66075
|
onClick: () => cycleSource(idx),
|
|
65602
|
-
title: mode === "plain" ? "Plain text
|
|
66076
|
+
title: mode === "plain" ? "Plain text - click to switch to encrypted secret" : mode === "encrypted" ? "Encrypted secret - click to switch to env var ref" : "OS env var reference - click to switch to plain text",
|
|
65603
66077
|
className: "flex items-center justify-center gap-1 px-1.5 py-0.5 rounded border transition-colors text-[10px] font-medium w-16 shrink-0 border-surface-700 hover:border-surface-500",
|
|
65604
66078
|
children: [
|
|
65605
66079
|
mode === "plain" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "abc" }),
|
|
@@ -65637,14 +66111,14 @@ function EnvironmentEditor({ onClose }) {
|
|
|
65637
66111
|
] }),
|
|
65638
66112
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-400", children: [
|
|
65639
66113
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-400", children: "🔒 Encrypted" }),
|
|
65640
|
-
"
|
|
66114
|
+
": AES-256-GCM, key from",
|
|
65641
66115
|
" ",
|
|
65642
66116
|
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-200", children: "API_SPECTOR_MASTER_KEY" }),
|
|
65643
66117
|
"."
|
|
65644
66118
|
] }),
|
|
65645
66119
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-400", children: [
|
|
65646
66120
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400", children: "$ Env var" }),
|
|
65647
|
-
"
|
|
66121
|
+
": read from",
|
|
65648
66122
|
" ",
|
|
65649
66123
|
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-200", children: "process.env" }),
|
|
65650
66124
|
" at send-time. Ideal for CI/CD."
|
|
@@ -65691,8 +66165,9 @@ function EnvironmentBar({ inline = false }) {
|
|
|
65691
66165
|
const setActiveEnvironment = useStore((s) => s.setActiveEnvironment);
|
|
65692
66166
|
const [showEditor, setShowEditor] = reactExports.useState(false);
|
|
65693
66167
|
const [pendingEnvId, setPendingEnvId] = reactExports.useState(null);
|
|
66168
|
+
const defaultEnvName = useStore((s) => s.workspace?.settings?.defaultEnvironment);
|
|
65694
66169
|
const envList = Object.values(environments);
|
|
65695
|
-
const activeEnv =
|
|
66170
|
+
const activeEnv = useActiveEnvironment();
|
|
65696
66171
|
const varCount = activeEnv?.variables.filter((v) => v.enabled).length ?? 0;
|
|
65697
66172
|
async function handleEnvChange(id2) {
|
|
65698
66173
|
if (id2) {
|
|
@@ -65727,7 +66202,7 @@ function EnvironmentBar({ inline = false }) {
|
|
|
65727
66202
|
style: { color: "var(--text-primary)" },
|
|
65728
66203
|
children: [
|
|
65729
66204
|
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "No env" }),
|
|
65730
|
-
envList.map(({ data: env }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: env.id, children: env.name }, env.id))
|
|
66205
|
+
envList.map(({ data: env }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: env.id, children: defaultEnvName && env.name.toLowerCase() === defaultEnvName.toLowerCase() ? `${env.name} (default)` : env.name }, env.id))
|
|
65731
66206
|
]
|
|
65732
66207
|
}
|
|
65733
66208
|
),
|
|
@@ -65758,12 +66233,14 @@ const ZOOM_STEPS = [0.75, 0.9, 1, 1.1, 1.25, 1.5];
|
|
|
65758
66233
|
function WorkspaceSettingsModal({ onClose }) {
|
|
65759
66234
|
const workspace = useStore((s) => s.workspace);
|
|
65760
66235
|
const updateWorkspaceSettings = useStore((s) => s.updateWorkspaceSettings);
|
|
66236
|
+
const environments = useStore((s) => s.environments);
|
|
65761
66237
|
const theme2 = useStore((s) => s.theme);
|
|
65762
66238
|
const zoom = useStore((s) => s.zoom);
|
|
65763
66239
|
const setTheme = useStore((s) => s.setTheme);
|
|
65764
66240
|
const setZoom = useStore((s) => s.setZoom);
|
|
65765
66241
|
const existing = workspace?.settings ?? {};
|
|
65766
|
-
const [activeTab, setActiveTab] = reactExports.useState("
|
|
66242
|
+
const [activeTab, setActiveTab] = reactExports.useState("general");
|
|
66243
|
+
const [defaultEnvironment, setDefaultEnvironment] = reactExports.useState(existing.defaultEnvironment ?? "");
|
|
65767
66244
|
const [proxyUrl, setProxyUrl] = reactExports.useState(existing.proxy?.url ?? "");
|
|
65768
66245
|
const [proxyUser, setProxyUser] = reactExports.useState(existing.proxy?.auth?.username ?? "");
|
|
65769
66246
|
const [proxyPass, setProxyPass] = reactExports.useState(existing.proxy?.auth?.password ?? "");
|
|
@@ -65774,6 +66251,7 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
65774
66251
|
existing.tls?.rejectUnauthorized !== false
|
|
65775
66252
|
// default true
|
|
65776
66253
|
);
|
|
66254
|
+
const [dashboardUrl, setDashboardUrl] = reactExports.useState(existing.dashboardUrl ?? "");
|
|
65777
66255
|
const [patterns, setPatterns] = reactExports.useState(
|
|
65778
66256
|
existing.piiMaskPatterns ?? DEFAULT_PII_PATTERNS
|
|
65779
66257
|
);
|
|
@@ -65805,6 +66283,10 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
65805
66283
|
rejectUnauthorized
|
|
65806
66284
|
};
|
|
65807
66285
|
settings.piiMaskPatterns = patterns;
|
|
66286
|
+
if (dashboardUrl.trim()) settings.dashboardUrl = dashboardUrl.trim();
|
|
66287
|
+
else delete settings.dashboardUrl;
|
|
66288
|
+
if (defaultEnvironment) settings.defaultEnvironment = defaultEnvironment;
|
|
66289
|
+
else delete settings.defaultEnvironment;
|
|
65808
66290
|
updateWorkspaceSettings(settings);
|
|
65809
66291
|
const updated = useStore.getState().workspace;
|
|
65810
66292
|
if (updated) await electron$b.saveWorkspace(updated);
|
|
@@ -65816,10 +66298,12 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
65816
66298
|
setZoom(next);
|
|
65817
66299
|
}
|
|
65818
66300
|
const tabs = [
|
|
66301
|
+
{ id: "general", label: "General" },
|
|
65819
66302
|
{ id: "appearance", label: "Appearance" },
|
|
65820
66303
|
{ id: "proxy", label: "Proxy" },
|
|
65821
66304
|
{ id: "tls", label: "TLS / Certificates" },
|
|
65822
|
-
{ id: "privacy", label: "Privacy" }
|
|
66305
|
+
{ id: "privacy", label: "Privacy" },
|
|
66306
|
+
{ id: "contracts", label: "Contracts" }
|
|
65823
66307
|
];
|
|
65824
66308
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
65825
66309
|
Modal,
|
|
@@ -65839,6 +66323,24 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
65839
66323
|
t2.id
|
|
65840
66324
|
)) }),
|
|
65841
66325
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 text-xs flex flex-col gap-4", children: [
|
|
66326
|
+
activeTab === "general" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
66327
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
66328
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Default environment" }),
|
|
66329
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
66330
|
+
"select",
|
|
66331
|
+
{
|
|
66332
|
+
value: defaultEnvironment,
|
|
66333
|
+
onChange: (e) => setDefaultEnvironment(e.target.value),
|
|
66334
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500",
|
|
66335
|
+
children: [
|
|
66336
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "(none)" }),
|
|
66337
|
+
Object.values(environments).map(({ data: env }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: env.name, children: env.name }, env.id))
|
|
66338
|
+
]
|
|
66339
|
+
}
|
|
66340
|
+
)
|
|
66341
|
+
] }),
|
|
66342
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-600 text-[11px]", children: "CLI runs without --environment use this environment, and the app selects it when no environment is active." })
|
|
66343
|
+
] }),
|
|
65842
66344
|
activeTab === "appearance" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
65843
66345
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
65844
66346
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Theme" }),
|
|
@@ -65928,6 +66430,21 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
65928
66430
|
] })
|
|
65929
66431
|
] })
|
|
65930
66432
|
] }),
|
|
66433
|
+
activeTab === "contracts" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
66434
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
66435
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Dashboard URL (optional)" }),
|
|
66436
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
66437
|
+
"input",
|
|
66438
|
+
{
|
|
66439
|
+
value: dashboardUrl,
|
|
66440
|
+
onChange: (e) => setDashboardUrl(e.target.value),
|
|
66441
|
+
placeholder: "http://localhost:8080",
|
|
66442
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
|
|
66443
|
+
}
|
|
66444
|
+
)
|
|
66445
|
+
] }),
|
|
66446
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-600 text-[11px]", children: 'Where your contract dashboard is served (api-spector contract report --serve, locally or as a docker container). Adds an "Open dashboard" link to the contract results panel. The link is view-only: recorded results reach the dashboard through the workspace files, not through this URL.' })
|
|
66447
|
+
] }),
|
|
65931
66448
|
activeTab === "tls" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
65932
66449
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
65933
66450
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "CA Certificate path" }),
|
|
@@ -66465,7 +66982,7 @@ function ImportModal({ onImport, onClose }) {
|
|
|
66465
66982
|
children: [
|
|
66466
66983
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
66467
66984
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-sm font-semibold text-surface-100", children: [
|
|
66468
|
-
"Import OpenAPI
|
|
66985
|
+
"Import OpenAPI - ",
|
|
66469
66986
|
previewCol.name
|
|
66470
66987
|
] }),
|
|
66471
66988
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -67513,7 +68030,7 @@ function RunnerModal() {
|
|
|
67513
68030
|
return { ...item, request: req };
|
|
67514
68031
|
});
|
|
67515
68032
|
}
|
|
67516
|
-
const env =
|
|
68033
|
+
const env = resolveEnvironmentById(environments, selectedEnvId || null);
|
|
67517
68034
|
setRunnerResults(items2.map((item) => ({
|
|
67518
68035
|
requestId: item.request.id,
|
|
67519
68036
|
name: item.request.name,
|
|
@@ -67580,7 +68097,7 @@ function RunnerModal() {
|
|
|
67580
68097
|
onChange: (e) => setSelectedEnvId(e.target.value),
|
|
67581
68098
|
className: "text-xs bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500",
|
|
67582
68099
|
children: [
|
|
67583
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "
|
|
68100
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "(no environment)" }),
|
|
67584
68101
|
Object.values(environments).map(({ data: env }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: env.id, children: env.name }, env.id))
|
|
67585
68102
|
]
|
|
67586
68103
|
}
|
|
@@ -67907,7 +68424,7 @@ function CollectionPanel() {
|
|
|
67907
68424
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto px-6 py-4", children: [
|
|
67908
68425
|
activeTab === "data" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 text-xs", children: [
|
|
67909
68426
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-surface-600 text-[11px]", children: [
|
|
67910
|
-
"Define variables here
|
|
68427
|
+
"Define variables here - each row runs the entire collection once with those values injected. Columns become ",
|
|
67911
68428
|
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-500", children: "{{variable}}" }),
|
|
67912
68429
|
" placeholders."
|
|
67913
68430
|
] }),
|
|
@@ -67920,14 +68437,14 @@ function CollectionPanel() {
|
|
|
67920
68437
|
{
|
|
67921
68438
|
onClick: () => csvFileRef.current?.click(),
|
|
67922
68439
|
className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors",
|
|
67923
|
-
title: "Import CSV
|
|
68440
|
+
title: "Import CSV - first row is column headers",
|
|
67924
68441
|
children: "↑ Import CSV"
|
|
67925
68442
|
}
|
|
67926
68443
|
),
|
|
67927
68444
|
hasColumns && iterCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: exportCSV, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors", children: "↓ Export CSV" }),
|
|
67928
68445
|
/* @__PURE__ */ jsxRuntimeExports.jsx("input", { ref: csvFileRef, type: "file", accept: ".csv,text/csv", className: "hidden", onChange: importCSV })
|
|
67929
68446
|
] }),
|
|
67930
|
-
hasColumns && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500", children: iterCount === 0 ? "No rows yet
|
|
68447
|
+
hasColumns && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500", children: iterCount === 0 ? "No rows yet - add rows or import a CSV." : `${iterCount} iteration${iterCount !== 1 ? "s" : ""} · columns: ${ds.columns.join(", ")}` }),
|
|
67931
68448
|
hasColumns ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full border-collapse text-xs", children: [
|
|
67932
68449
|
/* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-surface-700", children: [
|
|
67933
68450
|
/* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "w-8 px-2 py-1 text-surface-600 font-normal text-left", children: "#" }),
|
|
@@ -67958,7 +68475,7 @@ function CollectionPanel() {
|
|
|
67958
68475
|
) }, ci)),
|
|
67959
68476
|
/* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-1 py-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => removeRow(ri), className: "text-surface-400 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-all", children: "×" }) })
|
|
67960
68477
|
] }, ri)),
|
|
67961
|
-
iterCount === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("td", { colSpan: ds.columns.length + 2, className: "px-2 py-3 text-surface-600 text-center", children: 'No rows
|
|
68478
|
+
iterCount === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("td", { colSpan: ds.columns.length + 2, className: "px-2 py-3 text-surface-600 text-center", children: 'No rows - click "+ Row" or import a CSV' }) })
|
|
67962
68479
|
] })
|
|
67963
68480
|
] }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center gap-2 py-8 text-surface-600", children: [
|
|
67964
68481
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "No columns defined." }),
|
|
@@ -68263,7 +68780,7 @@ function MockPanel() {
|
|
|
68263
68780
|
] });
|
|
68264
68781
|
}
|
|
68265
68782
|
const { electron: electron$5 } = window;
|
|
68266
|
-
const METHODS_PLUS_ANY = ["ANY", "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
68783
|
+
const METHODS_PLUS_ANY = ["ANY", "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "QUERY"];
|
|
68267
68784
|
function RouteRow({
|
|
68268
68785
|
route,
|
|
68269
68786
|
onSave,
|
|
@@ -68568,7 +69085,7 @@ function HitRow({ hit, matched }) {
|
|
|
68568
69085
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-[10px] w-3 shrink-0", children: open ? "▾" : "▸" }),
|
|
68569
69086
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-bold w-16 shrink-0 text-xs ${getMethodColor(hit.method)}`, children: hit.method }),
|
|
68570
69087
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate text-surface-200", title: hit.path, children: hit.path }),
|
|
68571
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-32 shrink-0 truncate text-surface-600 text-xs font-sans", title: matched?.description ?? matched?.path ?? "", children: unmatched ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400", children: "no match" }) : matched?.description || matched?.path || "
|
|
69088
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-32 shrink-0 truncate text-surface-600 text-xs font-sans", title: matched?.description ?? matched?.path ?? "", children: unmatched ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400", children: "no match" }) : matched?.description || matched?.path || "-" }),
|
|
68572
69089
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `w-12 text-right shrink-0 text-xs ${hit.status < 300 ? "text-emerald-400" : hit.status < 400 ? "text-amber-400" : "text-red-400"}`, children: hit.status }),
|
|
68573
69090
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "w-14 text-right shrink-0 text-surface-600 text-xs", children: [
|
|
68574
69091
|
hit.durationMs,
|
|
@@ -68949,7 +69466,7 @@ function RecorderPanel({ onImportMock, onClose, defaultTargetMockId }) {
|
|
|
68949
69466
|
onChange: (e) => setImportTarget(e.target.value),
|
|
68950
69467
|
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-[11px] text-surface-200 focus:outline-none focus:border-blue-500",
|
|
68951
69468
|
children: [
|
|
68952
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "new", children: "
|
|
69469
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "new", children: "(new mock server)" }),
|
|
68953
69470
|
mockList.map((entry) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: entry.data.id, children: entry.data.name }, entry.data.id))
|
|
68954
69471
|
]
|
|
68955
69472
|
}
|
|
@@ -69038,7 +69555,7 @@ function EntryDetail({ entry }) {
|
|
|
69038
69555
|
const sc = statusColor$1(entry.response.status);
|
|
69039
69556
|
const prettyBody = (raw) => {
|
|
69040
69557
|
if (!raw) return "";
|
|
69041
|
-
if (raw.startsWith("base64:")) return "[binary content
|
|
69558
|
+
if (raw.startsWith("base64:")) return "[binary content - base64 encoded]";
|
|
69042
69559
|
try {
|
|
69043
69560
|
return JSON.stringify(JSON.parse(raw), null, 2);
|
|
69044
69561
|
} catch {
|
|
@@ -69080,7 +69597,7 @@ function EntryDetail({ entry }) {
|
|
|
69080
69597
|
tab === "response" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
69081
69598
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Section, { label: "Headers", children: /* @__PURE__ */ jsxRuntimeExports.jsx(HeadersTable, { headers: entry.response.headers }) }),
|
|
69082
69599
|
entry.response.body && /* @__PURE__ */ jsxRuntimeExports.jsx(Section, { label: "Body", children: /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "font-mono text-[11px] whitespace-pre-wrap break-all text-surface-200 bg-surface-900 border border-surface-700 rounded p-3 max-h-72 overflow-y-auto", children: prettyBody(entry.response.body) }) }),
|
|
69083
|
-
entry.response.binary && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-yellow-400", children: "Binary response
|
|
69600
|
+
entry.response.binary && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-yellow-400", children: "Binary response - body stored as base64" })
|
|
69084
69601
|
] })
|
|
69085
69602
|
] });
|
|
69086
69603
|
}
|
|
@@ -69102,7 +69619,7 @@ function HeadersTable({ headers }) {
|
|
|
69102
69619
|
] }, k)) });
|
|
69103
69620
|
}
|
|
69104
69621
|
const { electron: electron$3 } = window;
|
|
69105
|
-
function ContractPanel() {
|
|
69622
|
+
function ContractPanel({ fuzzReport, setFuzzReport }) {
|
|
69106
69623
|
const collections = useStore((s) => s.collections);
|
|
69107
69624
|
const environments = useStore((s) => s.environments);
|
|
69108
69625
|
const activeEnvId = useStore((s) => s.activeEnvironmentId);
|
|
@@ -69123,18 +69640,28 @@ function ContractPanel() {
|
|
|
69123
69640
|
const [running, setRunning] = reactExports.useState(false);
|
|
69124
69641
|
const [capturing, setCapturing] = reactExports.useState(false);
|
|
69125
69642
|
const [error2, setError] = reactExports.useState(null);
|
|
69643
|
+
const [casesPerOperation, setCasesPerOperation] = reactExports.useState(40);
|
|
69644
|
+
const [seed, setSeed] = reactExports.useState(1);
|
|
69645
|
+
const [includeWrites, setIncludeWrites] = reactExports.useState(false);
|
|
69646
|
+
const [strictStatus, setStrictStatus] = reactExports.useState(false);
|
|
69647
|
+
const [checkResponses, setCheckResponses] = reactExports.useState(false);
|
|
69648
|
+
const [trace, setTrace] = reactExports.useState(false);
|
|
69126
69649
|
const snapshotList = Object.entries(snapshots).map(([relPath, snapshot]) => ({ relPath, snapshot })).sort((a, b) => b.snapshot.capturedAt.localeCompare(a.snapshot.capturedAt));
|
|
69127
69650
|
const activeSnapshot = activeSnapshotRelPath ? snapshots[activeSnapshotRelPath] ?? null : null;
|
|
69128
69651
|
const allRequests = Object.values(collections).flatMap((c) => Object.values(c.data.requests));
|
|
69129
69652
|
const contractRequests = allRequests.filter(
|
|
69130
69653
|
(r) => r.contract && (r.contract.statusCode !== void 0 || r.contract.bodySchema || r.contract.bodyMatcher || r.contract.headers?.length)
|
|
69131
69654
|
);
|
|
69655
|
+
const isFuzz = mode === "fuzz";
|
|
69132
69656
|
const needsSpec = mode === "provider" || mode === "bidirectional";
|
|
69657
|
+
const showSpec = needsSpec || isFuzz;
|
|
69133
69658
|
const collectionVars = activeCollId ? collections[activeCollId]?.data.collectionVariables ?? {} : {};
|
|
69134
|
-
const
|
|
69135
|
-
|
|
69659
|
+
const resolvedEnv = resolveEnvironmentById(environments, activeEnvId);
|
|
69660
|
+
const envVars = resolvedEnv ? Object.fromEntries(
|
|
69661
|
+
resolvedEnv.variables.filter((v) => v.enabled).map((v) => [v.key, v.value])
|
|
69136
69662
|
) : {};
|
|
69137
69663
|
async function runContracts() {
|
|
69664
|
+
if (mode === "fuzz") return;
|
|
69138
69665
|
if (needsSpec && !specUrl.trim() && !activeSnapshotRelPath) {
|
|
69139
69666
|
setError("Provide an OpenAPI spec URL or pick a pinned snapshot for provider / bi-directional mode.");
|
|
69140
69667
|
return;
|
|
@@ -69159,7 +69686,44 @@ function ContractPanel() {
|
|
|
69159
69686
|
providerBaseUrl: providerBaseUrl.trim() || void 0,
|
|
69160
69687
|
stateHandlerUrl: stateHandlerUrl.trim() || void 0
|
|
69161
69688
|
});
|
|
69162
|
-
|
|
69689
|
+
const activeSnap = activeSnapshotRelPath ? snapshots[activeSnapshotRelPath] : void 0;
|
|
69690
|
+
const specLabel = activeSnap ? `${activeSnap.name}${activeSnap.specVersion ? ` v${activeSnap.specVersion}` : ""} (pinned)` : specUrl.trim() || void 0;
|
|
69691
|
+
setReport(result, {
|
|
69692
|
+
spec: needsSpec ? specLabel : void 0,
|
|
69693
|
+
provider: providerBaseUrl.trim() || void 0
|
|
69694
|
+
});
|
|
69695
|
+
} catch (e) {
|
|
69696
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
69697
|
+
} finally {
|
|
69698
|
+
setRunning(false);
|
|
69699
|
+
}
|
|
69700
|
+
}
|
|
69701
|
+
async function runFuzz() {
|
|
69702
|
+
if (!providerBaseUrl.trim()) {
|
|
69703
|
+
setError("Provide a provider base URL (e.g. http://localhost:3000) to fuzz against.");
|
|
69704
|
+
return;
|
|
69705
|
+
}
|
|
69706
|
+
setRunning(true);
|
|
69707
|
+
setError(null);
|
|
69708
|
+
setFuzzReport(null);
|
|
69709
|
+
try {
|
|
69710
|
+
const result = await electron$3.fuzzContracts({
|
|
69711
|
+
requests: allRequests,
|
|
69712
|
+
// fuzz every request in the workspace, like provider mode
|
|
69713
|
+
envVars,
|
|
69714
|
+
collectionVars,
|
|
69715
|
+
specUrl: specUrl.trim() || void 0,
|
|
69716
|
+
specSnapshotRelPath: activeSnapshotRelPath ?? void 0,
|
|
69717
|
+
providerBaseUrl: providerBaseUrl.trim(),
|
|
69718
|
+
requestBaseUrl: requestBaseUrl.trim() || void 0,
|
|
69719
|
+
casesPerOperation,
|
|
69720
|
+
seed,
|
|
69721
|
+
includeWrites,
|
|
69722
|
+
strictStatus,
|
|
69723
|
+
checkResponses,
|
|
69724
|
+
trace
|
|
69725
|
+
});
|
|
69726
|
+
setFuzzReport(result);
|
|
69163
69727
|
} catch (e) {
|
|
69164
69728
|
setError(e instanceof Error ? e.message : String(e));
|
|
69165
69729
|
} finally {
|
|
@@ -69203,21 +69767,23 @@ function ContractPanel() {
|
|
|
69203
69767
|
["consumer", "Consumer"],
|
|
69204
69768
|
["provider", "Provider"],
|
|
69205
69769
|
["provider-live", "Live"],
|
|
69206
|
-
["bidirectional", "Bi-dir"]
|
|
69770
|
+
["bidirectional", "Bi-dir"],
|
|
69771
|
+
["fuzz", "Fuzz"]
|
|
69207
69772
|
].map(([m, label]) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69208
69773
|
"button",
|
|
69209
69774
|
{
|
|
69210
69775
|
onClick: () => {
|
|
69211
69776
|
setMode(m);
|
|
69212
69777
|
setReport(null);
|
|
69778
|
+
setFuzzReport(null);
|
|
69213
69779
|
},
|
|
69214
69780
|
className: `flex-1 py-1 text-[10px] font-semibold rounded transition-colors ${mode === m ? "bg-blue-600 text-white" : "text-surface-400 hover:text-surface-200"}`,
|
|
69215
69781
|
children: label
|
|
69216
69782
|
},
|
|
69217
69783
|
m
|
|
69218
69784
|
)) }),
|
|
69219
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-500 leading-relaxed", children: mode === "consumer" ? "Sends requests to the real provider and validates each response against the contract defined in the Contract tab." : mode === "provider" ? "Static analysis
|
|
69220
|
-
mode === "provider-live" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
69785
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-500 leading-relaxed", children: mode === "consumer" ? "Sends requests to the real provider and validates each response against the contract defined in the Contract tab." : mode === "provider" ? "Static analysis: validates that your requests conform to the provider's published OpenAPI spec (no HTTP calls)." : mode === "provider-live" ? "Replays each contract against a running provider, seeding provider states first. The real provider verification." : mode === "fuzz" ? "Sends malformed inputs generated from the spec (or, with no spec, the request body) and flags responses that crash (5xx) or accept invalid input." : "Checks static schema compatibility between consumer contracts and provider spec, then verifies live responses." }),
|
|
69786
|
+
(mode === "provider-live" || isFuzz) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
69221
69787
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
69222
69788
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: "Provider base URL" }),
|
|
69223
69789
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -69229,9 +69795,9 @@ function ContractPanel() {
|
|
|
69229
69795
|
className: "w-full text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
|
|
69230
69796
|
}
|
|
69231
69797
|
),
|
|
69232
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 mt-1 leading-relaxed", children: "Each request is rebased onto this origin before being replayed against the live provider." })
|
|
69798
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 mt-1 leading-relaxed", children: isFuzz ? "Required. Each fuzzed request is rebased onto this origin before it is sent." : "Each request is rebased onto this origin before being replayed against the live provider." })
|
|
69233
69799
|
] }),
|
|
69234
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
69800
|
+
mode === "provider-live" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
69235
69801
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: [
|
|
69236
69802
|
"State handler URL ",
|
|
69237
69803
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "normal-case text-surface-600", children: "(optional)" })
|
|
@@ -69254,9 +69820,12 @@ function ContractPanel() {
|
|
|
69254
69820
|
] })
|
|
69255
69821
|
] })
|
|
69256
69822
|
] }),
|
|
69257
|
-
|
|
69823
|
+
showSpec && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
69258
69824
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
69259
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
69825
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: [
|
|
69826
|
+
"Spec version ",
|
|
69827
|
+
isFuzz && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "normal-case text-surface-600", children: "(optional)" })
|
|
69828
|
+
] }),
|
|
69260
69829
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1", children: [
|
|
69261
69830
|
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
69262
69831
|
"select",
|
|
@@ -69269,7 +69838,7 @@ function ContractPanel() {
|
|
|
69269
69838
|
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "Live URL (latest from provider)" }),
|
|
69270
69839
|
snapshotList.map(({ relPath, snapshot }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: relPath, children: [
|
|
69271
69840
|
snapshot.name,
|
|
69272
|
-
snapshot.specVersion ? "" : `
|
|
69841
|
+
snapshot.specVersion ? "" : ` - ${snapshot.capturedAt.slice(0, 10)}`
|
|
69273
69842
|
] }, relPath))
|
|
69274
69843
|
]
|
|
69275
69844
|
}
|
|
@@ -69287,7 +69856,7 @@ function ContractPanel() {
|
|
|
69287
69856
|
activeSnapshot && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-600 mt-1 font-mono truncate", children: [
|
|
69288
69857
|
"Captured ",
|
|
69289
69858
|
activeSnapshot.capturedAt.slice(0, 19).replace("T", " "),
|
|
69290
|
-
"
|
|
69859
|
+
" - sha ",
|
|
69291
69860
|
activeSnapshot.sha256.slice(0, 8)
|
|
69292
69861
|
] })
|
|
69293
69862
|
] }),
|
|
@@ -69333,15 +69902,93 @@ function ContractPanel() {
|
|
|
69333
69902
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 mt-1 leading-relaxed", children: "If your requests point at a different host than the spec, enter that host here so paths match correctly." })
|
|
69334
69903
|
] })
|
|
69335
69904
|
] }),
|
|
69905
|
+
isFuzz && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
69906
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
|
|
69907
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
|
|
69908
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: "Cases per operation" }),
|
|
69909
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69910
|
+
"input",
|
|
69911
|
+
{
|
|
69912
|
+
type: "number",
|
|
69913
|
+
min: 1,
|
|
69914
|
+
value: casesPerOperation,
|
|
69915
|
+
onChange: (e) => setCasesPerOperation(Math.max(1, Number(e.target.value) || 1)),
|
|
69916
|
+
className: "w-full text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono"
|
|
69917
|
+
}
|
|
69918
|
+
)
|
|
69919
|
+
] }),
|
|
69920
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
|
|
69921
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: "Seed" }),
|
|
69922
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69923
|
+
"input",
|
|
69924
|
+
{
|
|
69925
|
+
type: "number",
|
|
69926
|
+
value: seed,
|
|
69927
|
+
onChange: (e) => setSeed(Number(e.target.value) || 0),
|
|
69928
|
+
className: "w-full text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono"
|
|
69929
|
+
}
|
|
69930
|
+
)
|
|
69931
|
+
] })
|
|
69932
|
+
] }),
|
|
69933
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-[11px] text-surface-300 cursor-pointer", children: [
|
|
69934
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69935
|
+
"input",
|
|
69936
|
+
{
|
|
69937
|
+
type: "checkbox",
|
|
69938
|
+
checked: includeWrites,
|
|
69939
|
+
onChange: (e) => setIncludeWrites(e.target.checked),
|
|
69940
|
+
className: "accent-blue-600"
|
|
69941
|
+
}
|
|
69942
|
+
),
|
|
69943
|
+
"Include write methods (POST/PUT/PATCH/DELETE)"
|
|
69944
|
+
] }),
|
|
69945
|
+
includeWrites && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-amber-400 leading-relaxed -mt-1 ml-6", children: "This sends malformed writes to the provider. Target staging or a mock, not production." }),
|
|
69946
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-[11px] text-surface-300 cursor-pointer", children: [
|
|
69947
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69948
|
+
"input",
|
|
69949
|
+
{
|
|
69950
|
+
type: "checkbox",
|
|
69951
|
+
checked: strictStatus,
|
|
69952
|
+
onChange: (e) => setStrictStatus(e.target.checked),
|
|
69953
|
+
className: "accent-blue-600"
|
|
69954
|
+
}
|
|
69955
|
+
),
|
|
69956
|
+
"Strict status"
|
|
69957
|
+
] }),
|
|
69958
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-[11px] text-surface-300 cursor-pointer", children: [
|
|
69959
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69960
|
+
"input",
|
|
69961
|
+
{
|
|
69962
|
+
type: "checkbox",
|
|
69963
|
+
checked: checkResponses,
|
|
69964
|
+
onChange: (e) => setCheckResponses(e.target.checked),
|
|
69965
|
+
className: "accent-blue-600"
|
|
69966
|
+
}
|
|
69967
|
+
),
|
|
69968
|
+
"Check response schemas"
|
|
69969
|
+
] }),
|
|
69970
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-[11px] text-surface-300 cursor-pointer", children: [
|
|
69971
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69972
|
+
"input",
|
|
69973
|
+
{
|
|
69974
|
+
type: "checkbox",
|
|
69975
|
+
checked: trace,
|
|
69976
|
+
onChange: (e) => setTrace(e.target.checked),
|
|
69977
|
+
className: "accent-blue-600"
|
|
69978
|
+
}
|
|
69979
|
+
),
|
|
69980
|
+
"Record all cases"
|
|
69981
|
+
] })
|
|
69982
|
+
] }),
|
|
69336
69983
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
69337
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-surface-500", children: mode === "provider" ? `${allRequests.length} request${allRequests.length !== 1 ? "s" : ""}` : `${contractRequests.length} contract${contractRequests.length !== 1 ? "s" : ""} defined` }),
|
|
69984
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-surface-500", children: mode === "provider" || isFuzz ? `${allRequests.length} request${allRequests.length !== 1 ? "s" : ""}` : `${contractRequests.length} contract${contractRequests.length !== 1 ? "s" : ""} defined` }),
|
|
69338
69985
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69339
69986
|
"button",
|
|
69340
69987
|
{
|
|
69341
|
-
onClick: runContracts,
|
|
69342
|
-
disabled: running || needsSpec && !specUrl.trim() && !activeSnapshotRelPath || mode === "provider-live" && !providerBaseUrl.trim(),
|
|
69988
|
+
onClick: isFuzz ? runFuzz : runContracts,
|
|
69989
|
+
disabled: running || needsSpec && !specUrl.trim() && !activeSnapshotRelPath || (mode === "provider-live" || isFuzz) && !providerBaseUrl.trim(),
|
|
69343
69990
|
className: "px-3 py-1 text-xs bg-blue-700 hover:bg-blue-600 disabled:bg-surface-800 disabled:text-surface-600 rounded transition-colors font-medium",
|
|
69344
|
-
children: running ? "Running…" : "Run"
|
|
69991
|
+
children: running ? "Running…" : isFuzz ? "Run fuzz" : "Run"
|
|
69345
69992
|
}
|
|
69346
69993
|
)
|
|
69347
69994
|
] }),
|
|
@@ -69349,30 +69996,33 @@ function ContractPanel() {
|
|
|
69349
69996
|
] }),
|
|
69350
69997
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto min-h-0 p-3", children: [
|
|
69351
69998
|
running && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 text-center mt-4", children: "Running…" }),
|
|
69352
|
-
!
|
|
69353
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: "Configure
|
|
69354
|
-
|
|
69999
|
+
isFuzz ? !fuzzReport && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-2 text-center", children: [
|
|
70000
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: "Configure fuzzing above and click Run fuzz." }),
|
|
70001
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 max-w-[180px]", children: "Fuzzing needs a provider base URL. A spec is optional: without one, request bodies are mutated." })
|
|
70002
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
70003
|
+
!report && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-2 text-center", children: [
|
|
70004
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: "Configure a mode above and click Run." }),
|
|
70005
|
+
mode !== "provider" && contractRequests.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 max-w-[180px]", children: "Define a contract on a request via the Contract tab first." })
|
|
70006
|
+
] }),
|
|
70007
|
+
report && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-2 px-3 py-2 rounded-lg border text-xs ${report.failed === 0 ? "bg-emerald-800/30 border-emerald-400/50 text-emerald-400" : "bg-red-900/30 border-red-700 text-red-300"}`, children: [
|
|
70008
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold", children: report.failed === 0 ? "✓ All passed" : `✗ ${report.failed} failed` }),
|
|
70009
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 ml-auto", children: [
|
|
70010
|
+
report.passed,
|
|
70011
|
+
"/",
|
|
70012
|
+
report.total
|
|
70013
|
+
] })
|
|
70014
|
+
] })
|
|
69355
70015
|
] }),
|
|
69356
|
-
|
|
69357
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold", children:
|
|
70016
|
+
isFuzz && fuzzReport && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-2 px-3 py-2 rounded-lg border text-xs ${fuzzReport.totalFindings === 0 ? "bg-emerald-800/30 border-emerald-400/50 text-emerald-400" : "bg-red-900/30 border-red-700 text-red-300"}`, children: [
|
|
70017
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold", children: fuzzReport.totalFindings === 0 ? "✓ No findings" : `✗ ${fuzzReport.totalFindings} finding${fuzzReport.totalFindings !== 1 ? "s" : ""}` }),
|
|
69358
70018
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 ml-auto", children: [
|
|
69359
|
-
|
|
69360
|
-
"
|
|
69361
|
-
report.total
|
|
70019
|
+
fuzzReport.totalCases,
|
|
70020
|
+
" cases"
|
|
69362
70021
|
] })
|
|
69363
70022
|
] })
|
|
69364
70023
|
] })
|
|
69365
70024
|
] });
|
|
69366
70025
|
}
|
|
69367
|
-
const METHOD_COLOR = {
|
|
69368
|
-
GET: "text-emerald-400",
|
|
69369
|
-
POST: "text-blue-400",
|
|
69370
|
-
PUT: "text-amber-400",
|
|
69371
|
-
PATCH: "text-orange-400",
|
|
69372
|
-
DELETE: "text-red-400",
|
|
69373
|
-
HEAD: "text-purple-400",
|
|
69374
|
-
OPTIONS: "text-gray-400"
|
|
69375
|
-
};
|
|
69376
70026
|
function statusColor(code2) {
|
|
69377
70027
|
const d = String(code2)[0];
|
|
69378
70028
|
return d === "2" ? "text-emerald-400" : d === "3" ? "text-amber-400" : "text-red-400";
|
|
@@ -69406,7 +70056,7 @@ function ResultCard({ result }) {
|
|
|
69406
70056
|
className: `w-full flex items-center gap-3 px-4 py-3 text-left transition-colors ${result.passed ? "bg-surface-800 hover:bg-surface-750" : "bg-red-950/30 hover:bg-red-950/50"}`,
|
|
69407
70057
|
children: [
|
|
69408
70058
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-[10px] font-bold px-2 py-0.5 rounded ${result.passed ? "bg-emerald-900/50 text-emerald-400" : "bg-red-900/50 text-red-400"}`, children: result.passed ? "PASS" : "FAIL" }),
|
|
69409
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-bold font-mono w-14 ${
|
|
70059
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-bold font-mono w-14 ${getMethodColor(result.method)}`, children: result.method }),
|
|
69410
70060
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 text-sm text-white truncate", children: result.requestName }),
|
|
69411
70061
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden lg:block text-[11px] text-surface-500 font-mono truncate max-w-[260px]", children: result.url }),
|
|
69412
70062
|
result.actualStatus !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-mono font-bold ${statusColor(result.actualStatus)}`, children: result.actualStatus }),
|
|
@@ -69428,7 +70078,37 @@ function ResultCard({ result }) {
|
|
|
69428
70078
|
}
|
|
69429
70079
|
function ContractResultsPanel() {
|
|
69430
70080
|
const report = useStore((s) => s.lastContractReport);
|
|
70081
|
+
const runMeta = useStore((s) => s.lastContractRunMeta);
|
|
69431
70082
|
const clearReport = useStore((s) => s.setLastContractReport);
|
|
70083
|
+
const activeCollId = useStore((s) => s.activeCollectionId);
|
|
70084
|
+
const collections = useStore((s) => s.collections);
|
|
70085
|
+
const dashboardUrl = useStore((s) => s.workspace?.settings?.dashboardUrl);
|
|
70086
|
+
const [recordOpen, setRecordOpen] = reactExports.useState(false);
|
|
70087
|
+
const [pacticipant, setPacticipant] = reactExports.useState("");
|
|
70088
|
+
const [version, setVersion] = reactExports.useState("");
|
|
70089
|
+
const [saving, setSaving] = reactExports.useState(false);
|
|
70090
|
+
const { toast, show: showToast } = useToast();
|
|
70091
|
+
async function recordRun() {
|
|
70092
|
+
if (!report) return;
|
|
70093
|
+
if (!pacticipant.trim() || !version.trim()) {
|
|
70094
|
+
showToast("Pacticipant and version are required to record.", false);
|
|
70095
|
+
return;
|
|
70096
|
+
}
|
|
70097
|
+
setSaving(true);
|
|
70098
|
+
try {
|
|
70099
|
+
await window.electron.recordContractResult({
|
|
70100
|
+
pacticipant: pacticipant.trim(),
|
|
70101
|
+
version: version.trim(),
|
|
70102
|
+
report
|
|
70103
|
+
});
|
|
70104
|
+
showToast(`Recorded ${pacticipant.trim()}@${version.trim()} for the dashboard and can-i-deploy.`, true);
|
|
70105
|
+
setRecordOpen(false);
|
|
70106
|
+
} catch (e) {
|
|
70107
|
+
showToast(e instanceof Error ? e.message : String(e), false);
|
|
70108
|
+
} finally {
|
|
70109
|
+
setSaving(false);
|
|
70110
|
+
}
|
|
70111
|
+
}
|
|
69432
70112
|
if (!report) {
|
|
69433
70113
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-3 text-center", children: [
|
|
69434
70114
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-4xl opacity-30", children: "🔬" }),
|
|
@@ -69451,10 +70131,36 @@ function ContractResultsPanel() {
|
|
|
69451
70131
|
report.durationMs,
|
|
69452
70132
|
"ms"
|
|
69453
70133
|
] }),
|
|
70134
|
+
dashboardUrl && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70135
|
+
"button",
|
|
70136
|
+
{
|
|
70137
|
+
onClick: () => window.electron.openExternal(dashboardUrl),
|
|
70138
|
+
className: "text-[11px] text-surface-500 hover:text-surface-200 transition-colors",
|
|
70139
|
+
title: `Open the contract dashboard (${dashboardUrl})`,
|
|
70140
|
+
children: "Open dashboard"
|
|
70141
|
+
}
|
|
70142
|
+
),
|
|
69454
70143
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69455
70144
|
"button",
|
|
69456
70145
|
{
|
|
69457
|
-
onClick: () =>
|
|
70146
|
+
onClick: () => {
|
|
70147
|
+
if (!recordOpen && !pacticipant) {
|
|
70148
|
+
setPacticipant(activeCollId ? collections[activeCollId]?.data.name ?? "" : "");
|
|
70149
|
+
}
|
|
70150
|
+
setRecordOpen((o) => !o);
|
|
70151
|
+
},
|
|
70152
|
+
className: "text-[11px] text-surface-500 hover:text-surface-200 transition-colors",
|
|
70153
|
+
title: "Record this run for the contract dashboard and can-i-deploy gate",
|
|
70154
|
+
children: "Record"
|
|
70155
|
+
}
|
|
70156
|
+
),
|
|
70157
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70158
|
+
"button",
|
|
70159
|
+
{
|
|
70160
|
+
onClick: () => window.electron.exportContractReportHtml(report, {
|
|
70161
|
+
spec: runMeta?.spec,
|
|
70162
|
+
provider: runMeta?.provider
|
|
70163
|
+
}),
|
|
69458
70164
|
className: "text-[11px] text-surface-500 hover:text-surface-200 transition-colors",
|
|
69459
70165
|
title: "Export a self-contained HTML report",
|
|
69460
70166
|
children: "Export HTML"
|
|
@@ -69470,6 +70176,37 @@ function ContractResultsPanel() {
|
|
|
69470
70176
|
}
|
|
69471
70177
|
)
|
|
69472
70178
|
] }),
|
|
70179
|
+
recordOpen && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-6 py-2 border-b border-surface-800 bg-surface-900/60 flex-shrink-0", children: [
|
|
70180
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70181
|
+
"input",
|
|
70182
|
+
{
|
|
70183
|
+
value: pacticipant,
|
|
70184
|
+
onChange: (e) => setPacticipant(e.target.value),
|
|
70185
|
+
placeholder: "pacticipant (e.g. web-app)",
|
|
70186
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs w-48 focus:outline-none focus:border-blue-500"
|
|
70187
|
+
}
|
|
70188
|
+
),
|
|
70189
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70190
|
+
"input",
|
|
70191
|
+
{
|
|
70192
|
+
value: version,
|
|
70193
|
+
onChange: (e) => setVersion(e.target.value),
|
|
70194
|
+
placeholder: "version (e.g. 1.4.0)",
|
|
70195
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs w-32 focus:outline-none focus:border-blue-500"
|
|
70196
|
+
}
|
|
70197
|
+
),
|
|
70198
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70199
|
+
"button",
|
|
70200
|
+
{
|
|
70201
|
+
onClick: recordRun,
|
|
70202
|
+
disabled: saving,
|
|
70203
|
+
className: "text-[11px] px-3 py-1 rounded bg-blue-600 hover:bg-blue-500 text-white font-semibold disabled:opacity-50 transition-colors",
|
|
70204
|
+
children: saving ? "Recording..." : "Record result"
|
|
70205
|
+
}
|
|
70206
|
+
),
|
|
70207
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-surface-500", children: "Writes to contracts/results/ in the workspace; the dashboard picks it up on refresh." })
|
|
70208
|
+
] }),
|
|
70209
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }),
|
|
69473
70210
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto min-h-0 p-6", children: (() => {
|
|
69474
70211
|
const failed = report.results.filter((r) => !r.passed);
|
|
69475
70212
|
const passed = report.results.filter((r) => r.passed);
|
|
@@ -69569,7 +70306,7 @@ ${secretHint} - script: ${runCmd}
|
|
|
69569
70306
|
condition: always()
|
|
69570
70307
|
`;
|
|
69571
70308
|
}
|
|
69572
|
-
return `# Unsupported platform
|
|
70309
|
+
return `# Unsupported platform - adapt as needed
|
|
69573
70310
|
# ${runCmd}
|
|
69574
70311
|
`;
|
|
69575
70312
|
}
|
|
@@ -69747,7 +70484,7 @@ function ChangesTab({ status, onRefresh }) {
|
|
|
69747
70484
|
status.conflicted.length,
|
|
69748
70485
|
" merge conflict",
|
|
69749
70486
|
status.conflicted.length !== 1 ? "s" : "",
|
|
69750
|
-
"
|
|
70487
|
+
" - resolve below before committing"
|
|
69751
70488
|
] }),
|
|
69752
70489
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }),
|
|
69753
70490
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto", children: [
|
|
@@ -70142,7 +70879,7 @@ function BranchesTab({ onRefresh }) {
|
|
|
70142
70879
|
{
|
|
70143
70880
|
onClick: () => checkout(b.name, false),
|
|
70144
70881
|
className: "w-full flex items-center gap-2 px-3 py-1.5 text-left text-xs text-surface-400 hover:bg-surface-800/50 hover:text-surface-200 transition-colors",
|
|
70145
|
-
title: "Click to check out
|
|
70882
|
+
title: "Click to check out - creates a local tracking branch if needed",
|
|
70146
70883
|
children: [
|
|
70147
70884
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-3" }),
|
|
70148
70885
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate", children: b.name })
|
|
@@ -70303,7 +71040,7 @@ function CiTab() {
|
|
|
70303
71040
|
onChange: (e) => setEnvId(e.target.value),
|
|
70304
71041
|
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs focus:outline-none focus:border-blue-500",
|
|
70305
71042
|
children: [
|
|
70306
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "
|
|
71043
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "(none)" }),
|
|
70307
71044
|
envList.map((e) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: e.data.id, children: e.data.name }, e.data.id))
|
|
70308
71045
|
]
|
|
70309
71046
|
}
|
|
@@ -70660,7 +71397,8 @@ const TAB_METHOD_COLORS = {
|
|
|
70660
71397
|
PATCH: "text-orange-400",
|
|
70661
71398
|
DELETE: "text-red-400",
|
|
70662
71399
|
HEAD: "text-purple-400",
|
|
70663
|
-
OPTIONS: "text-gray-400"
|
|
71400
|
+
OPTIONS: "text-gray-400",
|
|
71401
|
+
QUERY: "text-fuchsia-400"
|
|
70664
71402
|
};
|
|
70665
71403
|
const TabRow = React$2.memo(function TabRow2({
|
|
70666
71404
|
tabId,
|
|
@@ -70728,6 +71466,7 @@ function App() {
|
|
|
70728
71466
|
const setCommandPaletteOpen = useStore((s) => s.setCommandPaletteOpen);
|
|
70729
71467
|
const setWsStatus = useStore((s) => s.setWsStatus);
|
|
70730
71468
|
const addWsMessage = useStore((s) => s.addWsMessage);
|
|
71469
|
+
const [fuzzReport, setFuzzReport] = reactExports.useState(null);
|
|
70731
71470
|
const [sidebarOpen, setSidebarOpen] = reactExports.useState(true);
|
|
70732
71471
|
const [responseOpen, setResponseOpen] = reactExports.useState(false);
|
|
70733
71472
|
const [docsModalOpen, setDocsModalOpen] = reactExports.useState(false);
|
|
@@ -70829,7 +71568,7 @@ function App() {
|
|
|
70829
71568
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
|
|
70830
71569
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
|
|
70831
71570
|
"v",
|
|
70832
|
-
"0.3.
|
|
71571
|
+
"0.3.6"
|
|
70833
71572
|
] }),
|
|
70834
71573
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", children: "·" }),
|
|
70835
71574
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -70918,7 +71657,7 @@ function App() {
|
|
|
70918
71657
|
)
|
|
70919
71658
|
] })
|
|
70920
71659
|
] }),
|
|
70921
|
-
sidebarTab === "collections" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionTree, {}) : sidebarTab === "history" ? /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryPanel, {}) : sidebarTab === "mocks" ? /* @__PURE__ */ jsxRuntimeExports.jsx(MockPanel, {}) : sidebarTab === "git" ? /* @__PURE__ */ jsxRuntimeExports.jsx(GitPanel, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(ContractPanel, {})
|
|
71660
|
+
sidebarTab === "collections" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionTree, {}) : sidebarTab === "history" ? /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryPanel, {}) : sidebarTab === "mocks" ? /* @__PURE__ */ jsxRuntimeExports.jsx(MockPanel, {}) : sidebarTab === "git" ? /* @__PURE__ */ jsxRuntimeExports.jsx(GitPanel, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(ContractPanel, { fuzzReport, setFuzzReport })
|
|
70922
71661
|
] }),
|
|
70923
71662
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70924
71663
|
"div",
|
|
@@ -71026,13 +71765,13 @@ function App() {
|
|
|
71026
71765
|
}
|
|
71027
71766
|
)
|
|
71028
71767
|
] }),
|
|
71029
|
-
sidebarTab === "git" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsxRuntimeExports.jsx(GitDiffPane, {}) }) : sidebarTab === "contracts" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ContractResultsPanel, {}) }) : sidebarTab === "mocks" && recorderRunning ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
71768
|
+
sidebarTab === "git" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsxRuntimeExports.jsx(GitDiffPane, {}) }) : sidebarTab === "contracts" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: fuzzReport ? /* @__PURE__ */ jsxRuntimeExports.jsx(FuzzResultsPanel, { report: fuzzReport, onClear: () => setFuzzReport(null) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ContractResultsPanel, {}) }) : sidebarTab === "mocks" && recorderRunning ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
71030
71769
|
RecorderPanel,
|
|
71031
71770
|
{
|
|
71032
71771
|
defaultTargetMockId: recorderTargetMockId,
|
|
71033
71772
|
onClose: () => setRecorderRunning(false),
|
|
71034
71773
|
onImportMock: async (session, targetMockId) => {
|
|
71035
|
-
const name2 = `Recorded
|
|
71774
|
+
const name2 = `Recorded - ${new URL(session.upstream).hostname}`;
|
|
71036
71775
|
const newRoutes = await electron.recordToMock(session.entries, session.upstream, name2, session.port);
|
|
71037
71776
|
if (targetMockId) {
|
|
71038
71777
|
const existing = useStore.getState().mocks[targetMockId];
|