@testsmith/api-spector 0.4.8 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +6 -0
- package/out/main/chunks/coverage-C54mZEh_.js +146 -0
- package/out/main/chunks/{handle-rimXXdJH.js → handle-b6bp3fb0.js} +22 -0
- package/out/main/chunks/{import-DcenB5Q_.js → import-BeY7gjWL.js} +2 -2
- package/out/main/chunks/{request-exec-BH-M3KqZ.js → request-exec-D_xyyt3_.js} +544 -9
- package/out/main/chunks/{snapshots-CtYxkSLz.js → snapshots-BJiZNuRN.js} +23 -21
- package/out/main/chunks/{soap-handler-BXx842MN.js → soap-handler-BYkE6MyE.js} +2 -2
- package/out/main/compare.js +218 -0
- package/out/main/contract.js +73 -149
- package/out/main/coverage.js +161 -0
- package/out/main/generate-tests.js +317 -0
- package/out/main/index.js +294 -18
- package/out/main/lib.js +11 -2
- package/out/main/runner.js +9 -4
- package/out/main/wsdl.js +3 -3
- package/out/preload/index.js +40 -0
- package/out/renderer/assets/index-Cgk7l8OV.css +2 -0
- package/out/renderer/assets/{index-CEKgq6t0.js → index-Cj7BRfK2.js} +1614 -194
- package/out/renderer/index.html +2 -2
- package/package.json +3 -1
- package/readme.md +20 -0
- package/out/renderer/assets/index-B_xyaTDo.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,7 @@ function InteractiveBody({ body, contentType, onAssert }) {
|
|
|
65828
66114
|
treeContent
|
|
65829
66115
|
] });
|
|
65830
66116
|
}
|
|
65831
|
-
const { electron: electron$
|
|
66117
|
+
const { electron: electron$n } = window;
|
|
65832
66118
|
const RENDER_TAIL = 500;
|
|
65833
66119
|
const CLOSE_LABEL = {
|
|
65834
66120
|
complete: "closed",
|
|
@@ -65869,7 +66155,7 @@ function StreamView({ events, streaming, streamId, streamClose, firstEventMs })
|
|
|
65869
66155
|
if (!streamId) return;
|
|
65870
66156
|
setStopping(true);
|
|
65871
66157
|
try {
|
|
65872
|
-
await electron$
|
|
66158
|
+
await electron$n.stopStream(streamId);
|
|
65873
66159
|
} catch {
|
|
65874
66160
|
}
|
|
65875
66161
|
}
|
|
@@ -66031,7 +66317,7 @@ function HookResultsPanel({ results }) {
|
|
|
66031
66317
|
}) })
|
|
66032
66318
|
] });
|
|
66033
66319
|
}
|
|
66034
|
-
const { electron: electron$
|
|
66320
|
+
const { electron: electron$m } = window;
|
|
66035
66321
|
function extractPath(url) {
|
|
66036
66322
|
try {
|
|
66037
66323
|
return new URL(url).pathname || "/";
|
|
@@ -66085,14 +66371,14 @@ function SaveAsMockModal({ onClose }) {
|
|
|
66085
66371
|
const entry = state.mocks[serverId];
|
|
66086
66372
|
const updated = { ...entry.data, name: newServerName, port: Number(newServerPort), routes: [route] };
|
|
66087
66373
|
updateMock(serverId, updated);
|
|
66088
|
-
await electron$
|
|
66374
|
+
await electron$m.saveMock(entry.relPath, updated);
|
|
66089
66375
|
const ws2 = useStore.getState().workspace;
|
|
66090
|
-
if (ws2) await electron$
|
|
66376
|
+
if (ws2) await electron$m.saveWorkspace(ws2);
|
|
66091
66377
|
} else {
|
|
66092
66378
|
const entry = useStore.getState().mocks[serverId];
|
|
66093
66379
|
const updated = { ...entry.data, routes: [...entry.data.routes, route] };
|
|
66094
66380
|
updateMock(serverId, updated);
|
|
66095
|
-
await electron$
|
|
66381
|
+
await electron$m.saveMock(entry.relPath, updated);
|
|
66096
66382
|
}
|
|
66097
66383
|
onClose();
|
|
66098
66384
|
} finally {
|
|
@@ -66519,7 +66805,7 @@ function validateHttpSemantics(res, opts = {}) {
|
|
|
66519
66805
|
const order = { error: 0, warning: 1, hint: 2 };
|
|
66520
66806
|
return f.sort((a, b) => order[a.severity] - order[b.severity]);
|
|
66521
66807
|
}
|
|
66522
|
-
const { electron: electron$
|
|
66808
|
+
const { electron: electron$l } = window;
|
|
66523
66809
|
function xmlWellFormed(body) {
|
|
66524
66810
|
try {
|
|
66525
66811
|
const doc2 = new DOMParser().parseFromString(body, "application/xml");
|
|
@@ -66538,6 +66824,8 @@ function requestBodyText(body) {
|
|
|
66538
66824
|
return body.graphql?.query ?? "";
|
|
66539
66825
|
case "soap":
|
|
66540
66826
|
return body.soap?.envelope ?? "";
|
|
66827
|
+
case "grpc":
|
|
66828
|
+
return body.grpc?.message ?? "";
|
|
66541
66829
|
case "form":
|
|
66542
66830
|
return (body.form ?? []).filter((p2) => p2.enabled && p2.key).map((p2) => `${p2.key}=${p2.value}`).join("\n");
|
|
66543
66831
|
default:
|
|
@@ -66657,7 +66945,7 @@ function ResponseViewer() {
|
|
|
66657
66945
|
const contractToast = useToast(2500);
|
|
66658
66946
|
async function saveAsContract() {
|
|
66659
66947
|
if (!response || !requestId || !activeTabId) return;
|
|
66660
|
-
const schema = response.body ? await electron$
|
|
66948
|
+
const schema = response.body ? await electron$l.inferContractSchema(response.body) : null;
|
|
66661
66949
|
const contentType2 = response.headers["content-type"];
|
|
66662
66950
|
const headers = contentType2 ? [{ key: "content-type", value: contentType2, required: true }] : [];
|
|
66663
66951
|
updateRequest(requestId, {
|
|
@@ -66965,7 +67253,7 @@ function ResponseViewer() {
|
|
|
66965
67253
|
] }) })
|
|
66966
67254
|
] });
|
|
66967
67255
|
}
|
|
66968
|
-
const { electron: electron$
|
|
67256
|
+
const { electron: electron$k } = window;
|
|
66969
67257
|
const TARGETS = [
|
|
66970
67258
|
{ id: "robot_framework", label: "Robot Framework", description: "Python RequestsLibrary keywords + test suite" },
|
|
66971
67259
|
{ id: "playwright_ts", label: "Playwright TS", description: "TypeScript page-object API classes + spec files" },
|
|
@@ -67006,7 +67294,7 @@ function GeneratorPanel() {
|
|
|
67006
67294
|
try {
|
|
67007
67295
|
const col = collections[selectedCollectionId]?.data;
|
|
67008
67296
|
const env = resolveEnvironmentById(environments, activeEnvironmentId);
|
|
67009
|
-
const generated = await electron$
|
|
67297
|
+
const generated = await electron$k.generateCode({ collection: col, environment: env, target });
|
|
67010
67298
|
setFiles(generated);
|
|
67011
67299
|
setSelectedFile(generated[0]?.path ?? null);
|
|
67012
67300
|
} catch (e) {
|
|
@@ -67018,7 +67306,7 @@ function GeneratorPanel() {
|
|
|
67018
67306
|
async function saveZip() {
|
|
67019
67307
|
if (files.length === 0) return;
|
|
67020
67308
|
const col = collections[selectedCollectionId]?.data;
|
|
67021
|
-
await electron$
|
|
67309
|
+
await electron$k.saveGeneratedFilesAsZip(files, col?.name ?? "api-tests", target);
|
|
67022
67310
|
}
|
|
67023
67311
|
const selectedContent = files.find((f) => f.path === selectedFile)?.content ?? "";
|
|
67024
67312
|
const activeTarget = TARGETS.find((t2) => t2.id === target);
|
|
@@ -67202,7 +67490,7 @@ function historyToHar(entries, creatorVersion = "1.0") {
|
|
|
67202
67490
|
}
|
|
67203
67491
|
}, null, 2);
|
|
67204
67492
|
}
|
|
67205
|
-
const { electron: electron$
|
|
67493
|
+
const { electron: electron$j } = window;
|
|
67206
67494
|
const STATUS_COLOR = {
|
|
67207
67495
|
"2": "text-emerald-400",
|
|
67208
67496
|
"3": "text-amber-400",
|
|
@@ -67250,7 +67538,7 @@ function HistoryPanel() {
|
|
|
67250
67538
|
}
|
|
67251
67539
|
async function downloadHar() {
|
|
67252
67540
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/[:T]/g, "-");
|
|
67253
|
-
await electron$
|
|
67541
|
+
await electron$j.saveResults(historyToHar(history2), `api-spector-history-${stamp}.har`);
|
|
67254
67542
|
}
|
|
67255
67543
|
function open(entry) {
|
|
67256
67544
|
setSelected(entry);
|
|
@@ -67381,30 +67669,30 @@ function HistoryRow({
|
|
|
67381
67669
|
}
|
|
67382
67670
|
);
|
|
67383
67671
|
}
|
|
67384
|
-
const { electron: electron$
|
|
67672
|
+
const { electron: electron$i } = window;
|
|
67385
67673
|
function WelcomeScreen() {
|
|
67386
67674
|
const { applyWorkspace } = useWorkspaceLoader();
|
|
67387
67675
|
const [recents, setRecents] = reactExports.useState([]);
|
|
67388
67676
|
const [update, setUpdate] = reactExports.useState(null);
|
|
67389
67677
|
reactExports.useEffect(() => {
|
|
67390
|
-
electron$
|
|
67391
|
-
electron$
|
|
67678
|
+
electron$i.getRecentWorkspaces().then(setRecents).catch(() => setRecents([]));
|
|
67679
|
+
electron$i.checkForUpdate().then((info) => {
|
|
67392
67680
|
if (info?.updateAvailable) setUpdate(info);
|
|
67393
67681
|
}).catch(() => {
|
|
67394
67682
|
});
|
|
67395
67683
|
}, []);
|
|
67396
67684
|
async function openWorkspace() {
|
|
67397
|
-
const result = await electron$
|
|
67685
|
+
const result = await electron$i.openWorkspace();
|
|
67398
67686
|
if (!result) return;
|
|
67399
67687
|
await applyWorkspace(result.workspace, result.workspacePath);
|
|
67400
67688
|
}
|
|
67401
67689
|
async function newWorkspace() {
|
|
67402
|
-
const result = await electron$
|
|
67690
|
+
const result = await electron$i.newWorkspace();
|
|
67403
67691
|
if (!result) return;
|
|
67404
67692
|
await applyWorkspace(result.workspace, result.workspacePath);
|
|
67405
67693
|
}
|
|
67406
67694
|
async function openRecent(path) {
|
|
67407
|
-
const result = await electron$
|
|
67695
|
+
const result = await electron$i.openWorkspacePath(path);
|
|
67408
67696
|
if (!result) {
|
|
67409
67697
|
setRecents((prev) => prev.filter((r) => r.path !== path));
|
|
67410
67698
|
return;
|
|
@@ -67444,7 +67732,7 @@ function WelcomeScreen() {
|
|
|
67444
67732
|
/* @__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
67733
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] mt-3", style: { color: "var(--text-muted)" }, children: [
|
|
67446
67734
|
"version ",
|
|
67447
|
-
"0.
|
|
67735
|
+
"0.5.0"
|
|
67448
67736
|
] })
|
|
67449
67737
|
] }),
|
|
67450
67738
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 w-64", children: [
|
|
@@ -67488,7 +67776,7 @@ function WelcomeScreen() {
|
|
|
67488
67776
|
] })
|
|
67489
67777
|
] });
|
|
67490
67778
|
}
|
|
67491
|
-
const { electron: electron$
|
|
67779
|
+
const { electron: electron$h } = window;
|
|
67492
67780
|
const EXAMPLES = [
|
|
67493
67781
|
{
|
|
67494
67782
|
label: "macOS / Linux (~/.zshrc or ~/.bashrc)",
|
|
@@ -67512,7 +67800,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
|
|
|
67512
67800
|
setError("Password cannot be empty.");
|
|
67513
67801
|
return;
|
|
67514
67802
|
}
|
|
67515
|
-
await electron$
|
|
67803
|
+
await electron$h.setMasterKey(password);
|
|
67516
67804
|
onSuccess(password);
|
|
67517
67805
|
}
|
|
67518
67806
|
function copy(idx, text) {
|
|
@@ -67596,7 +67884,7 @@ function MasterKeyModal({ onSuccess, onCancel }) {
|
|
|
67596
67884
|
}
|
|
67597
67885
|
);
|
|
67598
67886
|
}
|
|
67599
|
-
const { electron: electron$
|
|
67887
|
+
const { electron: electron$g } = window;
|
|
67600
67888
|
async function shortHash(value) {
|
|
67601
67889
|
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
67602
67890
|
return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 8);
|
|
@@ -67723,7 +68011,7 @@ function EnvironmentEditor({ onClose }) {
|
|
|
67723
68011
|
async function saveEncrypted(idx) {
|
|
67724
68012
|
const plaintext = secretInputs[idx] ?? "";
|
|
67725
68013
|
if (!plaintext) return;
|
|
67726
|
-
const { set: set2 } = await electron$
|
|
68014
|
+
const { set: set2 } = await electron$g.checkMasterKey();
|
|
67727
68015
|
if (!set2) {
|
|
67728
68016
|
setPendingEncryptIdx(idx);
|
|
67729
68017
|
return;
|
|
@@ -67760,9 +68048,9 @@ function EnvironmentEditor({ onClose }) {
|
|
|
67760
68048
|
} : state.workspace
|
|
67761
68049
|
}));
|
|
67762
68050
|
const ws2 = useStore.getState().workspace;
|
|
67763
|
-
if (ws2) await electron$
|
|
68051
|
+
if (ws2) await electron$g.saveWorkspace(ws2);
|
|
67764
68052
|
}
|
|
67765
|
-
await electron$
|
|
68053
|
+
await electron$g.saveEnvironment(newRelPath, env);
|
|
67766
68054
|
}
|
|
67767
68055
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
67768
68056
|
pendingEncryptIdx !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -68072,7 +68360,7 @@ function EnvironmentEditor({ onClose }) {
|
|
|
68072
68360
|
)
|
|
68073
68361
|
] });
|
|
68074
68362
|
}
|
|
68075
|
-
const { electron: electron$
|
|
68363
|
+
const { electron: electron$f } = window;
|
|
68076
68364
|
function EnvironmentBar({ inline = false }) {
|
|
68077
68365
|
const environments = useStore((s) => s.environments);
|
|
68078
68366
|
const activeEnvironmentId = useStore((s) => s.activeEnvironmentId);
|
|
@@ -68087,7 +68375,7 @@ function EnvironmentBar({ inline = false }) {
|
|
|
68087
68375
|
if (id2) {
|
|
68088
68376
|
const hasSecrets = environments[id2]?.data.variables.some((v) => v.enabled && v.secret);
|
|
68089
68377
|
if (hasSecrets) {
|
|
68090
|
-
const { set: set2 } = await electron$
|
|
68378
|
+
const { set: set2 } = await electron$f.checkMasterKey();
|
|
68091
68379
|
if (!set2) {
|
|
68092
68380
|
setPendingEnvId(id2);
|
|
68093
68381
|
return;
|
|
@@ -68141,7 +68429,7 @@ function EnvironmentBar({ inline = false }) {
|
|
|
68141
68429
|
controls
|
|
68142
68430
|
] });
|
|
68143
68431
|
}
|
|
68144
|
-
const { electron: electron$
|
|
68432
|
+
const { electron: electron$e } = window;
|
|
68145
68433
|
const DEFAULT_PII_PATTERNS = ["authorization", "password", "token", "secret", "api-key", "x-api-key"];
|
|
68146
68434
|
const ZOOM_STEPS = [0.75, 0.9, 1, 1.1, 1.25, 1.5];
|
|
68147
68435
|
function WorkspaceSettingsModal({ onClose }) {
|
|
@@ -68166,7 +68454,6 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
68166
68454
|
existing.tls?.rejectUnauthorized !== false
|
|
68167
68455
|
// default true
|
|
68168
68456
|
);
|
|
68169
|
-
const [dashboardUrl, setDashboardUrl] = reactExports.useState(existing.dashboardUrl ?? "");
|
|
68170
68457
|
const [cloudEnabled2, setCloudEnabled] = reactExports.useState(existing.cloud?.enabled ?? false);
|
|
68171
68458
|
const [cloudToken, setCloudToken] = reactExports.useState("");
|
|
68172
68459
|
const cloudTokenSet = existing.cloud?.tokenSet ?? false;
|
|
@@ -68174,13 +68461,44 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
68174
68461
|
async function testCloud() {
|
|
68175
68462
|
setCloudTest({ status: "testing" });
|
|
68176
68463
|
try {
|
|
68177
|
-
if (cloudToken.trim()) await electron$
|
|
68178
|
-
const me = await electron$
|
|
68464
|
+
if (cloudToken.trim()) await electron$e.setSecret("cloud:token", cloudToken.trim());
|
|
68465
|
+
const me = await electron$e.cloudTest();
|
|
68179
68466
|
setCloudTest({ status: "ok", msg: `Connected as ${me.email} · ${me.organization} (${me.plan})` });
|
|
68180
68467
|
} catch (e) {
|
|
68181
68468
|
setCloudTest({ status: "err", msg: e.message });
|
|
68182
68469
|
}
|
|
68183
68470
|
}
|
|
68471
|
+
const sec = existing.secrets ?? {};
|
|
68472
|
+
const [vAddress, setVAddress] = reactExports.useState(sec.vault?.address ?? "");
|
|
68473
|
+
const [vNamespace, setVNamespace] = reactExports.useState(sec.vault?.namespace ?? "");
|
|
68474
|
+
const [vAuthMethod, setVAuthMethod] = reactExports.useState(sec.vault?.authMethod ?? "token");
|
|
68475
|
+
const [vRoleId, setVRoleId] = reactExports.useState(sec.vault?.roleId ?? "");
|
|
68476
|
+
const [vJwtRole, setVJwtRole] = reactExports.useState(sec.vault?.jwtRole ?? "");
|
|
68477
|
+
const [vLoginMount, setVLoginMount] = reactExports.useState(sec.vault?.loginMount ?? "");
|
|
68478
|
+
const [vKvVersion, setVKvVersion] = reactExports.useState(sec.vault?.kvVersion ?? "auto");
|
|
68479
|
+
const [vSkipVerify, setVSkipVerify] = reactExports.useState(sec.vault?.skipVerify ?? false);
|
|
68480
|
+
const [awsRegion, setAwsRegion] = reactExports.useState(sec.aws?.region ?? "");
|
|
68481
|
+
const [azVault, setAzVault] = reactExports.useState(sec.azure?.vault ?? "");
|
|
68482
|
+
const [azTenantId, setAzTenantId] = reactExports.useState(sec.azure?.tenantId ?? "");
|
|
68483
|
+
const [azClientId, setAzClientId] = reactExports.useState(sec.azure?.clientId ?? "");
|
|
68484
|
+
const [opConnectHost, setOpConnectHost] = reactExports.useState(sec.onePassword?.connectHost ?? "");
|
|
68485
|
+
const [oidc, setOidc] = reactExports.useState({ status: "idle" });
|
|
68486
|
+
async function vaultOidcLogin() {
|
|
68487
|
+
setOidc({ status: "busy", msg: "Complete the sign-in in your browser…" });
|
|
68488
|
+
try {
|
|
68489
|
+
const r = await electron$e.vaultOidcLogin({
|
|
68490
|
+
address: vAddress.trim(),
|
|
68491
|
+
mount: vLoginMount.trim() || void 0,
|
|
68492
|
+
role: vJwtRole.trim() || void 0,
|
|
68493
|
+
namespace: vNamespace.trim() || void 0,
|
|
68494
|
+
skipVerify: vSkipVerify
|
|
68495
|
+
});
|
|
68496
|
+
const mins = Math.round((r.expiresInSeconds ?? 0) / 60);
|
|
68497
|
+
setOidc({ status: "ok", msg: `Signed in${mins ? ` · token expires in ~${mins} min` : ""}` });
|
|
68498
|
+
} catch (e) {
|
|
68499
|
+
setOidc({ status: "err", msg: e.message });
|
|
68500
|
+
}
|
|
68501
|
+
}
|
|
68184
68502
|
const [patterns, setPatterns] = reactExports.useState(
|
|
68185
68503
|
existing.piiMaskPatterns ?? DEFAULT_PII_PATTERNS
|
|
68186
68504
|
);
|
|
@@ -68212,8 +68530,6 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
68212
68530
|
rejectUnauthorized
|
|
68213
68531
|
};
|
|
68214
68532
|
settings.piiMaskPatterns = patterns;
|
|
68215
|
-
if (dashboardUrl.trim()) settings.dashboardUrl = dashboardUrl.trim();
|
|
68216
|
-
else delete settings.dashboardUrl;
|
|
68217
68533
|
if (cloudEnabled2 || cloudTokenSet || cloudToken.trim()) {
|
|
68218
68534
|
settings.cloud = {
|
|
68219
68535
|
enabled: cloudEnabled2,
|
|
@@ -68222,16 +68538,36 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
68222
68538
|
} else {
|
|
68223
68539
|
delete settings.cloud;
|
|
68224
68540
|
}
|
|
68225
|
-
if (cloudToken.trim()) await electron$
|
|
68541
|
+
if (cloudToken.trim()) await electron$e.setSecret("cloud:token", cloudToken.trim());
|
|
68226
68542
|
if (defaultEnvironment) settings.defaultEnvironment = defaultEnvironment;
|
|
68227
68543
|
else delete settings.defaultEnvironment;
|
|
68228
68544
|
if (persistHistory) settings.persistHistory = true;
|
|
68229
68545
|
else delete settings.persistHistory;
|
|
68546
|
+
const secrets = {};
|
|
68547
|
+
const vault = {};
|
|
68548
|
+
if (vAddress.trim()) vault.address = vAddress.trim();
|
|
68549
|
+
if (vNamespace.trim()) vault.namespace = vNamespace.trim();
|
|
68550
|
+
if (vAuthMethod !== "token") vault.authMethod = vAuthMethod;
|
|
68551
|
+
if (vRoleId.trim()) vault.roleId = vRoleId.trim();
|
|
68552
|
+
if (vJwtRole.trim()) vault.jwtRole = vJwtRole.trim();
|
|
68553
|
+
if (vLoginMount.trim()) vault.loginMount = vLoginMount.trim();
|
|
68554
|
+
if (vKvVersion !== "auto") vault.kvVersion = vKvVersion;
|
|
68555
|
+
if (vSkipVerify) vault.skipVerify = true;
|
|
68556
|
+
if (Object.keys(vault).length) secrets.vault = vault;
|
|
68557
|
+
if (awsRegion.trim()) secrets.aws = { region: awsRegion.trim() };
|
|
68558
|
+
const azure = {};
|
|
68559
|
+
if (azVault.trim()) azure.vault = azVault.trim();
|
|
68560
|
+
if (azTenantId.trim()) azure.tenantId = azTenantId.trim();
|
|
68561
|
+
if (azClientId.trim()) azure.clientId = azClientId.trim();
|
|
68562
|
+
if (Object.keys(azure).length) secrets.azure = azure;
|
|
68563
|
+
if (opConnectHost.trim()) secrets.onePassword = { connectHost: opConnectHost.trim() };
|
|
68564
|
+
if (Object.keys(secrets).length) settings.secrets = secrets;
|
|
68565
|
+
else delete settings.secrets;
|
|
68230
68566
|
updateWorkspaceSettings(settings);
|
|
68231
68567
|
const updated = useStore.getState().workspace;
|
|
68232
|
-
if (updated) await electron$
|
|
68568
|
+
if (updated) await electron$e.saveWorkspace(updated);
|
|
68233
68569
|
if (persistHistory) {
|
|
68234
|
-
await electron$
|
|
68570
|
+
await electron$e.saveHistory(useStore.getState().history).catch(() => {
|
|
68235
68571
|
});
|
|
68236
68572
|
}
|
|
68237
68573
|
onClose();
|
|
@@ -68247,7 +68583,7 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
68247
68583
|
{ id: "proxy", label: "Proxy" },
|
|
68248
68584
|
{ id: "tls", label: "TLS / Certificates" },
|
|
68249
68585
|
{ id: "privacy", label: "Privacy" },
|
|
68250
|
-
{ id: "
|
|
68586
|
+
{ id: "secrets", label: "Secrets" },
|
|
68251
68587
|
{ id: "cloud", label: "Cloud" }
|
|
68252
68588
|
];
|
|
68253
68589
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
@@ -68390,21 +68726,6 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
68390
68726
|
] })
|
|
68391
68727
|
] })
|
|
68392
68728
|
] }),
|
|
68393
|
-
activeTab === "contracts" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
68394
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
68395
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Dashboard URL (optional)" }),
|
|
68396
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
68397
|
-
"input",
|
|
68398
|
-
{
|
|
68399
|
-
value: dashboardUrl,
|
|
68400
|
-
onChange: (e) => setDashboardUrl(e.target.value),
|
|
68401
|
-
placeholder: "http://localhost:8080",
|
|
68402
|
-
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
|
|
68403
|
-
}
|
|
68404
|
-
)
|
|
68405
|
-
] }),
|
|
68406
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-600 text-[11px]", children: 'Where your contract dashboard is served (api-spector contract report --serve, locally or as a docker container). Adds an "Open dashboard" link to the contract results panel. The link is view-only: recorded results reach the dashboard through the workspace files, not through this URL.' })
|
|
68407
|
-
] }),
|
|
68408
68729
|
activeTab === "cloud" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
68409
68730
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-start gap-2 cursor-pointer", children: [
|
|
68410
68731
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
@@ -68549,6 +68870,200 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
68549
68870
|
}
|
|
68550
68871
|
)
|
|
68551
68872
|
] })
|
|
68873
|
+
] }),
|
|
68874
|
+
activeTab === "secrets" && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
68875
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-surface-600 text-[11px]", children: [
|
|
68876
|
+
"Reference secrets from an external manager instead of storing them. Put a reference like",
|
|
68877
|
+
" ",
|
|
68878
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-surface-400", children: "vault:secret/data/app#token" }),
|
|
68879
|
+
" in an environment variable or an auth field; it is resolved at send-time and never written to the workspace. This connection config is non-secret — tokens and secret-ids come from your environment (or the Vault sign-in below)."
|
|
68880
|
+
] }),
|
|
68881
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2 border border-surface-800 rounded p-3", children: [
|
|
68882
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[10px] uppercase tracking-wider text-surface-500 font-semibold", children: "HashiCorp Vault" }),
|
|
68883
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
68884
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Address (VAULT_ADDR)" }),
|
|
68885
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
68886
|
+
"input",
|
|
68887
|
+
{
|
|
68888
|
+
value: vAddress,
|
|
68889
|
+
onChange: (e) => setVAddress(e.target.value),
|
|
68890
|
+
placeholder: "https://vault.acme.internal:8200",
|
|
68891
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
|
|
68892
|
+
}
|
|
68893
|
+
)
|
|
68894
|
+
] }),
|
|
68895
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
68896
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
68897
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Namespace" }),
|
|
68898
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
68899
|
+
"input",
|
|
68900
|
+
{
|
|
68901
|
+
value: vNamespace,
|
|
68902
|
+
onChange: (e) => setVNamespace(e.target.value),
|
|
68903
|
+
placeholder: "(Enterprise)",
|
|
68904
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
|
|
68905
|
+
}
|
|
68906
|
+
)
|
|
68907
|
+
] }),
|
|
68908
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
68909
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Auth method" }),
|
|
68910
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
68911
|
+
"select",
|
|
68912
|
+
{
|
|
68913
|
+
value: vAuthMethod,
|
|
68914
|
+
onChange: (e) => setVAuthMethod(e.target.value),
|
|
68915
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500",
|
|
68916
|
+
children: [
|
|
68917
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "token", children: "Token / OIDC" }),
|
|
68918
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "approle", children: "AppRole" }),
|
|
68919
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "jwt", children: "JWT (CI)" })
|
|
68920
|
+
]
|
|
68921
|
+
}
|
|
68922
|
+
)
|
|
68923
|
+
] })
|
|
68924
|
+
] }),
|
|
68925
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
68926
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
68927
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Role (AppRole/JWT/OIDC)" }),
|
|
68928
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
68929
|
+
"input",
|
|
68930
|
+
{
|
|
68931
|
+
value: vJwtRole,
|
|
68932
|
+
onChange: (e) => setVJwtRole(e.target.value),
|
|
68933
|
+
placeholder: "apispector",
|
|
68934
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
|
|
68935
|
+
}
|
|
68936
|
+
)
|
|
68937
|
+
] }),
|
|
68938
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
68939
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Login mount" }),
|
|
68940
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
68941
|
+
"input",
|
|
68942
|
+
{
|
|
68943
|
+
value: vLoginMount,
|
|
68944
|
+
onChange: (e) => setVLoginMount(e.target.value),
|
|
68945
|
+
placeholder: "oidc / approle / jwt",
|
|
68946
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
|
|
68947
|
+
}
|
|
68948
|
+
)
|
|
68949
|
+
] })
|
|
68950
|
+
] }),
|
|
68951
|
+
vAuthMethod === "approle" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
68952
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: [
|
|
68953
|
+
"Role ID ",
|
|
68954
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 normal-case", children: "(secret id via VAULT_SECRET_ID)" })
|
|
68955
|
+
] }),
|
|
68956
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
68957
|
+
"input",
|
|
68958
|
+
{
|
|
68959
|
+
value: vRoleId,
|
|
68960
|
+
onChange: (e) => setVRoleId(e.target.value),
|
|
68961
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono"
|
|
68962
|
+
}
|
|
68963
|
+
)
|
|
68964
|
+
] }),
|
|
68965
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
68966
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
68967
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "KV version" }),
|
|
68968
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
68969
|
+
"select",
|
|
68970
|
+
{
|
|
68971
|
+
value: vKvVersion,
|
|
68972
|
+
onChange: (e) => setVKvVersion(e.target.value),
|
|
68973
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500",
|
|
68974
|
+
children: [
|
|
68975
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "auto", children: "auto" }),
|
|
68976
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "2", children: "v2" }),
|
|
68977
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "1", children: "v1" })
|
|
68978
|
+
]
|
|
68979
|
+
}
|
|
68980
|
+
)
|
|
68981
|
+
] }),
|
|
68982
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 mt-4 cursor-pointer", children: [
|
|
68983
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", checked: vSkipVerify, onChange: (e) => setVSkipVerify(e.target.checked) }),
|
|
68984
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-300", children: "Skip TLS verify (dev)" })
|
|
68985
|
+
] })
|
|
68986
|
+
] }),
|
|
68987
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mt-1", children: [
|
|
68988
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
68989
|
+
"button",
|
|
68990
|
+
{
|
|
68991
|
+
onClick: vaultOidcLogin,
|
|
68992
|
+
disabled: !vAddress.trim() || oidc.status === "busy",
|
|
68993
|
+
className: "px-3 py-1.5 bg-surface-700 hover:bg-surface-600 disabled:opacity-40 rounded transition-colors",
|
|
68994
|
+
children: oidc.status === "busy" ? "Waiting for browser…" : "Sign in with OIDC"
|
|
68995
|
+
}
|
|
68996
|
+
),
|
|
68997
|
+
oidc.msg && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[11px] ${oidc.status === "ok" ? "text-green-400" : oidc.status === "err" ? "text-red-400" : "text-surface-500"}`, children: oidc.msg })
|
|
68998
|
+
] }),
|
|
68999
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-600 text-[11px]", children: "Opens your browser to sign in; the short-lived token is used for this session only." })
|
|
69000
|
+
] }),
|
|
69001
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
69002
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: [
|
|
69003
|
+
"AWS region ",
|
|
69004
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 normal-case", children: "(credentials from AWS_* env)" })
|
|
69005
|
+
] }),
|
|
69006
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69007
|
+
"input",
|
|
69008
|
+
{
|
|
69009
|
+
value: awsRegion,
|
|
69010
|
+
onChange: (e) => setAwsRegion(e.target.value),
|
|
69011
|
+
placeholder: "eu-west-1",
|
|
69012
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
|
|
69013
|
+
}
|
|
69014
|
+
)
|
|
69015
|
+
] }),
|
|
69016
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-3 gap-2", children: [
|
|
69017
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
69018
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Azure vault" }),
|
|
69019
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69020
|
+
"input",
|
|
69021
|
+
{
|
|
69022
|
+
value: azVault,
|
|
69023
|
+
onChange: (e) => setAzVault(e.target.value),
|
|
69024
|
+
placeholder: "acme-kv",
|
|
69025
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
|
|
69026
|
+
}
|
|
69027
|
+
)
|
|
69028
|
+
] }),
|
|
69029
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
69030
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Tenant id" }),
|
|
69031
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69032
|
+
"input",
|
|
69033
|
+
{
|
|
69034
|
+
value: azTenantId,
|
|
69035
|
+
onChange: (e) => setAzTenantId(e.target.value),
|
|
69036
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono"
|
|
69037
|
+
}
|
|
69038
|
+
)
|
|
69039
|
+
] }),
|
|
69040
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
69041
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: "Client id" }),
|
|
69042
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69043
|
+
"input",
|
|
69044
|
+
{
|
|
69045
|
+
value: azClientId,
|
|
69046
|
+
onChange: (e) => setAzClientId(e.target.value),
|
|
69047
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono"
|
|
69048
|
+
}
|
|
69049
|
+
)
|
|
69050
|
+
] })
|
|
69051
|
+
] }),
|
|
69052
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
69053
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] uppercase tracking-wider text-surface-600 font-medium", children: [
|
|
69054
|
+
"1Password Connect host ",
|
|
69055
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-600 normal-case", children: "(token from OP_CONNECT_TOKEN)" })
|
|
69056
|
+
] }),
|
|
69057
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
69058
|
+
"input",
|
|
69059
|
+
{
|
|
69060
|
+
value: opConnectHost,
|
|
69061
|
+
onChange: (e) => setOpConnectHost(e.target.value),
|
|
69062
|
+
placeholder: "https://op.acme.internal",
|
|
69063
|
+
className: "bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
|
|
69064
|
+
}
|
|
69065
|
+
)
|
|
69066
|
+
] })
|
|
68552
69067
|
] })
|
|
68553
69068
|
] }),
|
|
68554
69069
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 px-4 py-3 border-t border-surface-800 flex-shrink-0", children: [
|
|
@@ -68573,7 +69088,7 @@ function WorkspaceSettingsModal({ onClose }) {
|
|
|
68573
69088
|
}
|
|
68574
69089
|
);
|
|
68575
69090
|
}
|
|
68576
|
-
const { electron: electron$
|
|
69091
|
+
const { electron: electron$d } = window;
|
|
68577
69092
|
function DocsGeneratorModal({ onClose }) {
|
|
68578
69093
|
const collections = useStore((s) => s.collections);
|
|
68579
69094
|
const collectionList = Object.values(collections);
|
|
@@ -68613,9 +69128,9 @@ function DocsGeneratorModal({ onClose }) {
|
|
|
68613
69128
|
setGenerating(true);
|
|
68614
69129
|
setError(null);
|
|
68615
69130
|
try {
|
|
68616
|
-
const content2 = await electron$
|
|
69131
|
+
const content2 = await electron$d.generateDocs(buildPayload());
|
|
68617
69132
|
const filename = format2 === "html" ? "api-docs.html" : "api-docs.md";
|
|
68618
|
-
await electron$
|
|
69133
|
+
await electron$d.saveResults(content2, filename);
|
|
68619
69134
|
} catch (err) {
|
|
68620
69135
|
setError(err instanceof Error ? err.message : String(err));
|
|
68621
69136
|
} finally {
|
|
@@ -68626,7 +69141,7 @@ function DocsGeneratorModal({ onClose }) {
|
|
|
68626
69141
|
setGenerating(true);
|
|
68627
69142
|
setError(null);
|
|
68628
69143
|
try {
|
|
68629
|
-
const content2 = await electron$
|
|
69144
|
+
const content2 = await electron$d.generateDocs(buildPayload());
|
|
68630
69145
|
setPreview(content2);
|
|
68631
69146
|
} catch (err) {
|
|
68632
69147
|
setError(err instanceof Error ? err.message : String(err));
|
|
@@ -68975,7 +69490,7 @@ function parseCurl(command2) {
|
|
|
68975
69490
|
}
|
|
68976
69491
|
return { name: name2, method: resolvedMethod, url, headers, params: [], auth, body };
|
|
68977
69492
|
}
|
|
68978
|
-
const { electron: electron$
|
|
69493
|
+
const { electron: electron$c } = window;
|
|
68979
69494
|
const OPTIONS = [
|
|
68980
69495
|
{ id: "postman", label: "Postman", description: "Collection v2.1 JSON" },
|
|
68981
69496
|
{ id: "openapi", label: "OpenAPI", description: "JSON or YAML (v3.x)", supportsUrl: true },
|
|
@@ -68987,16 +69502,16 @@ const OPTIONS = [
|
|
|
68987
69502
|
];
|
|
68988
69503
|
function listEndpoints(col) {
|
|
68989
69504
|
const result = [];
|
|
68990
|
-
function
|
|
69505
|
+
function walk2(folder, path) {
|
|
68991
69506
|
for (const id2 of folder.requestIds) {
|
|
68992
69507
|
const req = col.requests[id2];
|
|
68993
69508
|
if (req) result.push({ request: req, folderPath: path });
|
|
68994
69509
|
}
|
|
68995
69510
|
for (const sub of folder.folders) {
|
|
68996
|
-
|
|
69511
|
+
walk2(sub, [...path, sub.name]);
|
|
68997
69512
|
}
|
|
68998
69513
|
}
|
|
68999
|
-
|
|
69514
|
+
walk2(col.rootFolder, []);
|
|
69000
69515
|
return result;
|
|
69001
69516
|
}
|
|
69002
69517
|
function ImportModal({ onImport, onClose }) {
|
|
@@ -69050,12 +69565,12 @@ function ImportModal({ onImport, onClose }) {
|
|
|
69050
69565
|
setError(null);
|
|
69051
69566
|
try {
|
|
69052
69567
|
let col = null;
|
|
69053
|
-
if (opt2.id === "postman") col = await electron$
|
|
69054
|
-
if (opt2.id === "openapi") col = await electron$
|
|
69055
|
-
if (opt2.id === "insomnia") col = await electron$
|
|
69056
|
-
if (opt2.id === "bruno") col = await electron$
|
|
69057
|
-
if (opt2.id === "http") col = await electron$
|
|
69058
|
-
if (opt2.id === "spector") col = await electron$
|
|
69568
|
+
if (opt2.id === "postman") col = await electron$c.importPostman();
|
|
69569
|
+
if (opt2.id === "openapi") col = await electron$c.importOpenApi();
|
|
69570
|
+
if (opt2.id === "insomnia") col = await electron$c.importInsomnia();
|
|
69571
|
+
if (opt2.id === "bruno") col = await electron$c.importBruno();
|
|
69572
|
+
if (opt2.id === "http") col = await electron$c.importHttpFile();
|
|
69573
|
+
if (opt2.id === "spector") col = await electron$c.importSpectorCollection();
|
|
69059
69574
|
if (!col) {
|
|
69060
69575
|
setLoading(false);
|
|
69061
69576
|
return;
|
|
@@ -69078,7 +69593,7 @@ function ImportModal({ onImport, onClose }) {
|
|
|
69078
69593
|
setLoading(true);
|
|
69079
69594
|
setError(null);
|
|
69080
69595
|
try {
|
|
69081
|
-
const col = await electron$
|
|
69596
|
+
const col = await electron$c.importOpenApiFromUrl(trimmed);
|
|
69082
69597
|
if (col) enterPreview(col);
|
|
69083
69598
|
} catch (err) {
|
|
69084
69599
|
setError(err instanceof Error ? err.message : String(err));
|
|
@@ -69185,7 +69700,7 @@ function ImportModal({ onImport, onClose }) {
|
|
|
69185
69700
|
mergeIntoCollection(target, prunedRoot, prunedRequests);
|
|
69186
69701
|
const entry = useStore.getState().collections[target];
|
|
69187
69702
|
if (entry) {
|
|
69188
|
-
await electron$
|
|
69703
|
+
await electron$c.saveCollection(entry.relPath, entry.data);
|
|
69189
69704
|
markCollectionClean(target);
|
|
69190
69705
|
}
|
|
69191
69706
|
setActiveCollection(target);
|
|
@@ -69212,7 +69727,7 @@ function ImportModal({ onImport, onClose }) {
|
|
|
69212
69727
|
variables: [{ key: name2, value, enabled: true }]
|
|
69213
69728
|
};
|
|
69214
69729
|
const relPath = envRelPath(finalName, envId);
|
|
69215
|
-
await electron$
|
|
69730
|
+
await electron$c.saveEnvironment(relPath, env);
|
|
69216
69731
|
useStore.setState((s) => {
|
|
69217
69732
|
s.environments[envId] = { relPath, data: env };
|
|
69218
69733
|
if (!s.activeEnvironmentId) s.activeEnvironmentId = envId;
|
|
@@ -69222,7 +69737,7 @@ function ImportModal({ onImport, onClose }) {
|
|
|
69222
69737
|
return s;
|
|
69223
69738
|
});
|
|
69224
69739
|
const ws2 = useStore.getState().workspace;
|
|
69225
|
-
if (ws2) await electron$
|
|
69740
|
+
if (ws2) await electron$c.saveWorkspace(ws2);
|
|
69226
69741
|
} else {
|
|
69227
69742
|
const entry = state.environments[envTarget];
|
|
69228
69743
|
if (!entry) throw new Error("Target environment not found");
|
|
@@ -69237,7 +69752,7 @@ function ImportModal({ onImport, onClose }) {
|
|
|
69237
69752
|
} else {
|
|
69238
69753
|
updated.variables = [...updated.variables, { key: name2, value, enabled: true }];
|
|
69239
69754
|
}
|
|
69240
|
-
await electron$
|
|
69755
|
+
await electron$c.saveEnvironment(entry.relPath, updated);
|
|
69241
69756
|
useStore.getState().updateEnvironment(envTarget, updated);
|
|
69242
69757
|
}
|
|
69243
69758
|
}
|
|
@@ -69604,16 +70119,16 @@ function pruneFolder(src, _col, keep, _out) {
|
|
|
69604
70119
|
}
|
|
69605
70120
|
function collectRequestsByFolder(folder, src) {
|
|
69606
70121
|
const out = {};
|
|
69607
|
-
function
|
|
70122
|
+
function walk2(f) {
|
|
69608
70123
|
for (const id2 of f.requestIds) {
|
|
69609
70124
|
if (src.requests[id2]) out[id2] = src.requests[id2];
|
|
69610
70125
|
}
|
|
69611
|
-
for (const sub of f.folders)
|
|
70126
|
+
for (const sub of f.folders) walk2(sub);
|
|
69612
70127
|
}
|
|
69613
|
-
|
|
70128
|
+
walk2(folder);
|
|
69614
70129
|
return out;
|
|
69615
70130
|
}
|
|
69616
|
-
const { electron: electron$
|
|
70131
|
+
const { electron: electron$b } = window;
|
|
69617
70132
|
function Toolbar({ onOpenDocs: _onOpenDocs }) {
|
|
69618
70133
|
const { applyWorkspace } = useWorkspaceLoader();
|
|
69619
70134
|
const workspace = useStore((s) => s.workspace);
|
|
@@ -69636,13 +70151,13 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
|
|
|
69636
70151
|
try {
|
|
69637
70152
|
for (const { relPath, data, dirty } of Object.values(collections)) {
|
|
69638
70153
|
if (!dirty) continue;
|
|
69639
|
-
await electron$
|
|
70154
|
+
await electron$b.saveCollection(relPath, data);
|
|
69640
70155
|
markCollectionClean(data.id);
|
|
69641
70156
|
}
|
|
69642
70157
|
for (const { relPath, data } of Object.values(environments)) {
|
|
69643
|
-
await electron$
|
|
70158
|
+
await electron$b.saveEnvironment(relPath, data);
|
|
69644
70159
|
}
|
|
69645
|
-
if (workspace) await electron$
|
|
70160
|
+
if (workspace) await electron$b.saveWorkspace(workspace);
|
|
69646
70161
|
} finally {
|
|
69647
70162
|
setSaving(false);
|
|
69648
70163
|
}
|
|
@@ -69650,14 +70165,14 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
|
|
|
69650
70165
|
async function afterImport(col) {
|
|
69651
70166
|
if (!col) return;
|
|
69652
70167
|
const relPath = colRelPath(col.name, col.id);
|
|
69653
|
-
await electron$
|
|
70168
|
+
await electron$b.saveCollection(relPath, col);
|
|
69654
70169
|
loadCollection(relPath, col);
|
|
69655
70170
|
setActiveCollection(col.id);
|
|
69656
70171
|
const ws2 = useStore.getState().workspace;
|
|
69657
70172
|
if (ws2 && !ws2.collections.includes(relPath)) {
|
|
69658
70173
|
const updated = { ...ws2, collections: [...ws2.collections, relPath] };
|
|
69659
70174
|
useStore.setState({ workspace: updated });
|
|
69660
|
-
await electron$
|
|
70175
|
+
await electron$b.saveWorkspace(updated);
|
|
69661
70176
|
}
|
|
69662
70177
|
}
|
|
69663
70178
|
if (!workspace) return null;
|
|
@@ -69746,7 +70261,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
|
|
|
69746
70261
|
"button",
|
|
69747
70262
|
{
|
|
69748
70263
|
onClick: async () => {
|
|
69749
|
-
const result = await electron$
|
|
70264
|
+
const result = await electron$b.openWorkspace();
|
|
69750
70265
|
if (result) await applyWorkspace(result.workspace, result.workspacePath);
|
|
69751
70266
|
},
|
|
69752
70267
|
className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
|
|
@@ -69758,7 +70273,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
|
|
|
69758
70273
|
"button",
|
|
69759
70274
|
{
|
|
69760
70275
|
onClick: async () => {
|
|
69761
|
-
const result = await electron$
|
|
70276
|
+
const result = await electron$b.newWorkspace();
|
|
69762
70277
|
if (result) await applyWorkspace(result.workspace, result.workspacePath);
|
|
69763
70278
|
},
|
|
69764
70279
|
className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
|
|
@@ -69770,7 +70285,7 @@ function Toolbar({ onOpenDocs: _onOpenDocs }) {
|
|
|
69770
70285
|
"button",
|
|
69771
70286
|
{
|
|
69772
70287
|
onClick: async () => {
|
|
69773
|
-
await electron$
|
|
70288
|
+
await electron$b.closeWorkspace();
|
|
69774
70289
|
closeWorkspace();
|
|
69775
70290
|
},
|
|
69776
70291
|
className: "px-2.5 py-1 text-xs bg-surface-800 hover:bg-surface-700 rounded transition-colors",
|
|
@@ -70221,7 +70736,7 @@ api-tests:
|
|
|
70221
70736
|
function EmptyState({ message }) {
|
|
70222
70737
|
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center h-24 text-surface-400 text-xs", children: message });
|
|
70223
70738
|
}
|
|
70224
|
-
const { electron: electron$
|
|
70739
|
+
const { electron: electron$a } = window;
|
|
70225
70740
|
const HOOK_BADGE = {
|
|
70226
70741
|
beforeAll: { label: "BEFORE ALL", cls: "bg-violet-700 text-white" },
|
|
70227
70742
|
before: { label: "BEFORE", cls: "bg-violet-600 text-white" },
|
|
@@ -70341,13 +70856,13 @@ function RunnerModal() {
|
|
|
70341
70856
|
setSummary(null);
|
|
70342
70857
|
setRunnerRunning(true);
|
|
70343
70858
|
progressIdxRef.current = 0;
|
|
70344
|
-
electron$
|
|
70859
|
+
electron$a.onRunProgress((result) => {
|
|
70345
70860
|
const idx = progressIdxRef.current;
|
|
70346
70861
|
patchRunnerResult(idx, result);
|
|
70347
70862
|
if (result.status !== "running") progressIdxRef.current++;
|
|
70348
70863
|
});
|
|
70349
70864
|
try {
|
|
70350
|
-
const s = await electron$
|
|
70865
|
+
const s = await electron$a.runCollection({
|
|
70351
70866
|
items: items2,
|
|
70352
70867
|
environment: env,
|
|
70353
70868
|
globals,
|
|
@@ -70358,7 +70873,7 @@ function RunnerModal() {
|
|
|
70358
70873
|
});
|
|
70359
70874
|
setSummary(s);
|
|
70360
70875
|
} finally {
|
|
70361
|
-
electron$
|
|
70876
|
+
electron$a.offRunProgress();
|
|
70362
70877
|
setRunnerRunning(false);
|
|
70363
70878
|
}
|
|
70364
70879
|
}, [collectionId, folderId, filterTags, selectedEnvId, environments, globals, colEntry, requestDelay, workspaceSettings, setRunnerResults, patchRunnerResult, setRunnerRunning]);
|
|
@@ -70584,7 +71099,7 @@ function RunnerModal() {
|
|
|
70584
71099
|
};
|
|
70585
71100
|
const content2 = exportFormat === "junit" ? buildJUnitReport(runnerResults, summary, meta2) : exportFormat === "html" ? buildHtmlReport(runnerResults, summary, meta2) : buildJsonReport(runnerResults, summary, meta2);
|
|
70586
71101
|
const ext = exportFormat === "junit" ? "xml" : exportFormat === "html" ? "html" : "json";
|
|
70587
|
-
electron$
|
|
71102
|
+
electron$a.saveResults(content2, `spector-results.${ext}`);
|
|
70588
71103
|
},
|
|
70589
71104
|
className: "px-2.5 py-0.5 bg-surface-800 hover:bg-surface-700 rounded transition-colors text-[11px] whitespace-nowrap",
|
|
70590
71105
|
children: "Export results"
|
|
@@ -70596,11 +71111,843 @@ function RunnerModal() {
|
|
|
70596
71111
|
}
|
|
70597
71112
|
);
|
|
70598
71113
|
}
|
|
71114
|
+
const HTTP_METHODS$2 = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
71115
|
+
function normalizePath(url) {
|
|
71116
|
+
let u = (url || "").trim();
|
|
71117
|
+
u = u.split("#")[0].split("?")[0];
|
|
71118
|
+
u = u.replace(/\{\{[^}]*\}\}/g, "");
|
|
71119
|
+
u = u.replace(/^[a-z0-9+.-]+:\/\/[^/]*/i, "");
|
|
71120
|
+
u = u.replace(/\/{2,}/g, "/");
|
|
71121
|
+
if (!u.startsWith("/")) u = "/" + u;
|
|
71122
|
+
u = u.replace(/\/+$/, "");
|
|
71123
|
+
return u === "" ? "/" : u;
|
|
71124
|
+
}
|
|
71125
|
+
function segments(path) {
|
|
71126
|
+
return path.split("/").filter(Boolean);
|
|
71127
|
+
}
|
|
71128
|
+
function isParam(seg) {
|
|
71129
|
+
return seg.startsWith("{") && seg.endsWith("}");
|
|
71130
|
+
}
|
|
71131
|
+
function pathMatches(requestUrl, template) {
|
|
71132
|
+
const reqSegs = segments(normalizePath(requestUrl));
|
|
71133
|
+
const tplSegs = segments(template);
|
|
71134
|
+
if (reqSegs.length < tplSegs.length) return false;
|
|
71135
|
+
const tail = reqSegs.slice(reqSegs.length - tplSegs.length);
|
|
71136
|
+
return tplSegs.every((seg, i) => isParam(seg) ? tail[i] !== void 0 && tail[i] !== "" : tail[i] === seg);
|
|
71137
|
+
}
|
|
71138
|
+
function resolveRef$2(spec, ref2) {
|
|
71139
|
+
const parts = ref2.replace(/^#\//, "").split("/");
|
|
71140
|
+
return parts.reduce((o, k) => o?.[decodeURIComponent(k.replace(/~1/g, "/").replace(/~0/g, "~"))], spec);
|
|
71141
|
+
}
|
|
71142
|
+
function deref$2(spec, node, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
71143
|
+
if (!node || typeof node !== "object" || depth > 8) return node;
|
|
71144
|
+
if (Array.isArray(node)) return node.map((n) => deref$2(spec, n, depth + 1, seen));
|
|
71145
|
+
if ("$ref" in node) {
|
|
71146
|
+
const target = resolveRef$2(spec, node.$ref);
|
|
71147
|
+
if (!target || seen.has(target)) return {};
|
|
71148
|
+
return deref$2(spec, target, depth + 1, /* @__PURE__ */ new Set([...seen, target]));
|
|
71149
|
+
}
|
|
71150
|
+
const out = {};
|
|
71151
|
+
for (const [k, v] of Object.entries(node)) out[k] = deref$2(spec, v, depth + 1, seen);
|
|
71152
|
+
return out;
|
|
71153
|
+
}
|
|
71154
|
+
function flattenSchemaPaths(schema, prefix2 = "", depth = 0) {
|
|
71155
|
+
if (!schema || typeof schema !== "object" || depth > 8) return [];
|
|
71156
|
+
const type2 = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
71157
|
+
const out = [];
|
|
71158
|
+
if (type2 === "object" || schema.properties) {
|
|
71159
|
+
for (const [name2, sub] of Object.entries(schema.properties ?? {})) {
|
|
71160
|
+
const path = prefix2 ? `${prefix2}.${name2}` : name2;
|
|
71161
|
+
out.push(path);
|
|
71162
|
+
out.push(...flattenSchemaPaths(sub, path, depth + 1));
|
|
71163
|
+
}
|
|
71164
|
+
} else if (type2 === "array" && schema.items) {
|
|
71165
|
+
out.push(...flattenSchemaPaths(schema.items, `${prefix2}[]`, depth + 1));
|
|
71166
|
+
}
|
|
71167
|
+
return out;
|
|
71168
|
+
}
|
|
71169
|
+
function flattenValuePaths(value, prefix2 = "", depth = 0) {
|
|
71170
|
+
if (value == null || typeof value !== "object" || depth > 8) return [];
|
|
71171
|
+
const out = [];
|
|
71172
|
+
if (Array.isArray(value)) {
|
|
71173
|
+
for (const el of value) out.push(...flattenValuePaths(el, `${prefix2}[]`, depth + 1));
|
|
71174
|
+
} else {
|
|
71175
|
+
for (const [k, v] of Object.entries(value)) {
|
|
71176
|
+
const path = prefix2 ? `${prefix2}.${k}` : k;
|
|
71177
|
+
out.push(path);
|
|
71178
|
+
out.push(...flattenValuePaths(v, path, depth + 1));
|
|
71179
|
+
}
|
|
71180
|
+
}
|
|
71181
|
+
return [...new Set(out)];
|
|
71182
|
+
}
|
|
71183
|
+
function successResponseSchema$1(spec, responses) {
|
|
71184
|
+
const code2 = Object.keys(responses ?? {}).filter((c) => /^2\d\d$/.test(c)).sort()[0];
|
|
71185
|
+
if (!code2) return void 0;
|
|
71186
|
+
const schema = responses[code2]?.content?.["application/json"]?.schema ?? responses[code2]?.content?.["application/json;charset=utf-8"]?.schema;
|
|
71187
|
+
return schema ? deref$2(spec, schema) : void 0;
|
|
71188
|
+
}
|
|
71189
|
+
function enumerateOperations(spec) {
|
|
71190
|
+
const doc2 = spec ?? {};
|
|
71191
|
+
const ops2 = [];
|
|
71192
|
+
for (const [path, item] of Object.entries(doc2.paths ?? {})) {
|
|
71193
|
+
if (!item || typeof item !== "object") continue;
|
|
71194
|
+
for (const [method, op] of Object.entries(item)) {
|
|
71195
|
+
if (!HTTP_METHODS$2.includes(method.toLowerCase())) continue;
|
|
71196
|
+
if (!op || typeof op !== "object") continue;
|
|
71197
|
+
const declaredStatuses = Object.keys(op.responses ?? {}).filter((c) => /^\d{3}$/.test(c));
|
|
71198
|
+
ops2.push({ method: method.toUpperCase(), path, operationId: op.operationId, declaredStatuses, responses: op.responses });
|
|
71199
|
+
}
|
|
71200
|
+
}
|
|
71201
|
+
return ops2;
|
|
71202
|
+
}
|
|
71203
|
+
function round(n) {
|
|
71204
|
+
return Math.round(n * 10) / 10;
|
|
71205
|
+
}
|
|
71206
|
+
function computeCoverage(spec, requests, observations = []) {
|
|
71207
|
+
const doc2 = spec ?? {};
|
|
71208
|
+
const operations2 = enumerateOperations(spec).map((op) => {
|
|
71209
|
+
const mapped = requests.filter((r) => r.method.toUpperCase() === op.method && pathMatches(r.url, op.path));
|
|
71210
|
+
const obs = observations.filter((o) => o.method.toUpperCase() === op.method && pathMatches(o.url, op.path));
|
|
71211
|
+
const asserted = mapped.map((r) => r.expectedStatus).filter((s) => typeof s === "number");
|
|
71212
|
+
const seen = obs.map((o) => o.status);
|
|
71213
|
+
const covered = /* @__PURE__ */ new Set([...asserted, ...seen]);
|
|
71214
|
+
const coveredStatuses2 = op.declaredStatuses.filter((code2) => covered.has(Number(code2)));
|
|
71215
|
+
const hasNegativeTest = [...covered].some((s) => s >= 400);
|
|
71216
|
+
const declaredProperties2 = flattenSchemaPaths(successResponseSchema$1(doc2, op.responses ?? {}));
|
|
71217
|
+
const observedPaths = new Set(obs.flatMap((o) => o.responsePaths ?? []));
|
|
71218
|
+
const coveredProperties2 = declaredProperties2.filter((p2) => observedPaths.has(p2));
|
|
71219
|
+
return {
|
|
71220
|
+
method: op.method,
|
|
71221
|
+
path: op.path,
|
|
71222
|
+
operationId: op.operationId,
|
|
71223
|
+
tested: mapped.length > 0 || obs.length > 0,
|
|
71224
|
+
requests: mapped.map((r) => r.name),
|
|
71225
|
+
declaredStatuses: op.declaredStatuses,
|
|
71226
|
+
coveredStatuses: coveredStatuses2,
|
|
71227
|
+
hasNegativeTest,
|
|
71228
|
+
declaredProperties: declaredProperties2,
|
|
71229
|
+
coveredProperties: coveredProperties2
|
|
71230
|
+
};
|
|
71231
|
+
});
|
|
71232
|
+
const tested = operations2.filter((o) => o.tested).length;
|
|
71233
|
+
const declaredStatuses = operations2.reduce((n, o) => n + o.declaredStatuses.length, 0);
|
|
71234
|
+
const coveredStatuses = operations2.reduce((n, o) => n + o.coveredStatuses.length, 0);
|
|
71235
|
+
const declaredProperties = operations2.reduce((n, o) => n + o.declaredProperties.length, 0);
|
|
71236
|
+
const coveredProperties = operations2.reduce((n, o) => n + o.coveredProperties.length, 0);
|
|
71237
|
+
const withoutNegativeTest = operations2.filter((o) => o.tested && !o.hasNegativeTest).length;
|
|
71238
|
+
return {
|
|
71239
|
+
spec: { title: doc2.info?.title, version: doc2.info?.version },
|
|
71240
|
+
totals: {
|
|
71241
|
+
operations: operations2.length,
|
|
71242
|
+
tested,
|
|
71243
|
+
untested: operations2.length - tested,
|
|
71244
|
+
operationPct: operations2.length ? round(tested / operations2.length * 100) : 0,
|
|
71245
|
+
declaredStatuses,
|
|
71246
|
+
coveredStatuses,
|
|
71247
|
+
statusPct: declaredStatuses ? round(coveredStatuses / declaredStatuses * 100) : 0,
|
|
71248
|
+
withoutNegativeTest,
|
|
71249
|
+
declaredProperties,
|
|
71250
|
+
coveredProperties,
|
|
71251
|
+
propertyPct: declaredProperties ? round(coveredProperties / declaredProperties * 100) : 0
|
|
71252
|
+
},
|
|
71253
|
+
operations: operations2
|
|
71254
|
+
};
|
|
71255
|
+
}
|
|
71256
|
+
const HTTP_METHODS$1 = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
71257
|
+
function resolveRef$1(spec, ref2) {
|
|
71258
|
+
const parts = ref2.replace(/^#\//, "").split("/");
|
|
71259
|
+
return parts.reduce((o, k) => o?.[decodeURIComponent(k.replace(/~1/g, "/").replace(/~0/g, "~"))], spec);
|
|
71260
|
+
}
|
|
71261
|
+
function deref$1(spec, node, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
71262
|
+
if (!node || typeof node !== "object" || depth > 8) return node;
|
|
71263
|
+
if (Array.isArray(node)) return node.map((n) => deref$1(spec, n, depth + 1, seen));
|
|
71264
|
+
if ("$ref" in node) {
|
|
71265
|
+
const target = resolveRef$1(spec, node.$ref);
|
|
71266
|
+
if (!target || seen.has(target)) return {};
|
|
71267
|
+
return deref$1(spec, target, depth + 1, /* @__PURE__ */ new Set([...seen, target]));
|
|
71268
|
+
}
|
|
71269
|
+
const out = {};
|
|
71270
|
+
for (const [k, v] of Object.entries(node)) out[k] = deref$1(spec, v, depth + 1, seen);
|
|
71271
|
+
return out;
|
|
71272
|
+
}
|
|
71273
|
+
function jsonSchemaFor(spec, responses, code2) {
|
|
71274
|
+
const resp = responses?.[code2];
|
|
71275
|
+
const schema = resp?.content?.["application/json"]?.schema ?? resp?.content?.["application/json;charset=utf-8"]?.schema;
|
|
71276
|
+
return schema ? deref$1(spec, schema) : void 0;
|
|
71277
|
+
}
|
|
71278
|
+
function sampleValue(schema) {
|
|
71279
|
+
if (!schema || typeof schema !== "object") return "string";
|
|
71280
|
+
if (schema.example !== void 0) return schema.example;
|
|
71281
|
+
if (schema.default !== void 0) return schema.default;
|
|
71282
|
+
if (Array.isArray(schema.enum) && schema.enum.length) return schema.enum[0];
|
|
71283
|
+
const type2 = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
71284
|
+
switch (type2) {
|
|
71285
|
+
case "integer":
|
|
71286
|
+
case "number": {
|
|
71287
|
+
const min = schema.minimum ?? (schema.exclusiveMinimum != null ? schema.exclusiveMinimum + 1 : void 0);
|
|
71288
|
+
const max = schema.maximum ?? (schema.exclusiveMaximum != null ? schema.exclusiveMaximum - 1 : void 0);
|
|
71289
|
+
if (min != null) return min;
|
|
71290
|
+
if (max != null) return max;
|
|
71291
|
+
return type2 === "integer" ? 1 : 1.5;
|
|
71292
|
+
}
|
|
71293
|
+
case "boolean":
|
|
71294
|
+
return true;
|
|
71295
|
+
case "array":
|
|
71296
|
+
return [sampleValue(schema.items ?? {})];
|
|
71297
|
+
case "object": {
|
|
71298
|
+
const out = {};
|
|
71299
|
+
const props = schema.properties ?? {};
|
|
71300
|
+
const required2 = schema.required ?? [];
|
|
71301
|
+
for (const [name2, propSchema] of Object.entries(props)) out[name2] = sampleValue(propSchema);
|
|
71302
|
+
for (const name2 of required2) if (!(name2 in out)) out[name2] = "string";
|
|
71303
|
+
return out;
|
|
71304
|
+
}
|
|
71305
|
+
case "string":
|
|
71306
|
+
default:
|
|
71307
|
+
return sampleString(schema);
|
|
71308
|
+
}
|
|
71309
|
+
}
|
|
71310
|
+
function sampleString(schema) {
|
|
71311
|
+
switch (schema.format) {
|
|
71312
|
+
case "email":
|
|
71313
|
+
return "user@example.com";
|
|
71314
|
+
case "uuid":
|
|
71315
|
+
return "00000000-0000-0000-0000-000000000000";
|
|
71316
|
+
case "date":
|
|
71317
|
+
return "2020-01-01";
|
|
71318
|
+
case "date-time":
|
|
71319
|
+
return "2020-01-01T00:00:00Z";
|
|
71320
|
+
case "uri":
|
|
71321
|
+
case "url":
|
|
71322
|
+
return "https://example.com";
|
|
71323
|
+
case "hostname":
|
|
71324
|
+
return "example.com";
|
|
71325
|
+
case "ipv4":
|
|
71326
|
+
return "127.0.0.1";
|
|
71327
|
+
default: {
|
|
71328
|
+
const min = schema.minLength ?? 0;
|
|
71329
|
+
let s = "string";
|
|
71330
|
+
if (min > s.length) s = s.padEnd(min, "x");
|
|
71331
|
+
if (schema.maxLength != null && s.length > schema.maxLength) s = s.slice(0, schema.maxLength);
|
|
71332
|
+
return s;
|
|
71333
|
+
}
|
|
71334
|
+
}
|
|
71335
|
+
}
|
|
71336
|
+
function collectParams(spec, pathItem, op) {
|
|
71337
|
+
const raw = [...pathItem?.parameters ?? [], ...op?.parameters ?? []].map((p2) => deref$1(spec, p2));
|
|
71338
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
71339
|
+
for (const p2 of raw) if (p2?.name && p2?.in) byKey.set(`${p2.in}:${p2.name}`, p2);
|
|
71340
|
+
return [...byKey.values()];
|
|
71341
|
+
}
|
|
71342
|
+
function successCode(responses) {
|
|
71343
|
+
const codes = Object.keys(responses ?? {}).filter((c) => /^2\d\d$/.test(c)).map(Number).sort((a, b) => a - b);
|
|
71344
|
+
return codes[0] ?? 200;
|
|
71345
|
+
}
|
|
71346
|
+
function negativeCode(responses) {
|
|
71347
|
+
const codes = Object.keys(responses ?? {}).filter((c) => /^4\d\d$/.test(c)).map(Number).sort((a, b) => a - b);
|
|
71348
|
+
return codes[0] ?? 400;
|
|
71349
|
+
}
|
|
71350
|
+
function baseTest(method, path, op, params) {
|
|
71351
|
+
const pathParams = {};
|
|
71352
|
+
for (const p2 of params.filter((p22) => p22.in === "path")) pathParams[p2.name] = sampleValue(p2.schema ?? {});
|
|
71353
|
+
const query = params.filter((p2) => p2.in === "query" && p2.required).map((p2) => ({ key: p2.name, value: String(sampleValue(p2.schema ?? {})) }));
|
|
71354
|
+
const headers = params.filter((p2) => p2.in === "header" && p2.required).map((p2) => ({ key: p2.name, value: String(sampleValue(p2.schema ?? {})) }));
|
|
71355
|
+
return { operationId: op?.operationId, method, path, pathParams, query, headers };
|
|
71356
|
+
}
|
|
71357
|
+
function requestBodySchema(spec, op) {
|
|
71358
|
+
const rb = deref$1(spec, op?.requestBody);
|
|
71359
|
+
const schema = rb?.content?.["application/json"]?.schema;
|
|
71360
|
+
return schema;
|
|
71361
|
+
}
|
|
71362
|
+
function generateForOperation(spec, method, path, pathItem, op, opts) {
|
|
71363
|
+
const tests = [];
|
|
71364
|
+
const params = collectParams(spec, pathItem, op);
|
|
71365
|
+
const responses = op?.responses ?? {};
|
|
71366
|
+
const okCode = successCode(responses);
|
|
71367
|
+
const badCode = negativeCode(responses);
|
|
71368
|
+
const bodySchema = requestBodySchema(spec, op);
|
|
71369
|
+
const validBody = bodySchema ? sampleValue(bodySchema) : void 0;
|
|
71370
|
+
const base2 = baseTest(method, path, op, params);
|
|
71371
|
+
const label = `${method} ${path}`;
|
|
71372
|
+
tests.push({
|
|
71373
|
+
...base2,
|
|
71374
|
+
name: `${label} - happy path`,
|
|
71375
|
+
category: "happy",
|
|
71376
|
+
body: validBody !== void 0 ? JSON.stringify(validBody, null, 2) : void 0,
|
|
71377
|
+
expectedStatus: okCode,
|
|
71378
|
+
responseSchema: jsonSchemaFor(spec, responses, String(okCode)) ? JSON.stringify(jsonSchemaFor(spec, responses, String(okCode)), null, 2) : void 0
|
|
71379
|
+
});
|
|
71380
|
+
const props = bodySchema?.properties ?? {};
|
|
71381
|
+
const required2 = bodySchema?.required ?? [];
|
|
71382
|
+
if (opts.includeNegative && validBody && typeof validBody === "object") {
|
|
71383
|
+
let n = 0;
|
|
71384
|
+
for (const field of required2) {
|
|
71385
|
+
if (n >= opts.maxNegativePerOp) break;
|
|
71386
|
+
const mutated = { ...validBody };
|
|
71387
|
+
delete mutated[field];
|
|
71388
|
+
tests.push({ ...base2, name: `${label} - missing ${field}`, category: "negative", body: JSON.stringify(mutated, null, 2), expectedStatus: badCode });
|
|
71389
|
+
n++;
|
|
71390
|
+
}
|
|
71391
|
+
for (const [field, ps] of Object.entries(props)) {
|
|
71392
|
+
if (n >= opts.maxNegativePerOp) break;
|
|
71393
|
+
const t2 = Array.isArray(ps.type) ? ps.type[0] : ps.type;
|
|
71394
|
+
if (t2 !== "string" && t2 !== "integer" && t2 !== "number" && t2 !== "boolean") continue;
|
|
71395
|
+
const wrong = t2 === "string" ? 12345 : "not-a-valid-value";
|
|
71396
|
+
tests.push({ ...base2, name: `${label} - ${field} wrong type`, category: "negative", body: JSON.stringify({ ...validBody, [field]: wrong }, null, 2), expectedStatus: badCode });
|
|
71397
|
+
n++;
|
|
71398
|
+
}
|
|
71399
|
+
}
|
|
71400
|
+
if (opts.includeBoundary && validBody && typeof validBody === "object") {
|
|
71401
|
+
let n = 0;
|
|
71402
|
+
for (const [field, ps] of Object.entries(props)) {
|
|
71403
|
+
if (n >= opts.maxBoundaryPerOp) break;
|
|
71404
|
+
const t2 = Array.isArray(ps.type) ? ps.type[0] : ps.type;
|
|
71405
|
+
if ((t2 === "integer" || t2 === "number") && ps.minimum != null) {
|
|
71406
|
+
tests.push({ ...base2, name: `${label} - ${field} below minimum`, category: "boundary", body: JSON.stringify({ ...validBody, [field]: ps.minimum - 1 }, null, 2), expectedStatus: badCode });
|
|
71407
|
+
n++;
|
|
71408
|
+
} else if ((t2 === "integer" || t2 === "number") && ps.maximum != null) {
|
|
71409
|
+
tests.push({ ...base2, name: `${label} - ${field} above maximum`, category: "boundary", body: JSON.stringify({ ...validBody, [field]: ps.maximum + 1 }, null, 2), expectedStatus: badCode });
|
|
71410
|
+
n++;
|
|
71411
|
+
} else if (t2 === "string" && ps.maxLength != null) {
|
|
71412
|
+
tests.push({ ...base2, name: `${label} - ${field} too long`, category: "boundary", body: JSON.stringify({ ...validBody, [field]: "x".repeat(ps.maxLength + 1) }, null, 2), expectedStatus: badCode });
|
|
71413
|
+
n++;
|
|
71414
|
+
}
|
|
71415
|
+
}
|
|
71416
|
+
}
|
|
71417
|
+
return tests;
|
|
71418
|
+
}
|
|
71419
|
+
function generateTests(spec, options = {}) {
|
|
71420
|
+
const opts = {
|
|
71421
|
+
only: options.only ?? /* @__PURE__ */ new Set(),
|
|
71422
|
+
includeNegative: options.includeNegative ?? true,
|
|
71423
|
+
includeBoundary: options.includeBoundary ?? true,
|
|
71424
|
+
maxNegativePerOp: options.maxNegativePerOp ?? 4,
|
|
71425
|
+
maxBoundaryPerOp: options.maxBoundaryPerOp ?? 4
|
|
71426
|
+
};
|
|
71427
|
+
const doc2 = spec ?? {};
|
|
71428
|
+
const out = [];
|
|
71429
|
+
for (const [path, item] of Object.entries(doc2.paths ?? {})) {
|
|
71430
|
+
if (!item || typeof item !== "object") continue;
|
|
71431
|
+
for (const [method, op] of Object.entries(item)) {
|
|
71432
|
+
if (!HTTP_METHODS$1.includes(method.toLowerCase())) continue;
|
|
71433
|
+
if (!op || typeof op !== "object") continue;
|
|
71434
|
+
if (opts.only.size && !opts.only.has(`${method.toUpperCase()} ${path}`)) continue;
|
|
71435
|
+
out.push(...generateForOperation(doc2, method.toUpperCase(), path, item, op, opts));
|
|
71436
|
+
}
|
|
71437
|
+
}
|
|
71438
|
+
return out;
|
|
71439
|
+
}
|
|
71440
|
+
function testUrl(test, baseVar = "{{baseUrl}}") {
|
|
71441
|
+
let path = test.path;
|
|
71442
|
+
for (const [name2, value] of Object.entries(test.pathParams)) {
|
|
71443
|
+
path = path.replace(new RegExp(`\\{${name2}\\}`, "g"), encodeURIComponent(String(value)));
|
|
71444
|
+
}
|
|
71445
|
+
const qs = test.query.filter((q) => q.key).map((q) => `${encodeURIComponent(q.key)}=${encodeURIComponent(q.value)}`).join("&");
|
|
71446
|
+
return `${baseVar}${path}${qs ? `?${qs}` : ""}`;
|
|
71447
|
+
}
|
|
71448
|
+
function toApiRequest(test, id2) {
|
|
71449
|
+
const headers = test.headers.map((h) => ({ key: h.key, value: h.value, enabled: true }));
|
|
71450
|
+
return {
|
|
71451
|
+
id: id2,
|
|
71452
|
+
name: test.name,
|
|
71453
|
+
method: test.method,
|
|
71454
|
+
url: testUrl(test),
|
|
71455
|
+
headers,
|
|
71456
|
+
params: [],
|
|
71457
|
+
auth: { type: "none" },
|
|
71458
|
+
body: test.body !== void 0 ? { mode: "json", json: test.body } : { mode: "none" },
|
|
71459
|
+
contract: {
|
|
71460
|
+
statusCode: test.expectedStatus,
|
|
71461
|
+
...test.responseSchema ? { bodySchema: test.responseSchema } : {}
|
|
71462
|
+
},
|
|
71463
|
+
meta: { tags: [test.category] }
|
|
71464
|
+
};
|
|
71465
|
+
}
|
|
71466
|
+
const { electron: electron$9 } = window;
|
|
71467
|
+
function Bar({ pct }) {
|
|
71468
|
+
const color = pct >= 80 ? "bg-emerald-500" : pct >= 50 ? "bg-amber-500" : "bg-red-500";
|
|
71469
|
+
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}%` } }) });
|
|
71470
|
+
}
|
|
71471
|
+
function Stat({ value, label }) {
|
|
71472
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
|
|
71473
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-2xl font-bold text-white", children: value }),
|
|
71474
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[11px] text-surface-400", children: label })
|
|
71475
|
+
] });
|
|
71476
|
+
}
|
|
71477
|
+
function CoverageModal() {
|
|
71478
|
+
const open = useStore((s) => s.coverageOpen);
|
|
71479
|
+
const setOpen = useStore((s) => s.setCoverageOpen);
|
|
71480
|
+
const collections = useStore((s) => s.collections);
|
|
71481
|
+
const history2 = useStore((s) => s.history);
|
|
71482
|
+
const savedSpec = useStore((s) => s.workspace?.settings?.coverageSpec);
|
|
71483
|
+
const setCoverageSpec = useStore((s) => s.setCoverageSpec);
|
|
71484
|
+
const addCollectionObject = useStore((s) => s.addCollectionObject);
|
|
71485
|
+
const [source, setSource] = reactExports.useState(savedSpec ?? "");
|
|
71486
|
+
const [pasted, setPasted] = reactExports.useState("");
|
|
71487
|
+
const [report, setReport] = reactExports.useState(null);
|
|
71488
|
+
const [spec, setSpec] = reactExports.useState(null);
|
|
71489
|
+
const [error2, setError] = reactExports.useState(null);
|
|
71490
|
+
const [busy, setBusy] = reactExports.useState(false);
|
|
71491
|
+
const [onlyGaps, setOnlyGaps] = reactExports.useState(false);
|
|
71492
|
+
const [generated, setGenerated] = reactExports.useState(null);
|
|
71493
|
+
const requests = reactExports.useMemo(() => {
|
|
71494
|
+
const out = [];
|
|
71495
|
+
for (const entry of Object.values(collections)) {
|
|
71496
|
+
for (const req of Object.values(entry.data.requests)) {
|
|
71497
|
+
if (req.disabled) continue;
|
|
71498
|
+
out.push({ name: `${entry.data.name} / ${req.name}`, method: req.method, url: req.url, expectedStatus: req.contract?.statusCode });
|
|
71499
|
+
}
|
|
71500
|
+
}
|
|
71501
|
+
return out;
|
|
71502
|
+
}, [collections]);
|
|
71503
|
+
const observations = reactExports.useMemo(() => {
|
|
71504
|
+
return history2.map((h) => {
|
|
71505
|
+
let responsePaths;
|
|
71506
|
+
try {
|
|
71507
|
+
if (h.response?.body) responsePaths = flattenValuePaths(JSON.parse(h.response.body));
|
|
71508
|
+
} catch {
|
|
71509
|
+
}
|
|
71510
|
+
return { method: h.request.method, url: h.resolvedUrl, status: h.response?.status ?? 0, responsePaths };
|
|
71511
|
+
});
|
|
71512
|
+
}, [history2]);
|
|
71513
|
+
async function compute() {
|
|
71514
|
+
setBusy(true);
|
|
71515
|
+
setError(null);
|
|
71516
|
+
try {
|
|
71517
|
+
const isUrl = /^https?:\/\//i.test(source.trim());
|
|
71518
|
+
const spec2 = await electron$9.coverageLoadSpec(
|
|
71519
|
+
pasted.trim() ? { text: pasted } : isUrl ? { url: source.trim() } : { path: source.trim() }
|
|
71520
|
+
);
|
|
71521
|
+
if (source.trim() && !pasted.trim()) setCoverageSpec(source.trim());
|
|
71522
|
+
setSpec(spec2);
|
|
71523
|
+
setReport(computeCoverage(spec2, requests, observations));
|
|
71524
|
+
setGenerated(null);
|
|
71525
|
+
} catch (err) {
|
|
71526
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
71527
|
+
setReport(null);
|
|
71528
|
+
} finally {
|
|
71529
|
+
setBusy(false);
|
|
71530
|
+
}
|
|
71531
|
+
}
|
|
71532
|
+
function generateForGaps() {
|
|
71533
|
+
if (!spec || !report) return;
|
|
71534
|
+
const only = new Set(report.operations.filter((o) => !o.tested).map((o) => `${o.method} ${o.path}`));
|
|
71535
|
+
if (only.size === 0) return;
|
|
71536
|
+
const tests = generateTests(spec, { only });
|
|
71537
|
+
const requestIds = [];
|
|
71538
|
+
const requestMap = {};
|
|
71539
|
+
for (const t22 of tests) {
|
|
71540
|
+
const id2 = crypto.randomUUID();
|
|
71541
|
+
requestMap[id2] = toApiRequest(t22, id2);
|
|
71542
|
+
requestIds.push(id2);
|
|
71543
|
+
}
|
|
71544
|
+
const name2 = `${report.spec.title ?? "API"} tests (generated)`;
|
|
71545
|
+
const collection = {
|
|
71546
|
+
version: "1.0",
|
|
71547
|
+
id: crypto.randomUUID(),
|
|
71548
|
+
name: name2,
|
|
71549
|
+
description: "Generated for untested operations from the OpenAPI spec.",
|
|
71550
|
+
rootFolder: { id: crypto.randomUUID(), name: "root", description: "", folders: [], requestIds },
|
|
71551
|
+
requests: requestMap
|
|
71552
|
+
};
|
|
71553
|
+
addCollectionObject(collection);
|
|
71554
|
+
setGenerated(`Added ${tests.length} tests for ${only.size} untested operations as "${collection.name}".`);
|
|
71555
|
+
}
|
|
71556
|
+
if (!open) return null;
|
|
71557
|
+
const t2 = report?.totals;
|
|
71558
|
+
const shownOps = report?.operations.filter((o) => !onlyGaps || !o.tested || !o.hasNegativeTest) ?? [];
|
|
71559
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
71560
|
+
Modal,
|
|
71561
|
+
{
|
|
71562
|
+
onClose: () => setOpen(false),
|
|
71563
|
+
overlayClassName: "bg-black/50 z-50 flex items-start justify-center pt-16",
|
|
71564
|
+
panelClassName: "bg-surface-900 border border-surface-800 rounded-lg shadow-2xl flex flex-col w-[760px] max-h-[82vh]",
|
|
71565
|
+
title: "API test coverage",
|
|
71566
|
+
subtitle: "How much of an OpenAPI contract this workspace tests",
|
|
71567
|
+
children: [
|
|
71568
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 border-b border-surface-800 flex flex-col gap-2 flex-shrink-0", children: [
|
|
71569
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] text-surface-400", children: "OpenAPI spec (file path or URL)" }),
|
|
71570
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
|
|
71571
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
71572
|
+
"input",
|
|
71573
|
+
{
|
|
71574
|
+
value: source,
|
|
71575
|
+
onChange: (e) => setSource(e.target.value),
|
|
71576
|
+
placeholder: "./openapi.yaml or https://api.example.com/openapi.json",
|
|
71577
|
+
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"
|
|
71578
|
+
}
|
|
71579
|
+
),
|
|
71580
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
71581
|
+
"button",
|
|
71582
|
+
{
|
|
71583
|
+
onClick: compute,
|
|
71584
|
+
disabled: busy || !source.trim() && !pasted.trim(),
|
|
71585
|
+
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",
|
|
71586
|
+
children: busy ? "…" : "Measure"
|
|
71587
|
+
}
|
|
71588
|
+
)
|
|
71589
|
+
] }),
|
|
71590
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("details", { className: "text-[11px] text-surface-500", children: [
|
|
71591
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("summary", { className: "cursor-pointer hover:text-surface-300", children: "or paste spec" }),
|
|
71592
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
71593
|
+
"textarea",
|
|
71594
|
+
{
|
|
71595
|
+
value: pasted,
|
|
71596
|
+
onChange: (e) => setPasted(e.target.value),
|
|
71597
|
+
rows: 4,
|
|
71598
|
+
placeholder: "Paste OpenAPI JSON or YAML here",
|
|
71599
|
+
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"
|
|
71600
|
+
}
|
|
71601
|
+
)
|
|
71602
|
+
] }),
|
|
71603
|
+
error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-red-400", children: error2 })
|
|
71604
|
+
] }),
|
|
71605
|
+
/* @__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: [
|
|
71606
|
+
"Point at your OpenAPI spec and choose ",
|
|
71607
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-300", children: "Measure" }),
|
|
71608
|
+
" to see which operations are tested."
|
|
71609
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
71610
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-3", children: [
|
|
71611
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between text-xs mb-1", children: [
|
|
71612
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-300", children: [
|
|
71613
|
+
report.spec.title ?? "API",
|
|
71614
|
+
report.spec.version ? ` v${report.spec.version}` : ""
|
|
71615
|
+
] }),
|
|
71616
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-400", children: [
|
|
71617
|
+
t2.operationPct,
|
|
71618
|
+
"% operations tested"
|
|
71619
|
+
] })
|
|
71620
|
+
] }),
|
|
71621
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Bar, { pct: t2.operationPct })
|
|
71622
|
+
] }),
|
|
71623
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4 mb-4 border-b border-surface-800 pb-3", children: [
|
|
71624
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Stat, { value: `${t2.tested}/${t2.operations}`, label: "operations tested" }),
|
|
71625
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Stat, { value: `${t2.coveredStatuses}/${t2.declaredStatuses}`, label: "response codes covered" }),
|
|
71626
|
+
t2.declaredProperties > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(Stat, { value: `${t2.propertyPct}%`, label: "response shape seen in runs" }),
|
|
71627
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(Stat, { value: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: t2.untested ? "text-amber-400" : "text-emerald-400", children: t2.untested }), label: "never tested" }),
|
|
71628
|
+
/* @__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" })
|
|
71629
|
+
] }),
|
|
71630
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-2 gap-3", children: [
|
|
71631
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-xs text-surface-400", children: [
|
|
71632
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("input", { type: "checkbox", checked: onlyGaps, onChange: (e) => setOnlyGaps(e.target.checked) }),
|
|
71633
|
+
"Show only gaps (untested or missing a negative test)"
|
|
71634
|
+
] }),
|
|
71635
|
+
t2.untested > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
71636
|
+
"button",
|
|
71637
|
+
{
|
|
71638
|
+
onClick: generateForGaps,
|
|
71639
|
+
title: "Generate happy-path, negative, and boundary tests for the untested operations",
|
|
71640
|
+
className: "px-3 py-1 text-xs bg-violet-600 hover:bg-violet-500 rounded font-medium shrink-0",
|
|
71641
|
+
children: [
|
|
71642
|
+
"Generate tests for ",
|
|
71643
|
+
t2.untested,
|
|
71644
|
+
" gap",
|
|
71645
|
+
t2.untested !== 1 ? "s" : ""
|
|
71646
|
+
]
|
|
71647
|
+
}
|
|
71648
|
+
)
|
|
71649
|
+
] }),
|
|
71650
|
+
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: [
|
|
71651
|
+
generated,
|
|
71652
|
+
" Re-measure to see the coverage rise."
|
|
71653
|
+
] }),
|
|
71654
|
+
/* @__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: [
|
|
71655
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: op.tested ? "text-emerald-400" : "text-red-400", children: op.tested ? "✓" : "✗" }),
|
|
71656
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono font-bold text-surface-300 w-14 shrink-0", children: op.method }),
|
|
71657
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `font-mono flex-1 ${op.tested ? "text-white" : "text-surface-500"}`, children: op.path }),
|
|
71658
|
+
op.declaredStatuses.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-surface-500 shrink-0", children: [
|
|
71659
|
+
op.coveredStatuses.length,
|
|
71660
|
+
"/",
|
|
71661
|
+
op.declaredStatuses.length,
|
|
71662
|
+
" codes"
|
|
71663
|
+
] }),
|
|
71664
|
+
op.tested && !op.hasNegativeTest && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-amber-400 shrink-0", children: "no negative" })
|
|
71665
|
+
] }, `${op.method}-${op.path}-${i}`)) })
|
|
71666
|
+
] }) })
|
|
71667
|
+
]
|
|
71668
|
+
}
|
|
71669
|
+
);
|
|
71670
|
+
}
|
|
71671
|
+
const HTTP_METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
71672
|
+
function resolveRef(spec, ref2) {
|
|
71673
|
+
const parts = ref2.replace(/^#\//, "").split("/");
|
|
71674
|
+
return parts.reduce((o, k) => o?.[decodeURIComponent(k.replace(/~1/g, "/").replace(/~0/g, "~"))], spec);
|
|
71675
|
+
}
|
|
71676
|
+
function deref(spec, node, depth = 0, seen = /* @__PURE__ */ new Set()) {
|
|
71677
|
+
if (!node || typeof node !== "object" || depth > 8) return node;
|
|
71678
|
+
if (Array.isArray(node)) return node.map((n) => deref(spec, n, depth + 1, seen));
|
|
71679
|
+
if ("$ref" in node) {
|
|
71680
|
+
const target = resolveRef(spec, node.$ref);
|
|
71681
|
+
if (!target || seen.has(target)) return {};
|
|
71682
|
+
return deref(spec, target, depth + 1, /* @__PURE__ */ new Set([...seen, target]));
|
|
71683
|
+
}
|
|
71684
|
+
const out = {};
|
|
71685
|
+
for (const [k, v] of Object.entries(node)) out[k] = deref(spec, v, depth + 1, seen);
|
|
71686
|
+
return out;
|
|
71687
|
+
}
|
|
71688
|
+
function walk(schema, prefix2, depth, types2, required2) {
|
|
71689
|
+
if (!schema || typeof schema !== "object" || depth > 8) return;
|
|
71690
|
+
const type2 = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
71691
|
+
if (type2 === "object" || schema.properties) {
|
|
71692
|
+
const req = schema.required ?? [];
|
|
71693
|
+
for (const [name2, sub] of Object.entries(schema.properties ?? {})) {
|
|
71694
|
+
const p2 = prefix2 ? `${prefix2}.${name2}` : name2;
|
|
71695
|
+
const subType = (Array.isArray(sub?.type) ? sub.type[0] : sub?.type) ?? (sub?.properties ? "object" : "any");
|
|
71696
|
+
types2.set(p2, subType);
|
|
71697
|
+
if (req.includes(name2)) required2.add(p2);
|
|
71698
|
+
walk(sub, p2, depth + 1, types2, required2);
|
|
71699
|
+
}
|
|
71700
|
+
} else if (type2 === "array" && schema.items) {
|
|
71701
|
+
walk(schema.items, `${prefix2}[]`, depth + 1, types2, required2);
|
|
71702
|
+
}
|
|
71703
|
+
}
|
|
71704
|
+
function schemaInfo(spec, schema) {
|
|
71705
|
+
const types2 = /* @__PURE__ */ new Map();
|
|
71706
|
+
const required2 = /* @__PURE__ */ new Set();
|
|
71707
|
+
if (schema) walk(deref(spec, schema), "", 0, types2, required2);
|
|
71708
|
+
return { types: types2, required: required2 };
|
|
71709
|
+
}
|
|
71710
|
+
function requestSchema(spec, op) {
|
|
71711
|
+
return deref(spec, op?.requestBody)?.content?.["application/json"]?.schema;
|
|
71712
|
+
}
|
|
71713
|
+
function successResponseSchema(spec, op) {
|
|
71714
|
+
const responses = op?.responses ?? {};
|
|
71715
|
+
const code2 = Object.keys(responses).filter((c) => /^2\d\d$/.test(c)).sort()[0];
|
|
71716
|
+
return code2 ? deref(spec, responses[code2])?.content?.["application/json"]?.schema : void 0;
|
|
71717
|
+
}
|
|
71718
|
+
function paramRequired(spec, pathItem, op) {
|
|
71719
|
+
const raw = [...pathItem?.parameters ?? [], ...op?.parameters ?? []].map((p2) => deref(spec, p2));
|
|
71720
|
+
const m = /* @__PURE__ */ new Map();
|
|
71721
|
+
for (const p2 of raw) if (p2?.name && p2?.in) m.set(`${p2.in}:${p2.name}`, !!p2.required);
|
|
71722
|
+
return m;
|
|
71723
|
+
}
|
|
71724
|
+
function operations(spec) {
|
|
71725
|
+
const map = /* @__PURE__ */ new Map();
|
|
71726
|
+
for (const [path, item] of Object.entries(spec?.paths ?? {})) {
|
|
71727
|
+
if (!item || typeof item !== "object") continue;
|
|
71728
|
+
for (const [method, op] of Object.entries(item)) {
|
|
71729
|
+
if (!HTTP_METHODS.includes(method.toLowerCase())) continue;
|
|
71730
|
+
map.set(`${method.toUpperCase()} ${path}`, { method: method.toUpperCase(), path, pathItem: item, op });
|
|
71731
|
+
}
|
|
71732
|
+
}
|
|
71733
|
+
return map;
|
|
71734
|
+
}
|
|
71735
|
+
function diffSpecs(oldSpec, newSpec) {
|
|
71736
|
+
const oldOps = operations(oldSpec);
|
|
71737
|
+
const newOps = operations(newSpec);
|
|
71738
|
+
const changes = [];
|
|
71739
|
+
for (const [key, o] of oldOps) {
|
|
71740
|
+
if (!newOps.has(key)) {
|
|
71741
|
+
changes.push({ kind: "operation-removed", breaking: true, method: o.method, path: o.path, detail: `Operation ${key} was removed` });
|
|
71742
|
+
}
|
|
71743
|
+
}
|
|
71744
|
+
for (const [key, n] of newOps) {
|
|
71745
|
+
if (!oldOps.has(key)) {
|
|
71746
|
+
changes.push({ kind: "operation-added", breaking: false, method: n.method, path: n.path, detail: `Operation ${key} was added` });
|
|
71747
|
+
continue;
|
|
71748
|
+
}
|
|
71749
|
+
const o = oldOps.get(key);
|
|
71750
|
+
const label = `${n.method} ${n.path}`;
|
|
71751
|
+
const oReq = schemaInfo(oldSpec, requestSchema(oldSpec, o.op));
|
|
71752
|
+
const nReq = schemaInfo(newSpec, requestSchema(newSpec, n.op));
|
|
71753
|
+
for (const p2 of nReq.required) {
|
|
71754
|
+
if (!oReq.required.has(p2)) {
|
|
71755
|
+
changes.push({ kind: "request-required-added", breaking: true, method: n.method, path: n.path, detail: `${label}: request field "${p2}" is now required` });
|
|
71756
|
+
}
|
|
71757
|
+
}
|
|
71758
|
+
for (const [p2, t2] of nReq.types) {
|
|
71759
|
+
const ot = oReq.types.get(p2);
|
|
71760
|
+
if (ot && ot !== t2) {
|
|
71761
|
+
changes.push({ kind: "request-type-changed", breaking: true, method: n.method, path: n.path, detail: `${label}: request field "${p2}" type ${ot} -> ${t2}` });
|
|
71762
|
+
}
|
|
71763
|
+
}
|
|
71764
|
+
const oRes = schemaInfo(oldSpec, successResponseSchema(oldSpec, o.op));
|
|
71765
|
+
const nRes = schemaInfo(newSpec, successResponseSchema(newSpec, n.op));
|
|
71766
|
+
for (const [p2, t2] of oRes.types) {
|
|
71767
|
+
if (!nRes.types.has(p2)) {
|
|
71768
|
+
changes.push({ kind: "response-removed", breaking: true, method: n.method, path: n.path, detail: `${label}: response field "${p2}" was removed` });
|
|
71769
|
+
} else if (nRes.types.get(p2) !== t2) {
|
|
71770
|
+
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)}` });
|
|
71771
|
+
}
|
|
71772
|
+
}
|
|
71773
|
+
const oParams = paramRequired(oldSpec, o.pathItem, o.op);
|
|
71774
|
+
const nParams = paramRequired(newSpec, n.pathItem, n.op);
|
|
71775
|
+
for (const [k, req] of nParams) {
|
|
71776
|
+
if (req && !oParams.get(k)) {
|
|
71777
|
+
changes.push({ kind: "param-required-added", breaking: true, method: n.method, path: n.path, detail: `${label}: parameter "${k}" is now required` });
|
|
71778
|
+
}
|
|
71779
|
+
}
|
|
71780
|
+
const oCodes = Object.keys(o.op?.responses ?? {}).filter((c) => /^2\d\d$/.test(c));
|
|
71781
|
+
const nCodes = new Set(Object.keys(n.op?.responses ?? {}));
|
|
71782
|
+
for (const c of oCodes) {
|
|
71783
|
+
if (!nCodes.has(c)) {
|
|
71784
|
+
changes.push({ kind: "success-code-removed", breaking: true, method: n.method, path: n.path, detail: `${label}: success response ${c} was removed` });
|
|
71785
|
+
}
|
|
71786
|
+
}
|
|
71787
|
+
}
|
|
71788
|
+
return changes;
|
|
71789
|
+
}
|
|
71790
|
+
function summarizeDiff(changes) {
|
|
71791
|
+
return {
|
|
71792
|
+
breaking: changes.filter((c) => c.breaking).length,
|
|
71793
|
+
nonBreaking: changes.filter((c) => !c.breaking).length
|
|
71794
|
+
};
|
|
71795
|
+
}
|
|
71796
|
+
const { electron: electron$8 } = window;
|
|
71797
|
+
function SpecField({ label, value, onSource, onText }) {
|
|
71798
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1", children: [
|
|
71799
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "text-[11px] text-surface-400", children: label }),
|
|
71800
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
71801
|
+
"input",
|
|
71802
|
+
{
|
|
71803
|
+
value: value.source,
|
|
71804
|
+
onChange: (e) => onSource(e.target.value),
|
|
71805
|
+
placeholder: "./openapi.yaml or URL",
|
|
71806
|
+
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"
|
|
71807
|
+
}
|
|
71808
|
+
),
|
|
71809
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("details", { className: "text-[10px] text-surface-500 mt-1", children: [
|
|
71810
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("summary", { className: "cursor-pointer hover:text-surface-300", children: "or paste" }),
|
|
71811
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
71812
|
+
"textarea",
|
|
71813
|
+
{
|
|
71814
|
+
value: value.text,
|
|
71815
|
+
onChange: (e) => onText(e.target.value),
|
|
71816
|
+
rows: 3,
|
|
71817
|
+
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"
|
|
71818
|
+
}
|
|
71819
|
+
)
|
|
71820
|
+
] })
|
|
71821
|
+
] });
|
|
71822
|
+
}
|
|
71823
|
+
function CompareModal() {
|
|
71824
|
+
const open = useStore((s) => s.compareOpen);
|
|
71825
|
+
const setOpen = useStore((s) => s.setCompareOpen);
|
|
71826
|
+
const collections = useStore((s) => s.collections);
|
|
71827
|
+
const savedSpec = useStore((s) => s.workspace?.settings?.coverageSpec);
|
|
71828
|
+
const [oldIn, setOldIn] = reactExports.useState({ source: "", text: "" });
|
|
71829
|
+
const [newIn, setNewIn] = reactExports.useState({ source: savedSpec ?? "", text: "" });
|
|
71830
|
+
const [changes, setChanges] = reactExports.useState(null);
|
|
71831
|
+
const [error2, setError] = reactExports.useState(null);
|
|
71832
|
+
const [busy, setBusy] = reactExports.useState(false);
|
|
71833
|
+
const requests = reactExports.useMemo(() => {
|
|
71834
|
+
const out = [];
|
|
71835
|
+
for (const entry of Object.values(collections)) {
|
|
71836
|
+
for (const req of Object.values(entry.data.requests)) {
|
|
71837
|
+
if (req.disabled) continue;
|
|
71838
|
+
out.push({ name: `${entry.data.name} / ${req.name}`, method: req.method, url: req.url });
|
|
71839
|
+
}
|
|
71840
|
+
}
|
|
71841
|
+
return out;
|
|
71842
|
+
}, [collections]);
|
|
71843
|
+
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() });
|
|
71844
|
+
async function run() {
|
|
71845
|
+
setBusy(true);
|
|
71846
|
+
setError(null);
|
|
71847
|
+
try {
|
|
71848
|
+
const [oldSpec, newSpec] = await Promise.all([load(oldIn), load(newIn)]);
|
|
71849
|
+
setChanges(diffSpecs(oldSpec, newSpec));
|
|
71850
|
+
} catch (err) {
|
|
71851
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
71852
|
+
setChanges(null);
|
|
71853
|
+
} finally {
|
|
71854
|
+
setBusy(false);
|
|
71855
|
+
}
|
|
71856
|
+
}
|
|
71857
|
+
const impact = reactExports.useMemo(() => {
|
|
71858
|
+
if (!changes) return null;
|
|
71859
|
+
const affected = changes.filter((c) => c.breaking && c.method).map((change) => ({
|
|
71860
|
+
change,
|
|
71861
|
+
tests: requests.filter((r) => r.method.toUpperCase() === change.method && pathMatches(r.url, change.path)).map((r) => r.name)
|
|
71862
|
+
}));
|
|
71863
|
+
const hitTests = new Set(affected.flatMap((a) => a.tests));
|
|
71864
|
+
return { affected, hitTests: hitTests.size };
|
|
71865
|
+
}, [changes, requests]);
|
|
71866
|
+
if (!open) return null;
|
|
71867
|
+
const sum = changes ? summarizeDiff(changes) : null;
|
|
71868
|
+
const canRun = (oldIn.source.trim() || oldIn.text.trim()) && (newIn.source.trim() || newIn.text.trim());
|
|
71869
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
71870
|
+
Modal,
|
|
71871
|
+
{
|
|
71872
|
+
onClose: () => setOpen(false),
|
|
71873
|
+
overlayClassName: "bg-black/50 z-50 flex items-start justify-center pt-16",
|
|
71874
|
+
panelClassName: "bg-surface-900 border border-surface-800 rounded-lg shadow-2xl flex flex-col w-[760px] max-h-[82vh]",
|
|
71875
|
+
title: "Compare API specs",
|
|
71876
|
+
subtitle: "Breaking-change detection and impact on your tests",
|
|
71877
|
+
children: [
|
|
71878
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-4 py-3 border-b border-surface-800 flex-shrink-0", children: [
|
|
71879
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-3 items-start", children: [
|
|
71880
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SpecField, { label: "Baseline (old)", value: oldIn, onSource: (s) => setOldIn({ ...oldIn, source: s }), onText: (t2) => setOldIn({ ...oldIn, text: t2 }) }),
|
|
71881
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-surface-500 pt-6", children: "->" }),
|
|
71882
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(SpecField, { label: "Candidate (new)", value: newIn, onSource: (s) => setNewIn({ ...newIn, source: s }), onText: (t2) => setNewIn({ ...newIn, text: t2 }) })
|
|
71883
|
+
] }),
|
|
71884
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end mt-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
71885
|
+
"button",
|
|
71886
|
+
{
|
|
71887
|
+
onClick: run,
|
|
71888
|
+
disabled: busy || !canRun,
|
|
71889
|
+
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",
|
|
71890
|
+
children: busy ? "…" : "Compare"
|
|
71891
|
+
}
|
|
71892
|
+
) }),
|
|
71893
|
+
error2 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-red-400 mt-2", children: error2 })
|
|
71894
|
+
] }),
|
|
71895
|
+
/* @__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: [
|
|
71896
|
+
"Point at two spec versions and choose ",
|
|
71897
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-300", children: "Compare" }),
|
|
71898
|
+
"."
|
|
71899
|
+
] }) : 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: [
|
|
71900
|
+
sum.breaking > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-3", children: [
|
|
71901
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-red-400 font-bold uppercase tracking-wide text-[11px] mb-1", children: "Breaking changes" }),
|
|
71902
|
+
changes.filter((c) => c.breaking).map((c, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 text-surface-300 py-0.5", children: [
|
|
71903
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400", children: "✗" }),
|
|
71904
|
+
c.detail
|
|
71905
|
+
] }, i))
|
|
71906
|
+
] }),
|
|
71907
|
+
sum.nonBreaking > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-3", children: [
|
|
71908
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-emerald-400 font-bold uppercase tracking-wide text-[11px] mb-1", children: "Non-breaking" }),
|
|
71909
|
+
changes.filter((c) => !c.breaking).map((c, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 text-surface-400 py-0.5", children: [
|
|
71910
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-emerald-400", children: "✓" }),
|
|
71911
|
+
c.detail
|
|
71912
|
+
] }, i))
|
|
71913
|
+
] }),
|
|
71914
|
+
/* @__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: [
|
|
71915
|
+
sum.breaking,
|
|
71916
|
+
" breaking change",
|
|
71917
|
+
sum.breaking !== 1 ? "s" : "",
|
|
71918
|
+
", but no test in this workspace exercises the affected operations. Add tests, then re-check."
|
|
71919
|
+
] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
71920
|
+
impact.affected.filter((a) => a.tests.length).map((a, i) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-1", children: [
|
|
71921
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-amber-300", children: a.change.detail }),
|
|
71922
|
+
a.tests.map((t2) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-surface-500 pl-4", children: [
|
|
71923
|
+
"- ",
|
|
71924
|
+
t2
|
|
71925
|
+
] }, t2))
|
|
71926
|
+
] }, i)),
|
|
71927
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 text-red-400 font-bold", children: [
|
|
71928
|
+
"Block deployment: ",
|
|
71929
|
+
sum.breaking,
|
|
71930
|
+
" breaking change",
|
|
71931
|
+
sum.breaking !== 1 ? "s" : "",
|
|
71932
|
+
" affect ",
|
|
71933
|
+
impact.hitTests,
|
|
71934
|
+
" test",
|
|
71935
|
+
impact.hitTests !== 1 ? "s" : "",
|
|
71936
|
+
"."
|
|
71937
|
+
] })
|
|
71938
|
+
] }) })
|
|
71939
|
+
] }) })
|
|
71940
|
+
]
|
|
71941
|
+
}
|
|
71942
|
+
);
|
|
71943
|
+
}
|
|
70599
71944
|
function CollectionPanel() {
|
|
70600
71945
|
const activeCollectionId = useStore((s) => s.activeCollectionId);
|
|
70601
71946
|
const collections = useStore((s) => s.collections);
|
|
70602
71947
|
const updateCollectionDataSet = useStore((s) => s.updateCollectionDataSet);
|
|
70603
71948
|
const openRunner = useStore((s) => s.openRunner);
|
|
71949
|
+
const setCoverageOpen = useStore((s) => s.setCoverageOpen);
|
|
71950
|
+
const setCompareOpen = useStore((s) => s.setCompareOpen);
|
|
70604
71951
|
const [activeTab, setActiveTab] = reactExports.useState("data");
|
|
70605
71952
|
if (!activeCollectionId) {
|
|
70606
71953
|
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" });
|
|
@@ -70622,17 +71969,37 @@ function CollectionPanel() {
|
|
|
70622
71969
|
iterCount > 0 ? ` · ${iterCount} data row${iterCount !== 1 ? "s" : ""}` : ""
|
|
70623
71970
|
] })
|
|
70624
71971
|
] }),
|
|
70625
|
-
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
70626
|
-
|
|
70627
|
-
|
|
70628
|
-
|
|
70629
|
-
|
|
70630
|
-
|
|
70631
|
-
|
|
70632
|
-
|
|
70633
|
-
|
|
70634
|
-
|
|
70635
|
-
|
|
71972
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
71973
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
71974
|
+
"button",
|
|
71975
|
+
{
|
|
71976
|
+
onClick: () => setCoverageOpen(true),
|
|
71977
|
+
title: "Measure how much of an OpenAPI spec this workspace tests",
|
|
71978
|
+
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",
|
|
71979
|
+
children: "Coverage"
|
|
71980
|
+
}
|
|
71981
|
+
),
|
|
71982
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
71983
|
+
"button",
|
|
71984
|
+
{
|
|
71985
|
+
onClick: () => setCompareOpen(true),
|
|
71986
|
+
title: "Diff two OpenAPI versions: breaking changes and which tests they affect",
|
|
71987
|
+
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",
|
|
71988
|
+
children: "Compare"
|
|
71989
|
+
}
|
|
71990
|
+
),
|
|
71991
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
71992
|
+
"button",
|
|
71993
|
+
{
|
|
71994
|
+
onClick: () => openRunner(activeCollectionId),
|
|
71995
|
+
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",
|
|
71996
|
+
children: [
|
|
71997
|
+
/* @__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" }) }),
|
|
71998
|
+
"Run collection"
|
|
71999
|
+
]
|
|
72000
|
+
}
|
|
72001
|
+
)
|
|
72002
|
+
] })
|
|
70636
72003
|
] }),
|
|
70637
72004
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex border-b border-surface-800 px-6 flex-shrink-0", children: [
|
|
70638
72005
|
{ id: "data", label: "Data", badge: iterCount > 0 ? iterCount : 0 },
|
|
@@ -72575,10 +73942,13 @@ function ContractPanel({ fuzzReport, setFuzzReport }) {
|
|
|
72575
73942
|
},
|
|
72576
73943
|
m
|
|
72577
73944
|
)) }),
|
|
72578
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-500 leading-relaxed", children: mode === "consumer" ? "Sends requests to the real provider and validates each response against the contract defined in the Contract tab." : mode === "provider" ? "Static analysis: validates that your requests conform to the provider's published OpenAPI spec (no HTTP calls)." : mode === "provider-live" ? "Replays each contract against a running provider, seeding provider states first. The real provider verification." : mode === "fuzz" ? "Sends malformed inputs generated from the spec (or, with no spec, the request body) and flags responses that crash (5xx) or accept invalid input." : "Checks static schema compatibility between consumer contracts and provider spec, then verifies live responses." }),
|
|
72579
|
-
(mode === "provider-live" || isFuzz) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
73945
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-500 leading-relaxed", children: mode === "consumer" ? "Sends requests to the real provider and validates each response against the contract defined in the Contract tab. Set a base URL below to send host-less (design-first) contracts." : mode === "provider" ? "Static analysis: validates that your requests conform to the provider's published OpenAPI spec (no HTTP calls)." : mode === "provider-live" ? "Replays each contract against a running provider, seeding provider states first. The real provider verification." : mode === "fuzz" ? "Sends malformed inputs generated from the spec (or, with no spec, the request body) and flags responses that crash (5xx) or accept invalid input." : "Checks static schema compatibility between consumer contracts and provider spec, then verifies live responses." }),
|
|
73946
|
+
(mode === "consumer" || mode === "provider-live" || isFuzz) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
72580
73947
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
72581
|
-
/* @__PURE__ */ jsxRuntimeExports.
|
|
73948
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: [
|
|
73949
|
+
"Provider base URL ",
|
|
73950
|
+
mode === "consumer" && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "normal-case text-surface-600", children: "(optional)" })
|
|
73951
|
+
] }),
|
|
72582
73952
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
72583
73953
|
"input",
|
|
72584
73954
|
{
|
|
@@ -72588,7 +73958,7 @@ function ContractPanel({ fuzzReport, setFuzzReport }) {
|
|
|
72588
73958
|
className: "w-full text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 font-mono placeholder-surface-600"
|
|
72589
73959
|
}
|
|
72590
73960
|
),
|
|
72591
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 mt-1 leading-relaxed", children: isFuzz ? "Required. Each fuzzed request is rebased onto this origin before it is sent." : "Each request is rebased onto this origin before being replayed against the live provider." })
|
|
73961
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 mt-1 leading-relaxed", children: isFuzz ? "Required. Each fuzzed request is rebased onto this origin before it is sent." : mode === "consumer" ? "Optional. Rebase each request onto this origin before sending, so design-first contracts that carry only a path (e.g. /brands) can run. Requests with a full URL are sent as-is." : "Each request is rebased onto this origin before being replayed against the live provider." })
|
|
72592
73962
|
] }),
|
|
72593
73963
|
mode === "provider-live" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
|
|
72594
73964
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium block mb-1", children: [
|
|
@@ -72913,7 +74283,6 @@ function ContractResultsPanel() {
|
|
|
72913
74283
|
const clearReport = useStore((s) => s.setLastContractReport);
|
|
72914
74284
|
const activeCollId = useStore((s) => s.activeCollectionId);
|
|
72915
74285
|
const collections = useStore((s) => s.collections);
|
|
72916
|
-
const dashboardUrl = useStore((s) => s.workspace?.settings?.dashboardUrl);
|
|
72917
74286
|
const [recordOpen, setRecordOpen] = reactExports.useState(false);
|
|
72918
74287
|
const [pacticipant, setPacticipant] = reactExports.useState("");
|
|
72919
74288
|
const [version, setVersion] = reactExports.useState("");
|
|
@@ -72962,15 +74331,6 @@ function ContractResultsPanel() {
|
|
|
72962
74331
|
report.durationMs,
|
|
72963
74332
|
"ms"
|
|
72964
74333
|
] }),
|
|
72965
|
-
dashboardUrl && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
72966
|
-
"button",
|
|
72967
|
-
{
|
|
72968
|
-
onClick: () => window.electron.openExternal(dashboardUrl),
|
|
72969
|
-
className: "text-[11px] text-surface-500 hover:text-surface-200 transition-colors",
|
|
72970
|
-
title: `Open the contract dashboard (${dashboardUrl})`,
|
|
72971
|
-
children: "Open dashboard"
|
|
72972
|
-
}
|
|
72973
|
-
),
|
|
72974
74334
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
72975
74335
|
"button",
|
|
72976
74336
|
{
|
|
@@ -73056,18 +74416,70 @@ function detectPlatform(remotes) {
|
|
|
73056
74416
|
return "unknown";
|
|
73057
74417
|
}
|
|
73058
74418
|
const NODE_LTS = "lts/*";
|
|
73059
|
-
|
|
74419
|
+
const PROVIDER_ENV = {
|
|
74420
|
+
vault: ["VAULT_ADDR", "VAULT_ROLE_ID", "VAULT_SECRET_ID"],
|
|
74421
|
+
aws: ["AWS_REGION", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"],
|
|
74422
|
+
azure: ["AZURE_TENANT_ID", "AZURE_CLIENT_ID", "AZURE_CLIENT_SECRET"],
|
|
74423
|
+
op: ["OP_CONNECT_HOST", "OP_CONNECT_TOKEN"]
|
|
74424
|
+
};
|
|
74425
|
+
function secretManagerOf(ref2) {
|
|
74426
|
+
if (ref2.startsWith("op://")) return "op";
|
|
74427
|
+
const scheme = ref2.split(":", 1)[0];
|
|
74428
|
+
if (scheme === "vault" || scheme === "aws" || scheme === "azure") return scheme;
|
|
74429
|
+
return null;
|
|
74430
|
+
}
|
|
74431
|
+
const INLINE_REF_RE = /\{\{\s*(vault:|aws:|azure:|op:\/\/)/g;
|
|
74432
|
+
function inlineSecretManagers(text) {
|
|
74433
|
+
if (!text) return [];
|
|
74434
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
74435
|
+
for (const m of text.matchAll(INLINE_REF_RE)) {
|
|
74436
|
+
kinds.add(m[1] === "op://" ? "op" : m[1].slice(0, -1));
|
|
74437
|
+
}
|
|
74438
|
+
return [...kinds];
|
|
74439
|
+
}
|
|
74440
|
+
function requestSecretManagers(req) {
|
|
74441
|
+
return inlineSecretManagers(JSON.stringify(req));
|
|
74442
|
+
}
|
|
74443
|
+
function providerEnvVars(managers) {
|
|
74444
|
+
return [...new Set(managers.flatMap((k) => PROVIDER_ENV[k]))];
|
|
74445
|
+
}
|
|
74446
|
+
function generateCiContent(platform, envName, tags2, secretVars, secretManagers = []) {
|
|
73060
74447
|
const runCmd = [
|
|
73061
74448
|
"api-spector run --workspace .",
|
|
73062
74449
|
envName ? `--environment "${envName}"` : "",
|
|
73063
74450
|
tags2 ? `--tags "${tags2}"` : "",
|
|
73064
74451
|
"--output results.html"
|
|
73065
74452
|
].filter(Boolean).join(" ");
|
|
73066
|
-
const
|
|
74453
|
+
const managers = [...new Set(secretManagers)];
|
|
74454
|
+
const usesVault = managers.includes("vault");
|
|
74455
|
+
const encryptedVars = secretVars.length ? ["API_SPECTOR_MASTER_KEY", ...secretVars] : [];
|
|
73067
74456
|
if (platform === "github") {
|
|
73068
|
-
const
|
|
73069
|
-
|
|
73070
|
-
const
|
|
74457
|
+
const ghProviderEnv = providerEnvVars(managers).filter((v) => v !== "VAULT_ROLE_ID" && v !== "VAULT_SECRET_ID");
|
|
74458
|
+
const allSecretVars = [...encryptedVars, ...ghProviderEnv.filter((v) => v !== "VAULT_ADDR")];
|
|
74459
|
+
const hintLines = [
|
|
74460
|
+
...allSecretVars.map((v) => ` # ${v}`),
|
|
74461
|
+
...usesVault ? [" # VAULT_ADDR (as a repository variable, not a secret)"] : []
|
|
74462
|
+
];
|
|
74463
|
+
const secretHint = hintLines.length ? ` # ⚠ Add these in: Settings → Secrets and variables → Actions
|
|
74464
|
+
${hintLines.join("\n")}
|
|
74465
|
+
` : "";
|
|
74466
|
+
const vaultStep = usesVault ? ` - name: Authenticate to Vault (OIDC)
|
|
74467
|
+
uses: hashicorp/vault-action@v3
|
|
74468
|
+
with:
|
|
74469
|
+
url: \${{ vars.VAULT_ADDR }}
|
|
74470
|
+
method: jwt
|
|
74471
|
+
role: apispector-ci # your Vault JWT/OIDC role
|
|
74472
|
+
exportToken: true # sets VAULT_TOKEN for later steps
|
|
74473
|
+
` : "";
|
|
74474
|
+
const permissions = usesVault ? ` permissions:
|
|
74475
|
+
contents: read
|
|
74476
|
+
id-token: write # required for Vault OIDC
|
|
74477
|
+
` : "";
|
|
74478
|
+
const envEntries = [
|
|
74479
|
+
...allSecretVars.map((v) => ` ${v}: \${{ secrets.${v} }}`),
|
|
74480
|
+
...usesVault ? [" VAULT_ADDR: ${{ vars.VAULT_ADDR }}"] : []
|
|
74481
|
+
];
|
|
74482
|
+
const envBlock = envEntries.length ? "\n env:\n" + envEntries.join("\n") : "";
|
|
73071
74483
|
return `name: API Tests
|
|
73072
74484
|
|
|
73073
74485
|
on:
|
|
@@ -73078,13 +74490,13 @@ on:
|
|
|
73078
74490
|
jobs:
|
|
73079
74491
|
api-tests:
|
|
73080
74492
|
runs-on: ubuntu-latest
|
|
73081
|
-
steps:
|
|
74493
|
+
${permissions} steps:
|
|
73082
74494
|
- uses: actions/checkout@v4
|
|
73083
74495
|
- uses: actions/setup-node@v4
|
|
73084
74496
|
with:
|
|
73085
74497
|
node-version: '${NODE_LTS}'
|
|
73086
74498
|
- run: npm install -g @testsmith/api-spector
|
|
73087
|
-
${secretHint} - name: Run API tests
|
|
74499
|
+
${vaultStep}${secretHint} - name: Run API tests
|
|
73088
74500
|
run: ${runCmd}${envBlock}
|
|
73089
74501
|
- name: Upload test results
|
|
73090
74502
|
if: always()
|
|
@@ -73095,6 +74507,7 @@ ${secretHint} - name: Run API tests
|
|
|
73095
74507
|
`;
|
|
73096
74508
|
}
|
|
73097
74509
|
if (platform === "gitlab") {
|
|
74510
|
+
const allSecretVars = [...encryptedVars, ...providerEnvVars(managers)];
|
|
73098
74511
|
const secretHint = allSecretVars.length ? ` # ⚠ Add these in: Settings → CI/CD → Variables
|
|
73099
74512
|
` + allSecretVars.map((v) => ` # ${v}`).join("\n") + "\n" : "";
|
|
73100
74513
|
const envBlock = allSecretVars.length ? "\n variables:\n" + allSecretVars.map((v) => ` ${v}: $${v}`).join("\n") : "";
|
|
@@ -73113,6 +74526,7 @@ ${secretHint} script:
|
|
|
73113
74526
|
`;
|
|
73114
74527
|
}
|
|
73115
74528
|
if (platform === "azure") {
|
|
74529
|
+
const allSecretVars = [...encryptedVars, ...providerEnvVars(managers)];
|
|
73116
74530
|
const secretHint = allSecretVars.length ? ` # ⚠ Add these in: Pipelines → Library → Variable groups (mark as secret)
|
|
73117
74531
|
` + allSecretVars.map((v) => ` # ${v}`).join("\n") + "\n" : "";
|
|
73118
74532
|
const envBlock = allSecretVars.length ? "\n env:\n" + allSecretVars.map((v) => ` ${v}: $(${v})`).join("\n") : "";
|
|
@@ -73806,6 +75220,7 @@ function BranchesTab({ onRefresh }) {
|
|
|
73806
75220
|
function CiTab() {
|
|
73807
75221
|
const environments = useStore((s) => s.environments);
|
|
73808
75222
|
const envList = Object.values(environments);
|
|
75223
|
+
const collections = useStore((s) => s.collections);
|
|
73809
75224
|
const [remotes, setRemotes] = reactExports.useState([]);
|
|
73810
75225
|
const [platform, setPlatform] = reactExports.useState("unknown");
|
|
73811
75226
|
const [envId, setEnvId] = reactExports.useState("");
|
|
@@ -73825,10 +75240,13 @@ function CiTab() {
|
|
|
73825
75240
|
reactExports.useEffect(() => {
|
|
73826
75241
|
const env = envList.find((e) => e.data.id === envId);
|
|
73827
75242
|
const secretVars = env ? env.data.variables.filter((v) => v.secret && v.enabled).map((v) => v.key) : [];
|
|
75243
|
+
const envRefManagers = env ? env.data.variables.filter((v) => v.enabled && v.secretRef).map((v) => secretManagerOf(v.secretRef)).filter((k) => k !== null) : [];
|
|
75244
|
+
const inlineManagers = Object.values(collections).flatMap((c) => Object.values(c.data.requests).flatMap(requestSecretManagers));
|
|
75245
|
+
const secretManagers = [.../* @__PURE__ */ new Set([...envRefManagers, ...inlineManagers])];
|
|
73828
75246
|
const envName = env?.data.name ?? "";
|
|
73829
|
-
setPreview(generateCiContent(platform, envName, tags2, secretVars));
|
|
75247
|
+
setPreview(generateCiContent(platform, envName, tags2, secretVars, secretManagers));
|
|
73830
75248
|
setWritten(false);
|
|
73831
|
-
}, [platform, envId, tags2, environments]);
|
|
75249
|
+
}, [platform, envId, tags2, environments, collections]);
|
|
73832
75250
|
async function write() {
|
|
73833
75251
|
try {
|
|
73834
75252
|
setError(null);
|
|
@@ -74413,6 +75831,8 @@ function App() {
|
|
|
74413
75831
|
}
|
|
74414
75832
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-screen bg-surface-950", style: { color: "var(--text-primary)" }, children: [
|
|
74415
75833
|
/* @__PURE__ */ jsxRuntimeExports.jsx(RunnerModal, {}),
|
|
75834
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(CoverageModal, {}),
|
|
75835
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(CompareModal, {}),
|
|
74416
75836
|
/* @__PURE__ */ jsxRuntimeExports.jsx(CommandPalette, {}),
|
|
74417
75837
|
docsModalOpen && /* @__PURE__ */ jsxRuntimeExports.jsx(DocsGeneratorModal, { onClose: () => setDocsModalOpen(false) }),
|
|
74418
75838
|
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: [
|
|
@@ -74421,7 +75841,7 @@ function App() {
|
|
|
74421
75841
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
|
|
74422
75842
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
|
|
74423
75843
|
"v",
|
|
74424
|
-
"0.
|
|
75844
|
+
"0.5.0"
|
|
74425
75845
|
] }),
|
|
74426
75846
|
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", children: "·" }),
|
|
74427
75847
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|