@testsmith/api-spector 0.3.5 → 0.3.7
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-BCnNIZZr.js} +6 -2
- package/out/main/chunks/{import-C5Ngq8hk.js → import-DUKaMYGO.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-AQD6cEgT.js} +602 -146
- package/out/main/chunks/snapshots-NGkGQNZ4.js +1658 -0
- package/out/main/chunks/{soap-handler-B9x_YCtj.js → soap-handler-h0dWIjOK.js} +3 -3
- package/out/main/contract.js +508 -91
- package/out/main/index.js +112 -77
- 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 +10 -2
- package/out/renderer/assets/{index-BFxRlU1d.js → index-SXJZlbGN.js} +1342 -214
- package/out/renderer/assets/index-mDBOHLZ5.css +2 -0
- 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-DuMAjIiI.css +0 -2
|
@@ -13404,14 +13404,33 @@ const createWsSlice = (set2) => ({
|
|
|
13404
13404
|
})
|
|
13405
13405
|
});
|
|
13406
13406
|
const HISTORY_CAP = 200;
|
|
13407
|
-
|
|
13407
|
+
let saveTimer = null;
|
|
13408
|
+
function persistIfEnabled(get2) {
|
|
13409
|
+
if (!get2().workspace?.settings?.persistHistory) return;
|
|
13410
|
+
if (saveTimer) clearTimeout(saveTimer);
|
|
13411
|
+
saveTimer = setTimeout(() => {
|
|
13412
|
+
window.electron.saveHistory(get2().history).catch((err) => {
|
|
13413
|
+
console.warn("persistHistory: could not save history.json", err);
|
|
13414
|
+
});
|
|
13415
|
+
}, 800);
|
|
13416
|
+
}
|
|
13417
|
+
const createHistorySlice = (set2, get2) => ({
|
|
13408
13418
|
history: [],
|
|
13409
|
-
addHistoryEntry: (entry) =>
|
|
13410
|
-
s
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
|
|
13414
|
-
|
|
13419
|
+
addHistoryEntry: (entry) => {
|
|
13420
|
+
set2((s) => {
|
|
13421
|
+
s.history.unshift(entry);
|
|
13422
|
+
if (s.history.length > HISTORY_CAP) s.history.length = HISTORY_CAP;
|
|
13423
|
+
});
|
|
13424
|
+
persistIfEnabled(get2);
|
|
13425
|
+
},
|
|
13426
|
+
clearHistory: () => {
|
|
13427
|
+
set2((s) => {
|
|
13428
|
+
s.history = [];
|
|
13429
|
+
});
|
|
13430
|
+
persistIfEnabled(get2);
|
|
13431
|
+
},
|
|
13432
|
+
setHistory: (entries) => set2((s) => {
|
|
13433
|
+
s.history = entries.slice(0, HISTORY_CAP);
|
|
13415
13434
|
})
|
|
13416
13435
|
});
|
|
13417
13436
|
const createRunnerSlice = (set2) => ({
|
|
@@ -13460,10 +13479,12 @@ const createRecorderSlice = (set2) => ({
|
|
|
13460
13479
|
});
|
|
13461
13480
|
const createContractSlice = (set2) => ({
|
|
13462
13481
|
lastContractReport: null,
|
|
13482
|
+
lastContractRunMeta: null,
|
|
13463
13483
|
contractSnapshots: {},
|
|
13464
13484
|
activeContractSnapshotRelPath: null,
|
|
13465
|
-
setLastContractReport: (r) => set2((s) => {
|
|
13485
|
+
setLastContractReport: (r, meta2) => set2((s) => {
|
|
13466
13486
|
s.lastContractReport = r;
|
|
13487
|
+
s.lastContractRunMeta = r ? meta2 ?? null : null;
|
|
13467
13488
|
}),
|
|
13468
13489
|
loadContractSnapshot: (relPath, snapshot) => set2((s) => {
|
|
13469
13490
|
s.contractSnapshots[relPath] = snapshot;
|
|
@@ -13981,6 +14002,21 @@ const createEnvironmentsSlice = (set2, get2) => ({
|
|
|
13981
14002
|
updateEnvironment: (id2, data) => set2((s) => {
|
|
13982
14003
|
if (s.environments[id2]) s.environments[id2].data = data;
|
|
13983
14004
|
}),
|
|
14005
|
+
upsertEnvVar: (envId, key, value) => {
|
|
14006
|
+
set2((s) => {
|
|
14007
|
+
const env = s.environments[envId]?.data;
|
|
14008
|
+
if (!env) return;
|
|
14009
|
+
const existing = env.variables.find((v) => v.key === key && !v.secret);
|
|
14010
|
+
if (existing) existing.value = value;
|
|
14011
|
+
else env.variables.push({ key, value, enabled: true });
|
|
14012
|
+
});
|
|
14013
|
+
const entry = get2().environments[envId];
|
|
14014
|
+
if (entry) {
|
|
14015
|
+
window.electron.saveEnvironment(entry.relPath, entry.data).catch((err) => {
|
|
14016
|
+
console.warn("upsertEnvVar: could not save environment", entry.relPath, err);
|
|
14017
|
+
});
|
|
14018
|
+
}
|
|
14019
|
+
},
|
|
13984
14020
|
addEnvironment: () => set2((s) => {
|
|
13985
14021
|
const existingNames = Object.values(s.environments).map((e) => e.data.name);
|
|
13986
14022
|
const envName = uniqueName("New Environment", existingNames);
|
|
@@ -14150,7 +14186,7 @@ const useStore = create()(
|
|
|
14150
14186
|
immer((set2, get2, api) => ({
|
|
14151
14187
|
// ── Slice composition ─────────────────────────────────────────────────────
|
|
14152
14188
|
...createWsSlice(set2),
|
|
14153
|
-
...createHistorySlice(set2),
|
|
14189
|
+
...createHistorySlice(set2, get2),
|
|
14154
14190
|
...createRunnerSlice(set2),
|
|
14155
14191
|
...createRecorderSlice(set2),
|
|
14156
14192
|
...createContractSlice(set2),
|
|
@@ -14242,7 +14278,7 @@ const useStore = create()(
|
|
|
14242
14278
|
})
|
|
14243
14279
|
}))
|
|
14244
14280
|
);
|
|
14245
|
-
const { electron: electron$
|
|
14281
|
+
const { electron: electron$s } = window;
|
|
14246
14282
|
function useAutoSave() {
|
|
14247
14283
|
const collections = useStore((s) => s.collections);
|
|
14248
14284
|
useStore((s) => s.environments);
|
|
@@ -14258,7 +14294,7 @@ function useAutoSave() {
|
|
|
14258
14294
|
for (const { relPath, data, dirty } of dirtyCollections) {
|
|
14259
14295
|
if (!dirty) continue;
|
|
14260
14296
|
try {
|
|
14261
|
-
await electron$
|
|
14297
|
+
await electron$s.saveCollection(relPath, data);
|
|
14262
14298
|
markCollectionClean(data.id);
|
|
14263
14299
|
} catch (e) {
|
|
14264
14300
|
console.error("Auto-save failed for", relPath, e);
|
|
@@ -14274,7 +14310,7 @@ function useAutoSave() {
|
|
|
14274
14310
|
if (wsTimerRef.current) clearTimeout(wsTimerRef.current);
|
|
14275
14311
|
wsTimerRef.current = setTimeout(async () => {
|
|
14276
14312
|
try {
|
|
14277
|
-
await electron$
|
|
14313
|
+
await electron$s.saveWorkspace(workspace);
|
|
14278
14314
|
} catch {
|
|
14279
14315
|
}
|
|
14280
14316
|
}, 300);
|
|
@@ -14283,7 +14319,7 @@ function useAutoSave() {
|
|
|
14283
14319
|
};
|
|
14284
14320
|
}, [workspace]);
|
|
14285
14321
|
}
|
|
14286
|
-
const { electron: electron$
|
|
14322
|
+
const { electron: electron$r } = window;
|
|
14287
14323
|
function useWorkspaceLoader() {
|
|
14288
14324
|
const loadCollection = useStore((s) => s.loadCollection);
|
|
14289
14325
|
const loadEnvironment = useStore((s) => s.loadEnvironment);
|
|
@@ -14305,35 +14341,56 @@ function useWorkspaceLoader() {
|
|
|
14305
14341
|
});
|
|
14306
14342
|
if (ws2.settings?.theme) setTheme(ws2.settings.theme);
|
|
14307
14343
|
if (typeof ws2.settings?.zoom === "number") setZoom(ws2.settings.zoom);
|
|
14344
|
+
if (ws2.settings?.persistHistory) {
|
|
14345
|
+
try {
|
|
14346
|
+
const entries = await electron$r.loadHistory();
|
|
14347
|
+
useStore.getState().setHistory(entries);
|
|
14348
|
+
} catch {
|
|
14349
|
+
}
|
|
14350
|
+
} else {
|
|
14351
|
+
useStore.getState().setHistory([]);
|
|
14352
|
+
}
|
|
14308
14353
|
for (const colPath of ws2.collections) {
|
|
14309
14354
|
try {
|
|
14310
|
-
const col = await electron$
|
|
14355
|
+
const col = await electron$r.loadCollection(colPath);
|
|
14311
14356
|
loadCollection(colPath, col);
|
|
14312
14357
|
} catch {
|
|
14313
14358
|
}
|
|
14314
14359
|
}
|
|
14315
14360
|
for (const envPath of ws2.environments) {
|
|
14316
14361
|
try {
|
|
14317
|
-
const env = await electron$
|
|
14362
|
+
const env = await electron$r.loadEnvironment(envPath);
|
|
14318
14363
|
loadEnvironment(envPath, env);
|
|
14319
14364
|
} catch {
|
|
14320
14365
|
}
|
|
14321
14366
|
}
|
|
14367
|
+
{
|
|
14368
|
+
const state = useStore.getState();
|
|
14369
|
+
const activeId = state.activeEnvironmentId;
|
|
14370
|
+
const activeIsValid = activeId !== null && Boolean(state.environments[activeId]);
|
|
14371
|
+
const defaultName = ws2.settings?.defaultEnvironment;
|
|
14372
|
+
if (!activeIsValid && defaultName) {
|
|
14373
|
+
const match = Object.values(state.environments).find(
|
|
14374
|
+
(e) => e.data.name.toLowerCase() === defaultName.toLowerCase()
|
|
14375
|
+
);
|
|
14376
|
+
if (match) state.setActiveEnvironment(match.data.id);
|
|
14377
|
+
}
|
|
14378
|
+
}
|
|
14322
14379
|
for (const relPath of ws2.mocks ?? []) {
|
|
14323
14380
|
try {
|
|
14324
|
-
const mockData = await electron$
|
|
14381
|
+
const mockData = await electron$r.loadMock(relPath);
|
|
14325
14382
|
loadMock(relPath, mockData);
|
|
14326
14383
|
} catch {
|
|
14327
14384
|
}
|
|
14328
14385
|
}
|
|
14329
14386
|
try {
|
|
14330
|
-
const snapshots = await electron$
|
|
14387
|
+
const snapshots = await electron$r.listContractSnapshots(ws2.contracts ?? []);
|
|
14331
14388
|
for (const { relPath, snapshot } of snapshots) loadContractSnapshot(relPath, snapshot);
|
|
14332
14389
|
} catch {
|
|
14333
14390
|
}
|
|
14334
14391
|
if (ws2.collections.length > 0) {
|
|
14335
14392
|
try {
|
|
14336
|
-
const firstCol = await electron$
|
|
14393
|
+
const firstCol = await electron$r.loadCollection(ws2.collections[0]);
|
|
14337
14394
|
setActiveCollection(firstCol.id);
|
|
14338
14395
|
} catch {
|
|
14339
14396
|
}
|
|
@@ -14341,6 +14398,40 @@ function useWorkspaceLoader() {
|
|
|
14341
14398
|
}, [loadCollection, loadEnvironment, loadMock, loadContractSnapshot, setActiveCollection, setTheme, setZoom]);
|
|
14342
14399
|
return { applyWorkspace };
|
|
14343
14400
|
}
|
|
14401
|
+
function resolveEnvironmentChain(env, all) {
|
|
14402
|
+
if (!env.extends) return env;
|
|
14403
|
+
const chain = [env];
|
|
14404
|
+
const seen = /* @__PURE__ */ new Set([env.name]);
|
|
14405
|
+
let parentName = env.extends;
|
|
14406
|
+
while (parentName && !seen.has(parentName)) {
|
|
14407
|
+
const parent = all.find((e) => e.name === parentName);
|
|
14408
|
+
if (!parent) break;
|
|
14409
|
+
chain.push(parent);
|
|
14410
|
+
seen.add(parent.name);
|
|
14411
|
+
parentName = parent.extends;
|
|
14412
|
+
}
|
|
14413
|
+
if (chain.length === 1) return env;
|
|
14414
|
+
const merged = /* @__PURE__ */ new Map();
|
|
14415
|
+
for (const link of [...chain].reverse()) {
|
|
14416
|
+
for (const v of link.variables) merged.set(v.key, v);
|
|
14417
|
+
}
|
|
14418
|
+
return { ...env, variables: [...merged.values()] };
|
|
14419
|
+
}
|
|
14420
|
+
function resolveEnvironmentById(environments, id2) {
|
|
14421
|
+
if (!id2) return null;
|
|
14422
|
+
const env = environments[id2]?.data;
|
|
14423
|
+
if (!env) return null;
|
|
14424
|
+
const all = Object.values(environments).map((e) => e.data);
|
|
14425
|
+
return resolveEnvironmentChain(env, all);
|
|
14426
|
+
}
|
|
14427
|
+
function useActiveEnvironment() {
|
|
14428
|
+
const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
|
|
14429
|
+
const environments = useStore((s) => s.environments);
|
|
14430
|
+
return reactExports.useMemo(
|
|
14431
|
+
() => resolveEnvironmentById(environments, activeEnvironmentId),
|
|
14432
|
+
[environments, activeEnvironmentId]
|
|
14433
|
+
);
|
|
14434
|
+
}
|
|
14344
14435
|
let rangeFrom = [], rangeTo = [];
|
|
14345
14436
|
(() => {
|
|
14346
14437
|
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);
|
|
@@ -33089,8 +33180,8 @@ class CompletionTooltip {
|
|
|
33089
33180
|
if (typeof section != "string" && section.header) {
|
|
33090
33181
|
ul.appendChild(section.header(section));
|
|
33091
33182
|
} else {
|
|
33092
|
-
let
|
|
33093
|
-
|
|
33183
|
+
let header2 = ul.appendChild(document.createElement("completion-section"));
|
|
33184
|
+
header2.textContent = name2;
|
|
33094
33185
|
}
|
|
33095
33186
|
}
|
|
33096
33187
|
}
|
|
@@ -34301,7 +34392,7 @@ const DYNAMIC_VAR_NAMES = [
|
|
|
34301
34392
|
"$randomHexColor"
|
|
34302
34393
|
];
|
|
34303
34394
|
const DYNAMIC_VAR_INFO = {
|
|
34304
|
-
$uuid: "Random UUID v4
|
|
34395
|
+
$uuid: "Random UUID v4 - generated fresh each send",
|
|
34305
34396
|
$timestamp: "Current Unix timestamp in milliseconds",
|
|
34306
34397
|
$isoTimestamp: "Current date/time as ISO 8601 string",
|
|
34307
34398
|
$randomInt: "Random integer between 0 and 1000",
|
|
@@ -34341,7 +34432,7 @@ const SP_RESPONSE_MEMBERS = [
|
|
|
34341
34432
|
{ label: "statusText", type: "property", detail: "string", info: "Status text only" },
|
|
34342
34433
|
{ label: "responseTime", type: "property", detail: "number", info: "Request duration in ms" },
|
|
34343
34434
|
{ label: "responseSize", type: "property", detail: "number", info: "Body size in bytes" },
|
|
34344
|
-
{ label: "headers", type: "property", info: "Response headers
|
|
34435
|
+
{ label: "headers", type: "property", info: "Response headers - use .get(name) or .toObject()" },
|
|
34345
34436
|
{ label: "json", type: "function", detail: "()", info: "Parse body as JSON and return it" },
|
|
34346
34437
|
{ label: "text", type: "function", detail: "()", info: "Return body as a raw string" }
|
|
34347
34438
|
];
|
|
@@ -34727,7 +34818,7 @@ function mockBodyCompletionExtension(pathParamNames = [], varNames = []) {
|
|
|
34727
34818
|
const RESPONSE_MEMBERS = [
|
|
34728
34819
|
{ label: "statusCode", type: "property", detail: "number", info: "HTTP status code to send" },
|
|
34729
34820
|
{ label: "body", type: "property", detail: "string", info: "Response body string (overrides template)" },
|
|
34730
|
-
{ label: "headers", type: "property", detail: "object", info: 'Response headers
|
|
34821
|
+
{ label: "headers", type: "property", detail: "object", info: 'Response headers - modify with response.headers["X-Foo"] = "bar"' }
|
|
34731
34822
|
];
|
|
34732
34823
|
const REQUEST_SCRIPT_MEMBERS = [
|
|
34733
34824
|
{ label: "params", type: "property", info: "URL path params { id, slug, … }" },
|
|
@@ -34785,12 +34876,11 @@ function extractScriptVarNames(script) {
|
|
|
34785
34876
|
return names2;
|
|
34786
34877
|
}
|
|
34787
34878
|
function useVarNames() {
|
|
34788
|
-
const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
|
|
34789
34879
|
const activeCollectionId = useStore((s) => s.activeCollectionId);
|
|
34790
|
-
const environments = useStore((s) => s.environments);
|
|
34791
34880
|
const collections = useStore((s) => s.collections);
|
|
34792
34881
|
const globals = useStore((s) => s.globals);
|
|
34793
34882
|
const sessionVars = useStore((s) => s.sessionVars);
|
|
34883
|
+
const activeEnv = useActiveEnvironment();
|
|
34794
34884
|
return reactExports.useMemo(() => {
|
|
34795
34885
|
const names2 = /* @__PURE__ */ new Set();
|
|
34796
34886
|
Object.keys(globals).forEach((k) => names2.add(k));
|
|
@@ -34805,41 +34895,35 @@ function useVarNames() {
|
|
|
34805
34895
|
}
|
|
34806
34896
|
}
|
|
34807
34897
|
}
|
|
34808
|
-
|
|
34809
|
-
|
|
34810
|
-
envVars.filter((v) => v.enabled && v.key).forEach((v) => names2.add(v.key));
|
|
34811
|
-
}
|
|
34898
|
+
const envVars = activeEnv?.variables ?? [];
|
|
34899
|
+
envVars.filter((v) => v.enabled && v.key).forEach((v) => names2.add(v.key));
|
|
34812
34900
|
return [...DYNAMIC_VAR_NAMES, ...Array.from(names2).sort()];
|
|
34813
34901
|
}, [
|
|
34814
|
-
|
|
34902
|
+
activeEnv,
|
|
34815
34903
|
activeCollectionId,
|
|
34816
|
-
environments,
|
|
34817
34904
|
collections,
|
|
34818
34905
|
globals,
|
|
34819
34906
|
sessionVars
|
|
34820
34907
|
]);
|
|
34821
34908
|
}
|
|
34822
34909
|
function useVarValues() {
|
|
34823
|
-
const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
|
|
34824
34910
|
const activeCollectionId = useStore((s) => s.activeCollectionId);
|
|
34825
|
-
const environments = useStore((s) => s.environments);
|
|
34826
34911
|
const collections = useStore((s) => s.collections);
|
|
34827
34912
|
const globals = useStore((s) => s.globals);
|
|
34913
|
+
const activeEnv = useActiveEnvironment();
|
|
34828
34914
|
const result = { ...globals };
|
|
34829
34915
|
if (activeCollectionId) {
|
|
34830
34916
|
const colVars = collections[activeCollectionId]?.data.collectionVariables ?? {};
|
|
34831
34917
|
Object.assign(result, colVars);
|
|
34832
34918
|
}
|
|
34833
|
-
|
|
34834
|
-
|
|
34835
|
-
|
|
34836
|
-
|
|
34837
|
-
|
|
34838
|
-
|
|
34839
|
-
|
|
34840
|
-
|
|
34841
|
-
result[v.key] = v.value;
|
|
34842
|
-
}
|
|
34919
|
+
for (const v of activeEnv?.variables ?? []) {
|
|
34920
|
+
if (!v.enabled || !v.key) continue;
|
|
34921
|
+
if (v.secret && v.secretEncrypted) {
|
|
34922
|
+
result[v.key] = "••••••••";
|
|
34923
|
+
} else if (v.envRef) {
|
|
34924
|
+
result[v.key] = `$${v.envRef}`;
|
|
34925
|
+
} else {
|
|
34926
|
+
result[v.key] = v.value;
|
|
34843
34927
|
}
|
|
34844
34928
|
}
|
|
34845
34929
|
return result;
|
|
@@ -35181,7 +35265,7 @@ function KVTable({ rows, onChange, keyPlaceholder = "Key", valuePlaceholder = "V
|
|
|
35181
35265
|
{
|
|
35182
35266
|
value: row.paramType ?? "query",
|
|
35183
35267
|
onChange: (e) => update(idx, { paramType: e.target.value }),
|
|
35184
|
-
title: (row.paramType ?? "query") === "path" ? "Path variable
|
|
35268
|
+
title: (row.paramType ?? "query") === "path" ? "Path variable - substituted into the URL via {{name}}" : "Query string parameter - appended as ?key=value",
|
|
35185
35269
|
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
35270
|
children: [
|
|
35187
35271
|
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "query", children: "query" }),
|
|
@@ -35624,7 +35708,7 @@ function BearerPanel({
|
|
|
35624
35708
|
"Token",
|
|
35625
35709
|
" ",
|
|
35626
35710
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 text-[10px]", children: [
|
|
35627
|
-
"
|
|
35711
|
+
"- supports ",
|
|
35628
35712
|
"{{variables}}"
|
|
35629
35713
|
] })
|
|
35630
35714
|
] }),
|
|
@@ -35707,7 +35791,7 @@ function FolderSettingsModal({ collectionId, folder, onClose }) {
|
|
|
35707
35791
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold", children: "Folder settings" }),
|
|
35708
35792
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-600 mt-0.5", children: [
|
|
35709
35793
|
folder.name,
|
|
35710
|
-
"
|
|
35794
|
+
" - auth and headers inherited by all requests in this folder"
|
|
35711
35795
|
] })
|
|
35712
35796
|
] }),
|
|
35713
35797
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-white text-lg leading-none", children: "×" })
|
|
@@ -35800,7 +35884,7 @@ function CollectionSettingsModal({ collection, onClose }) {
|
|
|
35800
35884
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold", children: "Collection settings" }),
|
|
35801
35885
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-600 mt-0.5", children: [
|
|
35802
35886
|
collection.name,
|
|
35803
|
-
"
|
|
35887
|
+
" - auth and headers inherited by all requests in this collection"
|
|
35804
35888
|
] })
|
|
35805
35889
|
] }),
|
|
35806
35890
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-white text-lg leading-none", children: "×" })
|
|
@@ -35907,7 +35991,7 @@ function CollectionSettingsModal({ collection, onClose }) {
|
|
|
35907
35991
|
}
|
|
35908
35992
|
);
|
|
35909
35993
|
}
|
|
35910
|
-
const { electron: electron$
|
|
35994
|
+
const { electron: electron$q } = window;
|
|
35911
35995
|
function normalisePath(url) {
|
|
35912
35996
|
let path = url.replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/, "");
|
|
35913
35997
|
if (!path.startsWith("/")) path = "/" + path;
|
|
@@ -35994,7 +36078,7 @@ function SchemaSyncModal({
|
|
|
35994
36078
|
setLoading(true);
|
|
35995
36079
|
setError(null);
|
|
35996
36080
|
try {
|
|
35997
|
-
const entries = await electron$
|
|
36081
|
+
const entries = await electron$q.extractOpenApiSchemas();
|
|
35998
36082
|
if (!entries) {
|
|
35999
36083
|
setLoading(false);
|
|
36000
36084
|
return;
|
|
@@ -36013,7 +36097,7 @@ function SchemaSyncModal({
|
|
|
36013
36097
|
setLoading(true);
|
|
36014
36098
|
setError(null);
|
|
36015
36099
|
try {
|
|
36016
|
-
const entries = await electron$
|
|
36100
|
+
const entries = await electron$q.extractOpenApiSchemasFromUrl(trimmed);
|
|
36017
36101
|
setSpecEntries(entries);
|
|
36018
36102
|
autoSelectChanged(entries);
|
|
36019
36103
|
} catch (err) {
|
|
@@ -36046,7 +36130,7 @@ function SchemaSyncModal({
|
|
|
36046
36130
|
}
|
|
36047
36131
|
const entry = useStore.getState().collections[collectionId];
|
|
36048
36132
|
if (entry) {
|
|
36049
|
-
await electron$
|
|
36133
|
+
await electron$q.saveCollection(entry.relPath, entry.data);
|
|
36050
36134
|
markCollectionClean(collectionId);
|
|
36051
36135
|
}
|
|
36052
36136
|
onClose();
|
|
@@ -36065,7 +36149,7 @@ function SchemaSyncModal({
|
|
|
36065
36149
|
children: [
|
|
36066
36150
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
36067
36151
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-sm font-semibold text-surface-100", children: [
|
|
36068
|
-
"Sync schemas
|
|
36152
|
+
"Sync schemas - ",
|
|
36069
36153
|
scopeLabel
|
|
36070
36154
|
] }),
|
|
36071
36155
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-500 hover:text-surface-300 text-lg leading-none", children: "×" })
|
|
@@ -36184,7 +36268,7 @@ function SchemaSyncModal({
|
|
|
36184
36268
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "text-sm font-semibold text-surface-100", children: "Sync schemas from OpenAPI" }),
|
|
36185
36269
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-500 hover:text-surface-300 text-lg leading-none", children: "×" })
|
|
36186
36270
|
] }),
|
|
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
|
|
36271
|
+
/* @__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
36272
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
36189
36273
|
"button",
|
|
36190
36274
|
{
|
|
@@ -36260,7 +36344,7 @@ const METHOD_COLORS$1 = {
|
|
|
36260
36344
|
DELETE: "text-red-400",
|
|
36261
36345
|
HEAD: "text-purple-400",
|
|
36262
36346
|
OPTIONS: "text-surface-400",
|
|
36263
|
-
QUERY: "text-
|
|
36347
|
+
QUERY: "text-fuchsia-400",
|
|
36264
36348
|
ANY: "text-surface-400"
|
|
36265
36349
|
};
|
|
36266
36350
|
function getMethodColor(method) {
|
|
@@ -54698,9 +54782,8 @@ function GraphQLEditor({ request, onChange }) {
|
|
|
54698
54782
|
() => request.body.graphql ?? EMPTY_GQL,
|
|
54699
54783
|
[request.body.graphql]
|
|
54700
54784
|
);
|
|
54701
|
-
const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
|
|
54702
54785
|
const activeCollectionId = useStore((s) => s.activeCollectionId);
|
|
54703
|
-
const envData =
|
|
54786
|
+
const envData = useActiveEnvironment();
|
|
54704
54787
|
const colVarsData = useStore((s) => activeCollectionId ? s.collections[activeCollectionId]?.data.collectionVariables : null);
|
|
54705
54788
|
const globals = useStore((s) => s.globals);
|
|
54706
54789
|
const hookVars = reactExports.useMemo(() => {
|
|
@@ -55303,7 +55386,7 @@ function withContentType(headers, value) {
|
|
|
55303
55386
|
if (idx === -1) return [...headers, next];
|
|
55304
55387
|
return headers.map((h, i) => i === idx ? { ...h, value, enabled: true } : h);
|
|
55305
55388
|
}
|
|
55306
|
-
const { electron: electron$
|
|
55389
|
+
const { electron: electron$p } = window;
|
|
55307
55390
|
function ParamTree({ params, depth = 0 }) {
|
|
55308
55391
|
if (params.length === 0) {
|
|
55309
55392
|
return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 italic", children: "No parameters declared in WSDL." });
|
|
@@ -55329,7 +55412,7 @@ function SoapEditor({ request, onChange }) {
|
|
|
55329
55412
|
let cancelled = false;
|
|
55330
55413
|
(async () => {
|
|
55331
55414
|
try {
|
|
55332
|
-
const result = await electron$
|
|
55415
|
+
const result = await electron$p.wsdlFetch(url);
|
|
55333
55416
|
if (cancelled) return;
|
|
55334
55417
|
setOperations(result.operations);
|
|
55335
55418
|
setEndpoints(result.endpoints);
|
|
@@ -55366,7 +55449,7 @@ function SoapEditor({ request, onChange }) {
|
|
|
55366
55449
|
setFetching(true);
|
|
55367
55450
|
setFetchError(null);
|
|
55368
55451
|
try {
|
|
55369
|
-
const result = await electron$
|
|
55452
|
+
const result = await electron$p.wsdlFetch(soap.wsdlUrl.trim());
|
|
55370
55453
|
setOperations(result.operations);
|
|
55371
55454
|
setEndpoints(result.endpoints);
|
|
55372
55455
|
setTargetNs(result.targetNamespace);
|
|
@@ -55507,7 +55590,7 @@ function SoapEditor({ request, onChange }) {
|
|
|
55507
55590
|
"SOAPAction: ",
|
|
55508
55591
|
soap.soapAction
|
|
55509
55592
|
] }),
|
|
55510
|
-
!soap.operationName && !soap.soapAction && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] text-surface-500", children: soap.wsdlUrl?.trim() ? "WSDL not loaded
|
|
55593
|
+
!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." })
|
|
55511
55594
|
] }),
|
|
55512
55595
|
soap.wsdlUrl?.trim() && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
55513
55596
|
"button",
|
|
@@ -55537,7 +55620,7 @@ function SoapEditor({ request, onChange }) {
|
|
|
55537
55620
|
/* @__PURE__ */ jsxRuntimeExports.jsx("em", { children: "Fetch WSDL" }),
|
|
55538
55621
|
"."
|
|
55539
55622
|
] }),
|
|
55540
|
-
/* @__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
|
|
55623
|
+
/* @__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." })
|
|
55541
55624
|
] })
|
|
55542
55625
|
] });
|
|
55543
55626
|
}
|
|
@@ -55636,7 +55719,7 @@ function BodyTab({ request, onChange }) {
|
|
|
55636
55719
|
mode === "soap" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SoapEditor, { request, onChange }) })
|
|
55637
55720
|
] });
|
|
55638
55721
|
}
|
|
55639
|
-
const { electron: electron$
|
|
55722
|
+
const { electron: electron$o } = window;
|
|
55640
55723
|
const AUTH_TYPES = ["none", "bearer", "basic", "digest", "ntlm", "apikey", "oauth2"];
|
|
55641
55724
|
function AuthTab({ request, onChange }) {
|
|
55642
55725
|
const auth = request.auth;
|
|
@@ -55650,7 +55733,7 @@ function AuthTab({ request, onChange }) {
|
|
|
55650
55733
|
}
|
|
55651
55734
|
async function saveSecret(ref2) {
|
|
55652
55735
|
if (!secretValue || !ref2) return;
|
|
55653
|
-
await electron$
|
|
55736
|
+
await electron$o.setSecret(ref2, secretValue);
|
|
55654
55737
|
setSaved(true);
|
|
55655
55738
|
setSecretValue("");
|
|
55656
55739
|
setTimeout(() => setSaved(false), 2e3);
|
|
@@ -55662,7 +55745,7 @@ function AuthTab({ request, onChange }) {
|
|
|
55662
55745
|
setOauth2Error("");
|
|
55663
55746
|
try {
|
|
55664
55747
|
const vars = {};
|
|
55665
|
-
const result = await electron$
|
|
55748
|
+
const result = await electron$o.oauth2StartFlow(oauth2Auth, vars);
|
|
55666
55749
|
setAuth({
|
|
55667
55750
|
oauth2CachedToken: result.accessToken,
|
|
55668
55751
|
oauth2TokenExpiry: result.expiresAt
|
|
@@ -55680,7 +55763,7 @@ function AuthTab({ request, onChange }) {
|
|
|
55680
55763
|
setOauth2Status("fetching");
|
|
55681
55764
|
setOauth2Error("");
|
|
55682
55765
|
try {
|
|
55683
|
-
const result = await electron$
|
|
55766
|
+
const result = await electron$o.oauth2RefreshToken(oauth2Auth, {}, oauth2RefreshToken);
|
|
55684
55767
|
setAuth({
|
|
55685
55768
|
oauth2CachedToken: result.accessToken,
|
|
55686
55769
|
oauth2TokenExpiry: result.expiresAt
|
|
@@ -56453,7 +56536,7 @@ const SNIPPET_GROUPS = [
|
|
|
56453
56536
|
]
|
|
56454
56537
|
},
|
|
56455
56538
|
{
|
|
56456
|
-
group: "Variables
|
|
56539
|
+
group: "Variables - Get",
|
|
56457
56540
|
items: [
|
|
56458
56541
|
{
|
|
56459
56542
|
label: "Get variable",
|
|
@@ -56474,7 +56557,7 @@ const SNIPPET_GROUPS = [
|
|
|
56474
56557
|
]
|
|
56475
56558
|
},
|
|
56476
56559
|
{
|
|
56477
|
-
group: "Variables
|
|
56560
|
+
group: "Variables - Set",
|
|
56478
56561
|
items: [
|
|
56479
56562
|
{
|
|
56480
56563
|
label: "Save token from response (use in next requests)",
|
|
@@ -56500,7 +56583,7 @@ sp.collectionVariables.set("token", json.access_token);`
|
|
|
56500
56583
|
]
|
|
56501
56584
|
},
|
|
56502
56585
|
{
|
|
56503
|
-
group: "Variables
|
|
56586
|
+
group: "Variables - Clear",
|
|
56504
56587
|
items: [
|
|
56505
56588
|
{
|
|
56506
56589
|
label: "Clear variable",
|
|
@@ -62943,7 +63026,7 @@ function SchemaTab({ request, onChange }) {
|
|
|
62943
63026
|
)
|
|
62944
63027
|
] })
|
|
62945
63028
|
] }),
|
|
62946
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600", children: "Standalone schema for ad-hoc validation. Independent of the contract
|
|
63029
|
+
/* @__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." }),
|
|
62947
63030
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border border-surface-700 rounded overflow-hidden", children: [
|
|
62948
63031
|
/* @__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(
|
|
62949
63032
|
"button",
|
|
@@ -62974,9 +63057,9 @@ function SchemaTab({ request, onChange }) {
|
|
|
62974
63057
|
)
|
|
62975
63058
|
] }),
|
|
62976
63059
|
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 }),
|
|
62977
|
-
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
|
|
63060
|
+
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: [
|
|
62978
63061
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-red-400 font-semibold", children: [
|
|
62979
|
-
"Invalid
|
|
63062
|
+
"Invalid: ",
|
|
62980
63063
|
result.errors.length,
|
|
62981
63064
|
" error",
|
|
62982
63065
|
result.errors.length !== 1 ? "s" : ""
|
|
@@ -62988,7 +63071,7 @@ function SchemaTab({ request, onChange }) {
|
|
|
62988
63071
|
] }) })
|
|
62989
63072
|
] });
|
|
62990
63073
|
}
|
|
62991
|
-
const { electron: electron$
|
|
63074
|
+
const { electron: electron$n } = window;
|
|
62992
63075
|
const EMPTY = { statusCode: 200, headers: [], bodySchema: "" };
|
|
62993
63076
|
function ContractTab({ request, onChange }) {
|
|
62994
63077
|
const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
|
|
@@ -63002,7 +63085,7 @@ function ContractTab({ request, onChange }) {
|
|
|
63002
63085
|
if (!lastResponse?.body) return;
|
|
63003
63086
|
setInferring(true);
|
|
63004
63087
|
try {
|
|
63005
|
-
const schema = await electron$
|
|
63088
|
+
const schema = await electron$n.inferContractSchema(lastResponse.body);
|
|
63006
63089
|
if (schema) update({ bodySchema: schema });
|
|
63007
63090
|
} finally {
|
|
63008
63091
|
setInferring(false);
|
|
@@ -63023,7 +63106,7 @@ function ContractTab({ request, onChange }) {
|
|
|
63023
63106
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-4 h-full min-h-0", children: [
|
|
63024
63107
|
/* @__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: [
|
|
63025
63108
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `w-2 h-2 rounded-full ${hasContract ? "bg-blue-400" : "bg-surface-600"}` }),
|
|
63026
|
-
hasContract ? "Contract defined
|
|
63109
|
+
hasContract ? "Contract defined - will be verified in Contract panel" : "No contract defined yet"
|
|
63027
63110
|
] }),
|
|
63028
63111
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
63029
63112
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1.5", children: "Expected Status Code" }),
|
|
@@ -63128,7 +63211,7 @@ function ContractTab({ request, onChange }) {
|
|
|
63128
63211
|
] })
|
|
63129
63212
|
] });
|
|
63130
63213
|
}
|
|
63131
|
-
const { electron: electron$
|
|
63214
|
+
const { electron: electron$m } = window;
|
|
63132
63215
|
function formatTime$1(ts) {
|
|
63133
63216
|
const d = new Date(ts);
|
|
63134
63217
|
const hh = String(d.getHours()).padStart(2, "0");
|
|
@@ -63148,14 +63231,14 @@ function WebSocketPanel({ request }) {
|
|
|
63148
63231
|
const [sendText, setSendText] = reactExports.useState("");
|
|
63149
63232
|
const logEndRef = reactExports.useRef(null);
|
|
63150
63233
|
reactExports.useEffect(() => {
|
|
63151
|
-
electron$
|
|
63234
|
+
electron$m.onWsMessage(({ requestId, message }) => {
|
|
63152
63235
|
addWsMessage(requestId, message);
|
|
63153
63236
|
});
|
|
63154
|
-
electron$
|
|
63237
|
+
electron$m.onWsStatus(({ requestId, status, error: error2 }) => {
|
|
63155
63238
|
setWsStatus(requestId, status, error2);
|
|
63156
63239
|
});
|
|
63157
63240
|
return () => {
|
|
63158
|
-
electron$
|
|
63241
|
+
electron$m.offWsEvents();
|
|
63159
63242
|
};
|
|
63160
63243
|
}, [addWsMessage, setWsStatus]);
|
|
63161
63244
|
reactExports.useEffect(() => {
|
|
@@ -63168,19 +63251,19 @@ function WebSocketPanel({ request }) {
|
|
|
63168
63251
|
if (h.enabled && h.key) headers[h.key] = h.value;
|
|
63169
63252
|
}
|
|
63170
63253
|
try {
|
|
63171
|
-
await electron$
|
|
63254
|
+
await electron$m.wsConnect(request.id, request.url, headers);
|
|
63172
63255
|
} catch (err) {
|
|
63173
63256
|
setWsStatus(request.id, "error", err instanceof Error ? err.message : String(err));
|
|
63174
63257
|
}
|
|
63175
63258
|
}
|
|
63176
63259
|
async function disconnect() {
|
|
63177
|
-
await electron$
|
|
63260
|
+
await electron$m.wsDisconnect(request.id);
|
|
63178
63261
|
}
|
|
63179
63262
|
async function sendMessage() {
|
|
63180
63263
|
const text = sendText.trim();
|
|
63181
63264
|
if (!text || !isConnected) return;
|
|
63182
63265
|
try {
|
|
63183
|
-
await electron$
|
|
63266
|
+
await electron$m.wsSend(request.id, text);
|
|
63184
63267
|
const msg = {
|
|
63185
63268
|
id: crypto.randomUUID(),
|
|
63186
63269
|
direction: "sent",
|
|
@@ -63283,7 +63366,394 @@ function WebSocketPanel({ request }) {
|
|
|
63283
63366
|
] })
|
|
63284
63367
|
] });
|
|
63285
63368
|
}
|
|
63286
|
-
|
|
63369
|
+
function useToast(durationMs = 3e3) {
|
|
63370
|
+
const [toast, setToast] = reactExports.useState(null);
|
|
63371
|
+
const timer = reactExports.useRef(null);
|
|
63372
|
+
function show(msg, ok) {
|
|
63373
|
+
if (timer.current) clearTimeout(timer.current);
|
|
63374
|
+
setToast({ msg, ok });
|
|
63375
|
+
timer.current = setTimeout(() => setToast(null), durationMs);
|
|
63376
|
+
}
|
|
63377
|
+
return { toast, show };
|
|
63378
|
+
}
|
|
63379
|
+
function Toast({ toast }) {
|
|
63380
|
+
if (!toast) return null;
|
|
63381
|
+
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 });
|
|
63382
|
+
}
|
|
63383
|
+
function statusColor$3(code2) {
|
|
63384
|
+
const d = String(code2)[0];
|
|
63385
|
+
return d === "2" ? "text-emerald-400" : d === "3" ? "text-amber-400" : "text-red-400";
|
|
63386
|
+
}
|
|
63387
|
+
const ORACLE_META = {
|
|
63388
|
+
"never-5xx": { label: "Server crash", badge: "bg-red-900/50 text-red-400", border: "border-red-600" },
|
|
63389
|
+
"accepted-invalid": { label: "Accepted invalid", badge: "bg-amber-900/50 text-amber-400", border: "border-amber-500" },
|
|
63390
|
+
"undocumented-status": { label: "Undocumented status", badge: "bg-blue-900/50 text-blue-400", border: "border-blue-500" },
|
|
63391
|
+
"response-schema": { label: "Response schema", badge: "bg-orange-900/50 text-orange-400", border: "border-orange-500" }
|
|
63392
|
+
};
|
|
63393
|
+
function FindingRow({ finding, onCopy }) {
|
|
63394
|
+
const [open, setOpen] = reactExports.useState(false);
|
|
63395
|
+
const meta2 = ORACLE_META[finding.oracle];
|
|
63396
|
+
const req = finding.request;
|
|
63397
|
+
const sentText = `${req.method} ${req.url}
|
|
63398
|
+
` + Object.entries(req.headers).map(([k, v]) => `${k}: ${v}`).join("\n") + (req.body ? `
|
|
63399
|
+
|
|
63400
|
+
${req.body}` : "");
|
|
63401
|
+
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: [
|
|
63402
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
63403
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] font-bold px-1.5 py-0.5 rounded uppercase tracking-wide ${meta2.badge}`, children: meta2.label }),
|
|
63404
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs font-mono font-bold ${statusColor$3(finding.status)}`, children: finding.status === 0 ? "ERR" : finding.status }),
|
|
63405
|
+
/* @__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 }),
|
|
63406
|
+
/* @__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 })
|
|
63407
|
+
] }),
|
|
63408
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-200", children: finding.message }),
|
|
63409
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-surface-400", children: finding.mutation.description }),
|
|
63410
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63411
|
+
"button",
|
|
63412
|
+
{
|
|
63413
|
+
onClick: () => setOpen((v) => !v),
|
|
63414
|
+
className: "text-[10px] text-surface-500 hover:text-surface-300 transition-colors self-start",
|
|
63415
|
+
children: open ? "▲ Hide sent request" : "▼ Show sent request"
|
|
63416
|
+
}
|
|
63417
|
+
),
|
|
63418
|
+
open && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2 mt-0.5", children: [
|
|
63419
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-surface-800 bg-surface-900 overflow-hidden", children: [
|
|
63420
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-2.5 py-1.5 border-b border-surface-800", children: [
|
|
63421
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[11px] font-bold font-mono ${getMethodColor(req.method)}`, children: req.method }),
|
|
63422
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] font-mono text-surface-400 truncate flex-1", children: req.url }),
|
|
63423
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63424
|
+
"button",
|
|
63425
|
+
{
|
|
63426
|
+
onClick: () => onCopy(sentText),
|
|
63427
|
+
className: "text-[10px] text-surface-500 hover:text-surface-200 transition-colors shrink-0",
|
|
63428
|
+
title: "Copy the sent request",
|
|
63429
|
+
children: "Copy"
|
|
63430
|
+
}
|
|
63431
|
+
)
|
|
63432
|
+
] }),
|
|
63433
|
+
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 })
|
|
63434
|
+
] }),
|
|
63435
|
+
finding.responseSample && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded border border-surface-800 bg-surface-900 overflow-hidden", children: [
|
|
63436
|
+
/* @__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" }),
|
|
63437
|
+
/* @__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 })
|
|
63438
|
+
] })
|
|
63439
|
+
] })
|
|
63440
|
+
] });
|
|
63441
|
+
}
|
|
63442
|
+
function OperationCard({ result, onCopy }) {
|
|
63443
|
+
const [open, setOpen] = reactExports.useState(true);
|
|
63444
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border border-red-800/60 overflow-hidden", children: [
|
|
63445
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
63446
|
+
"button",
|
|
63447
|
+
{
|
|
63448
|
+
onClick: () => setOpen((v) => !v),
|
|
63449
|
+
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",
|
|
63450
|
+
children: [
|
|
63451
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-bold font-mono w-14 ${getMethodColor(result.method)}`, children: result.method }),
|
|
63452
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 text-sm text-white truncate", children: result.requestName }),
|
|
63453
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden lg:block text-[11px] text-surface-500 font-mono truncate max-w-[260px]", children: result.url }),
|
|
63454
|
+
/* @__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: [
|
|
63455
|
+
result.findings.length,
|
|
63456
|
+
" ",
|
|
63457
|
+
result.findings.length === 1 ? "finding" : "findings"
|
|
63458
|
+
] }),
|
|
63459
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 text-[11px] text-surface-500", children: [
|
|
63460
|
+
result.cases,
|
|
63461
|
+
" cases"
|
|
63462
|
+
] }),
|
|
63463
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-surface-600 text-xs ml-1", children: open ? "▲" : "▼" })
|
|
63464
|
+
]
|
|
63465
|
+
}
|
|
63466
|
+
),
|
|
63467
|
+
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)) })
|
|
63468
|
+
] });
|
|
63469
|
+
}
|
|
63470
|
+
function statusTone(status, finding) {
|
|
63471
|
+
if (finding) return "text-red-400";
|
|
63472
|
+
if (status === 0) return "text-surface-500";
|
|
63473
|
+
if (status >= 200 && status < 300) return "text-emerald-400";
|
|
63474
|
+
if (status >= 400) return "text-amber-400";
|
|
63475
|
+
return "text-surface-300";
|
|
63476
|
+
}
|
|
63477
|
+
function TraceRow({ trace, onCopy }) {
|
|
63478
|
+
const [open, setOpen] = reactExports.useState(false);
|
|
63479
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-surface-800 first:border-t-0", children: [
|
|
63480
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
63481
|
+
"button",
|
|
63482
|
+
{
|
|
63483
|
+
onClick: () => setOpen((v) => !v),
|
|
63484
|
+
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]",
|
|
63485
|
+
children: [
|
|
63486
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-surface-600 w-3", children: open ? "▾" : "▸" }),
|
|
63487
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 font-bold w-9 ${statusTone(trace.status, trace.finding)}`, children: trace.status || "ERR" }),
|
|
63488
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-surface-300", children: trace.mutation.target }),
|
|
63489
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500 truncate", children: trace.mutation.kind }),
|
|
63490
|
+
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" })
|
|
63491
|
+
]
|
|
63492
|
+
}
|
|
63493
|
+
),
|
|
63494
|
+
open && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 pb-3 pt-1 flex flex-col gap-2 bg-surface-950/40", children: [
|
|
63495
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-surface-400", children: trace.mutation.description }),
|
|
63496
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
63497
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
63498
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Request" }),
|
|
63499
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63500
|
+
"button",
|
|
63501
|
+
{
|
|
63502
|
+
onClick: () => onCopy(`${trace.request.method} ${trace.request.url}
|
|
63503
|
+
|
|
63504
|
+
${trace.request.body ?? ""}`),
|
|
63505
|
+
className: "text-[10px] text-surface-500 hover:text-surface-200 transition-colors",
|
|
63506
|
+
children: "copy"
|
|
63507
|
+
}
|
|
63508
|
+
)
|
|
63509
|
+
] }),
|
|
63510
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] font-mono text-surface-500 break-all", children: [
|
|
63511
|
+
trace.request.method,
|
|
63512
|
+
" ",
|
|
63513
|
+
trace.request.url
|
|
63514
|
+
] }),
|
|
63515
|
+
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 })
|
|
63516
|
+
] }),
|
|
63517
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
63518
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: [
|
|
63519
|
+
"Response (",
|
|
63520
|
+
trace.status || "no response",
|
|
63521
|
+
")"
|
|
63522
|
+
] }),
|
|
63523
|
+
/* @__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)" })
|
|
63524
|
+
] })
|
|
63525
|
+
] })
|
|
63526
|
+
] });
|
|
63527
|
+
}
|
|
63528
|
+
function TraceCard({ result, onCopy }) {
|
|
63529
|
+
const [open, setOpen] = reactExports.useState(false);
|
|
63530
|
+
if (!result.trace?.length) return null;
|
|
63531
|
+
const findingCount = result.trace.filter((t2) => t2.finding).length;
|
|
63532
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border border-surface-700 overflow-hidden", children: [
|
|
63533
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
63534
|
+
"button",
|
|
63535
|
+
{
|
|
63536
|
+
onClick: () => setOpen((v) => !v),
|
|
63537
|
+
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",
|
|
63538
|
+
children: [
|
|
63539
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-bold font-mono w-14 ${getMethodColor(result.method)}`, children: result.method }),
|
|
63540
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 text-xs text-surface-200 truncate", children: result.requestName }),
|
|
63541
|
+
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 }),
|
|
63542
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 text-[11px] text-surface-500", children: [
|
|
63543
|
+
result.trace.length,
|
|
63544
|
+
" cases sent"
|
|
63545
|
+
] }),
|
|
63546
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-surface-600 text-xs", children: open ? "▲" : "▼" })
|
|
63547
|
+
]
|
|
63548
|
+
}
|
|
63549
|
+
),
|
|
63550
|
+
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)) })
|
|
63551
|
+
] });
|
|
63552
|
+
}
|
|
63553
|
+
function FuzzResultsPanel({ report, onClear }) {
|
|
63554
|
+
const { toast, show: showToast } = useToast();
|
|
63555
|
+
async function copy(text) {
|
|
63556
|
+
try {
|
|
63557
|
+
await navigator.clipboard.writeText(text);
|
|
63558
|
+
showToast("Sent request copied to clipboard.", true);
|
|
63559
|
+
} catch (e) {
|
|
63560
|
+
showToast(e instanceof Error ? e.message : "Copy failed.", false);
|
|
63561
|
+
}
|
|
63562
|
+
}
|
|
63563
|
+
const withFindings = report.results.filter((r) => r.findings.length > 0);
|
|
63564
|
+
const clean = report.results.filter((r) => r.findings.length === 0);
|
|
63565
|
+
const clean5xx = report.totalFindings === 0;
|
|
63566
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full overflow-hidden", children: [
|
|
63567
|
+
/* @__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: [
|
|
63568
|
+
/* @__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" : ""}` }),
|
|
63569
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-sm text-surface-400", children: [
|
|
63570
|
+
report.totalCases,
|
|
63571
|
+
" case",
|
|
63572
|
+
report.totalCases !== 1 ? "s" : "",
|
|
63573
|
+
" across ",
|
|
63574
|
+
report.results.length,
|
|
63575
|
+
" operation",
|
|
63576
|
+
report.results.length !== 1 ? "s" : ""
|
|
63577
|
+
] }),
|
|
63578
|
+
/* @__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" }),
|
|
63579
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] bg-surface-800 text-surface-400 px-2 py-0.5 rounded font-mono", children: [
|
|
63580
|
+
"seed ",
|
|
63581
|
+
report.seed
|
|
63582
|
+
] }),
|
|
63583
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-surface-500 ml-auto", children: [
|
|
63584
|
+
report.durationMs,
|
|
63585
|
+
"ms"
|
|
63586
|
+
] }),
|
|
63587
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63588
|
+
"button",
|
|
63589
|
+
{
|
|
63590
|
+
onClick: onClear,
|
|
63591
|
+
className: "text-[11px] text-surface-600 hover:text-surface-300 transition-colors",
|
|
63592
|
+
title: "Clear results",
|
|
63593
|
+
children: "Clear"
|
|
63594
|
+
}
|
|
63595
|
+
)
|
|
63596
|
+
] }),
|
|
63597
|
+
(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: [
|
|
63598
|
+
report.skippedWrites > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-amber-400", children: [
|
|
63599
|
+
report.skippedWrites,
|
|
63600
|
+
" write-method request",
|
|
63601
|
+
report.skippedWrites !== 1 ? "s" : "",
|
|
63602
|
+
" skipped (enable Include write methods)."
|
|
63603
|
+
] }),
|
|
63604
|
+
report.skippedNoBody > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-surface-500", children: [
|
|
63605
|
+
report.skippedNoBody,
|
|
63606
|
+
" request",
|
|
63607
|
+
report.skippedNoBody !== 1 ? "s" : "",
|
|
63608
|
+
" had no body to fuzz."
|
|
63609
|
+
] })
|
|
63610
|
+
] }),
|
|
63611
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }),
|
|
63612
|
+
/* @__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: [
|
|
63613
|
+
withFindings.map((r) => /* @__PURE__ */ jsxRuntimeExports.jsx(OperationCard, { result: r, onCopy: copy }, r.requestId)),
|
|
63614
|
+
clean.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-lg border border-surface-700 bg-surface-800 px-4 py-3", children: [
|
|
63615
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-emerald-400 font-medium mb-1.5", children: [
|
|
63616
|
+
clean.length,
|
|
63617
|
+
" operation",
|
|
63618
|
+
clean.length !== 1 ? "s" : "",
|
|
63619
|
+
" clean"
|
|
63620
|
+
] }),
|
|
63621
|
+
/* @__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: [
|
|
63622
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-bold font-mono w-12 shrink-0 ${getMethodColor(r.method)}`, children: r.method }),
|
|
63623
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-300 truncate", children: r.requestName }),
|
|
63624
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-600 ml-auto shrink-0", children: [
|
|
63625
|
+
r.cases,
|
|
63626
|
+
" cases"
|
|
63627
|
+
] })
|
|
63628
|
+
] }, r.requestId)) })
|
|
63629
|
+
] }),
|
|
63630
|
+
report.results.some((r) => r.trace?.length) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
63631
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium mt-2", children: "All cases sent" }),
|
|
63632
|
+
report.results.filter((r) => r.trace?.length).map((r) => /* @__PURE__ */ jsxRuntimeExports.jsx(TraceCard, { result: r, onCopy: copy }, r.requestId))
|
|
63633
|
+
] }),
|
|
63634
|
+
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." })
|
|
63635
|
+
] }) })
|
|
63636
|
+
] });
|
|
63637
|
+
}
|
|
63638
|
+
const { electron: electron$l } = window;
|
|
63639
|
+
const WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
63640
|
+
function FuzzModal({ request, onClose }) {
|
|
63641
|
+
const environments = useStore((s) => s.environments);
|
|
63642
|
+
const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
|
|
63643
|
+
const activeCollectionId = useStore((s) => s.activeCollectionId);
|
|
63644
|
+
const collections = useStore((s) => s.collections);
|
|
63645
|
+
const snapshots = useStore((s) => s.contractSnapshots);
|
|
63646
|
+
const activeSnapshotRelPath = useStore((s) => s.activeContractSnapshotRelPath);
|
|
63647
|
+
const [cases, setCases] = reactExports.useState(40);
|
|
63648
|
+
const [seed, setSeed] = reactExports.useState(1);
|
|
63649
|
+
const [trace, setTrace] = reactExports.useState(true);
|
|
63650
|
+
const [running, setRunning] = reactExports.useState(false);
|
|
63651
|
+
const [error2, setError] = reactExports.useState(null);
|
|
63652
|
+
const [report, setReport] = reactExports.useState(null);
|
|
63653
|
+
const isWrite = WRITE_METHODS.has(request.method);
|
|
63654
|
+
const snapshotList = Object.entries(snapshots).map(([relPath, s]) => ({ relPath, snapshot: s }));
|
|
63655
|
+
const [snapshotRelPath, setSnapshotRelPath] = reactExports.useState(activeSnapshotRelPath ?? "");
|
|
63656
|
+
async function run() {
|
|
63657
|
+
setRunning(true);
|
|
63658
|
+
setError(null);
|
|
63659
|
+
setReport(null);
|
|
63660
|
+
try {
|
|
63661
|
+
const env = resolveEnvironmentById(environments, activeEnvironmentId);
|
|
63662
|
+
const envVars = env ? Object.fromEntries(env.variables.filter((v) => v.enabled).map((v) => [v.key, v.value])) : {};
|
|
63663
|
+
const collectionVars = activeCollectionId ? collections[activeCollectionId]?.data.collectionVariables ?? {} : {};
|
|
63664
|
+
const result = await electron$l.fuzzContracts({
|
|
63665
|
+
requests: [request],
|
|
63666
|
+
envVars,
|
|
63667
|
+
collectionVars,
|
|
63668
|
+
specSnapshotRelPath: snapshotRelPath || void 0,
|
|
63669
|
+
// No providerBaseUrl: the request is fuzzed against its own URL.
|
|
63670
|
+
casesPerOperation: cases,
|
|
63671
|
+
seed,
|
|
63672
|
+
trace,
|
|
63673
|
+
includeWrites: true
|
|
63674
|
+
// a per-request run is an explicit choice to fuzz this request
|
|
63675
|
+
});
|
|
63676
|
+
setReport(result);
|
|
63677
|
+
} catch (e) {
|
|
63678
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
63679
|
+
} finally {
|
|
63680
|
+
setRunning(false);
|
|
63681
|
+
}
|
|
63682
|
+
}
|
|
63683
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63684
|
+
Modal,
|
|
63685
|
+
{
|
|
63686
|
+
onClose,
|
|
63687
|
+
title: `Fuzz: ${request.name}`,
|
|
63688
|
+
subtitle: `${request.method} ${request.url}`,
|
|
63689
|
+
panelClassName: "bg-surface-900 border border-surface-800 rounded-lg shadow-2xl w-[720px] flex flex-col max-h-[85vh]",
|
|
63690
|
+
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col min-h-0 flex-1", children: [
|
|
63691
|
+
/* @__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: [
|
|
63692
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1", children: [
|
|
63693
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Cases" }),
|
|
63694
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63695
|
+
"input",
|
|
63696
|
+
{
|
|
63697
|
+
type: "number",
|
|
63698
|
+
min: 1,
|
|
63699
|
+
value: cases,
|
|
63700
|
+
onChange: (e) => setCases(Math.max(1, Number(e.target.value))),
|
|
63701
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs w-20 focus:outline-none focus:border-blue-500"
|
|
63702
|
+
}
|
|
63703
|
+
)
|
|
63704
|
+
] }),
|
|
63705
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1", children: [
|
|
63706
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Seed" }),
|
|
63707
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63708
|
+
"input",
|
|
63709
|
+
{
|
|
63710
|
+
type: "number",
|
|
63711
|
+
value: seed,
|
|
63712
|
+
onChange: (e) => setSeed(Number(e.target.value)),
|
|
63713
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs w-20 focus:outline-none focus:border-blue-500"
|
|
63714
|
+
}
|
|
63715
|
+
)
|
|
63716
|
+
] }),
|
|
63717
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1 flex-1 min-w-[180px]", children: [
|
|
63718
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Spec (optional)" }),
|
|
63719
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
63720
|
+
"select",
|
|
63721
|
+
{
|
|
63722
|
+
value: snapshotRelPath,
|
|
63723
|
+
onChange: (e) => setSnapshotRelPath(e.target.value),
|
|
63724
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500",
|
|
63725
|
+
children: [
|
|
63726
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "Request body (no spec)" }),
|
|
63727
|
+
snapshotList.map(({ relPath, snapshot }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: relPath, children: snapshot.name }, relPath))
|
|
63728
|
+
]
|
|
63729
|
+
}
|
|
63730
|
+
)
|
|
63731
|
+
] }),
|
|
63732
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-1.5 text-[11px] text-surface-400 select-none", children: [
|
|
63733
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", checked: trace, onChange: (e) => setTrace(e.target.checked) }),
|
|
63734
|
+
"Record all cases"
|
|
63735
|
+
] }),
|
|
63736
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
63737
|
+
"button",
|
|
63738
|
+
{
|
|
63739
|
+
onClick: run,
|
|
63740
|
+
disabled: running || !request.url,
|
|
63741
|
+
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",
|
|
63742
|
+
children: running ? "Fuzzing..." : "Run fuzz"
|
|
63743
|
+
}
|
|
63744
|
+
)
|
|
63745
|
+
] }),
|
|
63746
|
+
isWrite && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-amber-400 px-4 py-2 border-b border-surface-800 flex-shrink-0", children: [
|
|
63747
|
+
request.method,
|
|
63748
|
+
" sends malformed writes. Point this request at a staging environment or a mock, not production."
|
|
63749
|
+
] }),
|
|
63750
|
+
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 }),
|
|
63751
|
+
/* @__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." }) }) })
|
|
63752
|
+
] })
|
|
63753
|
+
}
|
|
63754
|
+
);
|
|
63755
|
+
}
|
|
63756
|
+
const { electron: electron$k } = window;
|
|
63287
63757
|
const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "QUERY"];
|
|
63288
63758
|
const METHOD_COLORS = {
|
|
63289
63759
|
GET: "text-emerald-400",
|
|
@@ -63293,7 +63763,7 @@ const METHOD_COLORS = {
|
|
|
63293
63763
|
DELETE: "text-red-400",
|
|
63294
63764
|
HEAD: "text-purple-400",
|
|
63295
63765
|
OPTIONS: "text-gray-400",
|
|
63296
|
-
QUERY: "text-
|
|
63766
|
+
QUERY: "text-fuchsia-400"
|
|
63297
63767
|
};
|
|
63298
63768
|
function deriveHookStatus(r) {
|
|
63299
63769
|
if (r.scriptResult.postScriptError) return "error";
|
|
@@ -63325,6 +63795,7 @@ function RequestBuilder({ request }) {
|
|
|
63325
63795
|
if (activeTabId) setTabRequestTab(activeTabId, t2);
|
|
63326
63796
|
}
|
|
63327
63797
|
const [editingName, setEditingName] = reactExports.useState(false);
|
|
63798
|
+
const [showFuzz, setShowFuzz] = reactExports.useState(false);
|
|
63328
63799
|
const [runHooks, setRunHooks] = reactExports.useState(() => localStorage.getItem("runHooks") !== "false");
|
|
63329
63800
|
function toggleRunHooks() {
|
|
63330
63801
|
setRunHooks((prev) => {
|
|
@@ -63343,7 +63814,7 @@ function RequestBuilder({ request }) {
|
|
|
63343
63814
|
setTabHookResults(activeTabId, null);
|
|
63344
63815
|
const collectedHookResults = [];
|
|
63345
63816
|
try {
|
|
63346
|
-
const activeEnv =
|
|
63817
|
+
const activeEnv = resolveEnvironmentById(environments, activeEnvironmentId);
|
|
63347
63818
|
const sessionVars = useStore.getState().sessionVars;
|
|
63348
63819
|
const tls = collectionTls ? { ...workspaceSettings?.tls, ...collectionTls } : workspaceSettings?.tls;
|
|
63349
63820
|
const basePayload = {
|
|
@@ -63369,9 +63840,12 @@ function RequestBuilder({ request }) {
|
|
|
63369
63840
|
for (const hook of hooks.before) {
|
|
63370
63841
|
const start = Date.now();
|
|
63371
63842
|
try {
|
|
63372
|
-
const hookEnv =
|
|
63843
|
+
const hookEnv = resolveEnvironmentById(
|
|
63844
|
+
useStore.getState().environments,
|
|
63845
|
+
activeEnvironmentId
|
|
63846
|
+
);
|
|
63373
63847
|
const hookSessionVars = useStore.getState().sessionVars;
|
|
63374
|
-
const r = await electron$
|
|
63848
|
+
const r = await electron$k.sendRequest({
|
|
63375
63849
|
...basePayload,
|
|
63376
63850
|
environment: hookEnv,
|
|
63377
63851
|
request: hook,
|
|
@@ -63410,9 +63884,12 @@ function RequestBuilder({ request }) {
|
|
|
63410
63884
|
});
|
|
63411
63885
|
}
|
|
63412
63886
|
}
|
|
63413
|
-
const freshEnv =
|
|
63887
|
+
const freshEnv = resolveEnvironmentById(
|
|
63888
|
+
useStore.getState().environments,
|
|
63889
|
+
activeEnvironmentId
|
|
63890
|
+
);
|
|
63414
63891
|
const freshSessionVars = useStore.getState().sessionVars;
|
|
63415
|
-
const result = await electron$
|
|
63892
|
+
const result = await electron$k.sendRequest({
|
|
63416
63893
|
...basePayload,
|
|
63417
63894
|
environment: freshEnv,
|
|
63418
63895
|
request: mergedRequest,
|
|
@@ -63435,9 +63912,12 @@ function RequestBuilder({ request }) {
|
|
|
63435
63912
|
for (const hook of hooks.after) {
|
|
63436
63913
|
const start = Date.now();
|
|
63437
63914
|
try {
|
|
63438
|
-
const hookEnv =
|
|
63915
|
+
const hookEnv = resolveEnvironmentById(
|
|
63916
|
+
useStore.getState().environments,
|
|
63917
|
+
activeEnvironmentId
|
|
63918
|
+
);
|
|
63439
63919
|
const hookSessionVars = useStore.getState().sessionVars;
|
|
63440
|
-
const r = await electron$
|
|
63920
|
+
const r = await electron$k.sendRequest({
|
|
63441
63921
|
...basePayload,
|
|
63442
63922
|
environment: hookEnv,
|
|
63443
63923
|
request: hook,
|
|
@@ -63550,7 +64030,7 @@ function RequestBuilder({ request }) {
|
|
|
63550
64030
|
if (activeTabId) setTabRequestTab(activeTabId, "body");
|
|
63551
64031
|
},
|
|
63552
64032
|
className: `px-2 py-1.5 transition-colors ${isSoap ? "bg-amber-700 text-amber-100" : "text-surface-500 hover:text-white"}`,
|
|
63553
|
-
title: "SOAP
|
|
64033
|
+
title: "SOAP - endpoint and method are derived from the WSDL",
|
|
63554
64034
|
children: "SOAP"
|
|
63555
64035
|
}
|
|
63556
64036
|
)
|
|
@@ -63587,7 +64067,7 @@ function RequestBuilder({ request }) {
|
|
|
63587
64067
|
"button",
|
|
63588
64068
|
{
|
|
63589
64069
|
onClick: toggleRunHooks,
|
|
63590
|
-
title: runHooks ? "Hooks enabled
|
|
64070
|
+
title: runHooks ? "Hooks enabled - click to disable" : "Hooks disabled - click to enable",
|
|
63591
64071
|
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"}`,
|
|
63592
64072
|
children: "hooks"
|
|
63593
64073
|
}
|
|
@@ -63603,19 +64083,32 @@ function RequestBuilder({ request }) {
|
|
|
63603
64083
|
)
|
|
63604
64084
|
] })
|
|
63605
64085
|
] }),
|
|
64086
|
+
showFuzz && /* @__PURE__ */ jsxRuntimeExports.jsx(FuzzModal, { request, onClose: () => setShowFuzz(false) }),
|
|
63606
64087
|
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: [
|
|
63607
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
63608
|
-
|
|
63609
|
-
|
|
63610
|
-
|
|
63611
|
-
|
|
63612
|
-
|
|
63613
|
-
|
|
63614
|
-
|
|
63615
|
-
|
|
63616
|
-
|
|
63617
|
-
|
|
63618
|
-
|
|
64088
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex border-b border-surface-800 px-4 gap-0 flex-shrink-0", children: [
|
|
64089
|
+
tabs.map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
64090
|
+
"button",
|
|
64091
|
+
{
|
|
64092
|
+
onClick: () => setActiveTab(tab.id),
|
|
64093
|
+
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"}`,
|
|
64094
|
+
children: [
|
|
64095
|
+
tab.label,
|
|
64096
|
+
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 })
|
|
64097
|
+
]
|
|
64098
|
+
},
|
|
64099
|
+
tab.id
|
|
64100
|
+
)),
|
|
64101
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
64102
|
+
"button",
|
|
64103
|
+
{
|
|
64104
|
+
onClick: () => setShowFuzz(true),
|
|
64105
|
+
disabled: !request.url,
|
|
64106
|
+
title: "Fuzz this request with malformed inputs (opens a dialog; nothing is sent until you confirm)",
|
|
64107
|
+
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",
|
|
64108
|
+
children: "fuzz"
|
|
64109
|
+
}
|
|
64110
|
+
)
|
|
64111
|
+
] }),
|
|
63619
64112
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 flex-1 overflow-y-auto min-h-0", children: [
|
|
63620
64113
|
activeTab === "params" && /* @__PURE__ */ jsxRuntimeExports.jsx(ParamsTab, { request, onChange: update }),
|
|
63621
64114
|
activeTab === "headers" && /* @__PURE__ */ jsxRuntimeExports.jsx(HeadersTab, { request, onChange: update }),
|
|
@@ -64190,7 +64683,7 @@ function HookResultsPanel({ results }) {
|
|
|
64190
64683
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2.passed ? "✓" : "✗" }),
|
|
64191
64684
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: t2.name }),
|
|
64192
64685
|
t2.error && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 text-[10px]", children: [
|
|
64193
|
-
"
|
|
64686
|
+
"- ",
|
|
64194
64687
|
t2.error
|
|
64195
64688
|
] })
|
|
64196
64689
|
] }, ti)),
|
|
@@ -64200,7 +64693,7 @@ function HookResultsPanel({ results }) {
|
|
|
64200
64693
|
}) })
|
|
64201
64694
|
] });
|
|
64202
64695
|
}
|
|
64203
|
-
const { electron: electron$
|
|
64696
|
+
const { electron: electron$j } = window;
|
|
64204
64697
|
function extractPath(url) {
|
|
64205
64698
|
try {
|
|
64206
64699
|
return new URL(url).pathname || "/";
|
|
@@ -64254,14 +64747,14 @@ function SaveAsMockModal({ onClose }) {
|
|
|
64254
64747
|
const entry = state.mocks[serverId];
|
|
64255
64748
|
const updated = { ...entry.data, name: newServerName, port: Number(newServerPort), routes: [route] };
|
|
64256
64749
|
updateMock(serverId, updated);
|
|
64257
|
-
await electron$
|
|
64750
|
+
await electron$j.saveMock(entry.relPath, updated);
|
|
64258
64751
|
const ws2 = useStore.getState().workspace;
|
|
64259
|
-
if (ws2) await electron$
|
|
64752
|
+
if (ws2) await electron$j.saveWorkspace(ws2);
|
|
64260
64753
|
} else {
|
|
64261
64754
|
const entry = useStore.getState().mocks[serverId];
|
|
64262
64755
|
const updated = { ...entry.data, routes: [...entry.data.routes, route] };
|
|
64263
64756
|
updateMock(serverId, updated);
|
|
64264
|
-
await electron$
|
|
64757
|
+
await electron$j.saveMock(entry.relPath, updated);
|
|
64265
64758
|
}
|
|
64266
64759
|
onClose();
|
|
64267
64760
|
} finally {
|
|
@@ -64544,7 +65037,7 @@ function RequestPanel({ sentRequest }) {
|
|
|
64544
65037
|
if (!sentRequest) {
|
|
64545
65038
|
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-full text-surface-400 text-xs", children: "Send a request to see what was transmitted." });
|
|
64546
65039
|
}
|
|
64547
|
-
const
|
|
65040
|
+
const hasBody2 = sentRequest.body !== void 0 && sentRequest.body !== "";
|
|
64548
65041
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto text-xs font-mono", children: [
|
|
64549
65042
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 border-b border-surface-800 flex items-center gap-3", children: [
|
|
64550
65043
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold text-blue-400 shrink-0", children: sentRequest.method }),
|
|
@@ -64557,7 +65050,7 @@ function RequestPanel({ sentRequest }) {
|
|
|
64557
65050
|
/* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1 text-white break-all", children: v })
|
|
64558
65051
|
] }, k)) }) })
|
|
64559
65052
|
] }),
|
|
64560
|
-
|
|
65053
|
+
hasBody2 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-2", children: [
|
|
64561
65054
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-400 uppercase tracking-wider font-medium mb-1.5", children: "Body" }),
|
|
64562
65055
|
/* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "text-white whitespace-pre-wrap break-all text-[11px]", children: sentRequest.body })
|
|
64563
65056
|
] })
|
|
@@ -64593,21 +65086,83 @@ function ConsolePanel({ scriptResult }) {
|
|
|
64593
65086
|
)) })
|
|
64594
65087
|
] });
|
|
64595
65088
|
}
|
|
64596
|
-
function
|
|
64597
|
-
const
|
|
64598
|
-
const
|
|
64599
|
-
|
|
64600
|
-
if (timer.current) clearTimeout(timer.current);
|
|
64601
|
-
setToast({ msg, ok });
|
|
64602
|
-
timer.current = setTimeout(() => setToast(null), durationMs);
|
|
65089
|
+
function header(headers, name2) {
|
|
65090
|
+
const lower = name2.toLowerCase();
|
|
65091
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
65092
|
+
if (k.toLowerCase() === lower) return v;
|
|
64603
65093
|
}
|
|
64604
|
-
return
|
|
65094
|
+
return void 0;
|
|
64605
65095
|
}
|
|
64606
|
-
function
|
|
64607
|
-
|
|
64608
|
-
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 });
|
|
65096
|
+
function hasBody(res) {
|
|
65097
|
+
return (res.bodySize ?? res.body.length) > 0;
|
|
64609
65098
|
}
|
|
64610
|
-
const
|
|
65099
|
+
const REDIRECTS_NEEDING_LOCATION = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
65100
|
+
function validateHttpSemantics(res) {
|
|
65101
|
+
if (!res.status || res.status === 0) return [];
|
|
65102
|
+
const f = [];
|
|
65103
|
+
const method = res.method.toUpperCase();
|
|
65104
|
+
const { status } = res;
|
|
65105
|
+
const body = hasBody(res);
|
|
65106
|
+
const contentType = header(res.headers, "content-type");
|
|
65107
|
+
const contentEncoding = header(res.headers, "content-encoding");
|
|
65108
|
+
if (status === 204 && body) {
|
|
65109
|
+
f.push({ rule: "no-body-204", severity: "error", message: "204 No Content must not include a message body.", ref: "RFC 9110 §15.3.5" });
|
|
65110
|
+
}
|
|
65111
|
+
if (status === 304 && body) {
|
|
65112
|
+
f.push({ rule: "no-body-304", severity: "error", message: "304 Not Modified must not include a message body.", ref: "RFC 9110 §15.4.5" });
|
|
65113
|
+
}
|
|
65114
|
+
if (status >= 100 && status < 200 && body) {
|
|
65115
|
+
f.push({ rule: "no-body-1xx", severity: "error", message: `${status} informational responses must not include a body.`, ref: "RFC 9110 §15.2" });
|
|
65116
|
+
}
|
|
65117
|
+
if (method === "HEAD" && body) {
|
|
65118
|
+
f.push({ rule: "no-body-head", severity: "error", message: "Response to a HEAD request must not include a body.", ref: "RFC 9110 §9.3.2" });
|
|
65119
|
+
}
|
|
65120
|
+
if (REDIRECTS_NEEDING_LOCATION.has(status) && !header(res.headers, "location")) {
|
|
65121
|
+
f.push({ rule: "redirect-no-location", severity: "error", message: `${status} redirect has no Location header, so the client cannot follow it.`, ref: "RFC 9110 §15.4" });
|
|
65122
|
+
}
|
|
65123
|
+
if (status === 401 && !header(res.headers, "www-authenticate")) {
|
|
65124
|
+
f.push({ rule: "401-no-www-authenticate", severity: "error", message: "401 Unauthorized must include a WWW-Authenticate header.", ref: "RFC 9110 §15.5.2" });
|
|
65125
|
+
}
|
|
65126
|
+
if (status === 405 && !header(res.headers, "allow")) {
|
|
65127
|
+
f.push({ rule: "405-no-allow", severity: "error", message: "405 Method Not Allowed must include an Allow header listing valid methods.", ref: "RFC 9110 §15.5.6" });
|
|
65128
|
+
}
|
|
65129
|
+
if (body && !contentType && status !== 204 && status !== 304) {
|
|
65130
|
+
f.push({ rule: "body-no-content-type", severity: "warning", message: "Response has a body but no Content-Type header; clients must guess how to parse it.", ref: "RFC 9110 §8.3" });
|
|
65131
|
+
}
|
|
65132
|
+
if (body && contentType && /application\/(json|.*\+json)/i.test(contentType)) {
|
|
65133
|
+
try {
|
|
65134
|
+
JSON.parse(res.body);
|
|
65135
|
+
} catch {
|
|
65136
|
+
f.push({ rule: "json-invalid", severity: "error", message: `Content-Type is "${contentType}" but the body is not valid JSON.`, ref: "RFC 8259" });
|
|
65137
|
+
}
|
|
65138
|
+
}
|
|
65139
|
+
if (contentType && /^text\//i.test(contentType) && !/charset=/i.test(contentType)) {
|
|
65140
|
+
f.push({ rule: "text-no-charset", severity: "hint", message: `"${contentType}" has no charset parameter; clients may misinterpret the encoding.`, ref: "RFC 9110 §8.3.2" });
|
|
65141
|
+
}
|
|
65142
|
+
const clRaw = header(res.headers, "content-length");
|
|
65143
|
+
if (clRaw !== void 0 && !contentEncoding && method !== "HEAD" && status !== 204 && status !== 304) {
|
|
65144
|
+
const cl = Number(clRaw);
|
|
65145
|
+
const actual = res.bodySize ?? res.body.length;
|
|
65146
|
+
if (Number.isFinite(cl) && cl !== actual) {
|
|
65147
|
+
f.push({ rule: "content-length-mismatch", severity: "error", message: `Content-Length is ${cl} but the body is ${actual} bytes.`, ref: "RFC 9110 §8.6" });
|
|
65148
|
+
}
|
|
65149
|
+
}
|
|
65150
|
+
if (status < 100 || status > 599) {
|
|
65151
|
+
f.push({ rule: "status-out-of-range", severity: "warning", message: `${status} is not a valid HTTP status code (must be 100-599).`, ref: "RFC 9110 §15" });
|
|
65152
|
+
}
|
|
65153
|
+
if (!header(res.headers, "date") && status >= 200) {
|
|
65154
|
+
f.push({ rule: "no-date", severity: "hint", message: "No Date header; origin servers are expected to send one.", ref: "RFC 9110 §6.6.1" });
|
|
65155
|
+
}
|
|
65156
|
+
if ((status === 429 || status === 503) && !header(res.headers, "retry-after")) {
|
|
65157
|
+
f.push({ rule: "no-retry-after", severity: "hint", message: `${status} should include a Retry-After header telling clients when to retry.`, ref: "RFC 9110 §10.2.3" });
|
|
65158
|
+
}
|
|
65159
|
+
if (status === 201 && !header(res.headers, "location")) {
|
|
65160
|
+
f.push({ rule: "201-no-location", severity: "warning", message: "201 Created should include a Location header pointing at the new resource.", ref: "RFC 9110 §15.3.2" });
|
|
65161
|
+
}
|
|
65162
|
+
const order = { error: 0, warning: 1, hint: 2 };
|
|
65163
|
+
return f.sort((a, b) => order[a.severity] - order[b.severity]);
|
|
65164
|
+
}
|
|
65165
|
+
const { electron: electron$i } = window;
|
|
64611
65166
|
function ResponseViewer() {
|
|
64612
65167
|
const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
|
|
64613
65168
|
const activeTabId = useStore((s) => s.activeTabId);
|
|
@@ -64622,7 +65177,25 @@ function ResponseViewer() {
|
|
|
64622
65177
|
const sentRequest = activeTab?.lastSentRequest ?? null;
|
|
64623
65178
|
const hookResults = activeTab?.lastHookResults ?? null;
|
|
64624
65179
|
const requestId = activeTab?.requestId ?? null;
|
|
65180
|
+
const setTabResponse = useStore((s) => s.setTabResponse);
|
|
65181
|
+
const history2 = useStore((s) => s.history);
|
|
65182
|
+
const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
|
|
65183
|
+
const environments = useStore((s) => s.environments);
|
|
65184
|
+
const upsertEnvVar = useStore((s) => s.upsertEnvVar);
|
|
64625
65185
|
const [tab, setTab] = reactExports.useState("body");
|
|
65186
|
+
const requestHistory = requestId ? history2.filter((e) => e.request.id === requestId) : [];
|
|
65187
|
+
const httpFindings = response && !response.error ? validateHttpSemantics({
|
|
65188
|
+
method: sentRequest?.method ?? "GET",
|
|
65189
|
+
status: response.status,
|
|
65190
|
+
statusText: response.statusText,
|
|
65191
|
+
headers: response.headers,
|
|
65192
|
+
body: response.body,
|
|
65193
|
+
bodySize: response.bodySize
|
|
65194
|
+
}) : [];
|
|
65195
|
+
const httpErrors = httpFindings.filter((x) => x.severity === "error").length;
|
|
65196
|
+
const [headerMenu, setHeaderMenu] = reactExports.useState(null);
|
|
65197
|
+
const [varDialog, setVarDialog] = reactExports.useState(null);
|
|
65198
|
+
const activeEnvName = activeEnvironmentId ? environments[activeEnvironmentId]?.data.name : void 0;
|
|
64626
65199
|
reactExports.useEffect(() => {
|
|
64627
65200
|
if (scriptResult?.preScriptError || scriptResult?.postScriptError) {
|
|
64628
65201
|
setTab("console");
|
|
@@ -64635,7 +65208,7 @@ function ResponseViewer() {
|
|
|
64635
65208
|
const contractToast = useToast(2500);
|
|
64636
65209
|
async function saveAsContract() {
|
|
64637
65210
|
if (!response || !requestId || !activeTabId) return;
|
|
64638
|
-
const schema = response.body ? await electron$
|
|
65211
|
+
const schema = response.body ? await electron$i.inferContractSchema(response.body) : null;
|
|
64639
65212
|
const contentType2 = response.headers["content-type"];
|
|
64640
65213
|
const headers = contentType2 ? [{ key: "content-type", value: contentType2, required: true }] : [];
|
|
64641
65214
|
updateRequest(requestId, {
|
|
@@ -64688,7 +65261,9 @@ function ResponseViewer() {
|
|
|
64688
65261
|
{ id: "body", label: "Body" },
|
|
64689
65262
|
{ id: "headers", label: "Headers" },
|
|
64690
65263
|
{ id: "tests", label: "Tests", badge: totalCount > 0 ? `${passedCount}/${totalCount}` : void 0 },
|
|
64691
|
-
{ id: "console", label: "Console", badge: hasScriptError ? "!" : consoleCount > 0 ? consoleCount : void 0, error: hasScriptError }
|
|
65264
|
+
{ id: "console", label: "Console", badge: hasScriptError ? "!" : consoleCount > 0 ? consoleCount : void 0, error: hasScriptError },
|
|
65265
|
+
{ id: "history", label: "History", badge: requestHistory.length > 0 ? requestHistory.length : void 0 },
|
|
65266
|
+
{ id: "http", label: "HTTP", badge: httpFindings.length > 0 ? httpErrors > 0 ? "!" : httpFindings.length : void 0, error: httpErrors > 0 }
|
|
64692
65267
|
];
|
|
64693
65268
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-full flex flex-col", children: [
|
|
64694
65269
|
hookResults && hookResults.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(HookResultsPanel, { results: hookResults }),
|
|
@@ -64727,7 +65302,7 @@ function ResponseViewer() {
|
|
|
64727
65302
|
{
|
|
64728
65303
|
onClick: () => setBodyView("tree"),
|
|
64729
65304
|
className: `px-2 py-0.5 text-[10px] transition-colors ${bodyView === "tree" ? "bg-surface-700 text-white" : "text-surface-600 hover:text-white"}`,
|
|
64730
|
-
title: "Interactive tree view
|
|
65305
|
+
title: "Interactive tree view - click values to add assertions",
|
|
64731
65306
|
children: "Tree"
|
|
64732
65307
|
}
|
|
64733
65308
|
),
|
|
@@ -64796,13 +65371,135 @@ function ResponseViewer() {
|
|
|
64796
65371
|
readOnly: true,
|
|
64797
65372
|
basicSetup: { lineNumbers: true, foldGutter: true }
|
|
64798
65373
|
}
|
|
64799
|
-
) : tab === "headers" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx("table", { className: "w-full text-xs px-4 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: Object.entries(response.headers).map(([k, v]) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
64800
|
-
|
|
64801
|
-
|
|
64802
|
-
|
|
65374
|
+
) : tab === "headers" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsx("table", { className: "w-full text-xs px-4 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: Object.entries(response.headers).map(([k, v]) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
65375
|
+
"tr",
|
|
65376
|
+
{
|
|
65377
|
+
className: "border-b border-surface-800 hover:bg-surface-800/40",
|
|
65378
|
+
onContextMenu: (e) => {
|
|
65379
|
+
e.preventDefault();
|
|
65380
|
+
setHeaderMenu({ x: e.clientX, y: e.clientY, key: k, value: v });
|
|
65381
|
+
},
|
|
65382
|
+
title: "Right-click to create an environment variable",
|
|
65383
|
+
children: [
|
|
65384
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5 px-4 text-surface-400 font-mono w-56 align-top", children: k }),
|
|
65385
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "py-1.5 px-4 text-white font-mono break-all", children: v })
|
|
65386
|
+
]
|
|
65387
|
+
},
|
|
65388
|
+
k
|
|
65389
|
+
)) }) }) }) : tab === "tests" ? /* @__PURE__ */ jsxRuntimeExports.jsx(TestsPanel, { scriptResult }) : tab === "console" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ConsolePanel, { scriptResult }) : tab === "request" ? /* @__PURE__ */ jsxRuntimeExports.jsx(RequestPanel, { sentRequest }) : tab === "http" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto p-4", children: httpFindings.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-2 text-center", children: [
|
|
65390
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-2xl", children: "✓" }),
|
|
65391
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-emerald-400", children: "Conforms to HTTP semantics" }),
|
|
65392
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 max-w-sm", children: "No violations of the HTTP specification (RFC 9110/9111) in this response. This check is automatic and needs no test or spec." })
|
|
65393
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col gap-2 max-w-3xl", children: httpFindings.map((find2, i) => {
|
|
65394
|
+
const tone = find2.severity === "error" ? "border-red-800/60 bg-red-950/20" : find2.severity === "warning" ? "border-amber-800/50 bg-amber-950/20" : "border-surface-700 bg-surface-800/40";
|
|
65395
|
+
const label = find2.severity === "error" ? "text-red-400" : find2.severity === "warning" ? "text-amber-400" : "text-surface-400";
|
|
65396
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `border-l-2 rounded-r px-3 py-2 ${tone}`, children: [
|
|
65397
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
65398
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] font-bold uppercase tracking-wider ${label}`, children: find2.severity }),
|
|
65399
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-500 bg-surface-900 px-1.5 py-0.5 rounded", children: find2.rule }),
|
|
65400
|
+
find2.ref && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-surface-600 ml-auto", children: find2.ref })
|
|
65401
|
+
] }),
|
|
65402
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-200 mt-1", children: find2.message })
|
|
65403
|
+
] }, i);
|
|
65404
|
+
}) }) }) : tab === "history" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-y-auto", children: requestHistory.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 text-center p-8", children: "No past responses for this request yet. Each send is recorded here." }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col", children: requestHistory.map((entry) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
65405
|
+
"button",
|
|
65406
|
+
{
|
|
65407
|
+
onClick: () => {
|
|
65408
|
+
if (activeTabId) setTabResponse(activeTabId, entry.response, entry.scriptResult ?? null);
|
|
65409
|
+
},
|
|
65410
|
+
className: "flex items-center gap-3 px-4 py-2 border-b border-surface-800 hover:bg-surface-800/50 text-left transition-colors",
|
|
65411
|
+
children: [
|
|
65412
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-xs font-bold font-mono shrink-0 w-8 ${getStatusColor(entry.response.status)}`, children: entry.response.status || "ERR" }),
|
|
65413
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-surface-400 shrink-0", children: [
|
|
65414
|
+
entry.response.durationMs,
|
|
65415
|
+
"ms"
|
|
65416
|
+
] }),
|
|
65417
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[11px] text-surface-500 shrink-0", children: [
|
|
65418
|
+
(entry.response.bodySize / 1024).toFixed(1),
|
|
65419
|
+
" KB"
|
|
65420
|
+
] }),
|
|
65421
|
+
entry.environmentName && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] bg-surface-800 text-surface-400 px-1.5 py-0.5 rounded shrink-0", children: entry.environmentName }),
|
|
65422
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-surface-500 ml-auto shrink-0", children: new Date(entry.timestamp).toLocaleString([], { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit" }) })
|
|
65423
|
+
]
|
|
65424
|
+
},
|
|
65425
|
+
entry.id
|
|
65426
|
+
)) }) }) : null }),
|
|
65427
|
+
headerMenu && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
65428
|
+
ContextMenu,
|
|
65429
|
+
{
|
|
65430
|
+
x: headerMenu.x,
|
|
65431
|
+
y: headerMenu.y,
|
|
65432
|
+
onClose: () => setHeaderMenu(null),
|
|
65433
|
+
items: [
|
|
65434
|
+
{ type: "header", label: headerMenu.key },
|
|
65435
|
+
activeEnvironmentId ? {
|
|
65436
|
+
type: "item",
|
|
65437
|
+
label: `Create variable in "${activeEnvName}"`,
|
|
65438
|
+
onClick: () => {
|
|
65439
|
+
setVarDialog({ name: headerMenu.key.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, ""), value: headerMenu.value });
|
|
65440
|
+
setHeaderMenu(null);
|
|
65441
|
+
}
|
|
65442
|
+
} : { type: "header", label: "Select an environment first" }
|
|
65443
|
+
]
|
|
65444
|
+
}
|
|
65445
|
+
),
|
|
65446
|
+
varDialog && /* @__PURE__ */ jsxRuntimeExports.jsx(Modal, { onClose: () => setVarDialog(null), title: "Create environment variable", panelClassName: "bg-surface-900 border border-surface-800 rounded-lg shadow-2xl w-[420px]", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 p-4", children: [
|
|
65447
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1", children: [
|
|
65448
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Variable name" }),
|
|
65449
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
65450
|
+
"input",
|
|
65451
|
+
{
|
|
65452
|
+
autoFocus: true,
|
|
65453
|
+
value: varDialog.name,
|
|
65454
|
+
onChange: (e) => setVarDialog((d) => d && { ...d, name: e.target.value }),
|
|
65455
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 text-sm font-mono focus:outline-none focus:border-blue-500"
|
|
65456
|
+
}
|
|
65457
|
+
)
|
|
65458
|
+
] }),
|
|
65459
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex flex-col gap-1", children: [
|
|
65460
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Value" }),
|
|
65461
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
65462
|
+
"input",
|
|
65463
|
+
{
|
|
65464
|
+
value: varDialog.value,
|
|
65465
|
+
onChange: (e) => setVarDialog((d) => d && { ...d, value: e.target.value }),
|
|
65466
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 text-sm font-mono focus:outline-none focus:border-blue-500"
|
|
65467
|
+
}
|
|
65468
|
+
)
|
|
65469
|
+
] }),
|
|
65470
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] text-surface-500", children: [
|
|
65471
|
+
"Saved to ",
|
|
65472
|
+
activeEnvName ? `"${activeEnvName}"` : "the active environment",
|
|
65473
|
+
". Use it as ",
|
|
65474
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("code", { className: "text-surface-300", children: [
|
|
65475
|
+
"{{",
|
|
65476
|
+
varDialog.name || "name",
|
|
65477
|
+
"}}"
|
|
65478
|
+
] }),
|
|
65479
|
+
"."
|
|
65480
|
+
] }),
|
|
65481
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mt-1", children: [
|
|
65482
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setVarDialog(null), className: "px-3 py-1.5 text-xs text-surface-400 hover:text-surface-200 transition-colors", children: "Cancel" }),
|
|
65483
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
65484
|
+
"button",
|
|
65485
|
+
{
|
|
65486
|
+
onClick: () => {
|
|
65487
|
+
if (activeEnvironmentId && varDialog.name.trim()) {
|
|
65488
|
+
upsertEnvVar(activeEnvironmentId, varDialog.name.trim(), varDialog.value);
|
|
65489
|
+
assertToast.show(`✓ Saved {{${varDialog.name.trim()}}}`, true);
|
|
65490
|
+
}
|
|
65491
|
+
setVarDialog(null);
|
|
65492
|
+
},
|
|
65493
|
+
disabled: !varDialog.name.trim(),
|
|
65494
|
+
className: "px-3 py-1.5 text-xs rounded bg-blue-600 hover:bg-blue-500 text-white font-semibold disabled:opacity-50 transition-colors",
|
|
65495
|
+
children: "Create"
|
|
65496
|
+
}
|
|
65497
|
+
)
|
|
65498
|
+
] })
|
|
65499
|
+
] }) })
|
|
64803
65500
|
] });
|
|
64804
65501
|
}
|
|
64805
|
-
const { electron: electron$
|
|
65502
|
+
const { electron: electron$h } = window;
|
|
64806
65503
|
const TARGETS = [
|
|
64807
65504
|
{ id: "robot_framework", label: "Robot Framework", description: "Python RequestsLibrary keywords + test suite" },
|
|
64808
65505
|
{ id: "playwright_ts", label: "Playwright TS", description: "TypeScript page-object API classes + spec files" },
|
|
@@ -64841,8 +65538,8 @@ function GeneratorPanel() {
|
|
|
64841
65538
|
setSelectedFile(null);
|
|
64842
65539
|
try {
|
|
64843
65540
|
const col = collections[selectedCollectionId]?.data;
|
|
64844
|
-
const env =
|
|
64845
|
-
const generated = await electron$
|
|
65541
|
+
const env = resolveEnvironmentById(environments, activeEnvironmentId);
|
|
65542
|
+
const generated = await electron$h.generateCode({ collection: col, environment: env, target });
|
|
64846
65543
|
setFiles(generated);
|
|
64847
65544
|
setSelectedFile(generated[0]?.path ?? null);
|
|
64848
65545
|
} catch (e) {
|
|
@@ -64854,7 +65551,7 @@ function GeneratorPanel() {
|
|
|
64854
65551
|
async function saveZip() {
|
|
64855
65552
|
if (files.length === 0) return;
|
|
64856
65553
|
const col = collections[selectedCollectionId]?.data;
|
|
64857
|
-
await electron$
|
|
65554
|
+
await electron$h.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
|
|
64858
65555
|
}
|
|
64859
65556
|
const selectedContent = files.find((f) => f.path === selectedFile)?.content ?? "";
|
|
64860
65557
|
const activeTarget = TARGETS.find((t2) => t2.id === target);
|
|
@@ -64953,6 +65650,92 @@ function GeneratorPanel() {
|
|
|
64953
65650
|
files.length === 0 && !generating && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-surface-400 text-xs text-center px-6", children: "Select a collection and hit Generate to preview the output code." })
|
|
64954
65651
|
] });
|
|
64955
65652
|
}
|
|
65653
|
+
function enabledHeaders(headers) {
|
|
65654
|
+
return headers.filter((h) => h.enabled && h.key).map((h) => ({ name: h.key, value: h.value }));
|
|
65655
|
+
}
|
|
65656
|
+
function queryStringOf(url) {
|
|
65657
|
+
const q = url.indexOf("?");
|
|
65658
|
+
if (q === -1) return [];
|
|
65659
|
+
const out = [];
|
|
65660
|
+
for (const pair2 of url.slice(q + 1).split("&")) {
|
|
65661
|
+
if (!pair2) continue;
|
|
65662
|
+
const eq = pair2.indexOf("=");
|
|
65663
|
+
const name2 = eq === -1 ? pair2 : pair2.slice(0, eq);
|
|
65664
|
+
const value = eq === -1 ? "" : pair2.slice(eq + 1);
|
|
65665
|
+
try {
|
|
65666
|
+
out.push({ name: decodeURIComponent(name2), value: decodeURIComponent(value) });
|
|
65667
|
+
} catch {
|
|
65668
|
+
out.push({ name: name2, value });
|
|
65669
|
+
}
|
|
65670
|
+
}
|
|
65671
|
+
return out;
|
|
65672
|
+
}
|
|
65673
|
+
function postDataOf(body) {
|
|
65674
|
+
if (!body || body.mode === "none") return void 0;
|
|
65675
|
+
switch (body.mode) {
|
|
65676
|
+
case "json":
|
|
65677
|
+
return body.json ? { mimeType: "application/json", text: body.json } : void 0;
|
|
65678
|
+
case "raw":
|
|
65679
|
+
return body.raw ? { mimeType: body.rawContentType ?? "text/plain", text: body.raw } : void 0;
|
|
65680
|
+
case "graphql":
|
|
65681
|
+
return body.graphql ? { mimeType: "application/json", text: JSON.stringify(body.graphql) } : void 0;
|
|
65682
|
+
case "soap":
|
|
65683
|
+
return body.soap ? { mimeType: "text/xml", text: body.soap.envelope ?? "" } : void 0;
|
|
65684
|
+
case "form": {
|
|
65685
|
+
const text = (body.form ?? []).filter((p2) => p2.enabled && p2.key).map((p2) => `${encodeURIComponent(p2.key)}=${encodeURIComponent(p2.value)}`).join("&");
|
|
65686
|
+
return { mimeType: "application/x-www-form-urlencoded", text };
|
|
65687
|
+
}
|
|
65688
|
+
default:
|
|
65689
|
+
return void 0;
|
|
65690
|
+
}
|
|
65691
|
+
}
|
|
65692
|
+
function historyToHar(entries, creatorVersion = "1.0") {
|
|
65693
|
+
const harEntries = entries.map((e) => {
|
|
65694
|
+
const post = postDataOf(e.request.body);
|
|
65695
|
+
const contentType = e.response.headers["content-type"] ?? e.response.headers["Content-Type"] ?? "text/plain";
|
|
65696
|
+
return {
|
|
65697
|
+
startedDateTime: new Date(e.timestamp).toISOString(),
|
|
65698
|
+
time: e.response.durationMs,
|
|
65699
|
+
request: {
|
|
65700
|
+
method: e.request.method,
|
|
65701
|
+
url: e.resolvedUrl,
|
|
65702
|
+
httpVersion: "HTTP/1.1",
|
|
65703
|
+
cookies: [],
|
|
65704
|
+
headers: enabledHeaders(e.request.headers),
|
|
65705
|
+
queryString: queryStringOf(e.resolvedUrl),
|
|
65706
|
+
...post ? { postData: post } : {},
|
|
65707
|
+
headersSize: -1,
|
|
65708
|
+
bodySize: post ? post.text.length : 0
|
|
65709
|
+
},
|
|
65710
|
+
response: {
|
|
65711
|
+
status: e.response.status,
|
|
65712
|
+
statusText: e.response.statusText,
|
|
65713
|
+
httpVersion: "HTTP/1.1",
|
|
65714
|
+
cookies: [],
|
|
65715
|
+
headers: Object.entries(e.response.headers).map(([name2, value]) => ({ name: name2, value })),
|
|
65716
|
+
content: {
|
|
65717
|
+
size: e.response.bodySize,
|
|
65718
|
+
mimeType: contentType,
|
|
65719
|
+
text: e.response.body
|
|
65720
|
+
},
|
|
65721
|
+
redirectURL: "",
|
|
65722
|
+
headersSize: -1,
|
|
65723
|
+
bodySize: e.response.bodySize
|
|
65724
|
+
},
|
|
65725
|
+
cache: {},
|
|
65726
|
+
timings: { send: 0, wait: e.response.durationMs, receive: 0 },
|
|
65727
|
+
...e.environmentName ? { comment: `environment: ${e.environmentName}` } : {}
|
|
65728
|
+
};
|
|
65729
|
+
});
|
|
65730
|
+
return JSON.stringify({
|
|
65731
|
+
log: {
|
|
65732
|
+
version: "1.2",
|
|
65733
|
+
creator: { name: "API Spector", version: creatorVersion },
|
|
65734
|
+
entries: harEntries
|
|
65735
|
+
}
|
|
65736
|
+
}, null, 2);
|
|
65737
|
+
}
|
|
65738
|
+
const { electron: electron$g } = window;
|
|
64956
65739
|
const STATUS_COLOR = {
|
|
64957
65740
|
"2": "text-emerald-400",
|
|
64958
65741
|
"3": "text-amber-400",
|
|
@@ -64980,6 +65763,8 @@ function HistoryPanel() {
|
|
|
64980
65763
|
const clearHistory = useStore((s) => s.clearHistory);
|
|
64981
65764
|
const activeTabId = useStore((s) => s.activeTabId);
|
|
64982
65765
|
const setTabResponse = useStore((s) => s.setTabResponse);
|
|
65766
|
+
const setActiveRequest = useStore((s) => s.setActiveRequest);
|
|
65767
|
+
const collections = useStore((s) => s.collections);
|
|
64983
65768
|
const [selected, setSelected] = reactExports.useState(null);
|
|
64984
65769
|
const [search, setSearch] = reactExports.useState("");
|
|
64985
65770
|
const filtered = search ? history2.filter(
|
|
@@ -64995,9 +65780,18 @@ function HistoryPanel() {
|
|
|
64995
65780
|
groups.push({ label, entries: [entry] });
|
|
64996
65781
|
}
|
|
64997
65782
|
}
|
|
65783
|
+
async function downloadHar() {
|
|
65784
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/[:T]/g, "-");
|
|
65785
|
+
await electron$g.saveResults(historyToHar(history2), `api-spector-history-${stamp}.har`);
|
|
65786
|
+
}
|
|
64998
65787
|
function open(entry) {
|
|
64999
65788
|
setSelected(entry);
|
|
65000
|
-
|
|
65789
|
+
const stillExists = Object.values(collections).some((c) => entry.request.id in c.data.requests);
|
|
65790
|
+
if (stillExists) {
|
|
65791
|
+
setActiveRequest(entry.request.id);
|
|
65792
|
+
const tabId = useStore.getState().activeTabId;
|
|
65793
|
+
if (tabId) setTabResponse(tabId, entry.response, entry.scriptResult ?? null);
|
|
65794
|
+
} else if (activeTabId) {
|
|
65001
65795
|
setTabResponse(activeTabId, entry.response, entry.scriptResult ?? null);
|
|
65002
65796
|
}
|
|
65003
65797
|
}
|
|
@@ -65012,18 +65806,29 @@ function HistoryPanel() {
|
|
|
65012
65806
|
className: "flex-1 bg-surface-800 rounded px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-blue-500"
|
|
65013
65807
|
}
|
|
65014
65808
|
),
|
|
65015
|
-
history2.length > 0 && /* @__PURE__ */ jsxRuntimeExports.
|
|
65016
|
-
|
|
65017
|
-
|
|
65018
|
-
|
|
65019
|
-
|
|
65020
|
-
|
|
65021
|
-
|
|
65022
|
-
|
|
65023
|
-
|
|
65024
|
-
|
|
65025
|
-
|
|
65026
|
-
|
|
65809
|
+
history2.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
65810
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
65811
|
+
"button",
|
|
65812
|
+
{
|
|
65813
|
+
onClick: downloadHar,
|
|
65814
|
+
className: "text-xs text-surface-400 hover:text-surface-100 transition-colors px-1",
|
|
65815
|
+
title: "Download history as a HAR file",
|
|
65816
|
+
children: "HAR"
|
|
65817
|
+
}
|
|
65818
|
+
),
|
|
65819
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
65820
|
+
"button",
|
|
65821
|
+
{
|
|
65822
|
+
onClick: () => {
|
|
65823
|
+
clearHistory();
|
|
65824
|
+
setSelected(null);
|
|
65825
|
+
},
|
|
65826
|
+
className: "text-xs text-surface-400 hover:text-red-400 transition-colors px-1",
|
|
65827
|
+
title: "Clear all history",
|
|
65828
|
+
children: "Clear"
|
|
65829
|
+
}
|
|
65830
|
+
)
|
|
65831
|
+
] })
|
|
65027
65832
|
] }),
|
|
65028
65833
|
history2.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-xs text-surface-400 px-4 text-center", children: "No history yet. Send a request to start recording." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto", children: [
|
|
65029
65834
|
filtered.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "px-3 py-4 text-xs text-surface-400", children: "No matches." }),
|
|
@@ -65134,7 +65939,7 @@ function WelcomeScreen() {
|
|
|
65134
65939
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-surface-400 text-xs max-w-xs", children: [
|
|
65135
65940
|
"A workspace is a ",
|
|
65136
65941
|
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-500", children: ".spector" }),
|
|
65137
|
-
" file. Commit it and your collections to Git
|
|
65942
|
+
" file. Commit it and your collections to Git - secrets are stored in your OS keychain, never on disk."
|
|
65138
65943
|
] })
|
|
65139
65944
|
] });
|
|
65140
65945
|
}
|
|
@@ -65145,11 +65950,11 @@ const EXAMPLES = [
|
|
|
65145
65950
|
code: (pw) => `export API_SPECTOR_MASTER_KEY="${pw || "<your-password>"}"`
|
|
65146
65951
|
},
|
|
65147
65952
|
{
|
|
65148
|
-
label: "Windows
|
|
65953
|
+
label: "Windows - PowerShell profile",
|
|
65149
65954
|
code: (pw) => `$env:API_SPECTOR_MASTER_KEY = "${pw || "<your-password>"}"`
|
|
65150
65955
|
},
|
|
65151
65956
|
{
|
|
65152
|
-
label: "Windows
|
|
65957
|
+
label: "Windows - Command Prompt (permanent)",
|
|
65153
65958
|
code: (pw) => `setx API_SPECTOR_MASTER_KEY "${pw || "<your-password>"}"`
|
|
65154
65959
|
}
|
|
65155
65960
|
];
|
|
@@ -65295,6 +66100,26 @@ function EnvironmentEditor({ onClose }) {
|
|
|
65295
66100
|
const duplicateEnvironment = useStore((s) => s.duplicateEnvironment);
|
|
65296
66101
|
const envList = Object.values(environments);
|
|
65297
66102
|
const env = selectedId ? environments[selectedId]?.data ?? null : null;
|
|
66103
|
+
const allEnvDatas = envList.map((e) => e.data);
|
|
66104
|
+
let inheritSummary = null;
|
|
66105
|
+
if (env?.extends) {
|
|
66106
|
+
const parentNames = [];
|
|
66107
|
+
const seen = /* @__PURE__ */ new Set([env.name]);
|
|
66108
|
+
let parentName = env.extends;
|
|
66109
|
+
while (parentName && !seen.has(parentName)) {
|
|
66110
|
+
const parent = allEnvDatas.find((e) => e.name === parentName);
|
|
66111
|
+
if (!parent) break;
|
|
66112
|
+
parentNames.push(parent.name);
|
|
66113
|
+
seen.add(parent.name);
|
|
66114
|
+
parentName = parent.extends;
|
|
66115
|
+
}
|
|
66116
|
+
if (parentNames.length > 0) {
|
|
66117
|
+
const ownKeys = new Set(env.variables.map((v) => v.key));
|
|
66118
|
+
const resolved = resolveEnvironmentChain(env, allEnvDatas);
|
|
66119
|
+
const inheritedCount = resolved.variables.filter((v) => !ownKeys.has(v.key)).length;
|
|
66120
|
+
inheritSummary = `Inherits ${inheritedCount} variable${inheritedCount === 1 ? "" : "s"} from ${parentNames.join(", ")}`;
|
|
66121
|
+
}
|
|
66122
|
+
}
|
|
65298
66123
|
function handleDelete(id2) {
|
|
65299
66124
|
deleteEnvironment(id2);
|
|
65300
66125
|
const remaining = Object.keys(environments).filter((k) => k !== id2);
|
|
@@ -65480,7 +66305,23 @@ function EnvironmentEditor({ onClose }) {
|
|
|
65480
66305
|
),
|
|
65481
66306
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-white text-lg leading-none", children: "×" })
|
|
65482
66307
|
] }),
|
|
65483
|
-
nameError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-red-400 mt-1", children: nameError })
|
|
66308
|
+
nameError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-red-400 mt-1", children: nameError }),
|
|
66309
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
|
|
66310
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-400 font-medium shrink-0", children: "Extends" }),
|
|
66311
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
66312
|
+
"select",
|
|
66313
|
+
{
|
|
66314
|
+
value: env.extends ?? "",
|
|
66315
|
+
onChange: (e) => updateEnvironment(env.id, { ...env, extends: e.target.value || void 0 }),
|
|
66316
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs focus:outline-none focus:border-blue-500",
|
|
66317
|
+
children: [
|
|
66318
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "(none)" }),
|
|
66319
|
+
envList.filter(({ data: other }) => other.id !== env.id).map(({ data: other }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: other.name, children: other.name }, other.id))
|
|
66320
|
+
]
|
|
66321
|
+
}
|
|
66322
|
+
)
|
|
66323
|
+
] }),
|
|
66324
|
+
inheritSummary && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-400 mt-1", children: inheritSummary })
|
|
65484
66325
|
] }),
|
|
65485
66326
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto", children: [
|
|
65486
66327
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-xs", children: [
|
|
@@ -65601,7 +66442,7 @@ function EnvironmentEditor({ onClose }) {
|
|
|
65601
66442
|
"button",
|
|
65602
66443
|
{
|
|
65603
66444
|
onClick: () => cycleSource(idx),
|
|
65604
|
-
title: mode === "plain" ? "Plain text
|
|
66445
|
+
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",
|
|
65605
66446
|
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",
|
|
65606
66447
|
children: [
|
|
65607
66448
|
mode === "plain" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "abc" }),
|
|
@@ -65639,14 +66480,14 @@ function EnvironmentEditor({ onClose }) {
|
|
|
65639
66480
|
] }),
|
|
65640
66481
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-400", children: [
|
|
65641
66482
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-400", children: "🔒 Encrypted" }),
|
|
65642
|
-
"
|
|
66483
|
+
": AES-256-GCM, key from",
|
|
65643
66484
|
" ",
|
|
65644
66485
|
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-200", children: "API_SPECTOR_MASTER_KEY" }),
|
|
65645
66486
|
"."
|
|
65646
66487
|
] }),
|
|
65647
66488
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-400", children: [
|
|
65648
66489
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-blue-400", children: "$ Env var" }),
|
|
65649
|
-
"
|
|
66490
|
+
": read from",
|
|
65650
66491
|
" ",
|
|
65651
66492
|
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-200", children: "process.env" }),
|
|
65652
66493
|
" at send-time. Ideal for CI/CD."
|
|
@@ -65693,8 +66534,9 @@ function EnvironmentBar({ inline = false }) {
|
|
|
65693
66534
|
const setActiveEnvironment = useStore((s) => s.setActiveEnvironment);
|
|
65694
66535
|
const [showEditor, setShowEditor] = reactExports.useState(false);
|
|
65695
66536
|
const [pendingEnvId, setPendingEnvId] = reactExports.useState(null);
|
|
66537
|
+
const defaultEnvName = useStore((s) => s.workspace?.settings?.defaultEnvironment);
|
|
65696
66538
|
const envList = Object.values(environments);
|
|
65697
|
-
const activeEnv =
|
|
66539
|
+
const activeEnv = useActiveEnvironment();
|
|
65698
66540
|
const varCount = activeEnv?.variables.filter((v) => v.enabled).length ?? 0;
|
|
65699
66541
|
async function handleEnvChange(id2) {
|
|
65700
66542
|
if (id2) {
|
|
@@ -65729,7 +66571,7 @@ function EnvironmentBar({ inline = false }) {
|
|
|
65729
66571
|
style: { color: "var(--text-primary)" },
|
|
65730
66572
|
children: [
|
|
65731
66573
|
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "No env" }),
|
|
65732
|
-
envList.map(({ data: env }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: env.id, children: env.name }, env.id))
|
|
66574
|
+
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))
|
|
65733
66575
|
]
|
|
65734
66576
|
}
|
|
65735
66577
|
),
|
|
@@ -65760,12 +66602,15 @@ const ZOOM_STEPS = [0.75, 0.9, 1, 1.1, 1.25, 1.5];
|
|
|
65760
66602
|
function WorkspaceSettingsModal({ onClose }) {
|
|
65761
66603
|
const workspace = useStore((s) => s.workspace);
|
|
65762
66604
|
const updateWorkspaceSettings = useStore((s) => s.updateWorkspaceSettings);
|
|
66605
|
+
const environments = useStore((s) => s.environments);
|
|
65763
66606
|
const theme2 = useStore((s) => s.theme);
|
|
65764
66607
|
const zoom = useStore((s) => s.zoom);
|
|
65765
66608
|
const setTheme = useStore((s) => s.setTheme);
|
|
65766
66609
|
const setZoom = useStore((s) => s.setZoom);
|
|
65767
66610
|
const existing = workspace?.settings ?? {};
|
|
65768
|
-
const [activeTab, setActiveTab] = reactExports.useState("
|
|
66611
|
+
const [activeTab, setActiveTab] = reactExports.useState("general");
|
|
66612
|
+
const [defaultEnvironment, setDefaultEnvironment] = reactExports.useState(existing.defaultEnvironment ?? "");
|
|
66613
|
+
const [persistHistory, setPersistHistory] = reactExports.useState(existing.persistHistory ?? false);
|
|
65769
66614
|
const [proxyUrl, setProxyUrl] = reactExports.useState(existing.proxy?.url ?? "");
|
|
65770
66615
|
const [proxyUser, setProxyUser] = reactExports.useState(existing.proxy?.auth?.username ?? "");
|
|
65771
66616
|
const [proxyPass, setProxyPass] = reactExports.useState(existing.proxy?.auth?.password ?? "");
|
|
@@ -65776,6 +66621,7 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
65776
66621
|
existing.tls?.rejectUnauthorized !== false
|
|
65777
66622
|
// default true
|
|
65778
66623
|
);
|
|
66624
|
+
const [dashboardUrl, setDashboardUrl] = reactExports.useState(existing.dashboardUrl ?? "");
|
|
65779
66625
|
const [patterns, setPatterns] = reactExports.useState(
|
|
65780
66626
|
existing.piiMaskPatterns ?? DEFAULT_PII_PATTERNS
|
|
65781
66627
|
);
|
|
@@ -65807,9 +66653,19 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
65807
66653
|
rejectUnauthorized
|
|
65808
66654
|
};
|
|
65809
66655
|
settings.piiMaskPatterns = patterns;
|
|
66656
|
+
if (dashboardUrl.trim()) settings.dashboardUrl = dashboardUrl.trim();
|
|
66657
|
+
else delete settings.dashboardUrl;
|
|
66658
|
+
if (defaultEnvironment) settings.defaultEnvironment = defaultEnvironment;
|
|
66659
|
+
else delete settings.defaultEnvironment;
|
|
66660
|
+
if (persistHistory) settings.persistHistory = true;
|
|
66661
|
+
else delete settings.persistHistory;
|
|
65810
66662
|
updateWorkspaceSettings(settings);
|
|
65811
66663
|
const updated = useStore.getState().workspace;
|
|
65812
66664
|
if (updated) await electron$b.saveWorkspace(updated);
|
|
66665
|
+
if (persistHistory) {
|
|
66666
|
+
await electron$b.saveHistory(useStore.getState().history).catch(() => {
|
|
66667
|
+
});
|
|
66668
|
+
}
|
|
65813
66669
|
onClose();
|
|
65814
66670
|
}
|
|
65815
66671
|
function zoomStep(dir) {
|
|
@@ -65818,10 +66674,12 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
65818
66674
|
setZoom(next);
|
|
65819
66675
|
}
|
|
65820
66676
|
const tabs = [
|
|
66677
|
+
{ id: "general", label: "General" },
|
|
65821
66678
|
{ id: "appearance", label: "Appearance" },
|
|
65822
66679
|
{ id: "proxy", label: "Proxy" },
|
|
65823
66680
|
{ id: "tls", label: "TLS / Certificates" },
|
|
65824
|
-
{ id: "privacy", label: "Privacy" }
|
|
66681
|
+
{ id: "privacy", label: "Privacy" },
|
|
66682
|
+
{ id: "contracts", label: "Contracts" }
|
|
65825
66683
|
];
|
|
65826
66684
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
65827
66685
|
Modal,
|
|
@@ -65841,6 +66699,39 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
65841
66699
|
t2.id
|
|
65842
66700
|
)) }),
|
|
65843
66701
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 text-xs flex flex-col gap-4", children: [
|
|
66702
|
+
activeTab === "general" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
66703
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
66704
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Default environment" }),
|
|
66705
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
66706
|
+
"select",
|
|
66707
|
+
{
|
|
66708
|
+
value: defaultEnvironment,
|
|
66709
|
+
onChange: (e) => setDefaultEnvironment(e.target.value),
|
|
66710
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500",
|
|
66711
|
+
children: [
|
|
66712
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "(none)" }),
|
|
66713
|
+
Object.values(environments).map(({ data: env }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: env.name, children: env.name }, env.id))
|
|
66714
|
+
]
|
|
66715
|
+
}
|
|
66716
|
+
)
|
|
66717
|
+
] }),
|
|
66718
|
+
/* @__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." }),
|
|
66719
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-start gap-2 mt-2 cursor-pointer", children: [
|
|
66720
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
66721
|
+
"input",
|
|
66722
|
+
{
|
|
66723
|
+
type: "checkbox",
|
|
66724
|
+
checked: persistHistory,
|
|
66725
|
+
onChange: (e) => setPersistHistory(e.target.checked),
|
|
66726
|
+
className: "mt-0.5"
|
|
66727
|
+
}
|
|
66728
|
+
),
|
|
66729
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex flex-col gap-0.5", children: [
|
|
66730
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-200", children: "Persist request history" }),
|
|
66731
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-[11px]", children: "Save history to history.json in the workspace folder so it survives restarts. The file is gitignored. Off by default; history stays in memory otherwise." })
|
|
66732
|
+
] })
|
|
66733
|
+
] })
|
|
66734
|
+
] }),
|
|
65844
66735
|
activeTab === "appearance" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
65845
66736
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
65846
66737
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Theme" }),
|
|
@@ -65930,6 +66821,21 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
65930
66821
|
] })
|
|
65931
66822
|
] })
|
|
65932
66823
|
] }),
|
|
66824
|
+
activeTab === "contracts" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
66825
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
66826
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Dashboard URL (optional)" }),
|
|
66827
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
66828
|
+
"input",
|
|
66829
|
+
{
|
|
66830
|
+
value: dashboardUrl,
|
|
66831
|
+
onChange: (e) => setDashboardUrl(e.target.value),
|
|
66832
|
+
placeholder: "http://localhost:8080",
|
|
66833
|
+
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"
|
|
66834
|
+
}
|
|
66835
|
+
)
|
|
66836
|
+
] }),
|
|
66837
|
+
/* @__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.' })
|
|
66838
|
+
] }),
|
|
65933
66839
|
activeTab === "tls" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
65934
66840
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
65935
66841
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "CA Certificate path" }),
|
|
@@ -66467,7 +67373,7 @@ function ImportModal({ onImport, onClose }) {
|
|
|
66467
67373
|
children: [
|
|
66468
67374
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
66469
67375
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-sm font-semibold text-surface-100", children: [
|
|
66470
|
-
"Import OpenAPI
|
|
67376
|
+
"Import OpenAPI - ",
|
|
66471
67377
|
previewCol.name
|
|
66472
67378
|
] }),
|
|
66473
67379
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -67515,7 +68421,7 @@ function RunnerModal() {
|
|
|
67515
68421
|
return { ...item, request: req };
|
|
67516
68422
|
});
|
|
67517
68423
|
}
|
|
67518
|
-
const env =
|
|
68424
|
+
const env = resolveEnvironmentById(environments, selectedEnvId || null);
|
|
67519
68425
|
setRunnerResults(items2.map((item) => ({
|
|
67520
68426
|
requestId: item.request.id,
|
|
67521
68427
|
name: item.request.name,
|
|
@@ -67582,7 +68488,7 @@ function RunnerModal() {
|
|
|
67582
68488
|
onChange: (e) => setSelectedEnvId(e.target.value),
|
|
67583
68489
|
className: "text-xs bg-surface-800 border border-surface-700 rounded px-2 py-1 focus:outline-none focus:border-blue-500",
|
|
67584
68490
|
children: [
|
|
67585
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "
|
|
68491
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "(no environment)" }),
|
|
67586
68492
|
Object.values(environments).map(({ data: env }) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: env.id, children: env.name }, env.id))
|
|
67587
68493
|
]
|
|
67588
68494
|
}
|
|
@@ -67909,7 +68815,7 @@ function CollectionPanel() {
|
|
|
67909
68815
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto px-6 py-4", children: [
|
|
67910
68816
|
activeTab === "data" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 text-xs", children: [
|
|
67911
68817
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-surface-600 text-[11px]", children: [
|
|
67912
|
-
"Define variables here
|
|
68818
|
+
"Define variables here - each row runs the entire collection once with those values injected. Columns become ",
|
|
67913
68819
|
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-500", children: "{{variable}}" }),
|
|
67914
68820
|
" placeholders."
|
|
67915
68821
|
] }),
|
|
@@ -67922,14 +68828,14 @@ function CollectionPanel() {
|
|
|
67922
68828
|
{
|
|
67923
68829
|
onClick: () => csvFileRef.current?.click(),
|
|
67924
68830
|
className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors",
|
|
67925
|
-
title: "Import CSV
|
|
68831
|
+
title: "Import CSV - first row is column headers",
|
|
67926
68832
|
children: "↑ Import CSV"
|
|
67927
68833
|
}
|
|
67928
68834
|
),
|
|
67929
68835
|
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" }),
|
|
67930
68836
|
/* @__PURE__ */ jsxRuntimeExports.jsx("input", { ref: csvFileRef, type: "file", accept: ".csv,text/csv", className: "hidden", onChange: importCSV })
|
|
67931
68837
|
] }),
|
|
67932
|
-
hasColumns && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500", children: iterCount === 0 ? "No rows yet
|
|
68838
|
+
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(", ")}` }),
|
|
67933
68839
|
hasColumns ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full border-collapse text-xs", children: [
|
|
67934
68840
|
/* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-surface-700", children: [
|
|
67935
68841
|
/* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "w-8 px-2 py-1 text-surface-600 font-normal text-left", children: "#" }),
|
|
@@ -67960,7 +68866,7 @@ function CollectionPanel() {
|
|
|
67960
68866
|
) }, ci)),
|
|
67961
68867
|
/* @__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: "×" }) })
|
|
67962
68868
|
] }, ri)),
|
|
67963
|
-
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
|
|
68869
|
+
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' }) })
|
|
67964
68870
|
] })
|
|
67965
68871
|
] }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center gap-2 py-8 text-surface-600", children: [
|
|
67966
68872
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "No columns defined." }),
|
|
@@ -68570,7 +69476,7 @@ function HitRow({ hit, matched }) {
|
|
|
68570
69476
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 text-[10px] w-3 shrink-0", children: open ? "▾" : "▸" }),
|
|
68571
69477
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-bold w-16 shrink-0 text-xs ${getMethodColor(hit.method)}`, children: hit.method }),
|
|
68572
69478
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 truncate text-surface-200", title: hit.path, children: hit.path }),
|
|
68573
|
-
/* @__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 || "
|
|
69479
|
+
/* @__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 || "-" }),
|
|
68574
69480
|
/* @__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 }),
|
|
68575
69481
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "w-14 text-right shrink-0 text-surface-600 text-xs", children: [
|
|
68576
69482
|
hit.durationMs,
|
|
@@ -68951,7 +69857,7 @@ function RecorderPanel({ onImportMock, onClose, defaultTargetMockId }) {
|
|
|
68951
69857
|
onChange: (e) => setImportTarget(e.target.value),
|
|
68952
69858
|
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",
|
|
68953
69859
|
children: [
|
|
68954
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "new", children: "
|
|
69860
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "new", children: "(new mock server)" }),
|
|
68955
69861
|
mockList.map((entry) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: entry.data.id, children: entry.data.name }, entry.data.id))
|
|
68956
69862
|
]
|
|
68957
69863
|
}
|
|
@@ -69040,7 +69946,7 @@ function EntryDetail({ entry }) {
|
|
|
69040
69946
|
const sc = statusColor$1(entry.response.status);
|
|
69041
69947
|
const prettyBody = (raw) => {
|
|
69042
69948
|
if (!raw) return "";
|
|
69043
|
-
if (raw.startsWith("base64:")) return "[binary content
|
|
69949
|
+
if (raw.startsWith("base64:")) return "[binary content - base64 encoded]";
|
|
69044
69950
|
try {
|
|
69045
69951
|
return JSON.stringify(JSON.parse(raw), null, 2);
|
|
69046
69952
|
} catch {
|
|
@@ -69082,7 +69988,7 @@ function EntryDetail({ entry }) {
|
|
|
69082
69988
|
tab === "response" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
69083
69989
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Section, { label: "Headers", children: /* @__PURE__ */ jsxRuntimeExports.jsx(HeadersTable, { headers: entry.response.headers }) }),
|
|
69084
69990
|
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) }) }),
|
|
69085
|
-
entry.response.binary && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-yellow-400", children: "Binary response
|
|
69991
|
+
entry.response.binary && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-yellow-400", children: "Binary response - body stored as base64" })
|
|
69086
69992
|
] })
|
|
69087
69993
|
] });
|
|
69088
69994
|
}
|
|
@@ -69104,7 +70010,7 @@ function HeadersTable({ headers }) {
|
|
|
69104
70010
|
] }, k)) });
|
|
69105
70011
|
}
|
|
69106
70012
|
const { electron: electron$3 } = window;
|
|
69107
|
-
function ContractPanel() {
|
|
70013
|
+
function ContractPanel({ fuzzReport, setFuzzReport }) {
|
|
69108
70014
|
const collections = useStore((s) => s.collections);
|
|
69109
70015
|
const environments = useStore((s) => s.environments);
|
|
69110
70016
|
const activeEnvId = useStore((s) => s.activeEnvironmentId);
|
|
@@ -69125,18 +70031,28 @@ function ContractPanel() {
|
|
|
69125
70031
|
const [running, setRunning] = reactExports.useState(false);
|
|
69126
70032
|
const [capturing, setCapturing] = reactExports.useState(false);
|
|
69127
70033
|
const [error2, setError] = reactExports.useState(null);
|
|
70034
|
+
const [casesPerOperation, setCasesPerOperation] = reactExports.useState(40);
|
|
70035
|
+
const [seed, setSeed] = reactExports.useState(1);
|
|
70036
|
+
const [includeWrites, setIncludeWrites] = reactExports.useState(false);
|
|
70037
|
+
const [strictStatus, setStrictStatus] = reactExports.useState(false);
|
|
70038
|
+
const [checkResponses, setCheckResponses] = reactExports.useState(false);
|
|
70039
|
+
const [trace, setTrace] = reactExports.useState(false);
|
|
69128
70040
|
const snapshotList = Object.entries(snapshots).map(([relPath, snapshot]) => ({ relPath, snapshot })).sort((a, b) => b.snapshot.capturedAt.localeCompare(a.snapshot.capturedAt));
|
|
69129
70041
|
const activeSnapshot = activeSnapshotRelPath ? snapshots[activeSnapshotRelPath] ?? null : null;
|
|
69130
70042
|
const allRequests = Object.values(collections).flatMap((c) => Object.values(c.data.requests));
|
|
69131
70043
|
const contractRequests = allRequests.filter(
|
|
69132
70044
|
(r) => r.contract && (r.contract.statusCode !== void 0 || r.contract.bodySchema || r.contract.bodyMatcher || r.contract.headers?.length)
|
|
69133
70045
|
);
|
|
70046
|
+
const isFuzz = mode === "fuzz";
|
|
69134
70047
|
const needsSpec = mode === "provider" || mode === "bidirectional";
|
|
70048
|
+
const showSpec = needsSpec || isFuzz;
|
|
69135
70049
|
const collectionVars = activeCollId ? collections[activeCollId]?.data.collectionVariables ?? {} : {};
|
|
69136
|
-
const
|
|
69137
|
-
|
|
70050
|
+
const resolvedEnv = resolveEnvironmentById(environments, activeEnvId);
|
|
70051
|
+
const envVars = resolvedEnv ? Object.fromEntries(
|
|
70052
|
+
resolvedEnv.variables.filter((v) => v.enabled).map((v) => [v.key, v.value])
|
|
69138
70053
|
) : {};
|
|
69139
70054
|
async function runContracts() {
|
|
70055
|
+
if (mode === "fuzz") return;
|
|
69140
70056
|
if (needsSpec && !specUrl.trim() && !activeSnapshotRelPath) {
|
|
69141
70057
|
setError("Provide an OpenAPI spec URL or pick a pinned snapshot for provider / bi-directional mode.");
|
|
69142
70058
|
return;
|
|
@@ -69161,7 +70077,44 @@ function ContractPanel() {
|
|
|
69161
70077
|
providerBaseUrl: providerBaseUrl.trim() || void 0,
|
|
69162
70078
|
stateHandlerUrl: stateHandlerUrl.trim() || void 0
|
|
69163
70079
|
});
|
|
69164
|
-
|
|
70080
|
+
const activeSnap = activeSnapshotRelPath ? snapshots[activeSnapshotRelPath] : void 0;
|
|
70081
|
+
const specLabel = activeSnap ? `${activeSnap.name}${activeSnap.specVersion ? ` v${activeSnap.specVersion}` : ""} (pinned)` : specUrl.trim() || void 0;
|
|
70082
|
+
setReport(result, {
|
|
70083
|
+
spec: needsSpec ? specLabel : void 0,
|
|
70084
|
+
provider: providerBaseUrl.trim() || void 0
|
|
70085
|
+
});
|
|
70086
|
+
} catch (e) {
|
|
70087
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
70088
|
+
} finally {
|
|
70089
|
+
setRunning(false);
|
|
70090
|
+
}
|
|
70091
|
+
}
|
|
70092
|
+
async function runFuzz() {
|
|
70093
|
+
if (!providerBaseUrl.trim()) {
|
|
70094
|
+
setError("Provide a provider base URL (e.g. http://localhost:3000) to fuzz against.");
|
|
70095
|
+
return;
|
|
70096
|
+
}
|
|
70097
|
+
setRunning(true);
|
|
70098
|
+
setError(null);
|
|
70099
|
+
setFuzzReport(null);
|
|
70100
|
+
try {
|
|
70101
|
+
const result = await electron$3.fuzzContracts({
|
|
70102
|
+
requests: allRequests,
|
|
70103
|
+
// fuzz every request in the workspace, like provider mode
|
|
70104
|
+
envVars,
|
|
70105
|
+
collectionVars,
|
|
70106
|
+
specUrl: specUrl.trim() || void 0,
|
|
70107
|
+
specSnapshotRelPath: activeSnapshotRelPath ?? void 0,
|
|
70108
|
+
providerBaseUrl: providerBaseUrl.trim(),
|
|
70109
|
+
requestBaseUrl: requestBaseUrl.trim() || void 0,
|
|
70110
|
+
casesPerOperation,
|
|
70111
|
+
seed,
|
|
70112
|
+
includeWrites,
|
|
70113
|
+
strictStatus,
|
|
70114
|
+
checkResponses,
|
|
70115
|
+
trace
|
|
70116
|
+
});
|
|
70117
|
+
setFuzzReport(result);
|
|
69165
70118
|
} catch (e) {
|
|
69166
70119
|
setError(e instanceof Error ? e.message : String(e));
|
|
69167
70120
|
} finally {
|
|
@@ -69205,21 +70158,23 @@ function ContractPanel() {
|
|
|
69205
70158
|
["consumer", "Consumer"],
|
|
69206
70159
|
["provider", "Provider"],
|
|
69207
70160
|
["provider-live", "Live"],
|
|
69208
|
-
["bidirectional", "Bi-dir"]
|
|
70161
|
+
["bidirectional", "Bi-dir"],
|
|
70162
|
+
["fuzz", "Fuzz"]
|
|
69209
70163
|
].map(([m, label]) => /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69210
70164
|
"button",
|
|
69211
70165
|
{
|
|
69212
70166
|
onClick: () => {
|
|
69213
70167
|
setMode(m);
|
|
69214
70168
|
setReport(null);
|
|
70169
|
+
setFuzzReport(null);
|
|
69215
70170
|
},
|
|
69216
70171
|
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"}`,
|
|
69217
70172
|
children: label
|
|
69218
70173
|
},
|
|
69219
70174
|
m
|
|
69220
70175
|
)) }),
|
|
69221
|
-
/* @__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
|
|
69222
|
-
mode === "provider-live" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
70176
|
+
/* @__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." }),
|
|
70177
|
+
(mode === "provider-live" || isFuzz) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
69223
70178
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
69224
70179
|
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: "Provider base URL" }),
|
|
69225
70180
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -69231,9 +70186,9 @@ function ContractPanel() {
|
|
|
69231
70186
|
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"
|
|
69232
70187
|
}
|
|
69233
70188
|
),
|
|
69234
|
-
/* @__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." })
|
|
70189
|
+
/* @__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." })
|
|
69235
70190
|
] }),
|
|
69236
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
70191
|
+
mode === "provider-live" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
69237
70192
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: [
|
|
69238
70193
|
"State handler URL ",
|
|
69239
70194
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "normal-case text-surface-600", children: "(optional)" })
|
|
@@ -69256,9 +70211,12 @@ function ContractPanel() {
|
|
|
69256
70211
|
] })
|
|
69257
70212
|
] })
|
|
69258
70213
|
] }),
|
|
69259
|
-
|
|
70214
|
+
showSpec && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
69260
70215
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
69261
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
70216
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: [
|
|
70217
|
+
"Spec version ",
|
|
70218
|
+
isFuzz && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "normal-case text-surface-600", children: "(optional)" })
|
|
70219
|
+
] }),
|
|
69262
70220
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1", children: [
|
|
69263
70221
|
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
69264
70222
|
"select",
|
|
@@ -69271,7 +70229,7 @@ function ContractPanel() {
|
|
|
69271
70229
|
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "Live URL (latest from provider)" }),
|
|
69272
70230
|
snapshotList.map(({ relPath, snapshot }) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: relPath, children: [
|
|
69273
70231
|
snapshot.name,
|
|
69274
|
-
snapshot.specVersion ? "" : `
|
|
70232
|
+
snapshot.specVersion ? "" : ` - ${snapshot.capturedAt.slice(0, 10)}`
|
|
69275
70233
|
] }, relPath))
|
|
69276
70234
|
]
|
|
69277
70235
|
}
|
|
@@ -69289,7 +70247,7 @@ function ContractPanel() {
|
|
|
69289
70247
|
activeSnapshot && /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-surface-600 mt-1 font-mono truncate", children: [
|
|
69290
70248
|
"Captured ",
|
|
69291
70249
|
activeSnapshot.capturedAt.slice(0, 19).replace("T", " "),
|
|
69292
|
-
"
|
|
70250
|
+
" - sha ",
|
|
69293
70251
|
activeSnapshot.sha256.slice(0, 8)
|
|
69294
70252
|
] })
|
|
69295
70253
|
] }),
|
|
@@ -69335,15 +70293,93 @@ function ContractPanel() {
|
|
|
69335
70293
|
/* @__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." })
|
|
69336
70294
|
] })
|
|
69337
70295
|
] }),
|
|
70296
|
+
isFuzz && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
70297
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
|
|
70298
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
|
|
70299
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: "Cases per operation" }),
|
|
70300
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70301
|
+
"input",
|
|
70302
|
+
{
|
|
70303
|
+
type: "number",
|
|
70304
|
+
min: 1,
|
|
70305
|
+
value: casesPerOperation,
|
|
70306
|
+
onChange: (e) => setCasesPerOperation(Math.max(1, Number(e.target.value) || 1)),
|
|
70307
|
+
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"
|
|
70308
|
+
}
|
|
70309
|
+
)
|
|
70310
|
+
] }),
|
|
70311
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
|
|
70312
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: "Seed" }),
|
|
70313
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70314
|
+
"input",
|
|
70315
|
+
{
|
|
70316
|
+
type: "number",
|
|
70317
|
+
value: seed,
|
|
70318
|
+
onChange: (e) => setSeed(Number(e.target.value) || 0),
|
|
70319
|
+
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"
|
|
70320
|
+
}
|
|
70321
|
+
)
|
|
70322
|
+
] })
|
|
70323
|
+
] }),
|
|
70324
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-[11px] text-surface-300 cursor-pointer", children: [
|
|
70325
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70326
|
+
"input",
|
|
70327
|
+
{
|
|
70328
|
+
type: "checkbox",
|
|
70329
|
+
checked: includeWrites,
|
|
70330
|
+
onChange: (e) => setIncludeWrites(e.target.checked),
|
|
70331
|
+
className: "accent-blue-600"
|
|
70332
|
+
}
|
|
70333
|
+
),
|
|
70334
|
+
"Include write methods (POST/PUT/PATCH/DELETE)"
|
|
70335
|
+
] }),
|
|
70336
|
+
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." }),
|
|
70337
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-[11px] text-surface-300 cursor-pointer", children: [
|
|
70338
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70339
|
+
"input",
|
|
70340
|
+
{
|
|
70341
|
+
type: "checkbox",
|
|
70342
|
+
checked: strictStatus,
|
|
70343
|
+
onChange: (e) => setStrictStatus(e.target.checked),
|
|
70344
|
+
className: "accent-blue-600"
|
|
70345
|
+
}
|
|
70346
|
+
),
|
|
70347
|
+
"Strict status"
|
|
70348
|
+
] }),
|
|
70349
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-[11px] text-surface-300 cursor-pointer", children: [
|
|
70350
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70351
|
+
"input",
|
|
70352
|
+
{
|
|
70353
|
+
type: "checkbox",
|
|
70354
|
+
checked: checkResponses,
|
|
70355
|
+
onChange: (e) => setCheckResponses(e.target.checked),
|
|
70356
|
+
className: "accent-blue-600"
|
|
70357
|
+
}
|
|
70358
|
+
),
|
|
70359
|
+
"Check response schemas"
|
|
70360
|
+
] }),
|
|
70361
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-[11px] text-surface-300 cursor-pointer", children: [
|
|
70362
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70363
|
+
"input",
|
|
70364
|
+
{
|
|
70365
|
+
type: "checkbox",
|
|
70366
|
+
checked: trace,
|
|
70367
|
+
onChange: (e) => setTrace(e.target.checked),
|
|
70368
|
+
className: "accent-blue-600"
|
|
70369
|
+
}
|
|
70370
|
+
),
|
|
70371
|
+
"Record all cases"
|
|
70372
|
+
] })
|
|
70373
|
+
] }),
|
|
69338
70374
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
69339
|
-
/* @__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` }),
|
|
70375
|
+
/* @__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` }),
|
|
69340
70376
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69341
70377
|
"button",
|
|
69342
70378
|
{
|
|
69343
|
-
onClick: runContracts,
|
|
69344
|
-
disabled: running || needsSpec && !specUrl.trim() && !activeSnapshotRelPath || mode === "provider-live" && !providerBaseUrl.trim(),
|
|
70379
|
+
onClick: isFuzz ? runFuzz : runContracts,
|
|
70380
|
+
disabled: running || needsSpec && !specUrl.trim() && !activeSnapshotRelPath || (mode === "provider-live" || isFuzz) && !providerBaseUrl.trim(),
|
|
69345
70381
|
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",
|
|
69346
|
-
children: running ? "Running…" : "Run"
|
|
70382
|
+
children: running ? "Running…" : isFuzz ? "Run fuzz" : "Run"
|
|
69347
70383
|
}
|
|
69348
70384
|
)
|
|
69349
70385
|
] }),
|
|
@@ -69351,30 +70387,33 @@ function ContractPanel() {
|
|
|
69351
70387
|
] }),
|
|
69352
70388
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto min-h-0 p-3", children: [
|
|
69353
70389
|
running && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500 text-center mt-4", children: "Running…" }),
|
|
69354
|
-
!
|
|
69355
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: "Configure
|
|
69356
|
-
|
|
70390
|
+
isFuzz ? !fuzzReport && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-2 text-center", children: [
|
|
70391
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: "Configure fuzzing above and click Run fuzz." }),
|
|
70392
|
+
/* @__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." })
|
|
70393
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
70394
|
+
!report && !running && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-2 text-center", children: [
|
|
70395
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-surface-500", children: "Configure a mode above and click Run." }),
|
|
70396
|
+
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." })
|
|
70397
|
+
] }),
|
|
70398
|
+
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: [
|
|
70399
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold", children: report.failed === 0 ? "✓ All passed" : `✗ ${report.failed} failed` }),
|
|
70400
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 ml-auto", children: [
|
|
70401
|
+
report.passed,
|
|
70402
|
+
"/",
|
|
70403
|
+
report.total
|
|
70404
|
+
] })
|
|
70405
|
+
] })
|
|
69357
70406
|
] }),
|
|
69358
|
-
|
|
69359
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold", children:
|
|
70407
|
+
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: [
|
|
70408
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold", children: fuzzReport.totalFindings === 0 ? "✓ No findings" : `✗ ${fuzzReport.totalFindings} finding${fuzzReport.totalFindings !== 1 ? "s" : ""}` }),
|
|
69360
70409
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 ml-auto", children: [
|
|
69361
|
-
|
|
69362
|
-
"
|
|
69363
|
-
report.total
|
|
70410
|
+
fuzzReport.totalCases,
|
|
70411
|
+
" cases"
|
|
69364
70412
|
] })
|
|
69365
70413
|
] })
|
|
69366
70414
|
] })
|
|
69367
70415
|
] });
|
|
69368
70416
|
}
|
|
69369
|
-
const METHOD_COLOR = {
|
|
69370
|
-
GET: "text-emerald-400",
|
|
69371
|
-
POST: "text-blue-400",
|
|
69372
|
-
PUT: "text-amber-400",
|
|
69373
|
-
PATCH: "text-orange-400",
|
|
69374
|
-
DELETE: "text-red-400",
|
|
69375
|
-
HEAD: "text-purple-400",
|
|
69376
|
-
OPTIONS: "text-gray-400"
|
|
69377
|
-
};
|
|
69378
70417
|
function statusColor(code2) {
|
|
69379
70418
|
const d = String(code2)[0];
|
|
69380
70419
|
return d === "2" ? "text-emerald-400" : d === "3" ? "text-amber-400" : "text-red-400";
|
|
@@ -69408,7 +70447,7 @@ function ResultCard({ result }) {
|
|
|
69408
70447
|
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"}`,
|
|
69409
70448
|
children: [
|
|
69410
70449
|
/* @__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" }),
|
|
69411
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-bold font-mono w-14 ${
|
|
70450
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-bold font-mono w-14 ${getMethodColor(result.method)}`, children: result.method }),
|
|
69412
70451
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 text-sm text-white truncate", children: result.requestName }),
|
|
69413
70452
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "hidden lg:block text-[11px] text-surface-500 font-mono truncate max-w-[260px]", children: result.url }),
|
|
69414
70453
|
result.actualStatus !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-xs font-mono font-bold ${statusColor(result.actualStatus)}`, children: result.actualStatus }),
|
|
@@ -69430,7 +70469,37 @@ function ResultCard({ result }) {
|
|
|
69430
70469
|
}
|
|
69431
70470
|
function ContractResultsPanel() {
|
|
69432
70471
|
const report = useStore((s) => s.lastContractReport);
|
|
70472
|
+
const runMeta = useStore((s) => s.lastContractRunMeta);
|
|
69433
70473
|
const clearReport = useStore((s) => s.setLastContractReport);
|
|
70474
|
+
const activeCollId = useStore((s) => s.activeCollectionId);
|
|
70475
|
+
const collections = useStore((s) => s.collections);
|
|
70476
|
+
const dashboardUrl = useStore((s) => s.workspace?.settings?.dashboardUrl);
|
|
70477
|
+
const [recordOpen, setRecordOpen] = reactExports.useState(false);
|
|
70478
|
+
const [pacticipant, setPacticipant] = reactExports.useState("");
|
|
70479
|
+
const [version, setVersion] = reactExports.useState("");
|
|
70480
|
+
const [saving, setSaving] = reactExports.useState(false);
|
|
70481
|
+
const { toast, show: showToast } = useToast();
|
|
70482
|
+
async function recordRun() {
|
|
70483
|
+
if (!report) return;
|
|
70484
|
+
if (!pacticipant.trim() || !version.trim()) {
|
|
70485
|
+
showToast("Pacticipant and version are required to record.", false);
|
|
70486
|
+
return;
|
|
70487
|
+
}
|
|
70488
|
+
setSaving(true);
|
|
70489
|
+
try {
|
|
70490
|
+
await window.electron.recordContractResult({
|
|
70491
|
+
pacticipant: pacticipant.trim(),
|
|
70492
|
+
version: version.trim(),
|
|
70493
|
+
report
|
|
70494
|
+
});
|
|
70495
|
+
showToast(`Recorded ${pacticipant.trim()}@${version.trim()} for the dashboard and can-i-deploy.`, true);
|
|
70496
|
+
setRecordOpen(false);
|
|
70497
|
+
} catch (e) {
|
|
70498
|
+
showToast(e instanceof Error ? e.message : String(e), false);
|
|
70499
|
+
} finally {
|
|
70500
|
+
setSaving(false);
|
|
70501
|
+
}
|
|
70502
|
+
}
|
|
69434
70503
|
if (!report) {
|
|
69435
70504
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-3 text-center", children: [
|
|
69436
70505
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-4xl opacity-30", children: "🔬" }),
|
|
@@ -69453,10 +70522,36 @@ function ContractResultsPanel() {
|
|
|
69453
70522
|
report.durationMs,
|
|
69454
70523
|
"ms"
|
|
69455
70524
|
] }),
|
|
70525
|
+
dashboardUrl && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70526
|
+
"button",
|
|
70527
|
+
{
|
|
70528
|
+
onClick: () => window.electron.openExternal(dashboardUrl),
|
|
70529
|
+
className: "text-[11px] text-surface-500 hover:text-surface-200 transition-colors",
|
|
70530
|
+
title: `Open the contract dashboard (${dashboardUrl})`,
|
|
70531
|
+
children: "Open dashboard"
|
|
70532
|
+
}
|
|
70533
|
+
),
|
|
70534
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70535
|
+
"button",
|
|
70536
|
+
{
|
|
70537
|
+
onClick: () => {
|
|
70538
|
+
if (!recordOpen && !pacticipant) {
|
|
70539
|
+
setPacticipant(activeCollId ? collections[activeCollId]?.data.name ?? "" : "");
|
|
70540
|
+
}
|
|
70541
|
+
setRecordOpen((o) => !o);
|
|
70542
|
+
},
|
|
70543
|
+
className: "text-[11px] text-surface-500 hover:text-surface-200 transition-colors",
|
|
70544
|
+
title: "Record this run for the contract dashboard and can-i-deploy gate",
|
|
70545
|
+
children: "Record"
|
|
70546
|
+
}
|
|
70547
|
+
),
|
|
69456
70548
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69457
70549
|
"button",
|
|
69458
70550
|
{
|
|
69459
|
-
onClick: () => window.electron.exportContractReportHtml(report
|
|
70551
|
+
onClick: () => window.electron.exportContractReportHtml(report, {
|
|
70552
|
+
spec: runMeta?.spec,
|
|
70553
|
+
provider: runMeta?.provider
|
|
70554
|
+
}),
|
|
69460
70555
|
className: "text-[11px] text-surface-500 hover:text-surface-200 transition-colors",
|
|
69461
70556
|
title: "Export a self-contained HTML report",
|
|
69462
70557
|
children: "Export HTML"
|
|
@@ -69472,6 +70567,37 @@ function ContractResultsPanel() {
|
|
|
69472
70567
|
}
|
|
69473
70568
|
)
|
|
69474
70569
|
] }),
|
|
70570
|
+
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: [
|
|
70571
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70572
|
+
"input",
|
|
70573
|
+
{
|
|
70574
|
+
value: pacticipant,
|
|
70575
|
+
onChange: (e) => setPacticipant(e.target.value),
|
|
70576
|
+
placeholder: "pacticipant (e.g. web-app)",
|
|
70577
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs w-48 focus:outline-none focus:border-blue-500"
|
|
70578
|
+
}
|
|
70579
|
+
),
|
|
70580
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70581
|
+
"input",
|
|
70582
|
+
{
|
|
70583
|
+
value: version,
|
|
70584
|
+
onChange: (e) => setVersion(e.target.value),
|
|
70585
|
+
placeholder: "version (e.g. 1.4.0)",
|
|
70586
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 text-xs w-32 focus:outline-none focus:border-blue-500"
|
|
70587
|
+
}
|
|
70588
|
+
),
|
|
70589
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70590
|
+
"button",
|
|
70591
|
+
{
|
|
70592
|
+
onClick: recordRun,
|
|
70593
|
+
disabled: saving,
|
|
70594
|
+
className: "text-[11px] px-3 py-1 rounded bg-blue-600 hover:bg-blue-500 text-white font-semibold disabled:opacity-50 transition-colors",
|
|
70595
|
+
children: saving ? "Recording..." : "Record result"
|
|
70596
|
+
}
|
|
70597
|
+
),
|
|
70598
|
+
/* @__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." })
|
|
70599
|
+
] }),
|
|
70600
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }),
|
|
69475
70601
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto min-h-0 p-6", children: (() => {
|
|
69476
70602
|
const failed = report.results.filter((r) => !r.passed);
|
|
69477
70603
|
const passed = report.results.filter((r) => r.passed);
|
|
@@ -69571,7 +70697,7 @@ ${secretHint} - script: ${runCmd}
|
|
|
69571
70697
|
condition: always()
|
|
69572
70698
|
`;
|
|
69573
70699
|
}
|
|
69574
|
-
return `# Unsupported platform
|
|
70700
|
+
return `# Unsupported platform - adapt as needed
|
|
69575
70701
|
# ${runCmd}
|
|
69576
70702
|
`;
|
|
69577
70703
|
}
|
|
@@ -69749,7 +70875,7 @@ function ChangesTab({ status, onRefresh }) {
|
|
|
69749
70875
|
status.conflicted.length,
|
|
69750
70876
|
" merge conflict",
|
|
69751
70877
|
status.conflicted.length !== 1 ? "s" : "",
|
|
69752
|
-
"
|
|
70878
|
+
" - resolve below before committing"
|
|
69753
70879
|
] }),
|
|
69754
70880
|
/* @__PURE__ */ jsxRuntimeExports.jsx(Toast, { toast }),
|
|
69755
70881
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto", children: [
|
|
@@ -70144,7 +71270,7 @@ function BranchesTab({ onRefresh }) {
|
|
|
70144
71270
|
{
|
|
70145
71271
|
onClick: () => checkout(b.name, false),
|
|
70146
71272
|
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",
|
|
70147
|
-
title: "Click to check out
|
|
71273
|
+
title: "Click to check out - creates a local tracking branch if needed",
|
|
70148
71274
|
children: [
|
|
70149
71275
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "w-3" }),
|
|
70150
71276
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono truncate", children: b.name })
|
|
@@ -70305,7 +71431,7 @@ function CiTab() {
|
|
|
70305
71431
|
onChange: (e) => setEnvId(e.target.value),
|
|
70306
71432
|
className: "bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs focus:outline-none focus:border-blue-500",
|
|
70307
71433
|
children: [
|
|
70308
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "
|
|
71434
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "(none)" }),
|
|
70309
71435
|
envList.map((e) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: e.data.id, children: e.data.name }, e.data.id))
|
|
70310
71436
|
]
|
|
70311
71437
|
}
|
|
@@ -70662,7 +71788,8 @@ const TAB_METHOD_COLORS = {
|
|
|
70662
71788
|
PATCH: "text-orange-400",
|
|
70663
71789
|
DELETE: "text-red-400",
|
|
70664
71790
|
HEAD: "text-purple-400",
|
|
70665
|
-
OPTIONS: "text-gray-400"
|
|
71791
|
+
OPTIONS: "text-gray-400",
|
|
71792
|
+
QUERY: "text-fuchsia-400"
|
|
70666
71793
|
};
|
|
70667
71794
|
const TabRow = React$2.memo(function TabRow2({
|
|
70668
71795
|
tabId,
|
|
@@ -70730,6 +71857,7 @@ function App() {
|
|
|
70730
71857
|
const setCommandPaletteOpen = useStore((s) => s.setCommandPaletteOpen);
|
|
70731
71858
|
const setWsStatus = useStore((s) => s.setWsStatus);
|
|
70732
71859
|
const addWsMessage = useStore((s) => s.addWsMessage);
|
|
71860
|
+
const [fuzzReport, setFuzzReport] = reactExports.useState(null);
|
|
70733
71861
|
const [sidebarOpen, setSidebarOpen] = reactExports.useState(true);
|
|
70734
71862
|
const [responseOpen, setResponseOpen] = reactExports.useState(false);
|
|
70735
71863
|
const [docsModalOpen, setDocsModalOpen] = reactExports.useState(false);
|
|
@@ -70831,7 +71959,7 @@ function App() {
|
|
|
70831
71959
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
|
|
70832
71960
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
|
|
70833
71961
|
"v",
|
|
70834
|
-
"0.3.
|
|
71962
|
+
"0.3.7"
|
|
70835
71963
|
] }),
|
|
70836
71964
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", children: "·" }),
|
|
70837
71965
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -70920,7 +72048,7 @@ function App() {
|
|
|
70920
72048
|
)
|
|
70921
72049
|
] })
|
|
70922
72050
|
] }),
|
|
70923
|
-
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, {})
|
|
72051
|
+
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 })
|
|
70924
72052
|
] }),
|
|
70925
72053
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
70926
72054
|
"div",
|
|
@@ -71028,13 +72156,13 @@ function App() {
|
|
|
71028
72156
|
}
|
|
71029
72157
|
)
|
|
71030
72158
|
] }),
|
|
71031
|
-
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(
|
|
72159
|
+
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(
|
|
71032
72160
|
RecorderPanel,
|
|
71033
72161
|
{
|
|
71034
72162
|
defaultTargetMockId: recorderTargetMockId,
|
|
71035
72163
|
onClose: () => setRecorderRunning(false),
|
|
71036
72164
|
onImportMock: async (session, targetMockId) => {
|
|
71037
|
-
const name2 = `Recorded
|
|
72165
|
+
const name2 = `Recorded - ${new URL(session.upstream).hostname}`;
|
|
71038
72166
|
const newRoutes = await electron.recordToMock(session.entries, session.upstream, name2, session.port);
|
|
71039
72167
|
if (targetMockId) {
|
|
71040
72168
|
const existing = useStore.getState().mocks[targetMockId];
|