@testsmith/api-spector 0.4.9 → 0.5.1
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 +6 -0
- package/out/main/chunks/coverage-C54mZEh_.js +146 -0
- package/out/main/chunks/{handle-CF2LjTGV.js → handle-b6bp3fb0.js} +18 -0
- package/out/main/chunks/{import-ChCkdHOB.js → import-BeY7gjWL.js} +2 -2
- package/out/main/chunks/{request-exec-DeXgxps8.js → request-exec-D_xyyt3_.js} +6 -6
- package/out/main/chunks/{snapshots-8wLrfQcq.js → snapshots-BJiZNuRN.js} +1 -1
- package/out/main/chunks/{soap-handler-pOrJ625E.js → soap-handler-BYkE6MyE.js} +2 -2
- package/out/main/compare.js +218 -0
- package/out/main/contract.js +3 -3
- package/out/main/coverage.js +161 -0
- package/out/main/generate-tests.js +317 -0
- package/out/main/index.js +213 -20
- package/out/main/lib.js +2 -2
- package/out/main/runner.js +3 -3
- package/out/main/wsdl.js +3 -3
- package/out/preload/index.js +34 -0
- package/out/renderer/assets/{index-b63nUtxA.js → index-CV7iFJoA.js} +1629 -163
- package/out/renderer/assets/index-feMbYxZb.css +2 -0
- package/out/renderer/index.html +2 -2
- package/package.json +3 -1
- package/out/renderer/assets/index-CFvibipL.css +0 -2
|
@@ -13403,6 +13403,38 @@ const createWsSlice = (set2) => ({
|
|
|
13403
13403
|
if (s.wsConnections[requestId]) s.wsConnections[requestId].messages = [];
|
|
13404
13404
|
})
|
|
13405
13405
|
});
|
|
13406
|
+
const GRPC_MESSAGE_CAP = 1e3;
|
|
13407
|
+
const blank = () => ({ status: "idle", messages: [], services: [] });
|
|
13408
|
+
const createGrpcSlice = (set2) => ({
|
|
13409
|
+
grpcCalls: {},
|
|
13410
|
+
setGrpcStatus: (requestId, status, extra) => set2((s) => {
|
|
13411
|
+
const c = s.grpcCalls[requestId] ??= blank();
|
|
13412
|
+
c.status = status;
|
|
13413
|
+
c.code = extra?.code;
|
|
13414
|
+
c.codeName = extra?.codeName;
|
|
13415
|
+
c.error = extra?.error;
|
|
13416
|
+
}),
|
|
13417
|
+
addGrpcMessage: (requestId, message) => set2((s) => {
|
|
13418
|
+
const c = s.grpcCalls[requestId] ??= blank();
|
|
13419
|
+
c.messages.push(message);
|
|
13420
|
+
if (c.messages.length > GRPC_MESSAGE_CAP) {
|
|
13421
|
+
c.messages.splice(0, c.messages.length - GRPC_MESSAGE_CAP);
|
|
13422
|
+
}
|
|
13423
|
+
}),
|
|
13424
|
+
clearGrpcMessages: (requestId) => set2((s) => {
|
|
13425
|
+
if (s.grpcCalls[requestId]) s.grpcCalls[requestId].messages = [];
|
|
13426
|
+
}),
|
|
13427
|
+
setGrpcServices: (requestId, services) => set2((s) => {
|
|
13428
|
+
const c = s.grpcCalls[requestId] ??= blank();
|
|
13429
|
+
c.services = services;
|
|
13430
|
+
c.protoError = void 0;
|
|
13431
|
+
}),
|
|
13432
|
+
setGrpcProtoError: (requestId, error2) => set2((s) => {
|
|
13433
|
+
const c = s.grpcCalls[requestId] ??= blank();
|
|
13434
|
+
c.protoError = error2;
|
|
13435
|
+
if (error2) c.services = [];
|
|
13436
|
+
})
|
|
13437
|
+
});
|
|
13406
13438
|
const LIVE_EVENT_CAP = 5e3;
|
|
13407
13439
|
const createStreamSlice = (set2) => ({
|
|
13408
13440
|
liveStream: null,
|
|
@@ -13706,6 +13738,19 @@ const createCollectionsSlice = (set2, get2) => ({
|
|
|
13706
13738
|
s.activeCollectionId = col.id;
|
|
13707
13739
|
if (s.workspace) s.workspace.collections.push(relPath);
|
|
13708
13740
|
}),
|
|
13741
|
+
addCollectionObject: (data) => {
|
|
13742
|
+
let id2 = data.id;
|
|
13743
|
+
set2((s) => {
|
|
13744
|
+
const existingNames = Object.values(s.collections).map((c) => c.data.name);
|
|
13745
|
+
data.name = uniqueName(data.name, existingNames);
|
|
13746
|
+
const relPath = colRelPath(data.name, data.id);
|
|
13747
|
+
s.collections[data.id] = { relPath, data, dirty: true };
|
|
13748
|
+
s.activeCollectionId = data.id;
|
|
13749
|
+
if (s.workspace) s.workspace.collections.push(relPath);
|
|
13750
|
+
id2 = data.id;
|
|
13751
|
+
});
|
|
13752
|
+
return id2;
|
|
13753
|
+
},
|
|
13709
13754
|
renameCollection: (id2, name2) => set2((s) => {
|
|
13710
13755
|
if (!s.collections[id2]) return;
|
|
13711
13756
|
const oldRelPath = s.collections[id2].relPath;
|
|
@@ -14278,6 +14323,8 @@ const createUiSlice = (set2) => ({
|
|
|
14278
14323
|
quickInsertsOpen: true,
|
|
14279
14324
|
sendSignal: 0,
|
|
14280
14325
|
collectionPanelOpen: false,
|
|
14326
|
+
coverageOpen: false,
|
|
14327
|
+
compareOpen: false,
|
|
14281
14328
|
setShowGeneratorPanel: (v) => set2((s) => {
|
|
14282
14329
|
s.showGeneratorPanel = v;
|
|
14283
14330
|
}),
|
|
@@ -14331,12 +14378,27 @@ const createUiSlice = (set2) => ({
|
|
|
14331
14378
|
}),
|
|
14332
14379
|
setCollectionPanelOpen: (open) => set2((s) => {
|
|
14333
14380
|
s.collectionPanelOpen = open;
|
|
14381
|
+
}),
|
|
14382
|
+
setCoverageOpen: (open) => set2((s) => {
|
|
14383
|
+
s.coverageOpen = open;
|
|
14384
|
+
}),
|
|
14385
|
+
setCompareOpen: (open) => set2((s) => {
|
|
14386
|
+
s.compareOpen = open;
|
|
14387
|
+
}),
|
|
14388
|
+
// The OpenAPI spec used for coverage travels with the workspace (persisted on
|
|
14389
|
+
// save), mirroring how theme/zoom are stored.
|
|
14390
|
+
setCoverageSpec: (spec) => set2((s) => {
|
|
14391
|
+
if (s.workspace) {
|
|
14392
|
+
if (!s.workspace.settings) s.workspace.settings = {};
|
|
14393
|
+
s.workspace.settings.coverageSpec = spec;
|
|
14394
|
+
}
|
|
14334
14395
|
})
|
|
14335
14396
|
});
|
|
14336
14397
|
const useStore = create()(
|
|
14337
14398
|
immer((set2, get2, api) => ({
|
|
14338
14399
|
// ── Slice composition ─────────────────────────────────────────────────────
|
|
14339
14400
|
...createWsSlice(set2),
|
|
14401
|
+
...createGrpcSlice(set2),
|
|
14340
14402
|
...createStreamSlice(set2),
|
|
14341
14403
|
...createHistorySlice(set2, get2),
|
|
14342
14404
|
...createRunnerSlice(set2),
|
|
@@ -14430,7 +14492,7 @@ const useStore = create()(
|
|
|
14430
14492
|
})
|
|
14431
14493
|
}))
|
|
14432
14494
|
);
|
|
14433
|
-
const { electron: electron$
|
|
14495
|
+
const { electron: electron$y } = window;
|
|
14434
14496
|
function useAutoSave() {
|
|
14435
14497
|
const collections = useStore((s) => s.collections);
|
|
14436
14498
|
useStore((s) => s.environments);
|
|
@@ -14446,7 +14508,7 @@ function useAutoSave() {
|
|
|
14446
14508
|
for (const { relPath, data, dirty } of dirtyCollections) {
|
|
14447
14509
|
if (!dirty) continue;
|
|
14448
14510
|
try {
|
|
14449
|
-
await electron$
|
|
14511
|
+
await electron$y.saveCollection(relPath, data);
|
|
14450
14512
|
markCollectionClean(data.id);
|
|
14451
14513
|
} catch (e) {
|
|
14452
14514
|
console.error("Auto-save failed for", relPath, e);
|
|
@@ -14462,7 +14524,7 @@ function useAutoSave() {
|
|
|
14462
14524
|
if (wsTimerRef.current) clearTimeout(wsTimerRef.current);
|
|
14463
14525
|
wsTimerRef.current = setTimeout(async () => {
|
|
14464
14526
|
try {
|
|
14465
|
-
await electron$
|
|
14527
|
+
await electron$y.saveWorkspace(workspace);
|
|
14466
14528
|
} catch {
|
|
14467
14529
|
}
|
|
14468
14530
|
}, 300);
|
|
@@ -14471,7 +14533,7 @@ function useAutoSave() {
|
|
|
14471
14533
|
};
|
|
14472
14534
|
}, [workspace]);
|
|
14473
14535
|
}
|
|
14474
|
-
const { electron: electron$
|
|
14536
|
+
const { electron: electron$x } = window;
|
|
14475
14537
|
function useWorkspaceLoader() {
|
|
14476
14538
|
const loadCollection = useStore((s) => s.loadCollection);
|
|
14477
14539
|
const loadEnvironment = useStore((s) => s.loadEnvironment);
|
|
@@ -14495,7 +14557,7 @@ function useWorkspaceLoader() {
|
|
|
14495
14557
|
if (typeof ws2.settings?.zoom === "number") setZoom(ws2.settings.zoom);
|
|
14496
14558
|
if (ws2.settings?.persistHistory) {
|
|
14497
14559
|
try {
|
|
14498
|
-
const entries = await electron$
|
|
14560
|
+
const entries = await electron$x.loadHistory();
|
|
14499
14561
|
useStore.getState().setHistory(entries);
|
|
14500
14562
|
} catch {
|
|
14501
14563
|
}
|
|
@@ -14504,14 +14566,14 @@ function useWorkspaceLoader() {
|
|
|
14504
14566
|
}
|
|
14505
14567
|
for (const colPath of ws2.collections ?? []) {
|
|
14506
14568
|
try {
|
|
14507
|
-
const col = await electron$
|
|
14569
|
+
const col = await electron$x.loadCollection(colPath);
|
|
14508
14570
|
loadCollection(colPath, col);
|
|
14509
14571
|
} catch {
|
|
14510
14572
|
}
|
|
14511
14573
|
}
|
|
14512
14574
|
for (const envPath of ws2.environments ?? []) {
|
|
14513
14575
|
try {
|
|
14514
|
-
const env = await electron$
|
|
14576
|
+
const env = await electron$x.loadEnvironment(envPath);
|
|
14515
14577
|
loadEnvironment(envPath, env);
|
|
14516
14578
|
} catch {
|
|
14517
14579
|
}
|
|
@@ -14530,19 +14592,19 @@ function useWorkspaceLoader() {
|
|
|
14530
14592
|
}
|
|
14531
14593
|
for (const relPath of ws2.mocks ?? []) {
|
|
14532
14594
|
try {
|
|
14533
|
-
const mockData = await electron$
|
|
14595
|
+
const mockData = await electron$x.loadMock(relPath);
|
|
14534
14596
|
loadMock(relPath, mockData);
|
|
14535
14597
|
} catch {
|
|
14536
14598
|
}
|
|
14537
14599
|
}
|
|
14538
14600
|
try {
|
|
14539
|
-
const snapshots = await electron$
|
|
14601
|
+
const snapshots = await electron$x.listContractSnapshots(ws2.contracts ?? []);
|
|
14540
14602
|
for (const { relPath, snapshot } of snapshots) loadContractSnapshot(relPath, snapshot);
|
|
14541
14603
|
} catch {
|
|
14542
14604
|
}
|
|
14543
14605
|
if ((ws2.collections ?? []).length > 0) {
|
|
14544
14606
|
try {
|
|
14545
|
-
const firstCol = await electron$
|
|
14607
|
+
const firstCol = await electron$x.loadCollection(ws2.collections[0]);
|
|
14546
14608
|
setActiveCollection(firstCol.id);
|
|
14547
14609
|
} catch {
|
|
14548
14610
|
}
|
|
@@ -36428,7 +36490,7 @@ function CollectionSettingsModal({ collection, onClose }) {
|
|
|
36428
36490
|
}
|
|
36429
36491
|
);
|
|
36430
36492
|
}
|
|
36431
|
-
const { electron: electron$
|
|
36493
|
+
const { electron: electron$w } = window;
|
|
36432
36494
|
function normalisePath(url) {
|
|
36433
36495
|
let path = url.replace(/^\{\{[^}]+\}\}/, "").replace(/^https?:\/\/[^/]+/, "");
|
|
36434
36496
|
if (!path.startsWith("/")) path = "/" + path;
|
|
@@ -36515,7 +36577,7 @@ function SchemaSyncModal({
|
|
|
36515
36577
|
setLoading(true);
|
|
36516
36578
|
setError(null);
|
|
36517
36579
|
try {
|
|
36518
|
-
const entries = await electron$
|
|
36580
|
+
const entries = await electron$w.extractOpenApiSchemas();
|
|
36519
36581
|
if (!entries) {
|
|
36520
36582
|
setLoading(false);
|
|
36521
36583
|
return;
|
|
@@ -36534,7 +36596,7 @@ function SchemaSyncModal({
|
|
|
36534
36596
|
setLoading(true);
|
|
36535
36597
|
setError(null);
|
|
36536
36598
|
try {
|
|
36537
|
-
const entries = await electron$
|
|
36599
|
+
const entries = await electron$w.extractOpenApiSchemasFromUrl(trimmed);
|
|
36538
36600
|
setSpecEntries(entries);
|
|
36539
36601
|
autoSelectChanged(entries);
|
|
36540
36602
|
} catch (err) {
|
|
@@ -36567,7 +36629,7 @@ function SchemaSyncModal({
|
|
|
36567
36629
|
}
|
|
36568
36630
|
const entry = useStore.getState().collections[collectionId];
|
|
36569
36631
|
if (entry) {
|
|
36570
|
-
await electron$
|
|
36632
|
+
await electron$w.saveCollection(entry.relPath, entry.data);
|
|
36571
36633
|
markCollectionClean(collectionId);
|
|
36572
36634
|
}
|
|
36573
36635
|
onClose();
|
|
@@ -36852,14 +36914,14 @@ function collectTagged(folder, requests, collectionVars, filterTags, parentPath
|
|
|
36852
36914
|
}
|
|
36853
36915
|
function collectAllTags(folder, requests) {
|
|
36854
36916
|
const tags2 = /* @__PURE__ */ new Set();
|
|
36855
|
-
function
|
|
36917
|
+
function walk2(f) {
|
|
36856
36918
|
(f.tags ?? []).forEach((t2) => tags2.add(t2));
|
|
36857
36919
|
for (const reqId of f.requestIds) {
|
|
36858
36920
|
(requests[reqId]?.meta?.tags ?? []).forEach((t2) => tags2.add(t2));
|
|
36859
36921
|
}
|
|
36860
|
-
f.folders.forEach(
|
|
36922
|
+
f.folders.forEach(walk2);
|
|
36861
36923
|
}
|
|
36862
|
-
|
|
36924
|
+
walk2(folder);
|
|
36863
36925
|
return Array.from(tags2).sort();
|
|
36864
36926
|
}
|
|
36865
36927
|
function folderPathTo(root2, requestId) {
|
|
@@ -36982,7 +37044,7 @@ function hasContract(r) {
|
|
|
36982
37044
|
const c = r.contract;
|
|
36983
37045
|
return !!c && (c.statusCode != null || !!c.bodySchema || !!c.bodyMatcher || (c.headers?.length ?? 0) > 0 || (c.providerStates?.length ?? 0) > 0);
|
|
36984
37046
|
}
|
|
36985
|
-
const { electron: electron$
|
|
37047
|
+
const { electron: electron$v } = window;
|
|
36986
37048
|
function cloudEnabled() {
|
|
36987
37049
|
return Boolean(useStore.getState().workspace?.settings?.cloud?.enabled);
|
|
36988
37050
|
}
|
|
@@ -36991,7 +37053,7 @@ function assertEnabled() {
|
|
|
36991
37053
|
}
|
|
36992
37054
|
async function pushContractToCloud(requests, opts) {
|
|
36993
37055
|
assertEnabled();
|
|
36994
|
-
return electron$
|
|
37056
|
+
return electron$v.cloudPushPact({
|
|
36995
37057
|
consumer: opts.consumer,
|
|
36996
37058
|
provider: opts.provider,
|
|
36997
37059
|
consumerVersion: opts.version,
|
|
@@ -37000,15 +37062,15 @@ async function pushContractToCloud(requests, opts) {
|
|
|
37000
37062
|
}
|
|
37001
37063
|
async function pushProviderSpecToCloud(opts) {
|
|
37002
37064
|
assertEnabled();
|
|
37003
|
-
return electron$
|
|
37065
|
+
return electron$v.cloudPushSpec(opts);
|
|
37004
37066
|
}
|
|
37005
37067
|
function openCloudMatrix() {
|
|
37006
|
-
void electron$
|
|
37068
|
+
void electron$v.cloudOpenMatrix();
|
|
37007
37069
|
}
|
|
37008
37070
|
async function getCloudMockRoutes(name2) {
|
|
37009
37071
|
if (!cloudEnabled()) return null;
|
|
37010
37072
|
try {
|
|
37011
|
-
const res = await electron$
|
|
37073
|
+
const res = await electron$v.cloudGetMock(name2);
|
|
37012
37074
|
return res.exists ? res.routes ?? [] : null;
|
|
37013
37075
|
} catch {
|
|
37014
37076
|
return null;
|
|
@@ -37017,7 +37079,7 @@ async function getCloudMockRoutes(name2) {
|
|
|
37017
37079
|
async function pushMockToCloud(server, routeIds) {
|
|
37018
37080
|
assertEnabled();
|
|
37019
37081
|
const routes = routeIds ? (server.routes ?? []).filter((r) => routeIds.includes(r.id)) : server.routes ?? [];
|
|
37020
|
-
return electron$
|
|
37082
|
+
return electron$v.cloudPushMock({ ...server, routes });
|
|
37021
37083
|
}
|
|
37022
37084
|
async function pushRequestAsMonitor(request, opts) {
|
|
37023
37085
|
assertEnabled();
|
|
@@ -37038,7 +37100,7 @@ async function pushRequestAsMonitor(request, opts) {
|
|
|
37038
37100
|
...request.headers
|
|
37039
37101
|
];
|
|
37040
37102
|
const mergedRequest = { ...request, auth: mergedAuth, headers: mergedHeaders };
|
|
37041
|
-
return electron$
|
|
37103
|
+
return electron$v.cloudPushMonitor(
|
|
37042
37104
|
{
|
|
37043
37105
|
request: mergedRequest,
|
|
37044
37106
|
setup,
|
|
@@ -37796,16 +37858,16 @@ function methodColor$2(method) {
|
|
|
37796
37858
|
}
|
|
37797
37859
|
function collectMatches(col, q) {
|
|
37798
37860
|
const out = [];
|
|
37799
|
-
const
|
|
37861
|
+
const walk2 = (folder, path) => {
|
|
37800
37862
|
for (const reqId of folder.requestIds) {
|
|
37801
37863
|
const req = col.requests[reqId];
|
|
37802
37864
|
if (!req) continue;
|
|
37803
37865
|
const hay = [req.name, req.url, req.method, ...req.meta?.tags ?? []].join(" ").toLowerCase();
|
|
37804
37866
|
if (hay.includes(q)) out.push({ collectionId: col.id, collectionName: col.name, req, path });
|
|
37805
37867
|
}
|
|
37806
|
-
for (const sub of folder.folders)
|
|
37868
|
+
for (const sub of folder.folders) walk2(sub, [...path, sub.name]);
|
|
37807
37869
|
};
|
|
37808
|
-
|
|
37870
|
+
walk2(col.rootFolder, []);
|
|
37809
37871
|
return out;
|
|
37810
37872
|
}
|
|
37811
37873
|
function CollectionTree() {
|
|
@@ -56448,7 +56510,7 @@ function withContentType(headers, value) {
|
|
|
56448
56510
|
if (idx === -1) return [...headers, next];
|
|
56449
56511
|
return headers.map((h, i) => i === idx ? { ...h, value, enabled: true } : h);
|
|
56450
56512
|
}
|
|
56451
|
-
const { electron: electron$
|
|
56513
|
+
const { electron: electron$u } = window;
|
|
56452
56514
|
function ParamTree({ params, depth = 0 }) {
|
|
56453
56515
|
if (params.length === 0) {
|
|
56454
56516
|
return /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 italic", children: "No parameters declared in WSDL." });
|
|
@@ -56462,7 +56524,7 @@ function ParamTree({ params, depth = 0 }) {
|
|
|
56462
56524
|
}
|
|
56463
56525
|
function SoapEditor({ request, onChange }) {
|
|
56464
56526
|
const soap = request.body.soap ?? { wsdlUrl: "", envelope: "" };
|
|
56465
|
-
const [
|
|
56527
|
+
const [operations2, setOperations] = reactExports.useState([]);
|
|
56466
56528
|
const [endpoints, setEndpoints] = reactExports.useState([]);
|
|
56467
56529
|
const [targetNs, setTargetNs] = reactExports.useState("");
|
|
56468
56530
|
const [fetchError, setFetchError] = reactExports.useState(null);
|
|
@@ -56474,7 +56536,7 @@ function SoapEditor({ request, onChange }) {
|
|
|
56474
56536
|
let cancelled = false;
|
|
56475
56537
|
(async () => {
|
|
56476
56538
|
try {
|
|
56477
|
-
const result = await electron$
|
|
56539
|
+
const result = await electron$u.wsdlFetch(url);
|
|
56478
56540
|
if (cancelled) return;
|
|
56479
56541
|
setOperations(result.operations);
|
|
56480
56542
|
setEndpoints(result.endpoints);
|
|
@@ -56511,7 +56573,7 @@ function SoapEditor({ request, onChange }) {
|
|
|
56511
56573
|
setFetching(true);
|
|
56512
56574
|
setFetchError(null);
|
|
56513
56575
|
try {
|
|
56514
|
-
const result = await electron$
|
|
56576
|
+
const result = await electron$u.wsdlFetch(soap.wsdlUrl.trim());
|
|
56515
56577
|
setOperations(result.operations);
|
|
56516
56578
|
setEndpoints(result.endpoints);
|
|
56517
56579
|
setTargetNs(result.targetNamespace);
|
|
@@ -56527,11 +56589,11 @@ function SoapEditor({ request, onChange }) {
|
|
|
56527
56589
|
function selectOperation(op) {
|
|
56528
56590
|
applyOperation(op);
|
|
56529
56591
|
}
|
|
56530
|
-
const selected =
|
|
56592
|
+
const selected = operations2.find((o) => o.name === soap.operationName) ?? operations2[0];
|
|
56531
56593
|
const primaryEndpoint = endpoints[0]?.address;
|
|
56532
|
-
const versions = Array.from(new Set(
|
|
56594
|
+
const versions = Array.from(new Set(operations2.map((o) => o.soapVersion))).sort();
|
|
56533
56595
|
const hasSavedSoap = Boolean(soap.envelope?.trim() || soap.operationName || soap.wsdlUrl?.trim());
|
|
56534
|
-
const showFullUi =
|
|
56596
|
+
const showFullUi = operations2.length > 0;
|
|
56535
56597
|
const showFallback = !showFullUi && hasSavedSoap;
|
|
56536
56598
|
const showEmpty = !showFullUi && !showFallback;
|
|
56537
56599
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 h-full min-h-0", children: [
|
|
@@ -56555,12 +56617,12 @@ function SoapEditor({ request, onChange }) {
|
|
|
56555
56617
|
onClick: fetchWsdl,
|
|
56556
56618
|
disabled: fetching || !soap.wsdlUrl.trim(),
|
|
56557
56619
|
className: "px-3 py-1.5 text-xs bg-blue-700 hover:bg-blue-600 disabled:bg-surface-800 disabled:text-surface-600 rounded transition-colors whitespace-nowrap",
|
|
56558
|
-
children: fetching ? "Fetching…" :
|
|
56620
|
+
children: fetching ? "Fetching…" : operations2.length > 0 ? "Refresh" : "Fetch WSDL"
|
|
56559
56621
|
}
|
|
56560
56622
|
)
|
|
56561
56623
|
] }),
|
|
56562
56624
|
fetchError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-red-400 bg-red-950/30 border border-red-900 rounded px-2.5 py-1.5", children: fetchError }),
|
|
56563
|
-
|
|
56625
|
+
operations2.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-surface-900/40 border border-surface-800 rounded-md px-3 py-2 text-[11px] flex flex-col gap-1", children: [
|
|
56564
56626
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-surface-500", children: [
|
|
56565
56627
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "uppercase tracking-wider text-[9px] font-semibold text-surface-600 w-20 shrink-0", children: "Endpoint" }),
|
|
56566
56628
|
/* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-200 font-mono truncate", children: primaryEndpoint ?? "(not declared)" })
|
|
@@ -56572,7 +56634,7 @@ function SoapEditor({ request, onChange }) {
|
|
|
56572
56634
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-surface-500", children: [
|
|
56573
56635
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "uppercase tracking-wider text-[9px] font-semibold text-surface-600 w-20 shrink-0", children: "Operations" }),
|
|
56574
56636
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-300", children: [
|
|
56575
|
-
|
|
56637
|
+
operations2.length,
|
|
56576
56638
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-600", children: [
|
|
56577
56639
|
" · SOAP ",
|
|
56578
56640
|
versions.join(", ")
|
|
@@ -56583,7 +56645,7 @@ function SoapEditor({ request, onChange }) {
|
|
|
56583
56645
|
showFullUi && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3 min-h-0 flex-1", children: [
|
|
56584
56646
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-44 shrink-0 flex flex-col bg-surface-900/40 border border-surface-800 rounded-md overflow-hidden", children: [
|
|
56585
56647
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-2.5 py-1.5 text-[9px] uppercase tracking-wider font-semibold text-surface-600 border-b border-surface-800", children: "Operations" }),
|
|
56586
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-y-auto flex-1", children:
|
|
56648
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-y-auto flex-1", children: operations2.map((op) => {
|
|
56587
56649
|
const active = selected && op.name === selected.name && op.soapVersion === selected.soapVersion;
|
|
56588
56650
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
56589
56651
|
"button",
|
|
@@ -56781,7 +56843,7 @@ function BodyTab({ request, onChange }) {
|
|
|
56781
56843
|
mode === "soap" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SoapEditor, { request, onChange }) })
|
|
56782
56844
|
] });
|
|
56783
56845
|
}
|
|
56784
|
-
const { electron: electron$
|
|
56846
|
+
const { electron: electron$t } = window;
|
|
56785
56847
|
const AUTH_TYPES = ["none", "bearer", "basic", "digest", "ntlm", "apikey", "oauth2"];
|
|
56786
56848
|
function AuthTab({ request, onChange }) {
|
|
56787
56849
|
const auth = request.auth;
|
|
@@ -56795,7 +56857,7 @@ function AuthTab({ request, onChange }) {
|
|
|
56795
56857
|
}
|
|
56796
56858
|
async function saveSecret(ref2) {
|
|
56797
56859
|
if (!secretValue || !ref2) return;
|
|
56798
|
-
await electron$
|
|
56860
|
+
await electron$t.setSecret(ref2, secretValue);
|
|
56799
56861
|
setSaved(true);
|
|
56800
56862
|
setSecretValue("");
|
|
56801
56863
|
setTimeout(() => setSaved(false), 2e3);
|
|
@@ -56807,7 +56869,7 @@ function AuthTab({ request, onChange }) {
|
|
|
56807
56869
|
setOauth2Error("");
|
|
56808
56870
|
try {
|
|
56809
56871
|
const vars = {};
|
|
56810
|
-
const result = await electron$
|
|
56872
|
+
const result = await electron$t.oauth2StartFlow(oauth2Auth, vars);
|
|
56811
56873
|
setAuth({
|
|
56812
56874
|
oauth2CachedToken: result.accessToken,
|
|
56813
56875
|
oauth2TokenExpiry: result.expiresAt
|
|
@@ -56825,7 +56887,7 @@ function AuthTab({ request, onChange }) {
|
|
|
56825
56887
|
setOauth2Status("fetching");
|
|
56826
56888
|
setOauth2Error("");
|
|
56827
56889
|
try {
|
|
56828
|
-
const result = await electron$
|
|
56890
|
+
const result = await electron$t.oauth2RefreshToken(oauth2Auth, {}, oauth2RefreshToken);
|
|
56829
56891
|
setAuth({
|
|
56830
56892
|
oauth2CachedToken: result.accessToken,
|
|
56831
56893
|
oauth2TokenExpiry: result.expiresAt
|
|
@@ -60546,8 +60608,8 @@ function getData($data, { dataLevel, dataNames, dataPathArr }) {
|
|
|
60546
60608
|
return data;
|
|
60547
60609
|
}
|
|
60548
60610
|
let expr = data;
|
|
60549
|
-
const
|
|
60550
|
-
for (const segment of
|
|
60611
|
+
const segments2 = jsonPointer.split("/");
|
|
60612
|
+
for (const segment of segments2) {
|
|
60551
60613
|
if (segment) {
|
|
60552
60614
|
data = (0, codegen_1$n._)`${data}${(0, codegen_1$n.getProperty)((0, util_1$m.unescapeJsonPointer)(segment))}`;
|
|
60553
60615
|
expr = (0, codegen_1$n._)`${expr} && ${data}`;
|
|
@@ -60695,7 +60757,7 @@ function compileSchema(sch) {
|
|
|
60695
60757
|
}
|
|
60696
60758
|
}
|
|
60697
60759
|
compile.compileSchema = compileSchema;
|
|
60698
|
-
function resolveRef(root2, baseId, ref2) {
|
|
60760
|
+
function resolveRef$3(root2, baseId, ref2) {
|
|
60699
60761
|
var _a2;
|
|
60700
60762
|
ref2 = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref2);
|
|
60701
60763
|
const schOrFunc = root2.refs[ref2];
|
|
@@ -60712,7 +60774,7 @@ function resolveRef(root2, baseId, ref2) {
|
|
|
60712
60774
|
return;
|
|
60713
60775
|
return root2.refs[ref2] = inlineOrCompile.call(this, _sch);
|
|
60714
60776
|
}
|
|
60715
|
-
compile.resolveRef = resolveRef;
|
|
60777
|
+
compile.resolveRef = resolveRef$3;
|
|
60716
60778
|
function inlineOrCompile(sch) {
|
|
60717
60779
|
if ((0, resolve_1.inlineRef)(sch.schema, this.opts.inlineRefs))
|
|
60718
60780
|
return sch.schema;
|
|
@@ -61909,9 +61971,9 @@ uri$1.default = uri;
|
|
|
61909
61971
|
const rules2 = this.RULES.all;
|
|
61910
61972
|
metaSchema = JSON.parse(JSON.stringify(metaSchema));
|
|
61911
61973
|
for (const jsonPointer of keywordsJsonPointers) {
|
|
61912
|
-
const
|
|
61974
|
+
const segments2 = jsonPointer.split("/").slice(1);
|
|
61913
61975
|
let keywords2 = metaSchema;
|
|
61914
|
-
for (const seg of
|
|
61976
|
+
for (const seg of segments2)
|
|
61915
61977
|
keywords2 = keywords2[seg];
|
|
61916
61978
|
for (const key in rules2) {
|
|
61917
61979
|
const rule = rules2[key];
|
|
@@ -64148,7 +64210,7 @@ function SchemaTab({ request, onChange }) {
|
|
|
64148
64210
|
] }) })
|
|
64149
64211
|
] });
|
|
64150
64212
|
}
|
|
64151
|
-
const { electron: electron$
|
|
64213
|
+
const { electron: electron$s } = window;
|
|
64152
64214
|
const EMPTY = { statusCode: 200, headers: [], bodySchema: "" };
|
|
64153
64215
|
function ContractTab({ request, onChange }) {
|
|
64154
64216
|
const activeTab = useStore((s) => s.tabs.find((t2) => t2.id === s.activeTabId));
|
|
@@ -64162,7 +64224,7 @@ function ContractTab({ request, onChange }) {
|
|
|
64162
64224
|
if (!lastResponse?.body) return;
|
|
64163
64225
|
setInferring(true);
|
|
64164
64226
|
try {
|
|
64165
|
-
const schema = await electron$
|
|
64227
|
+
const schema = await electron$s.inferContractSchema(lastResponse.body);
|
|
64166
64228
|
if (schema) update({ bodySchema: schema });
|
|
64167
64229
|
} finally {
|
|
64168
64230
|
setInferring(false);
|
|
@@ -64360,8 +64422,8 @@ function StreamTab({ request, onChange }) {
|
|
|
64360
64422
|
] })
|
|
64361
64423
|
] });
|
|
64362
64424
|
}
|
|
64363
|
-
const { electron: electron$
|
|
64364
|
-
function formatTime$
|
|
64425
|
+
const { electron: electron$r } = window;
|
|
64426
|
+
function formatTime$2(ts) {
|
|
64365
64427
|
const d = new Date(ts);
|
|
64366
64428
|
const hh = String(d.getHours()).padStart(2, "0");
|
|
64367
64429
|
const mm = String(d.getMinutes()).padStart(2, "0");
|
|
@@ -64380,14 +64442,14 @@ function WebSocketPanel({ request }) {
|
|
|
64380
64442
|
const [sendText, setSendText] = reactExports.useState("");
|
|
64381
64443
|
const logEndRef = reactExports.useRef(null);
|
|
64382
64444
|
reactExports.useEffect(() => {
|
|
64383
|
-
electron$
|
|
64445
|
+
electron$r.onWsMessage(({ requestId, message }) => {
|
|
64384
64446
|
addWsMessage(requestId, message);
|
|
64385
64447
|
});
|
|
64386
|
-
electron$
|
|
64448
|
+
electron$r.onWsStatus(({ requestId, status, error: error2 }) => {
|
|
64387
64449
|
setWsStatus(requestId, status, error2);
|
|
64388
64450
|
});
|
|
64389
64451
|
return () => {
|
|
64390
|
-
electron$
|
|
64452
|
+
electron$r.offWsEvents();
|
|
64391
64453
|
};
|
|
64392
64454
|
}, [addWsMessage, setWsStatus]);
|
|
64393
64455
|
reactExports.useEffect(() => {
|
|
@@ -64400,19 +64462,19 @@ function WebSocketPanel({ request }) {
|
|
|
64400
64462
|
if (h.enabled && h.key) headers[h.key] = h.value;
|
|
64401
64463
|
}
|
|
64402
64464
|
try {
|
|
64403
|
-
await electron$
|
|
64465
|
+
await electron$r.wsConnect(request.id, request.url, headers);
|
|
64404
64466
|
} catch (err) {
|
|
64405
64467
|
setWsStatus(request.id, "error", err instanceof Error ? err.message : String(err));
|
|
64406
64468
|
}
|
|
64407
64469
|
}
|
|
64408
64470
|
async function disconnect() {
|
|
64409
|
-
await electron$
|
|
64471
|
+
await electron$r.wsDisconnect(request.id);
|
|
64410
64472
|
}
|
|
64411
64473
|
async function sendMessage() {
|
|
64412
64474
|
const text = sendText.trim();
|
|
64413
64475
|
if (!text || !isConnected) return;
|
|
64414
64476
|
try {
|
|
64415
|
-
await electron$
|
|
64477
|
+
await electron$r.wsSend(request.id, text);
|
|
64416
64478
|
const msg = {
|
|
64417
64479
|
id: crypto.randomUUID(),
|
|
64418
64480
|
direction: "sent",
|
|
@@ -64475,7 +64537,7 @@ function WebSocketPanel({ request }) {
|
|
|
64475
64537
|
children: [
|
|
64476
64538
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `flex-shrink-0 font-mono font-bold ${msg.direction === "sent" ? "text-blue-400" : "text-emerald-400"}`, children: msg.direction === "sent" ? "→" : "←" }),
|
|
64477
64539
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 font-mono text-surface-300 whitespace-pre-wrap break-all", children: msg.data }),
|
|
64478
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-shrink-0 text-surface-400 font-mono text-[10px] mt-px", children: formatTime$
|
|
64540
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-shrink-0 text-surface-400 font-mono text-[10px] mt-px", children: formatTime$2(msg.timestamp) })
|
|
64479
64541
|
]
|
|
64480
64542
|
},
|
|
64481
64543
|
msg.id
|
|
@@ -64515,6 +64577,209 @@ function WebSocketPanel({ request }) {
|
|
|
64515
64577
|
] })
|
|
64516
64578
|
] });
|
|
64517
64579
|
}
|
|
64580
|
+
const { electron: electron$q } = window;
|
|
64581
|
+
function formatTime$1(ts) {
|
|
64582
|
+
const d = new Date(ts);
|
|
64583
|
+
const p2 = (n, w = 2) => String(n).padStart(w, "0");
|
|
64584
|
+
return `${p2(d.getHours())}:${p2(d.getMinutes())}:${p2(d.getSeconds())}.${p2(d.getMilliseconds(), 3)}`;
|
|
64585
|
+
}
|
|
64586
|
+
const DEFAULT_GRPC = { message: "{}", metadata: [], plaintext: false };
|
|
64587
|
+
function metaToText(meta2) {
|
|
64588
|
+
return (meta2 ?? []).map((m) => `${m.key}: ${m.value}`).join("\n");
|
|
64589
|
+
}
|
|
64590
|
+
function textToMeta(text) {
|
|
64591
|
+
return text.split("\n").map((line) => {
|
|
64592
|
+
const i = line.indexOf(":");
|
|
64593
|
+
if (i === -1) return null;
|
|
64594
|
+
const key = line.slice(0, i).trim();
|
|
64595
|
+
if (!key) return null;
|
|
64596
|
+
return { key, value: line.slice(i + 1).trim(), enabled: true };
|
|
64597
|
+
}).filter((x) => x !== null);
|
|
64598
|
+
}
|
|
64599
|
+
function GrpcPanel({ request, onChange }) {
|
|
64600
|
+
const grpc = { ...DEFAULT_GRPC, ...request.body.grpc ?? {} };
|
|
64601
|
+
const grpcCalls = useStore((s) => s.grpcCalls);
|
|
64602
|
+
const setGrpcStatus = useStore((s) => s.setGrpcStatus);
|
|
64603
|
+
const addGrpcMessage = useStore((s) => s.addGrpcMessage);
|
|
64604
|
+
const clearGrpcMessages = useStore((s) => s.clearGrpcMessages);
|
|
64605
|
+
const setGrpcServices = useStore((s) => s.setGrpcServices);
|
|
64606
|
+
const setGrpcProtoError = useStore((s) => s.setGrpcProtoError);
|
|
64607
|
+
const call = grpcCalls[request.id] ?? { status: "idle", messages: [], services: [] };
|
|
64608
|
+
const isRunning = call.status === "running";
|
|
64609
|
+
const [loadingProto, setLoadingProto] = reactExports.useState(false);
|
|
64610
|
+
const logEndRef = reactExports.useRef(null);
|
|
64611
|
+
function patchGrpc(changes) {
|
|
64612
|
+
onChange({ body: { ...request.body, mode: "grpc", grpc: { ...grpc, ...changes } } });
|
|
64613
|
+
}
|
|
64614
|
+
reactExports.useEffect(() => {
|
|
64615
|
+
electron$q.onGrpcMessage(({ requestId, message }) => {
|
|
64616
|
+
addGrpcMessage(requestId, message);
|
|
64617
|
+
});
|
|
64618
|
+
electron$q.onGrpcStatus(({ requestId, status, code: code2, codeName, error: error2 }) => {
|
|
64619
|
+
setGrpcStatus(requestId, status, { code: code2, codeName, error: error2 });
|
|
64620
|
+
});
|
|
64621
|
+
return () => electron$q.offGrpcEvents();
|
|
64622
|
+
}, [addGrpcMessage, setGrpcStatus]);
|
|
64623
|
+
reactExports.useEffect(() => {
|
|
64624
|
+
logEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
64625
|
+
}, [call.messages.length]);
|
|
64626
|
+
const selectedService = reactExports.useMemo(
|
|
64627
|
+
() => call.services.find((s) => s.name === grpc.serviceName),
|
|
64628
|
+
[call.services, grpc.serviceName]
|
|
64629
|
+
);
|
|
64630
|
+
async function loadProto() {
|
|
64631
|
+
if (!grpc.protoSource?.trim() && !grpc.protoPath?.trim()) return;
|
|
64632
|
+
setLoadingProto(true);
|
|
64633
|
+
try {
|
|
64634
|
+
const result = await electron$q.grpcLoadProto({ protoSource: grpc.protoSource, protoPath: grpc.protoPath });
|
|
64635
|
+
const services = result.services;
|
|
64636
|
+
setGrpcServices(request.id, services);
|
|
64637
|
+
if (services.length && !services.some((s) => s.name === grpc.serviceName)) {
|
|
64638
|
+
patchGrpc({ serviceName: services[0].name, methodName: services[0].methods[0]?.name });
|
|
64639
|
+
}
|
|
64640
|
+
} catch (err) {
|
|
64641
|
+
setGrpcProtoError(request.id, err instanceof Error ? err.message : String(err));
|
|
64642
|
+
} finally {
|
|
64643
|
+
setLoadingProto(false);
|
|
64644
|
+
}
|
|
64645
|
+
}
|
|
64646
|
+
async function invoke() {
|
|
64647
|
+
if (!request.url || !grpc.serviceName || !grpc.methodName) return;
|
|
64648
|
+
clearGrpcMessages(request.id);
|
|
64649
|
+
addGrpcMessage(request.id, { id: crypto.randomUUID(), direction: "sent", data: grpc.message || "{}", timestamp: Date.now() });
|
|
64650
|
+
const metadata2 = {};
|
|
64651
|
+
for (const m of grpc.metadata ?? []) if (m.enabled && m.key) metadata2[m.key] = m.value;
|
|
64652
|
+
try {
|
|
64653
|
+
await electron$q.grpcInvoke(request.id, {
|
|
64654
|
+
target: request.url,
|
|
64655
|
+
serviceName: grpc.serviceName,
|
|
64656
|
+
methodName: grpc.methodName,
|
|
64657
|
+
message: grpc.message || "{}",
|
|
64658
|
+
metadata: metadata2,
|
|
64659
|
+
plaintext: grpc.plaintext,
|
|
64660
|
+
protoSource: grpc.protoSource,
|
|
64661
|
+
protoPath: grpc.protoPath
|
|
64662
|
+
});
|
|
64663
|
+
} catch (err) {
|
|
64664
|
+
setGrpcStatus(request.id, "error", { error: err instanceof Error ? err.message : String(err) });
|
|
64665
|
+
}
|
|
64666
|
+
}
|
|
64667
|
+
function cancel() {
|
|
64668
|
+
electron$q.grpcCancel(request.id);
|
|
64669
|
+
}
|
|
64670
|
+
const statusColors = {
|
|
64671
|
+
running: "bg-amber-400",
|
|
64672
|
+
completed: "bg-emerald-500",
|
|
64673
|
+
error: "bg-red-500",
|
|
64674
|
+
idle: "bg-surface-600"
|
|
64675
|
+
};
|
|
64676
|
+
const inputCls = "bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs focus:outline-none focus:border-blue-500";
|
|
64677
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full min-h-0", children: [
|
|
64678
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 pt-2 pb-1 flex-shrink-0 flex items-center gap-2", children: [
|
|
64679
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-bold text-violet-400 bg-surface-800 border border-surface-700 rounded px-2 py-1", children: "gRPC" }),
|
|
64680
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-surface-600", children: "Set the target (host:port) in the URL field above." }),
|
|
64681
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `ml-auto w-2 h-2 rounded-full ${statusColors[call.status] ?? "bg-surface-600"}`, title: call.status }),
|
|
64682
|
+
call.codeName && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] font-mono text-surface-400", children: call.codeName })
|
|
64683
|
+
] }),
|
|
64684
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto px-4 pb-2 flex flex-col gap-3", children: [
|
|
64685
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
64686
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-1", children: [
|
|
64687
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] text-surface-400", children: "Proto (paste .proto source, or a file path below)" }),
|
|
64688
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: loadProto, disabled: loadingProto, className: "text-[10px] px-2 py-0.5 rounded border border-surface-700 text-surface-300 hover:border-violet-500 hover:text-violet-400 disabled:opacity-40", children: loadingProto ? "Loading…" : "Load proto" })
|
|
64689
|
+
] }),
|
|
64690
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
64691
|
+
"textarea",
|
|
64692
|
+
{
|
|
64693
|
+
value: grpc.protoSource ?? "",
|
|
64694
|
+
onChange: (e) => patchGrpc({ protoSource: e.target.value }),
|
|
64695
|
+
rows: 4,
|
|
64696
|
+
spellCheck: false,
|
|
64697
|
+
placeholder: 'syntax = "proto3";\npackage helloworld;\nservice Greeter { rpc SayHello (HelloRequest) returns (HelloReply); }',
|
|
64698
|
+
className: "w-full resize-y bg-surface-950 border border-surface-800 rounded px-3 py-2 text-xs font-mono focus:outline-none focus:border-violet-500 placeholder-surface-700"
|
|
64699
|
+
}
|
|
64700
|
+
),
|
|
64701
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
64702
|
+
"input",
|
|
64703
|
+
{
|
|
64704
|
+
value: grpc.protoPath ?? "",
|
|
64705
|
+
onChange: (e) => patchGrpc({ protoPath: e.target.value }),
|
|
64706
|
+
placeholder: "/path/to/service.proto (optional; used if source is empty)",
|
|
64707
|
+
className: `${inputCls} w-full font-mono mt-1`
|
|
64708
|
+
}
|
|
64709
|
+
),
|
|
64710
|
+
call.protoError && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-red-400 mt-1", children: call.protoError })
|
|
64711
|
+
] }),
|
|
64712
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-[1fr_1fr_auto] gap-2 items-end", children: [
|
|
64713
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
64714
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[11px] text-surface-400 mb-1", children: "Service" }),
|
|
64715
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("select", { value: grpc.serviceName ?? "", onChange: (e) => patchGrpc({ serviceName: e.target.value, methodName: call.services.find((s) => s.name === e.target.value)?.methods[0]?.name }), className: `${inputCls} w-full`, children: [
|
|
64716
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: call.services.length ? "Select…" : "Load a proto first" }),
|
|
64717
|
+
call.services.map((s) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: s.name, children: s.name }, s.name))
|
|
64718
|
+
] })
|
|
64719
|
+
] }),
|
|
64720
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
64721
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[11px] text-surface-400 mb-1", children: "Method" }),
|
|
64722
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("select", { value: grpc.methodName ?? "", onChange: (e) => patchGrpc({ methodName: e.target.value }), className: `${inputCls} w-full`, children: [
|
|
64723
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", children: "Select…" }),
|
|
64724
|
+
selectedService?.methods.map((m) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: m.name, children: [
|
|
64725
|
+
m.name,
|
|
64726
|
+
m.responseStream ? " (stream)" : ""
|
|
64727
|
+
] }, m.name))
|
|
64728
|
+
] })
|
|
64729
|
+
] }),
|
|
64730
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-1.5 text-xs text-surface-300 pb-1.5", children: [
|
|
64731
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", checked: !!grpc.plaintext, onChange: (e) => patchGrpc({ plaintext: e.target.checked }) }),
|
|
64732
|
+
"Plaintext"
|
|
64733
|
+
] })
|
|
64734
|
+
] }),
|
|
64735
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
64736
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[11px] text-surface-400 mb-1", children: "Request message (JSON)" }),
|
|
64737
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
64738
|
+
"textarea",
|
|
64739
|
+
{
|
|
64740
|
+
value: grpc.message,
|
|
64741
|
+
onChange: (e) => patchGrpc({ message: e.target.value }),
|
|
64742
|
+
rows: 4,
|
|
64743
|
+
spellCheck: false,
|
|
64744
|
+
placeholder: '{ "name": "world" }',
|
|
64745
|
+
className: "w-full resize-y bg-surface-950 border border-surface-800 rounded px-3 py-2 text-xs font-mono focus:outline-none focus:border-violet-500 placeholder-surface-700"
|
|
64746
|
+
}
|
|
64747
|
+
)
|
|
64748
|
+
] }),
|
|
64749
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
64750
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "block text-[11px] text-surface-400 mb-1", children: [
|
|
64751
|
+
"Metadata (one ",
|
|
64752
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono", children: "key: value" }),
|
|
64753
|
+
" per line)"
|
|
64754
|
+
] }),
|
|
64755
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
64756
|
+
"textarea",
|
|
64757
|
+
{
|
|
64758
|
+
value: metaToText(grpc.metadata),
|
|
64759
|
+
onChange: (e) => patchGrpc({ metadata: textToMeta(e.target.value) }),
|
|
64760
|
+
rows: 2,
|
|
64761
|
+
spellCheck: false,
|
|
64762
|
+
placeholder: "authorization: Bearer {{TOKEN}}",
|
|
64763
|
+
className: "w-full resize-y bg-surface-950 border border-surface-800 rounded px-3 py-2 text-xs font-mono focus:outline-none focus:border-violet-500 placeholder-surface-700"
|
|
64764
|
+
}
|
|
64765
|
+
)
|
|
64766
|
+
] }),
|
|
64767
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
64768
|
+
isRunning ? /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: cancel, className: "px-4 py-1.5 bg-red-700 hover:bg-red-600 rounded text-sm font-medium", children: "Cancel" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: invoke, disabled: !request.url || !grpc.serviceName || !grpc.methodName, className: "px-4 py-1.5 bg-violet-600 hover:bg-violet-500 disabled:bg-surface-800 disabled:text-surface-500 rounded text-sm font-medium", children: "Invoke" }),
|
|
64769
|
+
call.messages.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => clearGrpcMessages(request.id), className: "text-[10px] text-surface-600 hover:text-surface-400 ml-auto", children: "Clear" })
|
|
64770
|
+
] }),
|
|
64771
|
+
call.status === "error" && call.error && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-3 py-1.5 bg-red-900/40 border border-red-700/50 rounded text-xs text-red-400", children: call.error }),
|
|
64772
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border border-surface-800 rounded bg-surface-950 min-h-[120px]", children: call.messages.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 text-center text-xs text-surface-500", children: "No messages yet" }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-2 flex flex-col gap-1", children: [
|
|
64773
|
+
call.messages.map((msg) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex gap-2 items-start text-xs rounded px-2 py-1.5 ${msg.direction === "sent" ? "bg-blue-900/30 border border-blue-800/40" : "bg-emerald-900/20 border border-emerald-800/30"}`, children: [
|
|
64774
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `flex-shrink-0 font-mono font-bold ${msg.direction === "sent" ? "text-blue-400" : "text-emerald-400"}`, children: msg.direction === "sent" ? "→" : "←" }),
|
|
64775
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 font-mono text-surface-300 whitespace-pre-wrap break-all", children: msg.data }),
|
|
64776
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-shrink-0 text-surface-400 font-mono text-[10px] mt-px", children: formatTime$1(msg.timestamp) })
|
|
64777
|
+
] }, msg.id)),
|
|
64778
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: logEndRef })
|
|
64779
|
+
] }) })
|
|
64780
|
+
] })
|
|
64781
|
+
] });
|
|
64782
|
+
}
|
|
64518
64783
|
function statusColor$3(code2) {
|
|
64519
64784
|
const d = String(code2)[0];
|
|
64520
64785
|
return d === "2" ? "text-emerald-400" : d === "3" ? "text-amber-400" : "text-red-400";
|
|
@@ -64770,7 +65035,7 @@ function FuzzResultsPanel({ report, onClear }) {
|
|
|
64770
65035
|
] }) })
|
|
64771
65036
|
] });
|
|
64772
65037
|
}
|
|
64773
|
-
const { electron: electron$
|
|
65038
|
+
const { electron: electron$p } = window;
|
|
64774
65039
|
const WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
64775
65040
|
function FuzzModal({ request, onClose }) {
|
|
64776
65041
|
const environments = useStore((s) => s.environments);
|
|
@@ -64796,7 +65061,7 @@ function FuzzModal({ request, onClose }) {
|
|
|
64796
65061
|
const env = resolveEnvironmentById(environments, activeEnvironmentId);
|
|
64797
65062
|
const envVars = env ? Object.fromEntries(env.variables.filter((v) => v.enabled).map((v) => [v.key, v.value])) : {};
|
|
64798
65063
|
const collectionVars = activeCollectionId ? collections[activeCollectionId]?.data.collectionVariables ?? {} : {};
|
|
64799
|
-
const result = await electron$
|
|
65064
|
+
const result = await electron$p.fuzzContracts({
|
|
64800
65065
|
requests: [request],
|
|
64801
65066
|
envVars,
|
|
64802
65067
|
collectionVars,
|
|
@@ -64888,7 +65153,7 @@ function FuzzModal({ request, onClose }) {
|
|
|
64888
65153
|
}
|
|
64889
65154
|
);
|
|
64890
65155
|
}
|
|
64891
|
-
const { electron: electron$
|
|
65156
|
+
const { electron: electron$o } = window;
|
|
64892
65157
|
const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "QUERY"];
|
|
64893
65158
|
const METHOD_COLORS = {
|
|
64894
65159
|
GET: "text-emerald-400",
|
|
@@ -65013,7 +65278,7 @@ function RequestBuilder({ request }) {
|
|
|
65013
65278
|
activeEnvironmentId
|
|
65014
65279
|
);
|
|
65015
65280
|
const hookSessionVars = useStore.getState().sessionVars;
|
|
65016
|
-
const r = await electron$
|
|
65281
|
+
const r = await electron$o.sendRequest({
|
|
65017
65282
|
...basePayload,
|
|
65018
65283
|
environment: hookEnv,
|
|
65019
65284
|
request: hook,
|
|
@@ -65061,7 +65326,7 @@ function RequestBuilder({ request }) {
|
|
|
65061
65326
|
startLiveStream(activeTabId, streamId);
|
|
65062
65327
|
let result;
|
|
65063
65328
|
try {
|
|
65064
|
-
result = await electron$
|
|
65329
|
+
result = await electron$o.sendRequest({
|
|
65065
65330
|
...basePayload,
|
|
65066
65331
|
environment: freshEnv,
|
|
65067
65332
|
request: mergedRequest,
|
|
@@ -65093,7 +65358,7 @@ function RequestBuilder({ request }) {
|
|
|
65093
65358
|
activeEnvironmentId
|
|
65094
65359
|
);
|
|
65095
65360
|
const hookSessionVars = useStore.getState().sessionVars;
|
|
65096
|
-
const r = await electron$
|
|
65361
|
+
const r = await electron$o.sendRequest({
|
|
65097
65362
|
...basePayload,
|
|
65098
65363
|
environment: hookEnv,
|
|
65099
65364
|
request: hook,
|
|
@@ -65143,6 +65408,7 @@ function RequestBuilder({ request }) {
|
|
|
65143
65408
|
const hasPostScript = Boolean(request.postRequestScript?.trim());
|
|
65144
65409
|
const isWs = request.protocol === "websocket";
|
|
65145
65410
|
const isSoap = request.protocol === "soap";
|
|
65411
|
+
const isGrpc = request.protocol === "grpc";
|
|
65146
65412
|
const isExample = !!activeAppTab?.exampleId;
|
|
65147
65413
|
const tabs = [
|
|
65148
65414
|
// SOAP collapses Params + Body into a single "SOAP" tab — the WSDL drives both.
|
|
@@ -65190,7 +65456,7 @@ function RequestBuilder({ request }) {
|
|
|
65190
65456
|
"button",
|
|
65191
65457
|
{
|
|
65192
65458
|
onClick: () => update({ protocol: "http" }),
|
|
65193
|
-
className: `px-2 py-1.5 transition-colors ${
|
|
65459
|
+
className: `px-2 py-1.5 transition-colors ${!isWs && !isSoap && !isGrpc ? "bg-blue-600 text-white" : "text-surface-500 hover:text-white"}`,
|
|
65194
65460
|
title: "HTTP request",
|
|
65195
65461
|
children: "HTTP"
|
|
65196
65462
|
}
|
|
@@ -65219,9 +65485,21 @@ function RequestBuilder({ request }) {
|
|
|
65219
65485
|
title: "SOAP - endpoint and method are derived from the WSDL",
|
|
65220
65486
|
children: "SOAP"
|
|
65221
65487
|
}
|
|
65488
|
+
),
|
|
65489
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
65490
|
+
"button",
|
|
65491
|
+
{
|
|
65492
|
+
onClick: () => update({
|
|
65493
|
+
protocol: "grpc",
|
|
65494
|
+
body: request.body.mode === "grpc" ? request.body : { ...request.body, mode: "grpc", grpc: request.body.grpc ?? { message: "{}", metadata: [], plaintext: false } }
|
|
65495
|
+
}),
|
|
65496
|
+
className: `px-2 py-1.5 transition-colors ${isGrpc ? "bg-violet-700 text-violet-100" : "text-surface-500 hover:text-white"}`,
|
|
65497
|
+
title: "gRPC - proto-defined services over HTTP/2",
|
|
65498
|
+
children: "gRPC"
|
|
65499
|
+
}
|
|
65222
65500
|
)
|
|
65223
65501
|
] }),
|
|
65224
|
-
!isWs && !isSoap && (customVerb ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
65502
|
+
!isWs && !isSoap && !isGrpc && (customVerb ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
65225
65503
|
"input",
|
|
65226
65504
|
{
|
|
65227
65505
|
autoFocus: true,
|
|
@@ -65262,18 +65540,26 @@ function RequestBuilder({ request }) {
|
|
|
65262
65540
|
children: "POST"
|
|
65263
65541
|
}
|
|
65264
65542
|
),
|
|
65543
|
+
isGrpc && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
65544
|
+
"span",
|
|
65545
|
+
{
|
|
65546
|
+
className: "bg-surface-900 border border-surface-700 rounded px-2 py-1.5 text-xs font-bold text-violet-400 select-none",
|
|
65547
|
+
title: "gRPC target is host:port",
|
|
65548
|
+
children: "gRPC"
|
|
65549
|
+
}
|
|
65550
|
+
),
|
|
65265
65551
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
65266
65552
|
VarInput,
|
|
65267
65553
|
{
|
|
65268
65554
|
value: request.url,
|
|
65269
65555
|
onChange: (url) => update({ url }),
|
|
65270
65556
|
onPaste: handleUrlPaste,
|
|
65271
|
-
placeholder: isWs ? "ws://example.com/socket" : isSoap ? "Endpoint (auto-filled from WSDL <soap:address>)" : "https://api.example.com/endpoint",
|
|
65557
|
+
placeholder: isWs ? "ws://example.com/socket" : isSoap ? "Endpoint (auto-filled from WSDL <soap:address>)" : isGrpc ? "localhost:50051" : "https://api.example.com/endpoint",
|
|
65272
65558
|
wrapperClassName: "flex-1",
|
|
65273
65559
|
className: `border rounded px-3 py-1.5 text-sm focus:outline-none focus:border-blue-500 font-mono placeholder-surface-700 ${isSoap ? "bg-surface-900 border-amber-900/50 text-surface-300" : "bg-surface-800 border-surface-700"}`
|
|
65274
65560
|
}
|
|
65275
65561
|
),
|
|
65276
|
-
!isWs && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
65562
|
+
!isWs && !isGrpc && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
65277
65563
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
65278
65564
|
"button",
|
|
65279
65565
|
{
|
|
@@ -65295,7 +65581,7 @@ function RequestBuilder({ request }) {
|
|
|
65295
65581
|
] })
|
|
65296
65582
|
] }),
|
|
65297
65583
|
showFuzz && /* @__PURE__ */ jsxRuntimeExports.jsx(FuzzModal, { request, onClose: () => setShowFuzz(false) }),
|
|
65298
|
-
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: [
|
|
65584
|
+
isWs ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(WebSocketPanel, { request }) }) : isGrpc ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(GrpcPanel, { request, onChange: update }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
65299
65585
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex border-b border-surface-800 px-4 gap-0 flex-shrink-0", children: [
|
|
65300
65586
|
tabs.map((tab) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
65301
65587
|
"button",
|
|
@@ -65828,7 +66114,263 @@ function InteractiveBody({ body, contentType, onAssert }) {
|
|
|
65828
66114
|
treeContent
|
|
65829
66115
|
] });
|
|
65830
66116
|
}
|
|
65831
|
-
|
|
66117
|
+
function getByPath(data, path) {
|
|
66118
|
+
if (!path) return data;
|
|
66119
|
+
const tokens = path.match(/[^.[\]]+|\[\d+\]/g) ?? [];
|
|
66120
|
+
let cur2 = data;
|
|
66121
|
+
for (const tok of tokens) {
|
|
66122
|
+
if (cur2 == null || typeof cur2 !== "object") return void 0;
|
|
66123
|
+
cur2 = tok.startsWith("[") ? cur2[Number(tok.slice(1, -1))] : cur2[tok];
|
|
66124
|
+
}
|
|
66125
|
+
return cur2;
|
|
66126
|
+
}
|
|
66127
|
+
function joinPath(base2, index2, key) {
|
|
66128
|
+
return `${base2}[${index2}]${key ? `.${key}` : ""}`;
|
|
66129
|
+
}
|
|
66130
|
+
const PREFERRED = /^(data|items|results|records|rows|list|content|entries|values|payload|elements)$/i;
|
|
66131
|
+
function findPrimaryArray(data) {
|
|
66132
|
+
if (Array.isArray(data)) return { path: "", array: data };
|
|
66133
|
+
const found = [];
|
|
66134
|
+
const queue = [{ node: data, path: "", depth: 0 }];
|
|
66135
|
+
while (queue.length) {
|
|
66136
|
+
const { node, path, depth } = queue.shift();
|
|
66137
|
+
if (depth > 6 || node === null || typeof node !== "object") continue;
|
|
66138
|
+
if (Array.isArray(node)) {
|
|
66139
|
+
const key = (path.split(".").pop() ?? "").replace(/\[\d+\]/g, "");
|
|
66140
|
+
const objs = node.filter((x) => x && typeof x === "object" && !Array.isArray(x)).length;
|
|
66141
|
+
found.push({ path, array: node, pref: PREFERRED.test(key) ? 1 : 0, objs, depth });
|
|
66142
|
+
} else {
|
|
66143
|
+
for (const [k, v] of Object.entries(node)) {
|
|
66144
|
+
queue.push({ node: v, path: path ? `${path}.${k}` : k, depth: depth + 1 });
|
|
66145
|
+
}
|
|
66146
|
+
}
|
|
66147
|
+
}
|
|
66148
|
+
if (!found.length) return null;
|
|
66149
|
+
found.sort(
|
|
66150
|
+
(a, b) => b.pref - a.pref || Math.sign(b.objs) - Math.sign(a.objs) || b.array.length - a.array.length || a.depth - b.depth
|
|
66151
|
+
);
|
|
66152
|
+
return { path: found[0].path, array: found[0].array };
|
|
66153
|
+
}
|
|
66154
|
+
function tableColumns(rows, cap = 60) {
|
|
66155
|
+
const cols = [];
|
|
66156
|
+
const seen = /* @__PURE__ */ new Set();
|
|
66157
|
+
for (const row of rows) {
|
|
66158
|
+
if (row && typeof row === "object" && !Array.isArray(row)) {
|
|
66159
|
+
for (const k of Object.keys(row)) {
|
|
66160
|
+
if (!seen.has(k)) {
|
|
66161
|
+
seen.add(k);
|
|
66162
|
+
cols.push(k);
|
|
66163
|
+
if (cols.length >= cap) return cols;
|
|
66164
|
+
}
|
|
66165
|
+
}
|
|
66166
|
+
}
|
|
66167
|
+
}
|
|
66168
|
+
return cols;
|
|
66169
|
+
}
|
|
66170
|
+
function classify(v) {
|
|
66171
|
+
if (v === null || v === void 0) return "null";
|
|
66172
|
+
if (Array.isArray(v)) return "array";
|
|
66173
|
+
if (typeof v === "object") return "object";
|
|
66174
|
+
return "primitive";
|
|
66175
|
+
}
|
|
66176
|
+
function cellPreview(v) {
|
|
66177
|
+
switch (classify(v)) {
|
|
66178
|
+
case "null":
|
|
66179
|
+
return "";
|
|
66180
|
+
case "array":
|
|
66181
|
+
return `[${v.length}]`;
|
|
66182
|
+
case "object": {
|
|
66183
|
+
const s = JSON.stringify(v);
|
|
66184
|
+
return s.length > 60 ? s.slice(0, 57) + "…" : s;
|
|
66185
|
+
}
|
|
66186
|
+
default:
|
|
66187
|
+
return String(v);
|
|
66188
|
+
}
|
|
66189
|
+
}
|
|
66190
|
+
function compareValues(x, y) {
|
|
66191
|
+
if (x == null && y == null) return 0;
|
|
66192
|
+
if (x == null) return 1;
|
|
66193
|
+
if (y == null) return -1;
|
|
66194
|
+
const nx = Number(x), ny = Number(y);
|
|
66195
|
+
if (!Number.isNaN(nx) && !Number.isNaN(ny) && String(x).trim() !== "" && String(y).trim() !== "") return nx - ny;
|
|
66196
|
+
return String(x).localeCompare(String(y));
|
|
66197
|
+
}
|
|
66198
|
+
function cellAt(row, col) {
|
|
66199
|
+
return col === "$value" ? row : row && typeof row === "object" ? row[col] : void 0;
|
|
66200
|
+
}
|
|
66201
|
+
function sortedIndices(rows, col, dir) {
|
|
66202
|
+
const idx = rows.map((_, i) => i);
|
|
66203
|
+
idx.sort((a, b) => compareValues(cellAt(rows[a], col), cellAt(rows[b], col)));
|
|
66204
|
+
return dir === "desc" ? idx.reverse() : idx;
|
|
66205
|
+
}
|
|
66206
|
+
const MAX_ROWS = 1e3;
|
|
66207
|
+
function xmlToJson(node) {
|
|
66208
|
+
const out = {};
|
|
66209
|
+
for (const attr of Array.from(node.attributes)) out[`@${attr.name}`] = attr.value;
|
|
66210
|
+
const childEls = Array.from(node.children);
|
|
66211
|
+
if (childEls.length === 0) {
|
|
66212
|
+
const text = node.textContent?.trim() ?? "";
|
|
66213
|
+
return node.attributes.length ? { ...out, "#text": text } : text;
|
|
66214
|
+
}
|
|
66215
|
+
for (const child of childEls) {
|
|
66216
|
+
const val = xmlToJson(child);
|
|
66217
|
+
if (child.tagName in out) {
|
|
66218
|
+
const existing = out[child.tagName];
|
|
66219
|
+
if (Array.isArray(existing)) existing.push(val);
|
|
66220
|
+
else out[child.tagName] = [existing, val];
|
|
66221
|
+
} else {
|
|
66222
|
+
out[child.tagName] = val;
|
|
66223
|
+
}
|
|
66224
|
+
}
|
|
66225
|
+
return out;
|
|
66226
|
+
}
|
|
66227
|
+
function parseBody(body, contentType) {
|
|
66228
|
+
const isXml = !contentType.includes("json") && (contentType.includes("xml") || body.trim().startsWith("<"));
|
|
66229
|
+
try {
|
|
66230
|
+
if (isXml) {
|
|
66231
|
+
const doc2 = new DOMParser().parseFromString(body, "application/xml");
|
|
66232
|
+
if (doc2.querySelector("parsererror")) throw new Error("Malformed XML");
|
|
66233
|
+
return { ok: true, value: doc2.documentElement ? xmlToJson(doc2.documentElement) : {} };
|
|
66234
|
+
}
|
|
66235
|
+
return { ok: true, value: JSON.parse(body) };
|
|
66236
|
+
} catch (e) {
|
|
66237
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
66238
|
+
}
|
|
66239
|
+
}
|
|
66240
|
+
function bodyHasArray(body, contentType) {
|
|
66241
|
+
const parsed = parseBody(body, contentType);
|
|
66242
|
+
return parsed.ok && findPrimaryArray(parsed.value) !== null;
|
|
66243
|
+
}
|
|
66244
|
+
function ResponseTable({ body, contentType }) {
|
|
66245
|
+
const parsed = reactExports.useMemo(() => parseBody(body, contentType), [body, contentType]);
|
|
66246
|
+
const detected = reactExports.useMemo(() => parsed.ok ? findPrimaryArray(parsed.value) : null, [parsed]);
|
|
66247
|
+
const [path, setPath] = reactExports.useState("");
|
|
66248
|
+
const [sort, setSort] = reactExports.useState(null);
|
|
66249
|
+
reactExports.useEffect(() => {
|
|
66250
|
+
setPath(detected?.path ?? "");
|
|
66251
|
+
setSort(null);
|
|
66252
|
+
}, [detected]);
|
|
66253
|
+
if (!parsed.ok) return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 text-xs text-red-400", children: [
|
|
66254
|
+
"Could not parse body: ",
|
|
66255
|
+
parsed.error
|
|
66256
|
+
] });
|
|
66257
|
+
const target = getByPath(parsed.value, path);
|
|
66258
|
+
const rows = Array.isArray(target) ? target : null;
|
|
66259
|
+
const columns = rows ? tableColumns(rows) : [];
|
|
66260
|
+
const isPrimitiveRows = rows !== null && columns.length === 0;
|
|
66261
|
+
const sortCol = sort?.col ?? null;
|
|
66262
|
+
const order = !rows ? [] : (sort ? sortedIndices(rows, sort.col, sort.dir) : rows.map((_, i) => i)).slice(0, MAX_ROWS);
|
|
66263
|
+
function toggleSort(col) {
|
|
66264
|
+
setSort((s) => s?.col === col ? s.dir === "asc" ? { col, dir: "desc" } : null : { col, dir: "asc" });
|
|
66265
|
+
}
|
|
66266
|
+
function navigate(p2) {
|
|
66267
|
+
setPath(p2);
|
|
66268
|
+
setSort(null);
|
|
66269
|
+
}
|
|
66270
|
+
function drill(originalIndex, col, value) {
|
|
66271
|
+
if (classify(value) === "array" || classify(value) === "object") {
|
|
66272
|
+
navigate(joinPath(path, originalIndex, col ?? void 0));
|
|
66273
|
+
}
|
|
66274
|
+
}
|
|
66275
|
+
function drillKey(key, value) {
|
|
66276
|
+
if (classify(value) === "array" || classify(value) === "object") {
|
|
66277
|
+
navigate(path ? `${path}.${key}` : key);
|
|
66278
|
+
}
|
|
66279
|
+
}
|
|
66280
|
+
const crumbs = (path.match(/[^.[\]]+|\[\d+\]/g) ?? []).reduce((acc, tok) => {
|
|
66281
|
+
const prev = acc.length ? acc[acc.length - 1].path : "";
|
|
66282
|
+
const next = tok.startsWith("[") ? prev + tok : prev ? `${prev}.${tok}` : tok;
|
|
66283
|
+
acc.push({ label: tok, path: next });
|
|
66284
|
+
return acc;
|
|
66285
|
+
}, []);
|
|
66286
|
+
const parentPath = crumbs.length > 1 ? crumbs[crumbs.length - 2].path : "";
|
|
66287
|
+
const th = "px-3 py-1.5 text-left font-medium text-surface-300 border-b border-surface-800 cursor-pointer select-none whitespace-nowrap hover:text-white";
|
|
66288
|
+
const td = "px-3 py-1 border-b border-surface-800/50 align-top";
|
|
66289
|
+
const arrow = (col) => sortCol === col ? sort.dir === "asc" ? " ▲" : " ▼" : "";
|
|
66290
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full min-h-0", children: [
|
|
66291
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-b border-surface-800 flex-shrink-0", children: [
|
|
66292
|
+
path && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 px-3 pt-2 text-xs flex-wrap", children: [
|
|
66293
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => navigate(parentPath), title: "Up one level", className: "px-1.5 py-0.5 rounded bg-surface-800 hover:bg-surface-700 text-surface-300 mr-1", children: "↑ up" }),
|
|
66294
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => navigate(""), className: "text-surface-400 hover:text-white", children: "root" }),
|
|
66295
|
+
crumbs.map((c, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs(React$2.Fragment, { children: [
|
|
66296
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600", children: "›" }),
|
|
66297
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => navigate(c.path), className: `font-mono hover:text-white ${i === crumbs.length - 1 ? "text-white font-semibold" : "text-surface-400"}`, children: c.label })
|
|
66298
|
+
] }, c.path))
|
|
66299
|
+
] }),
|
|
66300
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-3 py-2 text-xs", children: [
|
|
66301
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500", children: "Path" }),
|
|
66302
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
66303
|
+
"input",
|
|
66304
|
+
{
|
|
66305
|
+
value: path,
|
|
66306
|
+
onChange: (e) => navigate(e.target.value),
|
|
66307
|
+
placeholder: "(root) e.g. data.items",
|
|
66308
|
+
className: "flex-1 bg-surface-800 border border-surface-700 rounded px-2 py-1 font-mono text-[11px] focus:outline-none focus:border-blue-500"
|
|
66309
|
+
}
|
|
66310
|
+
),
|
|
66311
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500 shrink-0", children: rows ? `${rows.length} rows${columns.length ? ` · ${columns.length} cols` : ""}` : classify(target) === "object" ? `${Object.keys(target).length} fields` : "" })
|
|
66312
|
+
] })
|
|
66313
|
+
] }),
|
|
66314
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-h-0 overflow-auto", children: [
|
|
66315
|
+
rows ? /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "text-[11px] font-mono w-full border-collapse", children: [
|
|
66316
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("thead", { className: "sticky top-0 bg-surface-900", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [
|
|
66317
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-1.5 text-right text-surface-600 border-b border-surface-800 w-10", children: "#" }),
|
|
66318
|
+
isPrimitiveRows ? /* @__PURE__ */ jsxRuntimeExports.jsxs("th", { className: th, onClick: () => toggleSort("$value"), children: [
|
|
66319
|
+
"value",
|
|
66320
|
+
arrow("$value")
|
|
66321
|
+
] }) : columns.map((col) => /* @__PURE__ */ jsxRuntimeExports.jsxs("th", { className: th, onClick: () => toggleSort(col), children: [
|
|
66322
|
+
col,
|
|
66323
|
+
arrow(col)
|
|
66324
|
+
] }, col))
|
|
66325
|
+
] }) }),
|
|
66326
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: order.map((i) => {
|
|
66327
|
+
const row = rows[i];
|
|
66328
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "hover:bg-surface-800/40", children: [
|
|
66329
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-1 text-right text-surface-600 border-b border-surface-800/50", children: i }),
|
|
66330
|
+
isPrimitiveRows ? /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: td, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Cell, { value: row, onDrill: () => drill(i, null, row) }) }) : columns.map((col) => {
|
|
66331
|
+
const v = row && typeof row === "object" ? row[col] : void 0;
|
|
66332
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: td, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Cell, { value: v, onDrill: () => drill(i, col, v) }) }, col);
|
|
66333
|
+
})
|
|
66334
|
+
] }, i);
|
|
66335
|
+
}) })
|
|
66336
|
+
] }) : classify(target) === "object" ? (
|
|
66337
|
+
// The path points at an object, not an array: show it as fields.
|
|
66338
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("table", { className: "text-[11px] font-mono w-full border-collapse", children: /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { children: Object.entries(target).map(([k, v]) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "hover:bg-surface-800/40", children: [
|
|
66339
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-1 text-surface-400 border-b border-surface-800/50 align-top whitespace-nowrap", children: k }),
|
|
66340
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: td, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Cell, { value: v, onDrill: () => drillKey(k, v) }) })
|
|
66341
|
+
] }, k)) }) })
|
|
66342
|
+
) : target !== void 0 ? (
|
|
66343
|
+
// A primitive (or null): just show the value.
|
|
66344
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 text-xs font-mono text-surface-300 whitespace-pre-wrap break-all", children: String(target) })
|
|
66345
|
+
) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 text-xs text-surface-500", children: [
|
|
66346
|
+
"Nothing at ",
|
|
66347
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-surface-400", children: path || "(root)" }),
|
|
66348
|
+
".",
|
|
66349
|
+
detected ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
66350
|
+
" Detected array: ",
|
|
66351
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "font-mono text-blue-400", onClick: () => navigate(detected.path), children: detected.path || "(root)" })
|
|
66352
|
+
] }) : ""
|
|
66353
|
+
] }),
|
|
66354
|
+
rows && rows.length > MAX_ROWS && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-2 text-[10px] text-surface-500", children: [
|
|
66355
|
+
"Showing first ",
|
|
66356
|
+
MAX_ROWS,
|
|
66357
|
+
" of ",
|
|
66358
|
+
rows.length,
|
|
66359
|
+
" rows."
|
|
66360
|
+
] })
|
|
66361
|
+
] })
|
|
66362
|
+
] });
|
|
66363
|
+
}
|
|
66364
|
+
function Cell({ value, onDrill }) {
|
|
66365
|
+
const kind = classify(value);
|
|
66366
|
+
if (kind === "array" || kind === "object") {
|
|
66367
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onDrill, title: "Open this nested value", className: "text-blue-400 hover:underline", children: kind === "array" ? cellPreview(value) : "{…}" });
|
|
66368
|
+
}
|
|
66369
|
+
if (kind === "null") return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600", children: "null" });
|
|
66370
|
+
const text = String(value);
|
|
66371
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "inline-block max-w-[26rem] truncate align-bottom text-surface-300", title: text.length > 40 ? text : void 0, children: text });
|
|
66372
|
+
}
|
|
66373
|
+
const { electron: electron$n } = window;
|
|
65832
66374
|
const RENDER_TAIL = 500;
|
|
65833
66375
|
const CLOSE_LABEL = {
|
|
65834
66376
|
complete: "closed",
|
|
@@ -65869,7 +66411,7 @@ function StreamView({ events, streaming, streamId, streamClose, firstEventMs })
|
|
|
65869
66411
|
if (!streamId) return;
|
|
65870
66412
|
setStopping(true);
|
|
65871
66413
|
try {
|
|
65872
|
-
await electron$
|
|
66414
|
+
await electron$n.stopStream(streamId);
|
|
65873
66415
|
} catch {
|
|
65874
66416
|
}
|
|
65875
66417
|
}
|
|
@@ -66031,7 +66573,7 @@ function HookResultsPanel({ results }) {
|
|
|
66031
66573
|
}) })
|
|
66032
66574
|
] });
|
|
66033
66575
|
}
|
|
66034
|
-
const { electron: electron$
|
|
66576
|
+
const { electron: electron$m } = window;
|
|
66035
66577
|
function extractPath(url) {
|
|
66036
66578
|
try {
|
|
66037
66579
|
return new URL(url).pathname || "/";
|
|
@@ -66085,14 +66627,14 @@ function SaveAsMockModal({ onClose }) {
|
|
|
66085
66627
|
const entry = state.mocks[serverId];
|
|
66086
66628
|
const updated = { ...entry.data, name: newServerName, port: Number(newServerPort), routes: [route] };
|
|
66087
66629
|
updateMock(serverId, updated);
|
|
66088
|
-
await electron$
|
|
66630
|
+
await electron$m.saveMock(entry.relPath, updated);
|
|
66089
66631
|
const ws2 = useStore.getState().workspace;
|
|
66090
|
-
if (ws2) await electron$
|
|
66632
|
+
if (ws2) await electron$m.saveWorkspace(ws2);
|
|
66091
66633
|
} else {
|
|
66092
66634
|
const entry = useStore.getState().mocks[serverId];
|
|
66093
66635
|
const updated = { ...entry.data, routes: [...entry.data.routes, route] };
|
|
66094
66636
|
updateMock(serverId, updated);
|
|
66095
|
-
await electron$
|
|
66637
|
+
await electron$m.saveMock(entry.relPath, updated);
|
|
66096
66638
|
}
|
|
66097
66639
|
onClose();
|
|
66098
66640
|
} finally {
|
|
@@ -66519,7 +67061,7 @@ function validateHttpSemantics(res, opts = {}) {
|
|
|
66519
67061
|
const order = { error: 0, warning: 1, hint: 2 };
|
|
66520
67062
|
return f.sort((a, b) => order[a.severity] - order[b.severity]);
|
|
66521
67063
|
}
|
|
66522
|
-
const { electron: electron$
|
|
67064
|
+
const { electron: electron$l } = window;
|
|
66523
67065
|
function xmlWellFormed(body) {
|
|
66524
67066
|
try {
|
|
66525
67067
|
const doc2 = new DOMParser().parseFromString(body, "application/xml");
|
|
@@ -66538,6 +67080,8 @@ function requestBodyText(body) {
|
|
|
66538
67080
|
return body.graphql?.query ?? "";
|
|
66539
67081
|
case "soap":
|
|
66540
67082
|
return body.soap?.envelope ?? "";
|
|
67083
|
+
case "grpc":
|
|
67084
|
+
return body.grpc?.message ?? "";
|
|
66541
67085
|
case "form":
|
|
66542
67086
|
return (body.form ?? []).filter((p2) => p2.enabled && p2.key).map((p2) => `${p2.key}=${p2.value}`).join("\n");
|
|
66543
67087
|
default:
|
|
@@ -66657,7 +67201,7 @@ function ResponseViewer() {
|
|
|
66657
67201
|
const contractToast = useToast(2500);
|
|
66658
67202
|
async function saveAsContract() {
|
|
66659
67203
|
if (!response || !requestId || !activeTabId) return;
|
|
66660
|
-
const schema = response.body ? await electron$
|
|
67204
|
+
const schema = response.body ? await electron$l.inferContractSchema(response.body) : null;
|
|
66661
67205
|
const contentType2 = response.headers["content-type"];
|
|
66662
67206
|
const headers = contentType2 ? [{ key: "content-type", value: contentType2, required: true }] : [];
|
|
66663
67207
|
updateRequest(requestId, {
|
|
@@ -66705,6 +67249,7 @@ function ResponseViewer() {
|
|
|
66705
67249
|
const isXml = !isJson && (contentType.includes("xml") || contentType.includes("html"));
|
|
66706
67250
|
const supportsTree = isJson || isXml;
|
|
66707
67251
|
const displayBody = isJson ? prettyJson(response.body) : isXml ? prettyXml(response.body) : response.body;
|
|
67252
|
+
const showTable = supportsTree && !response.streamed && bodyHasArray(response.body, contentType);
|
|
66708
67253
|
const bodyParseError = response.body.trim().length > 0 && (isJson && (() => {
|
|
66709
67254
|
try {
|
|
66710
67255
|
JSON.parse(response.body);
|
|
@@ -66791,6 +67336,15 @@ function ResponseViewer() {
|
|
|
66791
67336
|
title: "Raw body view",
|
|
66792
67337
|
children: "Raw"
|
|
66793
67338
|
}
|
|
67339
|
+
),
|
|
67340
|
+
showTable && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
67341
|
+
"button",
|
|
67342
|
+
{
|
|
67343
|
+
onClick: () => setBodyView("table"),
|
|
67344
|
+
className: `px-2 py-0.5 text-[10px] transition-colors ${bodyView === "table" ? "bg-surface-700 text-white" : "text-surface-600 hover:text-white"}`,
|
|
67345
|
+
title: "Show an array in the response as a sortable table",
|
|
67346
|
+
children: "Table"
|
|
67347
|
+
}
|
|
66794
67348
|
)
|
|
66795
67349
|
] }),
|
|
66796
67350
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -66843,7 +67397,7 @@ function ResponseViewer() {
|
|
|
66843
67397
|
streamClose: response.streamClose,
|
|
66844
67398
|
firstEventMs: response.firstEventMs
|
|
66845
67399
|
}
|
|
66846
|
-
) : tab === "body" && supportsTree && bodyView === "tree" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
67400
|
+
) : tab === "body" && showTable && bodyView === "table" ? /* @__PURE__ */ jsxRuntimeExports.jsx(ResponseTable, { body: response.body, contentType }) : tab === "body" && supportsTree && bodyView === "tree" ? /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
66847
67401
|
InteractiveBody,
|
|
66848
67402
|
{
|
|
66849
67403
|
body: response.body,
|
|
@@ -66965,7 +67519,7 @@ function ResponseViewer() {
|
|
|
66965
67519
|
] }) })
|
|
66966
67520
|
] });
|
|
66967
67521
|
}
|
|
66968
|
-
const { electron: electron$
|
|
67522
|
+
const { electron: electron$k } = window;
|
|
66969
67523
|
const TARGETS = [
|
|
66970
67524
|
{ id: "robot_framework", label: "Robot Framework", description: "Python RequestsLibrary keywords + test suite" },
|
|
66971
67525
|
{ id: "playwright_ts", label: "Playwright TS", description: "TypeScript page-object API classes + spec files" },
|
|
@@ -67006,7 +67560,7 @@ function GeneratorPanel() {
|
|
|
67006
67560
|
try {
|
|
67007
67561
|
const col = collections[selectedCollectionId]?.data;
|
|
67008
67562
|
const env = resolveEnvironmentById(environments, activeEnvironmentId);
|
|
67009
|
-
const generated = await electron$
|
|
67563
|
+
const generated = await electron$k.generateCode({ collection: col, environment: env, target });
|
|
67010
67564
|
setFiles(generated);
|
|
67011
67565
|
setSelectedFile(generated[0]?.path ?? null);
|
|
67012
67566
|
} catch (e) {
|
|
@@ -67018,7 +67572,7 @@ function GeneratorPanel() {
|
|
|
67018
67572
|
async function saveZip() {
|
|
67019
67573
|
if (files.length === 0) return;
|
|
67020
67574
|
const col = collections[selectedCollectionId]?.data;
|
|
67021
|
-
await electron$
|
|
67575
|
+
await electron$k.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
|
|
67022
67576
|
}
|
|
67023
67577
|
const selectedContent = files.find((f) => f.path === selectedFile)?.content ?? "";
|
|
67024
67578
|
const activeTarget = TARGETS.find((t2) => t2.id === target);
|
|
@@ -67202,7 +67756,7 @@ function historyToHar(entries, creatorVersion = "1.0") {
|
|
|
67202
67756
|
}
|
|
67203
67757
|
}, null, 2);
|
|
67204
67758
|
}
|
|
67205
|
-
const { electron: electron$
|
|
67759
|
+
const { electron: electron$j } = window;
|
|
67206
67760
|
const STATUS_COLOR = {
|
|
67207
67761
|
"2": "text-emerald-400",
|
|
67208
67762
|
"3": "text-amber-400",
|
|
@@ -67250,7 +67804,7 @@ function HistoryPanel() {
|
|
|
67250
67804
|
}
|
|
67251
67805
|
async function downloadHar() {
|
|
67252
67806
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/[:T]/g, "-");
|
|
67253
|
-
await electron$
|
|
67807
|
+
await electron$j.saveResults(historyToHar(history2), `api-spector-history-${stamp}.har`);
|
|
67254
67808
|
}
|
|
67255
67809
|
function open(entry) {
|
|
67256
67810
|
setSelected(entry);
|
|
@@ -67381,30 +67935,30 @@ function HistoryRow({
|
|
|
67381
67935
|
}
|
|
67382
67936
|
);
|
|
67383
67937
|
}
|
|
67384
|
-
const { electron: electron$
|
|
67938
|
+
const { electron: electron$i } = window;
|
|
67385
67939
|
function WelcomeScreen() {
|
|
67386
67940
|
const { applyWorkspace } = useWorkspaceLoader();
|
|
67387
67941
|
const [recents, setRecents] = reactExports.useState([]);
|
|
67388
67942
|
const [update, setUpdate] = reactExports.useState(null);
|
|
67389
67943
|
reactExports.useEffect(() => {
|
|
67390
|
-
electron$
|
|
67391
|
-
electron$
|
|
67944
|
+
electron$i.getRecentWorkspaces().then(setRecents).catch(() => setRecents([]));
|
|
67945
|
+
electron$i.checkForUpdate().then((info) => {
|
|
67392
67946
|
if (info?.updateAvailable) setUpdate(info);
|
|
67393
67947
|
}).catch(() => {
|
|
67394
67948
|
});
|
|
67395
67949
|
}, []);
|
|
67396
67950
|
async function openWorkspace() {
|
|
67397
|
-
const result = await electron$
|
|
67951
|
+
const result = await electron$i.openWorkspace();
|
|
67398
67952
|
if (!result) return;
|
|
67399
67953
|
await applyWorkspace(result.workspace, result.workspacePath);
|
|
67400
67954
|
}
|
|
67401
67955
|
async function newWorkspace() {
|
|
67402
|
-
const result = await electron$
|
|
67956
|
+
const result = await electron$i.newWorkspace();
|
|
67403
67957
|
if (!result) return;
|
|
67404
67958
|
await applyWorkspace(result.workspace, result.workspacePath);
|
|
67405
67959
|
}
|
|
67406
67960
|
async function openRecent(path) {
|
|
67407
|
-
const result = await electron$
|
|
67961
|
+
const result = await electron$i.openWorkspacePath(path);
|
|
67408
67962
|
if (!result) {
|
|
67409
67963
|
setRecents((prev) => prev.filter((r) => r.path !== path));
|
|
67410
67964
|
return;
|
|
@@ -67444,7 +67998,7 @@ function WelcomeScreen() {
|
|
|
67444
67998
|
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-400 text-sm max-w-sm", children: "Local-first API testing with Robot Framework & Playwright code generation. Secrets stay on your machine." }),
|
|
67445
67999
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] mt-3", style: { color: "var(--text-muted)" }, children: [
|
|
67446
68000
|
"version ",
|
|
67447
|
-
"0.
|
|
68001
|
+
"0.5.1"
|
|
67448
68002
|
] })
|
|
67449
68003
|
] }),
|
|
67450
68004
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 w-64", children: [
|
|
@@ -67488,7 +68042,7 @@ function WelcomeScreen() {
|
|
|
67488
68042
|
] })
|
|
67489
68043
|
] });
|
|
67490
68044
|
}
|
|
67491
|
-
const { electron: electron$
|
|
68045
|
+
const { electron: electron$h } = window;
|
|
67492
68046
|
const EXAMPLES = [
|
|
67493
68047
|
{
|
|
67494
68048
|
label: "macOS / Linux (~/.zshrc or ~/.bashrc)",
|
|
@@ -67512,7 +68066,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
|
|
|
67512
68066
|
setError("Password cannot be empty.");
|
|
67513
68067
|
return;
|
|
67514
68068
|
}
|
|
67515
|
-
await electron$
|
|
68069
|
+
await electron$h.setMasterKey(password);
|
|
67516
68070
|
onSuccess(password);
|
|
67517
68071
|
}
|
|
67518
68072
|
function copy(idx, text) {
|
|
@@ -67596,7 +68150,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
|
|
|
67596
68150
|
}
|
|
67597
68151
|
);
|
|
67598
68152
|
}
|
|
67599
|
-
const { electron: electron$
|
|
68153
|
+
const { electron: electron$g } = window;
|
|
67600
68154
|
async function shortHash(value) {
|
|
67601
68155
|
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
67602
68156
|
return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 8);
|
|
@@ -67723,7 +68277,7 @@ function EnvironmentEditor({ onClose }) {
|
|
|
67723
68277
|
async function saveEncrypted(idx) {
|
|
67724
68278
|
const plaintext = secretInputs[idx] ?? "";
|
|
67725
68279
|
if (!plaintext) return;
|
|
67726
|
-
const { set: set2 } = await electron$
|
|
68280
|
+
const { set: set2 } = await electron$g.checkMasterKey();
|
|
67727
68281
|
if (!set2) {
|
|
67728
68282
|
setPendingEncryptIdx(idx);
|
|
67729
68283
|
return;
|
|
@@ -67760,9 +68314,9 @@ function EnvironmentEditor({ onClose }) {
|
|
|
67760
68314
|
} : state.workspace
|
|
67761
68315
|
}));
|
|
67762
68316
|
const ws2 = useStore.getState().workspace;
|
|
67763
|
-
if (ws2) await electron$
|
|
68317
|
+
if (ws2) await electron$g.saveWorkspace(ws2);
|
|
67764
68318
|
}
|
|
67765
|
-
await electron$
|
|
68319
|
+
await electron$g.saveEnvironment(newRelPath, env);
|
|
67766
68320
|
}
|
|
67767
68321
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
67768
68322
|
pendingEncryptIdx !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -68072,7 +68626,7 @@ function EnvironmentEditor({ onClose }) {
|
|
|
68072
68626
|
)
|
|
68073
68627
|
] });
|
|
68074
68628
|
}
|
|
68075
|
-
const { electron: electron$
|
|
68629
|
+
const { electron: electron$f } = window;
|
|
68076
68630
|
function EnvironmentBar({ inline = false }) {
|
|
68077
68631
|
const environments = useStore((s) => s.environments);
|
|
68078
68632
|
const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
|
|
@@ -68087,7 +68641,7 @@ function EnvironmentBar({ inline = false }) {
|
|
|
68087
68641
|
if (id2) {
|
|
68088
68642
|
const hasSecrets = environments[id2]?.data.variables.some((v) => v.enabled && v.secret);
|
|
68089
68643
|
if (hasSecrets) {
|
|
68090
|
-
const { set: set2 } = await electron$
|
|
68644
|
+
const { set: set2 } = await electron$f.checkMasterKey();
|
|
68091
68645
|
if (!set2) {
|
|
68092
68646
|
setPendingEnvId(id2);
|
|
68093
68647
|
return;
|
|
@@ -68141,7 +68695,7 @@ function EnvironmentBar({ inline = false }) {
|
|
|
68141
68695
|
controls
|
|
68142
68696
|
] });
|
|
68143
68697
|
}
|
|
68144
|
-
const { electron: electron$
|
|
68698
|
+
const { electron: electron$e } = window;
|
|
68145
68699
|
const DEFAULT_PII_PATTERNS = ["authorization", "password", "token", "secret", "api-key", "x-api-key"];
|
|
68146
68700
|
const ZOOM_STEPS = [0.75, 0.9, 1, 1.1, 1.25, 1.5];
|
|
68147
68701
|
function WorkspaceSettingsModal({ onClose }) {
|
|
@@ -68173,8 +68727,8 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
68173
68727
|
async function testCloud() {
|
|
68174
68728
|
setCloudTest({ status: "testing" });
|
|
68175
68729
|
try {
|
|
68176
|
-
if (cloudToken.trim()) await electron$
|
|
68177
|
-
const me = await electron$
|
|
68730
|
+
if (cloudToken.trim()) await electron$e.setSecret("cloud:token", cloudToken.trim());
|
|
68731
|
+
const me = await electron$e.cloudTest();
|
|
68178
68732
|
setCloudTest({ status: "ok", msg: `Connected as ${me.email} · ${me.organization} (${me.plan})` });
|
|
68179
68733
|
} catch (e) {
|
|
68180
68734
|
setCloudTest({ status: "err", msg: e.message });
|
|
@@ -68198,7 +68752,7 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
68198
68752
|
async function vaultOidcLogin() {
|
|
68199
68753
|
setOidc({ status: "busy", msg: "Complete the sign-in in your browser…" });
|
|
68200
68754
|
try {
|
|
68201
|
-
const r = await electron$
|
|
68755
|
+
const r = await electron$e.vaultOidcLogin({
|
|
68202
68756
|
address: vAddress.trim(),
|
|
68203
68757
|
mount: vLoginMount.trim() || void 0,
|
|
68204
68758
|
role: vJwtRole.trim() || void 0,
|
|
@@ -68250,7 +68804,7 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
68250
68804
|
} else {
|
|
68251
68805
|
delete settings.cloud;
|
|
68252
68806
|
}
|
|
68253
|
-
if (cloudToken.trim()) await electron$
|
|
68807
|
+
if (cloudToken.trim()) await electron$e.setSecret("cloud:token", cloudToken.trim());
|
|
68254
68808
|
if (defaultEnvironment) settings.defaultEnvironment = defaultEnvironment;
|
|
68255
68809
|
else delete settings.defaultEnvironment;
|
|
68256
68810
|
if (persistHistory) settings.persistHistory = true;
|
|
@@ -68277,9 +68831,9 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
68277
68831
|
else delete settings.secrets;
|
|
68278
68832
|
updateWorkspaceSettings(settings);
|
|
68279
68833
|
const updated = useStore.getState().workspace;
|
|
68280
|
-
if (updated) await electron$
|
|
68834
|
+
if (updated) await electron$e.saveWorkspace(updated);
|
|
68281
68835
|
if (persistHistory) {
|
|
68282
|
-
await electron$
|
|
68836
|
+
await electron$e.saveHistory(useStore.getState().history).catch(() => {
|
|
68283
68837
|
});
|
|
68284
68838
|
}
|
|
68285
68839
|
onClose();
|
|
@@ -68800,7 +69354,7 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
68800
69354
|
}
|
|
68801
69355
|
);
|
|
68802
69356
|
}
|
|
68803
|
-
const { electron: electron$
|
|
69357
|
+
const { electron: electron$d } = window;
|
|
68804
69358
|
function DocsGeneratorModal({ onClose }) {
|
|
68805
69359
|
const collections = useStore((s) => s.collections);
|
|
68806
69360
|
const collectionList = Object.values(collections);
|
|
@@ -68840,9 +69394,9 @@ function DocsGeneratorModal({ onClose }) {
|
|
|
68840
69394
|
setGenerating(true);
|
|
68841
69395
|
setError(null);
|
|
68842
69396
|
try {
|
|
68843
|
-
const content2 = await electron$
|
|
69397
|
+
const content2 = await electron$d.generateDocs(buildPayload());
|
|
68844
69398
|
const filename = format2 === "html" ? "api-docs.html" : "api-docs.md";
|
|
68845
|
-
await electron$
|
|
69399
|
+
await electron$d.saveResults(content2, filename);
|
|
68846
69400
|
} catch (err) {
|
|
68847
69401
|
setError(err instanceof Error ? err.message : String(err));
|
|
68848
69402
|
} finally {
|
|
@@ -68853,7 +69407,7 @@ function DocsGeneratorModal({ onClose }) {
|
|
|
68853
69407
|
setGenerating(true);
|
|
68854
69408
|
setError(null);
|
|
68855
69409
|
try {
|
|
68856
|
-
const content2 = await electron$
|
|
69410
|
+
const content2 = await electron$d.generateDocs(buildPayload());
|
|
68857
69411
|
setPreview(content2);
|
|
68858
69412
|
} catch (err) {
|
|
68859
69413
|
setError(err instanceof Error ? err.message : String(err));
|
|
@@ -69202,7 +69756,7 @@ function parseCurl(command2) {
|
|
|
69202
69756
|
}
|
|
69203
69757
|
return { name: name2, method: resolvedMethod, url, headers, params: [], auth, body };
|
|
69204
69758
|
}
|
|
69205
|
-
const { electron: electron$
|
|
69759
|
+
const { electron: electron$c } = window;
|
|
69206
69760
|
const OPTIONS = [
|
|
69207
69761
|
{ id: "postman", label: "Postman", description: "Collection v2.1 JSON" },
|
|
69208
69762
|
{ id: "openapi", label: "OpenAPI", description: "JSON or YAML (v3.x)", supportsUrl: true },
|
|
@@ -69214,16 +69768,16 @@ const OPTIONS = [
|
|
|
69214
69768
|
];
|
|
69215
69769
|
function listEndpoints(col) {
|
|
69216
69770
|
const result = [];
|
|
69217
|
-
function
|
|
69771
|
+
function walk2(folder, path) {
|
|
69218
69772
|
for (const id2 of folder.requestIds) {
|
|
69219
69773
|
const req = col.requests[id2];
|
|
69220
69774
|
if (req) result.push({ request: req, folderPath: path });
|
|
69221
69775
|
}
|
|
69222
69776
|
for (const sub of folder.folders) {
|
|
69223
|
-
|
|
69777
|
+
walk2(sub, [...path, sub.name]);
|
|
69224
69778
|
}
|
|
69225
69779
|
}
|
|
69226
|
-
|
|
69780
|
+
walk2(col.rootFolder, []);
|
|
69227
69781
|
return result;
|
|
69228
69782
|
}
|
|
69229
69783
|
function ImportModal({ onImport, onClose }) {
|
|
@@ -69277,12 +69831,12 @@ function ImportModal({ onImport, onClose }) {
|
|
|
69277
69831
|
setError(null);
|
|
69278
69832
|
try {
|
|
69279
69833
|
let col = null;
|
|
69280
|
-
if (opt2.id === "postman") col = await electron$
|
|
69281
|
-
if (opt2.id === "openapi") col = await electron$
|
|
69282
|
-
if (opt2.id === "insomnia") col = await electron$
|
|
69283
|
-
if (opt2.id === "bruno") col = await electron$
|
|
69284
|
-
if (opt2.id === "http") col = await electron$
|
|
69285
|
-
if (opt2.id === "spector") col = await electron$
|
|
69834
|
+
if (opt2.id === "postman") col = await electron$c.importPostman();
|
|
69835
|
+
if (opt2.id === "openapi") col = await electron$c.importOpenApi();
|
|
69836
|
+
if (opt2.id === "insomnia") col = await electron$c.importInsomnia();
|
|
69837
|
+
if (opt2.id === "bruno") col = await electron$c.importBruno();
|
|
69838
|
+
if (opt2.id === "http") col = await electron$c.importHttpFile();
|
|
69839
|
+
if (opt2.id === "spector") col = await electron$c.importSpectorCollection();
|
|
69286
69840
|
if (!col) {
|
|
69287
69841
|
setLoading(false);
|
|
69288
69842
|
return;
|
|
@@ -69305,7 +69859,7 @@ function ImportModal({ onImport, onClose }) {
|
|
|
69305
69859
|
setLoading(true);
|
|
69306
69860
|
setError(null);
|
|
69307
69861
|
try {
|
|
69308
|
-
const col = await electron$
|
|
69862
|
+
const col = await electron$c.importOpenApiFromUrl(trimmed);
|
|
69309
69863
|
if (col) enterPreview(col);
|
|
69310
69864
|
} catch (err) {
|
|
69311
69865
|
setError(err instanceof Error ? err.message : String(err));
|
|
@@ -69412,7 +69966,7 @@ function ImportModal({ onImport, onClose }) {
|
|
|
69412
69966
|
mergeIntoCollection(target, prunedRoot, prunedRequests);
|
|
69413
69967
|
const entry = useStore.getState().collections[target];
|
|
69414
69968
|
if (entry) {
|
|
69415
|
-
await electron$
|
|
69969
|
+
await electron$c.saveCollection(entry.relPath, entry.data);
|
|
69416
69970
|
markCollectionClean(target);
|
|
69417
69971
|
}
|
|
69418
69972
|
setActiveCollection(target);
|
|
@@ -69439,7 +69993,7 @@ function ImportModal({ onImport, onClose }) {
|
|
|
69439
69993
|
variables: [{ key: name2, value, enabled: true }]
|
|
69440
69994
|
};
|
|
69441
69995
|
const relPath = envRelPath(finalName, envId);
|
|
69442
|
-
await electron$
|
|
69996
|
+
await electron$c.saveEnvironment(relPath, env);
|
|
69443
69997
|
useStore.setState((s) => {
|
|
69444
69998
|
s.environments[envId] = { relPath, data: env };
|
|
69445
69999
|
if (!s.activeEnvironmentId) s.activeEnvironmentId = envId;
|
|
@@ -69449,7 +70003,7 @@ function ImportModal({ onImport, onClose }) {
|
|
|
69449
70003
|
return s;
|
|
69450
70004
|
});
|
|
69451
70005
|
const ws2 = useStore.getState().workspace;
|
|
69452
|
-
if (ws2) await electron$
|
|
70006
|
+
if (ws2) await electron$c.saveWorkspace(ws2);
|
|
69453
70007
|
} else {
|
|
69454
70008
|
const entry = state.environments[envTarget];
|
|
69455
70009
|
if (!entry) throw new Error("Target environment not found");
|
|
@@ -69464,7 +70018,7 @@ function ImportModal({ onImport, onClose }) {
|
|
|
69464
70018
|
} else {
|
|
69465
70019
|
updated.variables = [...updated.variables, { key: name2, value, enabled: true }];
|
|
69466
70020
|
}
|
|
69467
|
-
await electron$
|
|
70021
|
+
await electron$c.saveEnvironment(entry.relPath, updated);
|
|
69468
70022
|
useStore.getState().updateEnvironment(envTarget, updated);
|
|
69469
70023
|
}
|
|
69470
70024
|
}
|
|
@@ -69831,16 +70385,16 @@ function pruneFolder(src, _col, keep, _out) {
|
|
|
69831
70385
|
}
|
|
69832
70386
|
function collectRequestsByFolder(folder, src) {
|
|
69833
70387
|
const out = {};
|
|
69834
|
-
function
|
|
70388
|
+
function walk2(f) {
|
|
69835
70389
|
for (const id2 of f.requestIds) {
|
|
69836
70390
|
if (src.requests[id2]) out[id2] = src.requests[id2];
|
|
69837
70391
|
}
|
|
69838
|
-
for (const sub of f.folders)
|
|
70392
|
+
for (const sub of f.folders) walk2(sub);
|
|
69839
70393
|
}
|
|
69840
|
-
|
|
70394
|
+
walk2(folder);
|
|
69841
70395
|
return out;
|
|
69842
70396
|
}
|
|
69843
|
-
const { electron: electron$
|
|
70397
|
+
const { electron: electron$b } = window;
|
|
69844
70398
|
function Toolbar({ onOpenDocs: _onOpenDocs }) {
|
|
69845
70399
|
const { applyWorkspace } = useWorkspaceLoader();
|
|
69846
70400
|
const workspace = useStore((s) => s.workspace);
|
|
@@ -69863,13 +70417,13 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
|
|
|
69863
70417
|
try {
|
|
69864
70418
|
for (const { relPath, data, dirty } of Object.values(collections)) {
|
|
69865
70419
|
if (!dirty) continue;
|
|
69866
|
-
await electron$
|
|
70420
|
+
await electron$b.saveCollection(relPath, data);
|
|
69867
70421
|
markCollectionClean(data.id);
|
|
69868
70422
|
}
|
|
69869
70423
|
for (const { relPath, data } of Object.values(environments)) {
|
|
69870
|
-
await electron$
|
|
70424
|
+
await electron$b.saveEnvironment(relPath, data);
|
|
69871
70425
|
}
|
|
69872
|
-
if (workspace) await electron$
|
|
70426
|
+
if (workspace) await electron$b.saveWorkspace(workspace);
|
|
69873
70427
|
} finally {
|
|
69874
70428
|
setSaving(false);
|
|
69875
70429
|
}
|
|
@@ -69877,14 +70431,14 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
|
|
|
69877
70431
|
async function afterImport(col) {
|
|
69878
70432
|
if (!col) return;
|
|
69879
70433
|
const relPath = colRelPath(col.name, col.id);
|
|
69880
|
-
await electron$
|
|
70434
|
+
await electron$b.saveCollection(relPath, col);
|
|
69881
70435
|
loadCollection(relPath, col);
|
|
69882
70436
|
setActiveCollection(col.id);
|
|
69883
70437
|
const ws2 = useStore.getState().workspace;
|
|
69884
70438
|
if (ws2 && !ws2.collections.includes(relPath)) {
|
|
69885
70439
|
const updated = { ...ws2, collections: [...ws2.collections, relPath] };
|
|
69886
70440
|
useStore.setState({ workspace: updated });
|
|
69887
|
-
await electron$
|
|
70441
|
+
await electron$b.saveWorkspace(updated);
|
|
69888
70442
|
}
|
|
69889
70443
|
}
|
|
69890
70444
|
if (!workspace) return null;
|
|
@@ -69973,7 +70527,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
|
|
|
69973
70527
|
"button",
|
|
69974
70528
|
{
|
|
69975
70529
|
onClick: async () => {
|
|
69976
|
-
const result = await electron$
|
|
70530
|
+
const result = await electron$b.openWorkspace();
|
|
69977
70531
|
if (result) await applyWorkspace(result.workspace, result.workspacePath);
|
|
69978
70532
|
},
|
|
69979
70533
|
className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
|
|
@@ -69985,7 +70539,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
|
|
|
69985
70539
|
"button",
|
|
69986
70540
|
{
|
|
69987
70541
|
onClick: async () => {
|
|
69988
|
-
const result = await electron$
|
|
70542
|
+
const result = await electron$b.newWorkspace();
|
|
69989
70543
|
if (result) await applyWorkspace(result.workspace, result.workspacePath);
|
|
69990
70544
|
},
|
|
69991
70545
|
className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
|
|
@@ -69997,7 +70551,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
|
|
|
69997
70551
|
"button",
|
|
69998
70552
|
{
|
|
69999
70553
|
onClick: async () => {
|
|
70000
|
-
await electron$
|
|
70554
|
+
await electron$b.closeWorkspace();
|
|
70001
70555
|
closeWorkspace();
|
|
70002
70556
|
},
|
|
70003
70557
|
className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
|
|
@@ -70448,7 +71002,7 @@ api-tests:
|
|
|
70448
71002
|
function EmptyState({ message }) {
|
|
70449
71003
|
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-24 text-surface-400 text-xs", children: message });
|
|
70450
71004
|
}
|
|
70451
|
-
const { electron: electron$
|
|
71005
|
+
const { electron: electron$a } = window;
|
|
70452
71006
|
const HOOK_BADGE = {
|
|
70453
71007
|
beforeAll: { label: "BEFORE ALL", cls: "bg-violet-700 text-white" },
|
|
70454
71008
|
before: { label: "BEFORE", cls: "bg-violet-600 text-white" },
|
|
@@ -70568,13 +71122,13 @@ function RunnerModal() {
|
|
|
70568
71122
|
setSummary(null);
|
|
70569
71123
|
setRunnerRunning(true);
|
|
70570
71124
|
progressIdxRef.current = 0;
|
|
70571
|
-
electron$
|
|
71125
|
+
electron$a.onRunProgress((result) => {
|
|
70572
71126
|
const idx = progressIdxRef.current;
|
|
70573
71127
|
patchRunnerResult(idx, result);
|
|
70574
71128
|
if (result.status !== "running") progressIdxRef.current++;
|
|
70575
71129
|
});
|
|
70576
71130
|
try {
|
|
70577
|
-
const s = await electron$
|
|
71131
|
+
const s = await electron$a.runCollection({
|
|
70578
71132
|
items: items2,
|
|
70579
71133
|
environment: env,
|
|
70580
71134
|
globals,
|
|
@@ -70585,7 +71139,7 @@ function RunnerModal() {
|
|
|
70585
71139
|
});
|
|
70586
71140
|
setSummary(s);
|
|
70587
71141
|
} finally {
|
|
70588
|
-
electron$
|
|
71142
|
+
electron$a.offRunProgress();
|
|
70589
71143
|
setRunnerRunning(false);
|
|
70590
71144
|
}
|
|
70591
71145
|
}, [collectionId, folderId, filterTags, selectedEnvId, environments, globals, colEntry, requestDelay, workspaceSettings, setRunnerResults, patchRunnerResult, setRunnerRunning]);
|
|
@@ -70811,7 +71365,7 @@ function RunnerModal() {
|
|
|
70811
71365
|
};
|
|
70812
71366
|
const content2 = exportFormat === "junit" ? buildJUnitReport(runnerResults, summary, meta2) : exportFormat === "html" ? buildHtmlReport(runnerResults, summary, meta2) : buildJsonReport(runnerResults, summary, meta2);
|
|
70813
71367
|
const ext = exportFormat === "junit" ? "xml" : exportFormat === "html" ? "html" : "json";
|
|
70814
|
-
electron$
|
|
71368
|
+
electron$a.saveResults(content2, `spector-results.${ext}`);
|
|
70815
71369
|
},
|
|
70816
71370
|
className: "px-2.5 py-0.5 bg-surface-800 hover:bg-surface-700 rounded transition-colors text-[11px] whitespace-nowrap",
|
|
70817
71371
|
children: "Export results"
|
|
@@ -70823,11 +71377,843 @@ function RunnerModal() {
|
|
|
70823
71377
|
}
|
|
70824
71378
|
);
|
|
70825
71379
|
}
|
|
71380
|
+
const HTTP_METHODS$2 = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
71381
|
+
function normalizePath(url) {
|
|
71382
|
+
let u = (url || "").trim();
|
|
71383
|
+
u = u.split("#")[0].split("?")[0];
|
|
71384
|
+
u = u.replace(/\{\{[^}]*\}\}/g, "");
|
|
71385
|
+
u = u.replace(/^[a-z0-9+.-]+:\/\/[^/]*/i, "");
|
|
71386
|
+
u = u.replace(/\/{2,}/g, "/");
|
|
71387
|
+
if (!u.startsWith("/")) u = "/" + u;
|
|
71388
|
+
u = u.replace(/\/+$/, "");
|
|
71389
|
+
return u === "" ? "/" : u;
|
|
71390
|
+
}
|
|
71391
|
+
function segments(path) {
|
|
71392
|
+
return path.split("/").filter(Boolean);
|
|
71393
|
+
}
|
|
71394
|
+
function isParam(seg) {
|
|
71395
|
+
return seg.startsWith("{") && seg.endsWith("}");
|
|
71396
|
+
}
|
|
71397
|
+
function pathMatches(requestUrl, template) {
|
|
71398
|
+
const reqSegs = segments(normalizePath(requestUrl));
|
|
71399
|
+
const tplSegs = segments(template);
|
|
71400
|
+
if (reqSegs.length < tplSegs.length) return false;
|
|
71401
|
+
const tail = reqSegs.slice(reqSegs.length - tplSegs.length);
|
|
71402
|
+
return tplSegs.every((seg, i) => isParam(seg) ? tail[i] !== void 0 && tail[i] !== "" : tail[i] === seg);
|
|
71403
|
+
}
|
|
71404
|
+
function resolveRef$2(spec, ref2) {
|
|
71405
|
+
const parts = ref2.replace(/^#\//, "").split("/");
|
|
71406
|
+
return parts.reduce((o, k) => o?.[decodeURIComponent(k.replace(/~1/g, "/").replace(/~0/g, "~"))], spec);
|
|
71407
|
+
}
|
|
71408
|
+
function deref$2(spec, node, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
71409
|
+
if (!node || typeof node !== "object" || depth > 8) return node;
|
|
71410
|
+
if (Array.isArray(node)) return node.map((n) => deref$2(spec, n, depth + 1, seen));
|
|
71411
|
+
if ("$ref" in node) {
|
|
71412
|
+
const target = resolveRef$2(spec, node.$ref);
|
|
71413
|
+
if (!target || seen.has(target)) return {};
|
|
71414
|
+
return deref$2(spec, target, depth + 1, /* @__PURE__ */ new Set([...seen, target]));
|
|
71415
|
+
}
|
|
71416
|
+
const out = {};
|
|
71417
|
+
for (const [k, v] of Object.entries(node)) out[k] = deref$2(spec, v, depth + 1, seen);
|
|
71418
|
+
return out;
|
|
71419
|
+
}
|
|
71420
|
+
function flattenSchemaPaths(schema, prefix2 = "", depth = 0) {
|
|
71421
|
+
if (!schema || typeof schema !== "object" || depth > 8) return [];
|
|
71422
|
+
const type2 = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
71423
|
+
const out = [];
|
|
71424
|
+
if (type2 === "object" || schema.properties) {
|
|
71425
|
+
for (const [name2, sub] of Object.entries(schema.properties ?? {})) {
|
|
71426
|
+
const path = prefix2 ? `${prefix2}.${name2}` : name2;
|
|
71427
|
+
out.push(path);
|
|
71428
|
+
out.push(...flattenSchemaPaths(sub, path, depth + 1));
|
|
71429
|
+
}
|
|
71430
|
+
} else if (type2 === "array" && schema.items) {
|
|
71431
|
+
out.push(...flattenSchemaPaths(schema.items, `${prefix2}[]`, depth + 1));
|
|
71432
|
+
}
|
|
71433
|
+
return out;
|
|
71434
|
+
}
|
|
71435
|
+
function flattenValuePaths(value, prefix2 = "", depth = 0) {
|
|
71436
|
+
if (value == null || typeof value !== "object" || depth > 8) return [];
|
|
71437
|
+
const out = [];
|
|
71438
|
+
if (Array.isArray(value)) {
|
|
71439
|
+
for (const el of value) out.push(...flattenValuePaths(el, `${prefix2}[]`, depth + 1));
|
|
71440
|
+
} else {
|
|
71441
|
+
for (const [k, v] of Object.entries(value)) {
|
|
71442
|
+
const path = prefix2 ? `${prefix2}.${k}` : k;
|
|
71443
|
+
out.push(path);
|
|
71444
|
+
out.push(...flattenValuePaths(v, path, depth + 1));
|
|
71445
|
+
}
|
|
71446
|
+
}
|
|
71447
|
+
return [...new Set(out)];
|
|
71448
|
+
}
|
|
71449
|
+
function successResponseSchema$1(spec, responses) {
|
|
71450
|
+
const code2 = Object.keys(responses ?? {}).filter((c) => /^2\d\d$/.test(c)).sort()[0];
|
|
71451
|
+
if (!code2) return void 0;
|
|
71452
|
+
const schema = responses[code2]?.content?.["application/json"]?.schema ?? responses[code2]?.content?.["application/json;charset=utf-8"]?.schema;
|
|
71453
|
+
return schema ? deref$2(spec, schema) : void 0;
|
|
71454
|
+
}
|
|
71455
|
+
function enumerateOperations(spec) {
|
|
71456
|
+
const doc2 = spec ?? {};
|
|
71457
|
+
const ops2 = [];
|
|
71458
|
+
for (const [path, item] of Object.entries(doc2.paths ?? {})) {
|
|
71459
|
+
if (!item || typeof item !== "object") continue;
|
|
71460
|
+
for (const [method, op] of Object.entries(item)) {
|
|
71461
|
+
if (!HTTP_METHODS$2.includes(method.toLowerCase())) continue;
|
|
71462
|
+
if (!op || typeof op !== "object") continue;
|
|
71463
|
+
const declaredStatuses = Object.keys(op.responses ?? {}).filter((c) => /^\d{3}$/.test(c));
|
|
71464
|
+
ops2.push({ method: method.toUpperCase(), path, operationId: op.operationId, declaredStatuses, responses: op.responses });
|
|
71465
|
+
}
|
|
71466
|
+
}
|
|
71467
|
+
return ops2;
|
|
71468
|
+
}
|
|
71469
|
+
function round(n) {
|
|
71470
|
+
return Math.round(n * 10) / 10;
|
|
71471
|
+
}
|
|
71472
|
+
function computeCoverage(spec, requests, observations = []) {
|
|
71473
|
+
const doc2 = spec ?? {};
|
|
71474
|
+
const operations2 = enumerateOperations(spec).map((op) => {
|
|
71475
|
+
const mapped = requests.filter((r) => r.method.toUpperCase() === op.method && pathMatches(r.url, op.path));
|
|
71476
|
+
const obs = observations.filter((o) => o.method.toUpperCase() === op.method && pathMatches(o.url, op.path));
|
|
71477
|
+
const asserted = mapped.map((r) => r.expectedStatus).filter((s) => typeof s === "number");
|
|
71478
|
+
const seen = obs.map((o) => o.status);
|
|
71479
|
+
const covered = /* @__PURE__ */ new Set([...asserted, ...seen]);
|
|
71480
|
+
const coveredStatuses2 = op.declaredStatuses.filter((code2) => covered.has(Number(code2)));
|
|
71481
|
+
const hasNegativeTest = [...covered].some((s) => s >= 400);
|
|
71482
|
+
const declaredProperties2 = flattenSchemaPaths(successResponseSchema$1(doc2, op.responses ?? {}));
|
|
71483
|
+
const observedPaths = new Set(obs.flatMap((o) => o.responsePaths ?? []));
|
|
71484
|
+
const coveredProperties2 = declaredProperties2.filter((p2) => observedPaths.has(p2));
|
|
71485
|
+
return {
|
|
71486
|
+
method: op.method,
|
|
71487
|
+
path: op.path,
|
|
71488
|
+
operationId: op.operationId,
|
|
71489
|
+
tested: mapped.length > 0 || obs.length > 0,
|
|
71490
|
+
requests: mapped.map((r) => r.name),
|
|
71491
|
+
declaredStatuses: op.declaredStatuses,
|
|
71492
|
+
coveredStatuses: coveredStatuses2,
|
|
71493
|
+
hasNegativeTest,
|
|
71494
|
+
declaredProperties: declaredProperties2,
|
|
71495
|
+
coveredProperties: coveredProperties2
|
|
71496
|
+
};
|
|
71497
|
+
});
|
|
71498
|
+
const tested = operations2.filter((o) => o.tested).length;
|
|
71499
|
+
const declaredStatuses = operations2.reduce((n, o) => n + o.declaredStatuses.length, 0);
|
|
71500
|
+
const coveredStatuses = operations2.reduce((n, o) => n + o.coveredStatuses.length, 0);
|
|
71501
|
+
const declaredProperties = operations2.reduce((n, o) => n + o.declaredProperties.length, 0);
|
|
71502
|
+
const coveredProperties = operations2.reduce((n, o) => n + o.coveredProperties.length, 0);
|
|
71503
|
+
const withoutNegativeTest = operations2.filter((o) => o.tested && !o.hasNegativeTest).length;
|
|
71504
|
+
return {
|
|
71505
|
+
spec: { title: doc2.info?.title, version: doc2.info?.version },
|
|
71506
|
+
totals: {
|
|
71507
|
+
operations: operations2.length,
|
|
71508
|
+
tested,
|
|
71509
|
+
untested: operations2.length - tested,
|
|
71510
|
+
operationPct: operations2.length ? round(tested / operations2.length * 100) : 0,
|
|
71511
|
+
declaredStatuses,
|
|
71512
|
+
coveredStatuses,
|
|
71513
|
+
statusPct: declaredStatuses ? round(coveredStatuses / declaredStatuses * 100) : 0,
|
|
71514
|
+
withoutNegativeTest,
|
|
71515
|
+
declaredProperties,
|
|
71516
|
+
coveredProperties,
|
|
71517
|
+
propertyPct: declaredProperties ? round(coveredProperties / declaredProperties * 100) : 0
|
|
71518
|
+
},
|
|
71519
|
+
operations: operations2
|
|
71520
|
+
};
|
|
71521
|
+
}
|
|
71522
|
+
const HTTP_METHODS$1 = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
71523
|
+
function resolveRef$1(spec, ref2) {
|
|
71524
|
+
const parts = ref2.replace(/^#\//, "").split("/");
|
|
71525
|
+
return parts.reduce((o, k) => o?.[decodeURIComponent(k.replace(/~1/g, "/").replace(/~0/g, "~"))], spec);
|
|
71526
|
+
}
|
|
71527
|
+
function deref$1(spec, node, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
71528
|
+
if (!node || typeof node !== "object" || depth > 8) return node;
|
|
71529
|
+
if (Array.isArray(node)) return node.map((n) => deref$1(spec, n, depth + 1, seen));
|
|
71530
|
+
if ("$ref" in node) {
|
|
71531
|
+
const target = resolveRef$1(spec, node.$ref);
|
|
71532
|
+
if (!target || seen.has(target)) return {};
|
|
71533
|
+
return deref$1(spec, target, depth + 1, /* @__PURE__ */ new Set([...seen, target]));
|
|
71534
|
+
}
|
|
71535
|
+
const out = {};
|
|
71536
|
+
for (const [k, v] of Object.entries(node)) out[k] = deref$1(spec, v, depth + 1, seen);
|
|
71537
|
+
return out;
|
|
71538
|
+
}
|
|
71539
|
+
function jsonSchemaFor(spec, responses, code2) {
|
|
71540
|
+
const resp = responses?.[code2];
|
|
71541
|
+
const schema = resp?.content?.["application/json"]?.schema ?? resp?.content?.["application/json;charset=utf-8"]?.schema;
|
|
71542
|
+
return schema ? deref$1(spec, schema) : void 0;
|
|
71543
|
+
}
|
|
71544
|
+
function sampleValue(schema) {
|
|
71545
|
+
if (!schema || typeof schema !== "object") return "string";
|
|
71546
|
+
if (schema.example !== void 0) return schema.example;
|
|
71547
|
+
if (schema.default !== void 0) return schema.default;
|
|
71548
|
+
if (Array.isArray(schema.enum) && schema.enum.length) return schema.enum[0];
|
|
71549
|
+
const type2 = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
71550
|
+
switch (type2) {
|
|
71551
|
+
case "integer":
|
|
71552
|
+
case "number": {
|
|
71553
|
+
const min = schema.minimum ?? (schema.exclusiveMinimum != null ? schema.exclusiveMinimum + 1 : void 0);
|
|
71554
|
+
const max = schema.maximum ?? (schema.exclusiveMaximum != null ? schema.exclusiveMaximum - 1 : void 0);
|
|
71555
|
+
if (min != null) return min;
|
|
71556
|
+
if (max != null) return max;
|
|
71557
|
+
return type2 === "integer" ? 1 : 1.5;
|
|
71558
|
+
}
|
|
71559
|
+
case "boolean":
|
|
71560
|
+
return true;
|
|
71561
|
+
case "array":
|
|
71562
|
+
return [sampleValue(schema.items ?? {})];
|
|
71563
|
+
case "object": {
|
|
71564
|
+
const out = {};
|
|
71565
|
+
const props = schema.properties ?? {};
|
|
71566
|
+
const required2 = schema.required ?? [];
|
|
71567
|
+
for (const [name2, propSchema] of Object.entries(props)) out[name2] = sampleValue(propSchema);
|
|
71568
|
+
for (const name2 of required2) if (!(name2 in out)) out[name2] = "string";
|
|
71569
|
+
return out;
|
|
71570
|
+
}
|
|
71571
|
+
case "string":
|
|
71572
|
+
default:
|
|
71573
|
+
return sampleString(schema);
|
|
71574
|
+
}
|
|
71575
|
+
}
|
|
71576
|
+
function sampleString(schema) {
|
|
71577
|
+
switch (schema.format) {
|
|
71578
|
+
case "email":
|
|
71579
|
+
return "user@example.com";
|
|
71580
|
+
case "uuid":
|
|
71581
|
+
return "00000000-0000-0000-0000-000000000000";
|
|
71582
|
+
case "date":
|
|
71583
|
+
return "2020-01-01";
|
|
71584
|
+
case "date-time":
|
|
71585
|
+
return "2020-01-01T00:00:00Z";
|
|
71586
|
+
case "uri":
|
|
71587
|
+
case "url":
|
|
71588
|
+
return "https://example.com";
|
|
71589
|
+
case "hostname":
|
|
71590
|
+
return "example.com";
|
|
71591
|
+
case "ipv4":
|
|
71592
|
+
return "127.0.0.1";
|
|
71593
|
+
default: {
|
|
71594
|
+
const min = schema.minLength ?? 0;
|
|
71595
|
+
let s = "string";
|
|
71596
|
+
if (min > s.length) s = s.padEnd(min, "x");
|
|
71597
|
+
if (schema.maxLength != null && s.length > schema.maxLength) s = s.slice(0, schema.maxLength);
|
|
71598
|
+
return s;
|
|
71599
|
+
}
|
|
71600
|
+
}
|
|
71601
|
+
}
|
|
71602
|
+
function collectParams(spec, pathItem, op) {
|
|
71603
|
+
const raw = [...pathItem?.parameters ?? [], ...op?.parameters ?? []].map((p2) => deref$1(spec, p2));
|
|
71604
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
71605
|
+
for (const p2 of raw) if (p2?.name && p2?.in) byKey.set(`${p2.in}:${p2.name}`, p2);
|
|
71606
|
+
return [...byKey.values()];
|
|
71607
|
+
}
|
|
71608
|
+
function successCode(responses) {
|
|
71609
|
+
const codes = Object.keys(responses ?? {}).filter((c) => /^2\d\d$/.test(c)).map(Number).sort((a, b) => a - b);
|
|
71610
|
+
return codes[0] ?? 200;
|
|
71611
|
+
}
|
|
71612
|
+
function negativeCode(responses) {
|
|
71613
|
+
const codes = Object.keys(responses ?? {}).filter((c) => /^4\d\d$/.test(c)).map(Number).sort((a, b) => a - b);
|
|
71614
|
+
return codes[0] ?? 400;
|
|
71615
|
+
}
|
|
71616
|
+
function baseTest(method, path, op, params) {
|
|
71617
|
+
const pathParams = {};
|
|
71618
|
+
for (const p2 of params.filter((p22) => p22.in === "path")) pathParams[p2.name] = sampleValue(p2.schema ?? {});
|
|
71619
|
+
const query = params.filter((p2) => p2.in === "query" && p2.required).map((p2) => ({ key: p2.name, value: String(sampleValue(p2.schema ?? {})) }));
|
|
71620
|
+
const headers = params.filter((p2) => p2.in === "header" && p2.required).map((p2) => ({ key: p2.name, value: String(sampleValue(p2.schema ?? {})) }));
|
|
71621
|
+
return { operationId: op?.operationId, method, path, pathParams, query, headers };
|
|
71622
|
+
}
|
|
71623
|
+
function requestBodySchema(spec, op) {
|
|
71624
|
+
const rb = deref$1(spec, op?.requestBody);
|
|
71625
|
+
const schema = rb?.content?.["application/json"]?.schema;
|
|
71626
|
+
return schema;
|
|
71627
|
+
}
|
|
71628
|
+
function generateForOperation(spec, method, path, pathItem, op, opts) {
|
|
71629
|
+
const tests = [];
|
|
71630
|
+
const params = collectParams(spec, pathItem, op);
|
|
71631
|
+
const responses = op?.responses ?? {};
|
|
71632
|
+
const okCode = successCode(responses);
|
|
71633
|
+
const badCode = negativeCode(responses);
|
|
71634
|
+
const bodySchema = requestBodySchema(spec, op);
|
|
71635
|
+
const validBody = bodySchema ? sampleValue(bodySchema) : void 0;
|
|
71636
|
+
const base2 = baseTest(method, path, op, params);
|
|
71637
|
+
const label = `${method} ${path}`;
|
|
71638
|
+
tests.push({
|
|
71639
|
+
...base2,
|
|
71640
|
+
name: `${label} - happy path`,
|
|
71641
|
+
category: "happy",
|
|
71642
|
+
body: validBody !== void 0 ? JSON.stringify(validBody, null, 2) : void 0,
|
|
71643
|
+
expectedStatus: okCode,
|
|
71644
|
+
responseSchema: jsonSchemaFor(spec, responses, String(okCode)) ? JSON.stringify(jsonSchemaFor(spec, responses, String(okCode)), null, 2) : void 0
|
|
71645
|
+
});
|
|
71646
|
+
const props = bodySchema?.properties ?? {};
|
|
71647
|
+
const required2 = bodySchema?.required ?? [];
|
|
71648
|
+
if (opts.includeNegative && validBody && typeof validBody === "object") {
|
|
71649
|
+
let n = 0;
|
|
71650
|
+
for (const field of required2) {
|
|
71651
|
+
if (n >= opts.maxNegativePerOp) break;
|
|
71652
|
+
const mutated = { ...validBody };
|
|
71653
|
+
delete mutated[field];
|
|
71654
|
+
tests.push({ ...base2, name: `${label} - missing ${field}`, category: "negative", body: JSON.stringify(mutated, null, 2), expectedStatus: badCode });
|
|
71655
|
+
n++;
|
|
71656
|
+
}
|
|
71657
|
+
for (const [field, ps] of Object.entries(props)) {
|
|
71658
|
+
if (n >= opts.maxNegativePerOp) break;
|
|
71659
|
+
const t2 = Array.isArray(ps.type) ? ps.type[0] : ps.type;
|
|
71660
|
+
if (t2 !== "string" && t2 !== "integer" && t2 !== "number" && t2 !== "boolean") continue;
|
|
71661
|
+
const wrong = t2 === "string" ? 12345 : "not-a-valid-value";
|
|
71662
|
+
tests.push({ ...base2, name: `${label} - ${field} wrong type`, category: "negative", body: JSON.stringify({ ...validBody, [field]: wrong }, null, 2), expectedStatus: badCode });
|
|
71663
|
+
n++;
|
|
71664
|
+
}
|
|
71665
|
+
}
|
|
71666
|
+
if (opts.includeBoundary && validBody && typeof validBody === "object") {
|
|
71667
|
+
let n = 0;
|
|
71668
|
+
for (const [field, ps] of Object.entries(props)) {
|
|
71669
|
+
if (n >= opts.maxBoundaryPerOp) break;
|
|
71670
|
+
const t2 = Array.isArray(ps.type) ? ps.type[0] : ps.type;
|
|
71671
|
+
if ((t2 === "integer" || t2 === "number") && ps.minimum != null) {
|
|
71672
|
+
tests.push({ ...base2, name: `${label} - ${field} below minimum`, category: "boundary", body: JSON.stringify({ ...validBody, [field]: ps.minimum - 1 }, null, 2), expectedStatus: badCode });
|
|
71673
|
+
n++;
|
|
71674
|
+
} else if ((t2 === "integer" || t2 === "number") && ps.maximum != null) {
|
|
71675
|
+
tests.push({ ...base2, name: `${label} - ${field} above maximum`, category: "boundary", body: JSON.stringify({ ...validBody, [field]: ps.maximum + 1 }, null, 2), expectedStatus: badCode });
|
|
71676
|
+
n++;
|
|
71677
|
+
} else if (t2 === "string" && ps.maxLength != null) {
|
|
71678
|
+
tests.push({ ...base2, name: `${label} - ${field} too long`, category: "boundary", body: JSON.stringify({ ...validBody, [field]: "x".repeat(ps.maxLength + 1) }, null, 2), expectedStatus: badCode });
|
|
71679
|
+
n++;
|
|
71680
|
+
}
|
|
71681
|
+
}
|
|
71682
|
+
}
|
|
71683
|
+
return tests;
|
|
71684
|
+
}
|
|
71685
|
+
function generateTests(spec, options = {}) {
|
|
71686
|
+
const opts = {
|
|
71687
|
+
only: options.only ?? /* @__PURE__ */ new Set(),
|
|
71688
|
+
includeNegative: options.includeNegative ?? true,
|
|
71689
|
+
includeBoundary: options.includeBoundary ?? true,
|
|
71690
|
+
maxNegativePerOp: options.maxNegativePerOp ?? 4,
|
|
71691
|
+
maxBoundaryPerOp: options.maxBoundaryPerOp ?? 4
|
|
71692
|
+
};
|
|
71693
|
+
const doc2 = spec ?? {};
|
|
71694
|
+
const out = [];
|
|
71695
|
+
for (const [path, item] of Object.entries(doc2.paths ?? {})) {
|
|
71696
|
+
if (!item || typeof item !== "object") continue;
|
|
71697
|
+
for (const [method, op] of Object.entries(item)) {
|
|
71698
|
+
if (!HTTP_METHODS$1.includes(method.toLowerCase())) continue;
|
|
71699
|
+
if (!op || typeof op !== "object") continue;
|
|
71700
|
+
if (opts.only.size && !opts.only.has(`${method.toUpperCase()} ${path}`)) continue;
|
|
71701
|
+
out.push(...generateForOperation(doc2, method.toUpperCase(), path, item, op, opts));
|
|
71702
|
+
}
|
|
71703
|
+
}
|
|
71704
|
+
return out;
|
|
71705
|
+
}
|
|
71706
|
+
function testUrl(test, baseVar = "{{baseUrl}}") {
|
|
71707
|
+
let path = test.path;
|
|
71708
|
+
for (const [name2, value] of Object.entries(test.pathParams)) {
|
|
71709
|
+
path = path.replace(new RegExp(`\\{${name2}\\}`, "g"), encodeURIComponent(String(value)));
|
|
71710
|
+
}
|
|
71711
|
+
const qs = test.query.filter((q) => q.key).map((q) => `${encodeURIComponent(q.key)}=${encodeURIComponent(q.value)}`).join("&");
|
|
71712
|
+
return `${baseVar}${path}${qs ? `?${qs}` : ""}`;
|
|
71713
|
+
}
|
|
71714
|
+
function toApiRequest(test, id2) {
|
|
71715
|
+
const headers = test.headers.map((h) => ({ key: h.key, value: h.value, enabled: true }));
|
|
71716
|
+
return {
|
|
71717
|
+
id: id2,
|
|
71718
|
+
name: test.name,
|
|
71719
|
+
method: test.method,
|
|
71720
|
+
url: testUrl(test),
|
|
71721
|
+
headers,
|
|
71722
|
+
params: [],
|
|
71723
|
+
auth: { type: "none" },
|
|
71724
|
+
body: test.body !== void 0 ? { mode: "json", json: test.body } : { mode: "none" },
|
|
71725
|
+
contract: {
|
|
71726
|
+
statusCode: test.expectedStatus,
|
|
71727
|
+
...test.responseSchema ? { bodySchema: test.responseSchema } : {}
|
|
71728
|
+
},
|
|
71729
|
+
meta: { tags: [test.category] }
|
|
71730
|
+
};
|
|
71731
|
+
}
|
|
71732
|
+
const { electron: electron$9 } = window;
|
|
71733
|
+
function Bar({ pct }) {
|
|
71734
|
+
const color = pct >= 80 ? "bg-emerald-500" : pct >= 50 ? "bg-amber-500" : "bg-red-500";
|
|
71735
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-2 w-full rounded bg-surface-800 overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `h-full ${color}`, style: { width: `${pct}%` } }) });
|
|
71736
|
+
}
|
|
71737
|
+
function Stat({ value, label }) {
|
|
71738
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
|
|
71739
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-2xl font-bold text-white", children: value }),
|
|
71740
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[11px] text-surface-400", children: label })
|
|
71741
|
+
] });
|
|
71742
|
+
}
|
|
71743
|
+
function CoverageModal() {
|
|
71744
|
+
const open = useStore((s) => s.coverageOpen);
|
|
71745
|
+
const setOpen = useStore((s) => s.setCoverageOpen);
|
|
71746
|
+
const collections = useStore((s) => s.collections);
|
|
71747
|
+
const history2 = useStore((s) => s.history);
|
|
71748
|
+
const savedSpec = useStore((s) => s.workspace?.settings?.coverageSpec);
|
|
71749
|
+
const setCoverageSpec = useStore((s) => s.setCoverageSpec);
|
|
71750
|
+
const addCollectionObject = useStore((s) => s.addCollectionObject);
|
|
71751
|
+
const [source, setSource] = reactExports.useState(savedSpec ?? "");
|
|
71752
|
+
const [pasted, setPasted] = reactExports.useState("");
|
|
71753
|
+
const [report, setReport] = reactExports.useState(null);
|
|
71754
|
+
const [spec, setSpec] = reactExports.useState(null);
|
|
71755
|
+
const [error2, setError] = reactExports.useState(null);
|
|
71756
|
+
const [busy, setBusy] = reactExports.useState(false);
|
|
71757
|
+
const [onlyGaps, setOnlyGaps] = reactExports.useState(false);
|
|
71758
|
+
const [generated, setGenerated] = reactExports.useState(null);
|
|
71759
|
+
const requests = reactExports.useMemo(() => {
|
|
71760
|
+
const out = [];
|
|
71761
|
+
for (const entry of Object.values(collections)) {
|
|
71762
|
+
for (const req of Object.values(entry.data.requests)) {
|
|
71763
|
+
if (req.disabled) continue;
|
|
71764
|
+
out.push({ name: `${entry.data.name} / ${req.name}`, method: req.method, url: req.url, expectedStatus: req.contract?.statusCode });
|
|
71765
|
+
}
|
|
71766
|
+
}
|
|
71767
|
+
return out;
|
|
71768
|
+
}, [collections]);
|
|
71769
|
+
const observations = reactExports.useMemo(() => {
|
|
71770
|
+
return history2.map((h) => {
|
|
71771
|
+
let responsePaths;
|
|
71772
|
+
try {
|
|
71773
|
+
if (h.response?.body) responsePaths = flattenValuePaths(JSON.parse(h.response.body));
|
|
71774
|
+
} catch {
|
|
71775
|
+
}
|
|
71776
|
+
return { method: h.request.method, url: h.resolvedUrl, status: h.response?.status ?? 0, responsePaths };
|
|
71777
|
+
});
|
|
71778
|
+
}, [history2]);
|
|
71779
|
+
async function compute() {
|
|
71780
|
+
setBusy(true);
|
|
71781
|
+
setError(null);
|
|
71782
|
+
try {
|
|
71783
|
+
const isUrl = /^https?:\/\//i.test(source.trim());
|
|
71784
|
+
const spec2 = await electron$9.coverageLoadSpec(
|
|
71785
|
+
pasted.trim() ? { text: pasted } : isUrl ? { url: source.trim() } : { path: source.trim() }
|
|
71786
|
+
);
|
|
71787
|
+
if (source.trim() && !pasted.trim()) setCoverageSpec(source.trim());
|
|
71788
|
+
setSpec(spec2);
|
|
71789
|
+
setReport(computeCoverage(spec2, requests, observations));
|
|
71790
|
+
setGenerated(null);
|
|
71791
|
+
} catch (err) {
|
|
71792
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
71793
|
+
setReport(null);
|
|
71794
|
+
} finally {
|
|
71795
|
+
setBusy(false);
|
|
71796
|
+
}
|
|
71797
|
+
}
|
|
71798
|
+
function generateForGaps() {
|
|
71799
|
+
if (!spec || !report) return;
|
|
71800
|
+
const only = new Set(report.operations.filter((o) => !o.tested).map((o) => `${o.method} ${o.path}`));
|
|
71801
|
+
if (only.size === 0) return;
|
|
71802
|
+
const tests = generateTests(spec, { only });
|
|
71803
|
+
const requestIds = [];
|
|
71804
|
+
const requestMap = {};
|
|
71805
|
+
for (const t22 of tests) {
|
|
71806
|
+
const id2 = crypto.randomUUID();
|
|
71807
|
+
requestMap[id2] = toApiRequest(t22, id2);
|
|
71808
|
+
requestIds.push(id2);
|
|
71809
|
+
}
|
|
71810
|
+
const name2 = `${report.spec.title ?? "API"} tests (generated)`;
|
|
71811
|
+
const collection = {
|
|
71812
|
+
version: "1.0",
|
|
71813
|
+
id: crypto.randomUUID(),
|
|
71814
|
+
name: name2,
|
|
71815
|
+
description: "Generated for untested operations from the OpenAPI spec.",
|
|
71816
|
+
rootFolder: { id: crypto.randomUUID(), name: "root", description: "", folders: [], requestIds },
|
|
71817
|
+
requests: requestMap
|
|
71818
|
+
};
|
|
71819
|
+
addCollectionObject(collection);
|
|
71820
|
+
setGenerated(`Added ${tests.length} tests for ${only.size} untested operations as "${collection.name}".`);
|
|
71821
|
+
}
|
|
71822
|
+
if (!open) return null;
|
|
71823
|
+
const t2 = report?.totals;
|
|
71824
|
+
const shownOps = report?.operations.filter((o) => !onlyGaps || !o.tested || !o.hasNegativeTest) ?? [];
|
|
71825
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
71826
|
+
Modal,
|
|
71827
|
+
{
|
|
71828
|
+
onClose: () => setOpen(false),
|
|
71829
|
+
overlayClassName: "bg-black/50 z-50 flex items-start justify-center pt-16",
|
|
71830
|
+
panelClassName: "bg-surface-900 border border-surface-800 rounded-lg shadow-2xl flex flex-col w-[760px] max-h-[82vh]",
|
|
71831
|
+
title: "API test coverage",
|
|
71832
|
+
subtitle: "How much of an OpenAPI contract this workspace tests",
|
|
71833
|
+
children: [
|
|
71834
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 border-b border-surface-800 flex flex-col gap-2 flex-shrink-0", children: [
|
|
71835
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] text-surface-400", children: "OpenAPI spec (file path or URL)" }),
|
|
71836
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
|
|
71837
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
71838
|
+
"input",
|
|
71839
|
+
{
|
|
71840
|
+
value: source,
|
|
71841
|
+
onChange: (e) => setSource(e.target.value),
|
|
71842
|
+
placeholder: "./openapi.yaml or https://api.example.com/openapi.json",
|
|
71843
|
+
className: "flex-1 bg-surface-800 border border-surface-700 rounded px-3 py-1.5 text-sm font-mono focus:outline-none focus:border-blue-500"
|
|
71844
|
+
}
|
|
71845
|
+
),
|
|
71846
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
71847
|
+
"button",
|
|
71848
|
+
{
|
|
71849
|
+
onClick: compute,
|
|
71850
|
+
disabled: busy || !source.trim() && !pasted.trim(),
|
|
71851
|
+
className: "px-4 py-1.5 bg-blue-600 hover:bg-blue-500 disabled:bg-surface-800 disabled:text-surface-500 rounded text-sm font-medium",
|
|
71852
|
+
children: busy ? "…" : "Measure"
|
|
71853
|
+
}
|
|
71854
|
+
)
|
|
71855
|
+
] }),
|
|
71856
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("details", { className: "text-[11px] text-surface-500", children: [
|
|
71857
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("summary", { className: "cursor-pointer hover:text-surface-300", children: "or paste spec" }),
|
|
71858
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
71859
|
+
"textarea",
|
|
71860
|
+
{
|
|
71861
|
+
value: pasted,
|
|
71862
|
+
onChange: (e) => setPasted(e.target.value),
|
|
71863
|
+
rows: 4,
|
|
71864
|
+
placeholder: "Paste OpenAPI JSON or YAML here",
|
|
71865
|
+
className: "mt-1 w-full resize-y bg-surface-950 border border-surface-800 rounded px-3 py-2 text-xs font-mono focus:outline-none focus:border-blue-500"
|
|
71866
|
+
}
|
|
71867
|
+
)
|
|
71868
|
+
] }),
|
|
71869
|
+
error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-red-400", children: error2 })
|
|
71870
|
+
] }),
|
|
71871
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3 flex-1 overflow-y-auto min-h-0", children: !report ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-sm text-surface-500 text-center py-10", children: [
|
|
71872
|
+
"Point at your OpenAPI spec and choose ",
|
|
71873
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-300", children: "Measure" }),
|
|
71874
|
+
" to see which operations are tested."
|
|
71875
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
71876
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-3", children: [
|
|
71877
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-xs mb-1", children: [
|
|
71878
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-300", children: [
|
|
71879
|
+
report.spec.title ?? "API",
|
|
71880
|
+
report.spec.version ? ` v${report.spec.version}` : ""
|
|
71881
|
+
] }),
|
|
71882
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-400", children: [
|
|
71883
|
+
t2.operationPct,
|
|
71884
|
+
"% operations tested"
|
|
71885
|
+
] })
|
|
71886
|
+
] }),
|
|
71887
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Bar, { pct: t2.operationPct })
|
|
71888
|
+
] }),
|
|
71889
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4 mb-4 border-b border-surface-800 pb-3", children: [
|
|
71890
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Stat, { value: `${t2.tested}/${t2.operations}`, label: "operations tested" }),
|
|
71891
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Stat, { value: `${t2.coveredStatuses}/${t2.declaredStatuses}`, label: "response codes covered" }),
|
|
71892
|
+
t2.declaredProperties > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(Stat, { value: `${t2.propertyPct}%`, label: "response shape seen in runs" }),
|
|
71893
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Stat, { value: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: t2.untested ? "text-amber-400" : "text-emerald-400", children: t2.untested }), label: "never tested" }),
|
|
71894
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Stat, { value: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: t2.withoutNegativeTest ? "text-amber-400" : "text-emerald-400", children: t2.withoutNegativeTest }), label: "no negative test" })
|
|
71895
|
+
] }),
|
|
71896
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-2 gap-3", children: [
|
|
71897
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-xs text-surface-400", children: [
|
|
71898
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", checked: onlyGaps, onChange: (e) => setOnlyGaps(e.target.checked) }),
|
|
71899
|
+
"Show only gaps (untested or missing a negative test)"
|
|
71900
|
+
] }),
|
|
71901
|
+
t2.untested > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
71902
|
+
"button",
|
|
71903
|
+
{
|
|
71904
|
+
onClick: generateForGaps,
|
|
71905
|
+
title: "Generate happy-path, negative, and boundary tests for the untested operations",
|
|
71906
|
+
className: "px-3 py-1 text-xs bg-violet-600 hover:bg-violet-500 rounded font-medium shrink-0",
|
|
71907
|
+
children: [
|
|
71908
|
+
"Generate tests for ",
|
|
71909
|
+
t2.untested,
|
|
71910
|
+
" gap",
|
|
71911
|
+
t2.untested !== 1 ? "s" : ""
|
|
71912
|
+
]
|
|
71913
|
+
}
|
|
71914
|
+
)
|
|
71915
|
+
] }),
|
|
71916
|
+
generated && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-2 px-3 py-1.5 rounded bg-emerald-900/20 border border-emerald-800/40 text-xs text-emerald-300", children: [
|
|
71917
|
+
generated,
|
|
71918
|
+
" Re-measure to see the coverage rise."
|
|
71919
|
+
] }),
|
|
71920
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col gap-0.5", children: shownOps.map((op, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs py-1 border-b border-surface-800/50", children: [
|
|
71921
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: op.tested ? "text-emerald-400" : "text-red-400", children: op.tested ? "✓" : "✗" }),
|
|
71922
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono font-bold text-surface-300 w-14 shrink-0", children: op.method }),
|
|
71923
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-mono flex-1 ${op.tested ? "text-white" : "text-surface-500"}`, children: op.path }),
|
|
71924
|
+
op.declaredStatuses.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 shrink-0", children: [
|
|
71925
|
+
op.coveredStatuses.length,
|
|
71926
|
+
"/",
|
|
71927
|
+
op.declaredStatuses.length,
|
|
71928
|
+
" codes"
|
|
71929
|
+
] }),
|
|
71930
|
+
op.tested && !op.hasNegativeTest && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-400 shrink-0", children: "no negative" })
|
|
71931
|
+
] }, `${op.method}-${op.path}-${i}`)) })
|
|
71932
|
+
] }) })
|
|
71933
|
+
]
|
|
71934
|
+
}
|
|
71935
|
+
);
|
|
71936
|
+
}
|
|
71937
|
+
const HTTP_METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
71938
|
+
function resolveRef(spec, ref2) {
|
|
71939
|
+
const parts = ref2.replace(/^#\//, "").split("/");
|
|
71940
|
+
return parts.reduce((o, k) => o?.[decodeURIComponent(k.replace(/~1/g, "/").replace(/~0/g, "~"))], spec);
|
|
71941
|
+
}
|
|
71942
|
+
function deref(spec, node, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
71943
|
+
if (!node || typeof node !== "object" || depth > 8) return node;
|
|
71944
|
+
if (Array.isArray(node)) return node.map((n) => deref(spec, n, depth + 1, seen));
|
|
71945
|
+
if ("$ref" in node) {
|
|
71946
|
+
const target = resolveRef(spec, node.$ref);
|
|
71947
|
+
if (!target || seen.has(target)) return {};
|
|
71948
|
+
return deref(spec, target, depth + 1, /* @__PURE__ */ new Set([...seen, target]));
|
|
71949
|
+
}
|
|
71950
|
+
const out = {};
|
|
71951
|
+
for (const [k, v] of Object.entries(node)) out[k] = deref(spec, v, depth + 1, seen);
|
|
71952
|
+
return out;
|
|
71953
|
+
}
|
|
71954
|
+
function walk(schema, prefix2, depth, types2, required2) {
|
|
71955
|
+
if (!schema || typeof schema !== "object" || depth > 8) return;
|
|
71956
|
+
const type2 = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
71957
|
+
if (type2 === "object" || schema.properties) {
|
|
71958
|
+
const req = schema.required ?? [];
|
|
71959
|
+
for (const [name2, sub] of Object.entries(schema.properties ?? {})) {
|
|
71960
|
+
const p2 = prefix2 ? `${prefix2}.${name2}` : name2;
|
|
71961
|
+
const subType = (Array.isArray(sub?.type) ? sub.type[0] : sub?.type) ?? (sub?.properties ? "object" : "any");
|
|
71962
|
+
types2.set(p2, subType);
|
|
71963
|
+
if (req.includes(name2)) required2.add(p2);
|
|
71964
|
+
walk(sub, p2, depth + 1, types2, required2);
|
|
71965
|
+
}
|
|
71966
|
+
} else if (type2 === "array" && schema.items) {
|
|
71967
|
+
walk(schema.items, `${prefix2}[]`, depth + 1, types2, required2);
|
|
71968
|
+
}
|
|
71969
|
+
}
|
|
71970
|
+
function schemaInfo(spec, schema) {
|
|
71971
|
+
const types2 = /* @__PURE__ */ new Map();
|
|
71972
|
+
const required2 = /* @__PURE__ */ new Set();
|
|
71973
|
+
if (schema) walk(deref(spec, schema), "", 0, types2, required2);
|
|
71974
|
+
return { types: types2, required: required2 };
|
|
71975
|
+
}
|
|
71976
|
+
function requestSchema(spec, op) {
|
|
71977
|
+
return deref(spec, op?.requestBody)?.content?.["application/json"]?.schema;
|
|
71978
|
+
}
|
|
71979
|
+
function successResponseSchema(spec, op) {
|
|
71980
|
+
const responses = op?.responses ?? {};
|
|
71981
|
+
const code2 = Object.keys(responses).filter((c) => /^2\d\d$/.test(c)).sort()[0];
|
|
71982
|
+
return code2 ? deref(spec, responses[code2])?.content?.["application/json"]?.schema : void 0;
|
|
71983
|
+
}
|
|
71984
|
+
function paramRequired(spec, pathItem, op) {
|
|
71985
|
+
const raw = [...pathItem?.parameters ?? [], ...op?.parameters ?? []].map((p2) => deref(spec, p2));
|
|
71986
|
+
const m = /* @__PURE__ */ new Map();
|
|
71987
|
+
for (const p2 of raw) if (p2?.name && p2?.in) m.set(`${p2.in}:${p2.name}`, !!p2.required);
|
|
71988
|
+
return m;
|
|
71989
|
+
}
|
|
71990
|
+
function operations(spec) {
|
|
71991
|
+
const map = /* @__PURE__ */ new Map();
|
|
71992
|
+
for (const [path, item] of Object.entries(spec?.paths ?? {})) {
|
|
71993
|
+
if (!item || typeof item !== "object") continue;
|
|
71994
|
+
for (const [method, op] of Object.entries(item)) {
|
|
71995
|
+
if (!HTTP_METHODS.includes(method.toLowerCase())) continue;
|
|
71996
|
+
map.set(`${method.toUpperCase()} ${path}`, { method: method.toUpperCase(), path, pathItem: item, op });
|
|
71997
|
+
}
|
|
71998
|
+
}
|
|
71999
|
+
return map;
|
|
72000
|
+
}
|
|
72001
|
+
function diffSpecs(oldSpec, newSpec) {
|
|
72002
|
+
const oldOps = operations(oldSpec);
|
|
72003
|
+
const newOps = operations(newSpec);
|
|
72004
|
+
const changes = [];
|
|
72005
|
+
for (const [key, o] of oldOps) {
|
|
72006
|
+
if (!newOps.has(key)) {
|
|
72007
|
+
changes.push({ kind: "operation-removed", breaking: true, method: o.method, path: o.path, detail: `Operation ${key} was removed` });
|
|
72008
|
+
}
|
|
72009
|
+
}
|
|
72010
|
+
for (const [key, n] of newOps) {
|
|
72011
|
+
if (!oldOps.has(key)) {
|
|
72012
|
+
changes.push({ kind: "operation-added", breaking: false, method: n.method, path: n.path, detail: `Operation ${key} was added` });
|
|
72013
|
+
continue;
|
|
72014
|
+
}
|
|
72015
|
+
const o = oldOps.get(key);
|
|
72016
|
+
const label = `${n.method} ${n.path}`;
|
|
72017
|
+
const oReq = schemaInfo(oldSpec, requestSchema(oldSpec, o.op));
|
|
72018
|
+
const nReq = schemaInfo(newSpec, requestSchema(newSpec, n.op));
|
|
72019
|
+
for (const p2 of nReq.required) {
|
|
72020
|
+
if (!oReq.required.has(p2)) {
|
|
72021
|
+
changes.push({ kind: "request-required-added", breaking: true, method: n.method, path: n.path, detail: `${label}: request field "${p2}" is now required` });
|
|
72022
|
+
}
|
|
72023
|
+
}
|
|
72024
|
+
for (const [p2, t2] of nReq.types) {
|
|
72025
|
+
const ot = oReq.types.get(p2);
|
|
72026
|
+
if (ot && ot !== t2) {
|
|
72027
|
+
changes.push({ kind: "request-type-changed", breaking: true, method: n.method, path: n.path, detail: `${label}: request field "${p2}" type ${ot} -> ${t2}` });
|
|
72028
|
+
}
|
|
72029
|
+
}
|
|
72030
|
+
const oRes = schemaInfo(oldSpec, successResponseSchema(oldSpec, o.op));
|
|
72031
|
+
const nRes = schemaInfo(newSpec, successResponseSchema(newSpec, n.op));
|
|
72032
|
+
for (const [p2, t2] of oRes.types) {
|
|
72033
|
+
if (!nRes.types.has(p2)) {
|
|
72034
|
+
changes.push({ kind: "response-removed", breaking: true, method: n.method, path: n.path, detail: `${label}: response field "${p2}" was removed` });
|
|
72035
|
+
} else if (nRes.types.get(p2) !== t2) {
|
|
72036
|
+
changes.push({ kind: "response-type-changed", breaking: true, method: n.method, path: n.path, detail: `${label}: response field "${p2}" type ${t2} -> ${nRes.types.get(p2)}` });
|
|
72037
|
+
}
|
|
72038
|
+
}
|
|
72039
|
+
const oParams = paramRequired(oldSpec, o.pathItem, o.op);
|
|
72040
|
+
const nParams = paramRequired(newSpec, n.pathItem, n.op);
|
|
72041
|
+
for (const [k, req] of nParams) {
|
|
72042
|
+
if (req && !oParams.get(k)) {
|
|
72043
|
+
changes.push({ kind: "param-required-added", breaking: true, method: n.method, path: n.path, detail: `${label}: parameter "${k}" is now required` });
|
|
72044
|
+
}
|
|
72045
|
+
}
|
|
72046
|
+
const oCodes = Object.keys(o.op?.responses ?? {}).filter((c) => /^2\d\d$/.test(c));
|
|
72047
|
+
const nCodes = new Set(Object.keys(n.op?.responses ?? {}));
|
|
72048
|
+
for (const c of oCodes) {
|
|
72049
|
+
if (!nCodes.has(c)) {
|
|
72050
|
+
changes.push({ kind: "success-code-removed", breaking: true, method: n.method, path: n.path, detail: `${label}: success response ${c} was removed` });
|
|
72051
|
+
}
|
|
72052
|
+
}
|
|
72053
|
+
}
|
|
72054
|
+
return changes;
|
|
72055
|
+
}
|
|
72056
|
+
function summarizeDiff(changes) {
|
|
72057
|
+
return {
|
|
72058
|
+
breaking: changes.filter((c) => c.breaking).length,
|
|
72059
|
+
nonBreaking: changes.filter((c) => !c.breaking).length
|
|
72060
|
+
};
|
|
72061
|
+
}
|
|
72062
|
+
const { electron: electron$8 } = window;
|
|
72063
|
+
function SpecField({ label, value, onSource, onText }) {
|
|
72064
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
|
|
72065
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] text-surface-400", children: label }),
|
|
72066
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
72067
|
+
"input",
|
|
72068
|
+
{
|
|
72069
|
+
value: value.source,
|
|
72070
|
+
onChange: (e) => onSource(e.target.value),
|
|
72071
|
+
placeholder: "./openapi.yaml or URL",
|
|
72072
|
+
className: "mt-1 w-full bg-surface-800 border border-surface-700 rounded px-2 py-1.5 text-xs font-mono focus:outline-none focus:border-blue-500"
|
|
72073
|
+
}
|
|
72074
|
+
),
|
|
72075
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("details", { className: "text-[10px] text-surface-500 mt-1", children: [
|
|
72076
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("summary", { className: "cursor-pointer hover:text-surface-300", children: "or paste" }),
|
|
72077
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
72078
|
+
"textarea",
|
|
72079
|
+
{
|
|
72080
|
+
value: value.text,
|
|
72081
|
+
onChange: (e) => onText(e.target.value),
|
|
72082
|
+
rows: 3,
|
|
72083
|
+
className: "mt-1 w-full resize-y bg-surface-950 border border-surface-800 rounded px-2 py-1.5 text-[11px] font-mono focus:outline-none focus:border-blue-500"
|
|
72084
|
+
}
|
|
72085
|
+
)
|
|
72086
|
+
] })
|
|
72087
|
+
] });
|
|
72088
|
+
}
|
|
72089
|
+
function CompareModal() {
|
|
72090
|
+
const open = useStore((s) => s.compareOpen);
|
|
72091
|
+
const setOpen = useStore((s) => s.setCompareOpen);
|
|
72092
|
+
const collections = useStore((s) => s.collections);
|
|
72093
|
+
const savedSpec = useStore((s) => s.workspace?.settings?.coverageSpec);
|
|
72094
|
+
const [oldIn, setOldIn] = reactExports.useState({ source: "", text: "" });
|
|
72095
|
+
const [newIn, setNewIn] = reactExports.useState({ source: savedSpec ?? "", text: "" });
|
|
72096
|
+
const [changes, setChanges] = reactExports.useState(null);
|
|
72097
|
+
const [error2, setError] = reactExports.useState(null);
|
|
72098
|
+
const [busy, setBusy] = reactExports.useState(false);
|
|
72099
|
+
const requests = reactExports.useMemo(() => {
|
|
72100
|
+
const out = [];
|
|
72101
|
+
for (const entry of Object.values(collections)) {
|
|
72102
|
+
for (const req of Object.values(entry.data.requests)) {
|
|
72103
|
+
if (req.disabled) continue;
|
|
72104
|
+
out.push({ name: `${entry.data.name} / ${req.name}`, method: req.method, url: req.url });
|
|
72105
|
+
}
|
|
72106
|
+
}
|
|
72107
|
+
return out;
|
|
72108
|
+
}, [collections]);
|
|
72109
|
+
const load = (i) => electron$8.coverageLoadSpec(i.text.trim() ? { text: i.text } : /^https?:\/\//i.test(i.source.trim()) ? { url: i.source.trim() } : { path: i.source.trim() });
|
|
72110
|
+
async function run() {
|
|
72111
|
+
setBusy(true);
|
|
72112
|
+
setError(null);
|
|
72113
|
+
try {
|
|
72114
|
+
const [oldSpec, newSpec] = await Promise.all([load(oldIn), load(newIn)]);
|
|
72115
|
+
setChanges(diffSpecs(oldSpec, newSpec));
|
|
72116
|
+
} catch (err) {
|
|
72117
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
72118
|
+
setChanges(null);
|
|
72119
|
+
} finally {
|
|
72120
|
+
setBusy(false);
|
|
72121
|
+
}
|
|
72122
|
+
}
|
|
72123
|
+
const impact = reactExports.useMemo(() => {
|
|
72124
|
+
if (!changes) return null;
|
|
72125
|
+
const affected = changes.filter((c) => c.breaking && c.method).map((change) => ({
|
|
72126
|
+
change,
|
|
72127
|
+
tests: requests.filter((r) => r.method.toUpperCase() === change.method && pathMatches(r.url, change.path)).map((r) => r.name)
|
|
72128
|
+
}));
|
|
72129
|
+
const hitTests = new Set(affected.flatMap((a) => a.tests));
|
|
72130
|
+
return { affected, hitTests: hitTests.size };
|
|
72131
|
+
}, [changes, requests]);
|
|
72132
|
+
if (!open) return null;
|
|
72133
|
+
const sum = changes ? summarizeDiff(changes) : null;
|
|
72134
|
+
const canRun = (oldIn.source.trim() || oldIn.text.trim()) && (newIn.source.trim() || newIn.text.trim());
|
|
72135
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
72136
|
+
Modal,
|
|
72137
|
+
{
|
|
72138
|
+
onClose: () => setOpen(false),
|
|
72139
|
+
overlayClassName: "bg-black/50 z-50 flex items-start justify-center pt-16",
|
|
72140
|
+
panelClassName: "bg-surface-900 border border-surface-800 rounded-lg shadow-2xl flex flex-col w-[760px] max-h-[82vh]",
|
|
72141
|
+
title: "Compare API specs",
|
|
72142
|
+
subtitle: "Breaking-change detection and impact on your tests",
|
|
72143
|
+
children: [
|
|
72144
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 border-b border-surface-800 flex-shrink-0", children: [
|
|
72145
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3 items-start", children: [
|
|
72146
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SpecField, { label: "Baseline (old)", value: oldIn, onSource: (s) => setOldIn({ ...oldIn, source: s }), onText: (t2) => setOldIn({ ...oldIn, text: t2 }) }),
|
|
72147
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-surface-500 pt-6", children: "->" }),
|
|
72148
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SpecField, { label: "Candidate (new)", value: newIn, onSource: (s) => setNewIn({ ...newIn, source: s }), onText: (t2) => setNewIn({ ...newIn, text: t2 }) })
|
|
72149
|
+
] }),
|
|
72150
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end mt-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
72151
|
+
"button",
|
|
72152
|
+
{
|
|
72153
|
+
onClick: run,
|
|
72154
|
+
disabled: busy || !canRun,
|
|
72155
|
+
className: "px-4 py-1.5 bg-blue-600 hover:bg-blue-500 disabled:bg-surface-800 disabled:text-surface-500 rounded text-sm font-medium",
|
|
72156
|
+
children: busy ? "…" : "Compare"
|
|
72157
|
+
}
|
|
72158
|
+
) }),
|
|
72159
|
+
error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-red-400 mt-2", children: error2 })
|
|
72160
|
+
] }),
|
|
72161
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "px-4 py-3 flex-1 overflow-y-auto min-h-0 text-xs", children: !changes ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-surface-500 text-center py-10", children: [
|
|
72162
|
+
"Point at two spec versions and choose ",
|
|
72163
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-300", children: "Compare" }),
|
|
72164
|
+
"."
|
|
72165
|
+
] }) : changes.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-emerald-400 text-center py-10", children: "No differences between the specs." }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
72166
|
+
sum.breaking > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-3", children: [
|
|
72167
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-red-400 font-bold uppercase tracking-wide text-[11px] mb-1", children: "Breaking changes" }),
|
|
72168
|
+
changes.filter((c) => c.breaking).map((c, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 text-surface-300 py-0.5", children: [
|
|
72169
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400", children: "✗" }),
|
|
72170
|
+
c.detail
|
|
72171
|
+
] }, i))
|
|
72172
|
+
] }),
|
|
72173
|
+
sum.nonBreaking > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-3", children: [
|
|
72174
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-emerald-400 font-bold uppercase tracking-wide text-[11px] mb-1", children: "Non-breaking" }),
|
|
72175
|
+
changes.filter((c) => !c.breaking).map((c, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 text-surface-400 py-0.5", children: [
|
|
72176
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-emerald-400", children: "✓" }),
|
|
72177
|
+
c.detail
|
|
72178
|
+
] }, i))
|
|
72179
|
+
] }),
|
|
72180
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-3 pt-3 border-t border-surface-800", children: sum.breaking === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-emerald-400 font-medium", children: "No breaking changes. Safe to deploy." }) : impact.hitTests === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-amber-400", children: [
|
|
72181
|
+
sum.breaking,
|
|
72182
|
+
" breaking change",
|
|
72183
|
+
sum.breaking !== 1 ? "s" : "",
|
|
72184
|
+
", but no test in this workspace exercises the affected operations. Add tests, then re-check."
|
|
72185
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
72186
|
+
impact.affected.filter((a) => a.tests.length).map((a, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-1", children: [
|
|
72187
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-amber-300", children: a.change.detail }),
|
|
72188
|
+
a.tests.map((t2) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-surface-500 pl-4", children: [
|
|
72189
|
+
"- ",
|
|
72190
|
+
t2
|
|
72191
|
+
] }, t2))
|
|
72192
|
+
] }, i)),
|
|
72193
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 text-red-400 font-bold", children: [
|
|
72194
|
+
"Block deployment: ",
|
|
72195
|
+
sum.breaking,
|
|
72196
|
+
" breaking change",
|
|
72197
|
+
sum.breaking !== 1 ? "s" : "",
|
|
72198
|
+
" affect ",
|
|
72199
|
+
impact.hitTests,
|
|
72200
|
+
" test",
|
|
72201
|
+
impact.hitTests !== 1 ? "s" : "",
|
|
72202
|
+
"."
|
|
72203
|
+
] })
|
|
72204
|
+
] }) })
|
|
72205
|
+
] }) })
|
|
72206
|
+
]
|
|
72207
|
+
}
|
|
72208
|
+
);
|
|
72209
|
+
}
|
|
70826
72210
|
function CollectionPanel() {
|
|
70827
72211
|
const activeCollectionId = useStore((s) => s.activeCollectionId);
|
|
70828
72212
|
const collections = useStore((s) => s.collections);
|
|
70829
72213
|
const updateCollectionDataSet = useStore((s) => s.updateCollectionDataSet);
|
|
70830
72214
|
const openRunner = useStore((s) => s.openRunner);
|
|
72215
|
+
const setCoverageOpen = useStore((s) => s.setCoverageOpen);
|
|
72216
|
+
const setCompareOpen = useStore((s) => s.setCompareOpen);
|
|
70831
72217
|
const [activeTab, setActiveTab] = reactExports.useState("data");
|
|
70832
72218
|
if (!activeCollectionId) {
|
|
70833
72219
|
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-full text-surface-400 text-sm", children: "Select a request from the sidebar" });
|
|
@@ -70849,17 +72235,37 @@ function CollectionPanel() {
|
|
|
70849
72235
|
iterCount > 0 ? ` · ${iterCount} data row${iterCount !== 1 ? "s" : ""}` : ""
|
|
70850
72236
|
] })
|
|
70851
72237
|
] }),
|
|
70852
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
70853
|
-
|
|
70854
|
-
|
|
70855
|
-
|
|
70856
|
-
|
|
70857
|
-
|
|
70858
|
-
|
|
70859
|
-
|
|
70860
|
-
|
|
70861
|
-
|
|
70862
|
-
|
|
72238
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
72239
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
72240
|
+
"button",
|
|
72241
|
+
{
|
|
72242
|
+
onClick: () => setCoverageOpen(true),
|
|
72243
|
+
title: "Measure how much of an OpenAPI spec this workspace tests",
|
|
72244
|
+
className: "px-3 py-1.5 text-xs border border-surface-700 text-surface-300 hover:text-white hover:border-surface-500 rounded font-medium transition-colors",
|
|
72245
|
+
children: "Coverage"
|
|
72246
|
+
}
|
|
72247
|
+
),
|
|
72248
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
72249
|
+
"button",
|
|
72250
|
+
{
|
|
72251
|
+
onClick: () => setCompareOpen(true),
|
|
72252
|
+
title: "Diff two OpenAPI versions: breaking changes and which tests they affect",
|
|
72253
|
+
className: "px-3 py-1.5 text-xs border border-surface-700 text-surface-300 hover:text-white hover:border-surface-500 rounded font-medium transition-colors",
|
|
72254
|
+
children: "Compare"
|
|
72255
|
+
}
|
|
72256
|
+
),
|
|
72257
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
72258
|
+
"button",
|
|
72259
|
+
{
|
|
72260
|
+
onClick: () => openRunner(activeCollectionId),
|
|
72261
|
+
className: "px-3 py-1.5 text-xs bg-emerald-700 hover:bg-emerald-600 rounded font-medium transition-colors flex items-center gap-1.5",
|
|
72262
|
+
children: [
|
|
72263
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z", clipRule: "evenodd" }) }),
|
|
72264
|
+
"Run collection"
|
|
72265
|
+
]
|
|
72266
|
+
}
|
|
72267
|
+
)
|
|
72268
|
+
] })
|
|
70863
72269
|
] }),
|
|
70864
72270
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex border-b border-surface-800 px-6 flex-shrink-0", children: [
|
|
70865
72271
|
{ id: "data", label: "Data", badge: iterCount > 0 ? iterCount : 0 },
|
|
@@ -73276,18 +74682,70 @@ function detectPlatform(remotes) {
|
|
|
73276
74682
|
return "unknown";
|
|
73277
74683
|
}
|
|
73278
74684
|
const NODE_LTS = "lts/*";
|
|
73279
|
-
|
|
74685
|
+
const PROVIDER_ENV = {
|
|
74686
|
+
vault: ["VAULT_ADDR", "VAULT_ROLE_ID", "VAULT_SECRET_ID"],
|
|
74687
|
+
aws: ["AWS_REGION", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"],
|
|
74688
|
+
azure: ["AZURE_TENANT_ID", "AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET"],
|
|
74689
|
+
op: ["OP_CONNECT_HOST", "OP_CONNECT_TOKEN"]
|
|
74690
|
+
};
|
|
74691
|
+
function secretManagerOf(ref2) {
|
|
74692
|
+
if (ref2.startsWith("op://")) return "op";
|
|
74693
|
+
const scheme = ref2.split(":", 1)[0];
|
|
74694
|
+
if (scheme === "vault" || scheme === "aws" || scheme === "azure") return scheme;
|
|
74695
|
+
return null;
|
|
74696
|
+
}
|
|
74697
|
+
const INLINE_REF_RE = /\{\{\s*(vault:|aws:|azure:|op:\/\/)/g;
|
|
74698
|
+
function inlineSecretManagers(text) {
|
|
74699
|
+
if (!text) return [];
|
|
74700
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
74701
|
+
for (const m of text.matchAll(INLINE_REF_RE)) {
|
|
74702
|
+
kinds.add(m[1] === "op://" ? "op" : m[1].slice(0, -1));
|
|
74703
|
+
}
|
|
74704
|
+
return [...kinds];
|
|
74705
|
+
}
|
|
74706
|
+
function requestSecretManagers(req) {
|
|
74707
|
+
return inlineSecretManagers(JSON.stringify(req));
|
|
74708
|
+
}
|
|
74709
|
+
function providerEnvVars(managers) {
|
|
74710
|
+
return [...new Set(managers.flatMap((k) => PROVIDER_ENV[k]))];
|
|
74711
|
+
}
|
|
74712
|
+
function generateCiContent(platform, envName, tags2, secretVars, secretManagers = []) {
|
|
73280
74713
|
const runCmd = [
|
|
73281
74714
|
"api-spector run --workspace .",
|
|
73282
74715
|
envName ? `--environment "${envName}"` : "",
|
|
73283
74716
|
tags2 ? `--tags "${tags2}"` : "",
|
|
73284
74717
|
"--output results.html"
|
|
73285
74718
|
].filter(Boolean).join(" ");
|
|
73286
|
-
const
|
|
74719
|
+
const managers = [...new Set(secretManagers)];
|
|
74720
|
+
const usesVault = managers.includes("vault");
|
|
74721
|
+
const encryptedVars = secretVars.length ? ["API_SPECTOR_MASTER_KEY", ...secretVars] : [];
|
|
73287
74722
|
if (platform === "github") {
|
|
73288
|
-
const
|
|
73289
|
-
|
|
73290
|
-
const
|
|
74723
|
+
const ghProviderEnv = providerEnvVars(managers).filter((v) => v !== "VAULT_ROLE_ID" && v !== "VAULT_SECRET_ID");
|
|
74724
|
+
const allSecretVars = [...encryptedVars, ...ghProviderEnv.filter((v) => v !== "VAULT_ADDR")];
|
|
74725
|
+
const hintLines = [
|
|
74726
|
+
...allSecretVars.map((v) => ` # ${v}`),
|
|
74727
|
+
...usesVault ? [" # VAULT_ADDR (as a repository variable, not a secret)"] : []
|
|
74728
|
+
];
|
|
74729
|
+
const secretHint = hintLines.length ? ` # ⚠ Add these in: Settings → Secrets and variables → Actions
|
|
74730
|
+
${hintLines.join("\n")}
|
|
74731
|
+
` : "";
|
|
74732
|
+
const vaultStep = usesVault ? ` - name: Authenticate to Vault (OIDC)
|
|
74733
|
+
uses: hashicorp/vault-action@v3
|
|
74734
|
+
with:
|
|
74735
|
+
url: \${{ vars.VAULT_ADDR }}
|
|
74736
|
+
method: jwt
|
|
74737
|
+
role: apispector-ci # your Vault JWT/OIDC role
|
|
74738
|
+
exportToken: true # sets VAULT_TOKEN for later steps
|
|
74739
|
+
` : "";
|
|
74740
|
+
const permissions = usesVault ? ` permissions:
|
|
74741
|
+
contents: read
|
|
74742
|
+
id-token: write # required for Vault OIDC
|
|
74743
|
+
` : "";
|
|
74744
|
+
const envEntries = [
|
|
74745
|
+
...allSecretVars.map((v) => ` ${v}: \${{ secrets.${v} }}`),
|
|
74746
|
+
...usesVault ? [" VAULT_ADDR: ${{ vars.VAULT_ADDR }}"] : []
|
|
74747
|
+
];
|
|
74748
|
+
const envBlock = envEntries.length ? "\n env:\n" + envEntries.join("\n") : "";
|
|
73291
74749
|
return `name: API Tests
|
|
73292
74750
|
|
|
73293
74751
|
on:
|
|
@@ -73298,13 +74756,13 @@ on:
|
|
|
73298
74756
|
jobs:
|
|
73299
74757
|
api-tests:
|
|
73300
74758
|
runs-on: ubuntu-latest
|
|
73301
|
-
steps:
|
|
74759
|
+
${permissions} steps:
|
|
73302
74760
|
- uses: actions/checkout@v4
|
|
73303
74761
|
- uses: actions/setup-node@v4
|
|
73304
74762
|
with:
|
|
73305
74763
|
node-version: '${NODE_LTS}'
|
|
73306
74764
|
- run: npm install -g @testsmith/api-spector
|
|
73307
|
-
${secretHint} - name: Run API tests
|
|
74765
|
+
${vaultStep}${secretHint} - name: Run API tests
|
|
73308
74766
|
run: ${runCmd}${envBlock}
|
|
73309
74767
|
- name: Upload test results
|
|
73310
74768
|
if: always()
|
|
@@ -73315,6 +74773,7 @@ ${secretHint} - name: Run API tests
|
|
|
73315
74773
|
`;
|
|
73316
74774
|
}
|
|
73317
74775
|
if (platform === "gitlab") {
|
|
74776
|
+
const allSecretVars = [...encryptedVars, ...providerEnvVars(managers)];
|
|
73318
74777
|
const secretHint = allSecretVars.length ? ` # ⚠ Add these in: Settings → CI/CD → Variables
|
|
73319
74778
|
` + allSecretVars.map((v) => ` # ${v}`).join("\n") + "\n" : "";
|
|
73320
74779
|
const envBlock = allSecretVars.length ? "\n variables:\n" + allSecretVars.map((v) => ` ${v}: $${v}`).join("\n") : "";
|
|
@@ -73333,6 +74792,7 @@ ${secretHint} script:
|
|
|
73333
74792
|
`;
|
|
73334
74793
|
}
|
|
73335
74794
|
if (platform === "azure") {
|
|
74795
|
+
const allSecretVars = [...encryptedVars, ...providerEnvVars(managers)];
|
|
73336
74796
|
const secretHint = allSecretVars.length ? ` # ⚠ Add these in: Pipelines → Library → Variable groups (mark as secret)
|
|
73337
74797
|
` + allSecretVars.map((v) => ` # ${v}`).join("\n") + "\n" : "";
|
|
73338
74798
|
const envBlock = allSecretVars.length ? "\n env:\n" + allSecretVars.map((v) => ` ${v}: $(${v})`).join("\n") : "";
|
|
@@ -74026,6 +75486,7 @@ function BranchesTab({ onRefresh }) {
|
|
|
74026
75486
|
function CiTab() {
|
|
74027
75487
|
const environments = useStore((s) => s.environments);
|
|
74028
75488
|
const envList = Object.values(environments);
|
|
75489
|
+
const collections = useStore((s) => s.collections);
|
|
74029
75490
|
const [remotes, setRemotes] = reactExports.useState([]);
|
|
74030
75491
|
const [platform, setPlatform] = reactExports.useState("unknown");
|
|
74031
75492
|
const [envId, setEnvId] = reactExports.useState("");
|
|
@@ -74045,10 +75506,13 @@ function CiTab() {
|
|
|
74045
75506
|
reactExports.useEffect(() => {
|
|
74046
75507
|
const env = envList.find((e) => e.data.id === envId);
|
|
74047
75508
|
const secretVars = env ? env.data.variables.filter((v) => v.secret && v.enabled).map((v) => v.key) : [];
|
|
75509
|
+
const envRefManagers = env ? env.data.variables.filter((v) => v.enabled && v.secretRef).map((v) => secretManagerOf(v.secretRef)).filter((k) => k !== null) : [];
|
|
75510
|
+
const inlineManagers = Object.values(collections).flatMap((c) => Object.values(c.data.requests).flatMap(requestSecretManagers));
|
|
75511
|
+
const secretManagers = [.../* @__PURE__ */ new Set([...envRefManagers, ...inlineManagers])];
|
|
74048
75512
|
const envName = env?.data.name ?? "";
|
|
74049
|
-
setPreview(generateCiContent(platform, envName, tags2, secretVars));
|
|
75513
|
+
setPreview(generateCiContent(platform, envName, tags2, secretVars, secretManagers));
|
|
74050
75514
|
setWritten(false);
|
|
74051
|
-
}, [platform, envId, tags2, environments]);
|
|
75515
|
+
}, [platform, envId, tags2, environments, collections]);
|
|
74052
75516
|
async function write() {
|
|
74053
75517
|
try {
|
|
74054
75518
|
setError(null);
|
|
@@ -74633,6 +76097,8 @@ function App() {
|
|
|
74633
76097
|
}
|
|
74634
76098
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-screen bg-surface-950", style: { color: "var(--text-primary)" }, children: [
|
|
74635
76099
|
/* @__PURE__ */ jsxRuntimeExports.jsx(RunnerModal, {}),
|
|
76100
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(CoverageModal, {}),
|
|
76101
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(CompareModal, {}),
|
|
74636
76102
|
/* @__PURE__ */ jsxRuntimeExports.jsx(CommandPalette, {}),
|
|
74637
76103
|
docsModalOpen && /* @__PURE__ */ jsxRuntimeExports.jsx(DocsGeneratorModal, { onClose: () => setDocsModalOpen(false) }),
|
|
74638
76104
|
window.electron.platform !== "win32" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "drag-region flex-shrink-0 bg-surface-950 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "no-drag text-[11px] font-medium tracking-widest select-none", style: { color: "var(--text-muted)" }, children: [
|
|
@@ -74641,7 +76107,7 @@ function App() {
|
|
|
74641
76107
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
|
|
74642
76108
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
|
|
74643
76109
|
"v",
|
|
74644
|
-
"0.
|
|
76110
|
+
"0.5.1"
|
|
74645
76111
|
] }),
|
|
74646
76112
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", children: "·" }),
|
|
74647
76113
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|