@testsmith/api-spector 0.8.3 → 0.8.4

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.
@@ -1,4 +1,4 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./GraphQLEditor-03mRa3Kf.js","./codemirror-CdIFBPs2.js","./ajv-CJPwFjiP.js","./SoapEditor-IfKqnYlR.js"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./GraphQLEditor-B-UCMNXB.js","./codemirror-CdIFBPs2.js","./ajv-CJPwFjiP.js","./SoapEditor-DK5ccc1_.js"])))=>i.map(i=>d[i]);
2
2
  import { r as requireReact, g as getDefaultExportFromCjs, R as React, a as reactExports, b as autocompletion, h as hoverTooltip, j as jsxRuntimeExports, k as keymap, t as toggleComment, x as xml, c as json, d as ReactCodeMirror, o as oneDark, e as javascript } from "./codemirror-CdIFBPs2.js";
3
3
  import { r as requireAjv } from "./ajv-CJPwFjiP.js";
4
4
  var client = { exports: {} };
@@ -13335,6 +13335,17 @@ function cloneAndReId(folder2, mapRequestId) {
13335
13335
  reIdFolderTree(cloned, mapRequestId);
13336
13336
  return cloned;
13337
13337
  }
13338
+ function collectFolderVars(root2, reqId) {
13339
+ const walk2 = (folder2) => {
13340
+ if ((folder2.requestIds ?? []).includes(reqId)) return { ...folder2.variables ?? {} };
13341
+ for (const sub of folder2.folders ?? []) {
13342
+ const inner = walk2(sub);
13343
+ if (inner) return { ...folder2.variables ?? {}, ...inner };
13344
+ }
13345
+ return null;
13346
+ };
13347
+ return walk2(root2) ?? {};
13348
+ }
13338
13349
  const WRITE_METHODS$1 = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
13339
13350
  function newId$1() {
13340
13351
  try {
@@ -13772,6 +13783,24 @@ const createCollectionsSlice = (set2, get2) => ({
13772
13783
  entry.dirty = true;
13773
13784
  });
13774
13785
  },
13786
+ reorderExample: (requestId, fromId, toId) => set2((s) => {
13787
+ const entry = Object.values(s.collections).find((c) => c.data.requests[requestId]);
13788
+ const list = entry?.data.requests[requestId].examples;
13789
+ if (!entry || !list) return;
13790
+ const from = list.findIndex((e) => e.id === fromId);
13791
+ const to = list.findIndex((e) => e.id === toId);
13792
+ if (from < 0 || to < 0 || from === to) return;
13793
+ const [moved] = list.splice(from, 1);
13794
+ list.splice(to, 0, moved);
13795
+ entry.dirty = true;
13796
+ }),
13797
+ sortExamples: (requestId) => set2((s) => {
13798
+ const entry = Object.values(s.collections).find((c) => c.data.requests[requestId]);
13799
+ const list = entry?.data.requests[requestId].examples;
13800
+ if (!entry || !list) return;
13801
+ list.sort((a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base", numeric: true }));
13802
+ entry.dirty = true;
13803
+ }),
13775
13804
  openExample: (requestId, collectionId, exampleId) => set2((s) => {
13776
13805
  const existing = s.tabs.find((t) => t.requestId === requestId && t.exampleId === exampleId);
13777
13806
  if (existing) {
@@ -15440,9 +15469,18 @@ function useVarNames() {
15440
15469
  const collections = useStore$1((s) => s.collections);
15441
15470
  const globals = useStore$1((s) => s.globals);
15442
15471
  const sessionVars = useStore$1((s) => s.sessionVars);
15472
+ const activeTabId = useStore$1((s) => s.activeTabId);
15473
+ const tabs = useStore$1((s) => s.tabs);
15443
15474
  const activeEnv = useActiveEnvironment();
15444
15475
  return reactExports.useMemo(() => {
15445
15476
  const names = /* @__PURE__ */ new Set();
15477
+ const tab = tabs.find((x) => x.id === activeTabId);
15478
+ const tabCol = tab?.collectionId ? collections[tab.collectionId]?.data : void 0;
15479
+ const activeReq = tab?.requestId && tabCol ? tabCol.requests[tab.requestId] : void 0;
15480
+ if (activeReq) {
15481
+ Object.keys(activeReq.variables ?? {}).forEach((k) => names.add(k));
15482
+ Object.keys(collectFolderVars(tabCol.rootFolder, activeReq.id)).forEach((k) => names.add(k));
15483
+ }
15446
15484
  Object.keys(globals).forEach((k) => names.add(k));
15447
15485
  Object.keys(sessionVars).forEach((k) => names.add(k));
15448
15486
  if (activeCollectionId) {
@@ -15473,19 +15511,27 @@ function useVarNames() {
15473
15511
  activeCollectionId,
15474
15512
  collections,
15475
15513
  globals,
15476
- sessionVars
15514
+ sessionVars,
15515
+ activeTabId,
15516
+ tabs
15477
15517
  ]);
15478
15518
  }
15479
15519
  function useVarValues() {
15480
15520
  const activeCollectionId = useStore$1((s) => s.activeCollectionId);
15481
15521
  const collections = useStore$1((s) => s.collections);
15482
15522
  const globals = useStore$1((s) => s.globals);
15523
+ const activeTabId = useStore$1((s) => s.activeTabId);
15524
+ const tabs = useStore$1((s) => s.tabs);
15483
15525
  const activeEnv = useActiveEnvironment();
15484
15526
  const result = { ...globals };
15485
15527
  if (activeCollectionId) {
15486
15528
  const colVars = collections[activeCollectionId]?.data.collectionVariables ?? {};
15487
15529
  Object.assign(result, colVars);
15488
15530
  }
15531
+ const tab = tabs.find((x) => x.id === activeTabId);
15532
+ const tabCol = tab?.collectionId ? collections[tab.collectionId]?.data : void 0;
15533
+ const activeReq = tab?.requestId && tabCol ? tabCol.requests[tab.requestId] : void 0;
15534
+ if (activeReq && tabCol) Object.assign(result, collectFolderVars(tabCol.rootFolder, activeReq.id));
15489
15535
  for (const v of activeEnv?.variables ?? []) {
15490
15536
  if (!v.enabled || !v.key) continue;
15491
15537
  if (v.secret && v.secretEncrypted) {
@@ -15496,6 +15542,7 @@ function useVarValues() {
15496
15542
  result[v.key] = v.value;
15497
15543
  }
15498
15544
  }
15545
+ if (activeReq) Object.assign(result, activeReq.variables ?? {});
15499
15546
  return result;
15500
15547
  }
15501
15548
  const Collections$6 = "Collections";
@@ -27048,8 +27095,11 @@ function RequestRow({
27048
27095
  onOpenExample,
27049
27096
  onRenameExample,
27050
27097
  onDeleteExample,
27051
- onDuplicateExample
27098
+ onDuplicateExample,
27099
+ onReorderExample,
27100
+ onSortExamples
27052
27101
  }) {
27102
+ const [dragExampleId, setDragExampleId] = reactExports.useState(null);
27053
27103
  const t = useT();
27054
27104
  const canAddExample = !protocol || protocol === "http" || protocol === "soap" || protocol === "grpc";
27055
27105
  const [renaming, setRenaming] = reactExports.useState(autoRename);
@@ -27207,9 +27257,15 @@ function RequestRow({
27207
27257
  { type: "item", label: t("Add Example"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: () => {
27208
27258
  onAddExample();
27209
27259
  setExamplesOpen(true);
27210
- } },
27211
- { type: "separator" }
27260
+ } }
27212
27261
  ] : [],
27262
+ ...onSortExamples && examples.length > 1 ? [
27263
+ { type: "item", label: t("Sort examples A-Z"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SortAzIcon, {}), onClick: () => {
27264
+ onSortExamples();
27265
+ setExamplesOpen(true);
27266
+ } }
27267
+ ] : [],
27268
+ ...onAddExample && canAddExample || onSortExamples && examples.length > 1 ? [{ type: "separator" }] : [],
27213
27269
  { type: "item", label: t("Rename"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(PencilIcon, {}), onClick: () => setRenaming(true) },
27214
27270
  { type: "item", label: t("Duplicate"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CopyIcon, {}), onClick: onDuplicate },
27215
27271
  { type: "item", label: t("Add tag"), icon: /* @__PURE__ */ jsxRuntimeExports.jsx(TagIcon, {}), onClick: () => setAddingTag(true) },
@@ -27237,6 +27293,15 @@ function RequestRow({
27237
27293
  name: ex.name,
27238
27294
  indent: indent + 18,
27239
27295
  isActive: activeExampleId === ex.id,
27296
+ reorderable: !!onReorderExample && examples.length > 1,
27297
+ dragging: dragExampleId === ex.id,
27298
+ dragActive: dragExampleId !== null && dragExampleId !== ex.id,
27299
+ onDragStart: () => setDragExampleId(ex.id),
27300
+ onDragEnd: () => setDragExampleId(null),
27301
+ onDropOn: () => {
27302
+ if (dragExampleId && dragExampleId !== ex.id) onReorderExample?.(dragExampleId, ex.id);
27303
+ setDragExampleId(null);
27304
+ },
27240
27305
  onOpen: () => onOpenExample?.(ex.id),
27241
27306
  onRename: (n2) => onRenameExample?.(ex.id, n2),
27242
27307
  onDelete: () => onDeleteExample?.(ex.id),
@@ -27254,20 +27319,47 @@ function RequestRow({
27254
27319
  )
27255
27320
  ] });
27256
27321
  }
27257
- function ExampleRow({ name, indent, isActive, onOpen, onRename, onDelete, onDuplicate }) {
27322
+ function ExampleRow({ name, indent, isActive, reorderable, dragging, dragActive, onDragStart, onDragEnd, onDropOn, onOpen, onRename, onDelete, onDuplicate }) {
27258
27323
  const t = useT();
27259
27324
  const [renaming, setRenaming] = reactExports.useState(false);
27325
+ const [over, setOver] = reactExports.useState(false);
27260
27326
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
27261
27327
  "div",
27262
27328
  {
27263
27329
  "data-row": true,
27264
- className: `group flex items-center gap-1.5 py-1 pr-1 rounded-sm cursor-pointer transition-colors ${isActive ? "bg-surface-800 text-[var(--text-primary)]" : "text-surface-400 hover:bg-surface-800"}`,
27330
+ draggable: reorderable && !renaming,
27331
+ className: `group flex items-center gap-1.5 py-1 pr-1 rounded-sm cursor-pointer transition-colors ${isActive ? "bg-surface-800 text-[var(--text-primary)]" : "text-surface-400 hover:bg-surface-800"} ${dragging ? "opacity-40" : ""} ${over ? "ring-1 ring-blue-500" : ""}`,
27265
27332
  style: { paddingLeft: indent },
27266
27333
  role: "button",
27267
27334
  tabIndex: 0,
27268
27335
  onClick: onOpen,
27269
27336
  onKeyDown: onActivateKey(onOpen),
27270
27337
  onDoubleClick: () => setRenaming(true),
27338
+ onDragStart: (e) => {
27339
+ e.dataTransfer.effectAllowed = "move";
27340
+ e.dataTransfer.setData("text/plain", name);
27341
+ onDragStart();
27342
+ },
27343
+ onDragEnd: () => {
27344
+ setOver(false);
27345
+ onDragEnd();
27346
+ },
27347
+ onDragOver: (e) => {
27348
+ if (dragActive) {
27349
+ e.preventDefault();
27350
+ e.dataTransfer.dropEffect = "move";
27351
+ setOver(true);
27352
+ }
27353
+ },
27354
+ onDragLeave: () => setOver(false),
27355
+ onDrop: (e) => {
27356
+ if (dragActive) {
27357
+ e.preventDefault();
27358
+ e.stopPropagation();
27359
+ }
27360
+ setOver(false);
27361
+ onDropOn();
27362
+ },
27271
27363
  children: [
27272
27364
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[8px] font-bold px-1 py-px rounded bg-surface-700 text-surface-300", title: t("Example"), children: "EX" }),
27273
27365
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-w-0", children: renaming ? /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -28137,6 +28229,8 @@ function FolderContents({
28137
28229
  const renameExample = useStore$1((s) => s.renameExample);
28138
28230
  const deleteExample = useStore$1((s) => s.deleteExample);
28139
28231
  const duplicateExample = useStore$1((s) => s.duplicateExample);
28232
+ const reorderExample = useStore$1((s) => s.reorderExample);
28233
+ const sortExamples = useStore$1((s) => s.sortExamples);
28140
28234
  const { confirm: confirm2, dialog: confirmDialog } = useConfirm();
28141
28235
  const t = useT();
28142
28236
  const dropIndent = (depth + 1) * 12 + 8;
@@ -28230,7 +28324,9 @@ function FolderContents({
28230
28324
  const ex = req.examples?.find((e) => e.id === exId);
28231
28325
  confirm2(t('Delete example ":name"?', { name: ex?.name ?? "" }), () => deleteExample(req.id, exId));
28232
28326
  },
28233
- onDuplicateExample: (exId) => duplicateExample(req.id, exId)
28327
+ onDuplicateExample: (exId) => duplicateExample(req.id, exId),
28328
+ onReorderExample: (fromId, toId) => reorderExample(req.id, fromId, toId),
28329
+ onSortExamples: () => sortExamples(req.id)
28234
28330
  }
28235
28331
  );
28236
28332
  };
@@ -28277,7 +28373,8 @@ function extractQueryParams(rawUrl, existing) {
28277
28373
  const kept = (existing ?? []).filter((p) => (p.key || p.value) && !pastedKeys.has(p.key));
28278
28374
  return { url: nextUrl, params: [...kept, ...parsed], changed: true };
28279
28375
  }
28280
- const PATH_PARAM_RE = /\[([A-Za-z_]\w*)\]/g;
28376
+ const PATH_PARAM_PATTERN = String.raw`\[([A-Za-z_]\w*)\]|(?<!\{)\{([A-Za-z_]\w*)\}(?!\})`;
28377
+ const PATH_PARAM_RE = new RegExp(PATH_PARAM_PATTERN, "g");
28281
28378
  function interpTokens(str, vars) {
28282
28379
  return str.replaceAll(/\{\{([^}]+)\}\}/g, (m, key) => {
28283
28380
  const k = String(key).trim();
@@ -28287,14 +28384,17 @@ function interpTokens(str, vars) {
28287
28384
  function buildPreviewUrl(baseUrl, params, varValues = {}) {
28288
28385
  const templateTokens = /* @__PURE__ */ new Set();
28289
28386
  for (const m of baseUrl.matchAll(/\{\{([^}]+)\}\}/g)) templateTokens.add(m[1].trim());
28290
- for (const m of baseUrl.matchAll(PATH_PARAM_RE)) templateTokens.add(m[1]);
28387
+ for (const m of baseUrl.matchAll(PATH_PARAM_RE)) templateTokens.add(m[1] ?? m[2]);
28291
28388
  const enabled = (params ?? []).filter((p) => p.enabled && p.key);
28292
28389
  const pathRows = enabled.filter((p) => p.paramType === "path" || templateTokens.has(p.key));
28293
28390
  const queryRows = enabled.filter((p) => !(p.paramType === "path" || templateTokens.has(p.key)));
28294
28391
  const scope = { ...varValues };
28295
28392
  for (const p of pathRows) scope[p.key] = interpTokens(p.value, varValues);
28296
28393
  let url = interpTokens(baseUrl, scope);
28297
- url = url.replaceAll(PATH_PARAM_RE, (m, name) => name in scope ? scope[name] : m);
28394
+ url = url.replaceAll(PATH_PARAM_RE, (m, g1, g2) => {
28395
+ const name = g1 ?? g2;
28396
+ return name in scope ? scope[name] : m;
28397
+ });
28298
28398
  if (!queryRows.length) return url;
28299
28399
  const sep = url.includes("?") ? "&" : "?";
28300
28400
  const qs = queryRows.map((p) => `${encodeURIComponent(interpTokens(p.key, varValues))}=${encodeURIComponent(interpTokens(p.value, varValues))}`).join("&");
@@ -28303,7 +28403,8 @@ function buildPreviewUrl(baseUrl, params, varValues = {}) {
28303
28403
  function extractPathParamNames(url) {
28304
28404
  const names = [];
28305
28405
  for (const m of url.matchAll(PATH_PARAM_RE)) {
28306
- if (!names.includes(m[1])) names.push(m[1]);
28406
+ const name = m[1] ?? m[2];
28407
+ if (!names.includes(name)) names.push(name);
28307
28408
  }
28308
28409
  return names;
28309
28410
  }
@@ -28458,8 +28559,8 @@ function jsonWithComments() {
28458
28559
  function xmlWithComments() {
28459
28560
  return [xml(), commentKeymap];
28460
28561
  }
28461
- const GraphQLEditor = reactExports.lazy(() => __vitePreload(() => import("./GraphQLEditor-03mRa3Kf.js"), true ? __vite__mapDeps([0,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.GraphQLEditor })));
28462
- const SoapEditor = reactExports.lazy(() => __vitePreload(() => import("./SoapEditor-IfKqnYlR.js"), true ? __vite__mapDeps([3,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.SoapEditor })));
28562
+ const GraphQLEditor = reactExports.lazy(() => __vitePreload(() => import("./GraphQLEditor-B-UCMNXB.js"), true ? __vite__mapDeps([0,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.GraphQLEditor })));
28563
+ const SoapEditor = reactExports.lazy(() => __vitePreload(() => import("./SoapEditor-DK5ccc1_.js"), true ? __vite__mapDeps([3,1,2]) : void 0, import.meta.url).then((m) => ({ default: m.SoapEditor })));
28463
28564
  function BodyTab({ request: request2, onChange }) {
28464
28565
  const t = useT();
28465
28566
  const editorFallback = /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 text-xs text-surface-500", children: t("Loading editor…") });
@@ -29426,6 +29527,13 @@ function StreamTab({ request: request2, onChange }) {
29426
29527
  ] })
29427
29528
  ] });
29428
29529
  }
29530
+ const SCOPE_BADGE = {
29531
+ global: "text-amber-400 border-amber-500/30",
29532
+ collection: "text-blue-400 border-blue-500/30",
29533
+ folder: "text-violet-400 border-violet-500/30",
29534
+ environment: "text-emerald-400 border-emerald-500/30",
29535
+ request: "text-fuchsia-400 border-fuchsia-500/30"
29536
+ };
29429
29537
  function isDynamicToken(name) {
29430
29538
  return name.startsWith("$") || name.includes("faker.") || name.includes("dayjs") || name.includes("(");
29431
29539
  }
@@ -29448,41 +29556,126 @@ function collectUsedTokens(req) {
29448
29556
  }
29449
29557
  return [...found];
29450
29558
  }
29451
- const SCOPE_BADGE = {
29452
- environment: "text-emerald-400 border-emerald-500/30",
29453
- collection: "text-blue-400 border-blue-500/30",
29454
- global: "text-amber-400 border-amber-500/30"
29455
- };
29456
29559
  function VariablesTab({ request: request2 }) {
29457
29560
  const t = useT();
29458
29561
  const activeCollectionId = useStore$1((s) => s.activeCollectionId);
29459
29562
  const collections = useStore$1((s) => s.collections);
29460
29563
  const globals = useStore$1((s) => s.globals);
29564
+ const updateRequest = useStore$1((s) => s.updateRequest);
29461
29565
  const activeEnv = useActiveEnvironment();
29462
- const values = useVarValues();
29463
- const { sourceOf, inScope } = reactExports.useMemo(() => {
29464
- const envKeys = /* @__PURE__ */ new Set();
29465
- for (const v of activeEnv?.variables ?? []) if (v.enabled && v.key) envKeys.add(v.key);
29466
- const colKeys = new Set(Object.keys(
29467
- activeCollectionId ? collections[activeCollectionId]?.data.collectionVariables ?? {} : {}
29468
- ));
29469
- const globalKeys = new Set(Object.keys(globals));
29470
- const all2 = /* @__PURE__ */ new Set([...envKeys, ...colKeys, ...globalKeys]);
29471
- const sourceOf2 = (name) => envKeys.has(name) ? "environment" : colKeys.has(name) ? "collection" : "global";
29472
- return { sourceOf: sourceOf2, inScope: all2 };
29473
- }, [activeEnv, activeCollectionId, collections, globals]);
29566
+ const col = activeCollectionId ? collections[activeCollectionId]?.data : void 0;
29567
+ const { envDisplay, envKeys, folderVars, collectionVars } = reactExports.useMemo(() => {
29568
+ const envDisplay2 = {};
29569
+ const envKeys2 = /* @__PURE__ */ new Set();
29570
+ for (const v of activeEnv?.variables ?? []) {
29571
+ if (!v.enabled || !v.key) continue;
29572
+ envKeys2.add(v.key);
29573
+ envDisplay2[v.key] = v.secret && v.secretEncrypted ? "••••••••" : v.envRef ? `$${v.envRef}` : v.value;
29574
+ }
29575
+ const folderVars2 = col ? collectFolderVars(col.rootFolder, request2.id) : {};
29576
+ const collectionVars2 = col?.collectionVariables ?? {};
29577
+ return { envDisplay: envDisplay2, envKeys: envKeys2, folderVars: folderVars2, collectionVars: collectionVars2 };
29578
+ }, [activeEnv, col, request2.id]);
29579
+ const requestVars = reactExports.useMemo(() => request2.variables ?? {}, [request2.variables]);
29580
+ const sourceOf = (name) => name in requestVars ? "request" : envKeys.has(name) ? "environment" : name in folderVars ? "folder" : name in collectionVars ? "collection" : "global";
29581
+ const valueOf = (name) => {
29582
+ switch (sourceOf(name)) {
29583
+ case "request":
29584
+ return requestVars[name];
29585
+ case "environment":
29586
+ return envDisplay[name] ?? "";
29587
+ case "folder":
29588
+ return folderVars[name];
29589
+ case "collection":
29590
+ return collectionVars[name];
29591
+ default:
29592
+ return globals[name] ?? "";
29593
+ }
29594
+ };
29595
+ const inScope = reactExports.useMemo(() => /* @__PURE__ */ new Set([
29596
+ ...Object.keys(requestVars),
29597
+ ...envKeys,
29598
+ ...Object.keys(folderVars),
29599
+ ...Object.keys(collectionVars),
29600
+ ...Object.keys(globals)
29601
+ ]), [requestVars, envKeys, folderVars, collectionVars, globals]);
29474
29602
  const used = reactExports.useMemo(() => collectUsedTokens(request2), [request2]);
29475
29603
  const usedSet = reactExports.useMemo(() => new Set(used), [used]);
29476
- const allNames = reactExports.useMemo(
29477
- () => [...inScope].sort((a, b) => a.localeCompare(b)),
29478
- [inScope]
29479
- );
29604
+ const allNames = reactExports.useMemo(() => [...inScope].sort((a, b) => a.localeCompare(b)), [inScope]);
29480
29605
  const SCOPE_LABEL = {
29481
- environment: t("environment"),
29606
+ global: t("global"),
29482
29607
  collection: t("collection"),
29483
- global: t("global")
29608
+ folder: t("folder"),
29609
+ environment: t("environment"),
29610
+ request: t("request")
29611
+ };
29612
+ const [rows, setRows] = reactExports.useState(
29613
+ () => Object.entries(requestVars).map(([key, value2]) => ({ key, value: value2 }))
29614
+ );
29615
+ reactExports.useEffect(() => {
29616
+ setRows(Object.entries(request2.variables ?? {}).map(([key, value2]) => ({ key, value: value2 })));
29617
+ }, [request2.id]);
29618
+ function persist(next) {
29619
+ setRows(next);
29620
+ const record = {};
29621
+ for (const r of next) {
29622
+ const k = r.key.trim();
29623
+ if (k) record[k] = r.value;
29624
+ }
29625
+ updateRequest(request2.id, { variables: record });
29626
+ }
29627
+ const setRow = (i, patch) => persist(rows.map((r, idx) => idx === i ? { ...r, ...patch } : r));
29628
+ const removeRow = (i) => persist(rows.filter((_, idx) => idx !== i));
29629
+ const addRow = (key = "", value2 = "") => persist([...rows, { key, value: value2 }]);
29630
+ const overrideHere = (name) => {
29631
+ if (name in requestVars) return;
29632
+ addRow(name, valueOf(name) === "••••••••" ? "" : `{{${name}}}`);
29484
29633
  };
29485
29634
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-5 text-xs", children: [
29635
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
29636
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
29637
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-[11px] uppercase tracking-wider text-surface-400", children: [
29638
+ t("Request variables"),
29639
+ rows.filter((r) => r.key.trim()).length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-1.5 text-surface-500", children: [
29640
+ "(",
29641
+ rows.filter((r) => r.key.trim()).length,
29642
+ ")"
29643
+ ] })
29644
+ ] }),
29645
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { onClick: () => addRow(), className: "text-[11px] text-fuchsia-400 hover:text-fuchsia-300", children: [
29646
+ "+ ",
29647
+ t("Add variable")
29648
+ ] })
29649
+ ] }),
29650
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500 mb-2", children: t("Scoped to this request and its examples. Override environment, folder, collection and global variables with the same name.") }),
29651
+ rows.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col gap-1", children: rows.map((r, i) => {
29652
+ const key = r.key.trim();
29653
+ const shadows = !!key && (envKeys.has(key) || key in folderVars || key in collectionVars || key in globals);
29654
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1.5", children: [
29655
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
29656
+ "input",
29657
+ {
29658
+ value: r.key,
29659
+ onChange: (e) => setRow(i, { key: e.target.value }),
29660
+ placeholder: t("name"),
29661
+ className: "w-40 bg-surface-800 border border-surface-700 rounded px-2 py-1 font-mono focus:outline-none focus:border-blue-500"
29662
+ }
29663
+ ),
29664
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
29665
+ VarInput,
29666
+ {
29667
+ value: r.value,
29668
+ onChange: (v) => setRow(i, { value: v }),
29669
+ placeholder: t("value"),
29670
+ wrapperClassName: "flex-1",
29671
+ className: "bg-surface-800 border border-surface-700 rounded px-2 py-1 font-mono focus:outline-none focus:border-blue-500"
29672
+ }
29673
+ ),
29674
+ shadows && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[9px] uppercase tracking-wider text-fuchsia-400 border border-fuchsia-500/30 rounded px-1 py-px", title: t("Overrides a higher-level variable of the same name"), children: t("override") }),
29675
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => removeRow(i), className: "shrink-0 text-surface-500 hover:text-red-400 p-1", title: t("Remove"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(TrashIcon, {}) })
29676
+ ] }, i);
29677
+ }) })
29678
+ ] }),
29486
29679
  /* @__PURE__ */ jsxRuntimeExports.jsxs("section", { children: [
29487
29680
  /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "text-[11px] uppercase tracking-wider text-surface-400 mb-2", children: [
29488
29681
  t("Referenced by this request"),
@@ -29497,9 +29690,8 @@ function VariablesTab({ request: request2 }) {
29497
29690
  const dynamic = !defined && isDynamicToken(name);
29498
29691
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-3 py-1.5 border-b border-surface-800 last:border-b-0", children: [
29499
29692
  /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "font-mono text-surface-200 shrink-0", children: `{{${name}}}` }),
29500
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0 truncate text-surface-400 font-mono", children: defined ? values[name] || /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "italic text-surface-600", children: t("(empty)") }) : dynamic ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500", children: t("generated at send time") }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400", children: t("not defined") }) }),
29501
- defined && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-[9px] uppercase tracking-wider border rounded px-1 py-px ${SCOPE_BADGE[sourceOf(name)]}`, children: SCOPE_LABEL[sourceOf(name)] }),
29502
- !defined && !dynamic && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[9px] uppercase tracking-wider text-red-400 border border-red-500/30 rounded px-1 py-px", children: t("missing") })
29693
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0 truncate text-surface-400 font-mono", children: defined ? valueOf(name) || /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "italic text-surface-600", children: t("(empty)") }) : dynamic ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-surface-500", children: t("generated at send time") }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-red-400", children: t("not defined") }) }),
29694
+ defined ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-[9px] uppercase tracking-wider border rounded px-1 py-px ${SCOPE_BADGE[sourceOf(name)]}`, children: SCOPE_LABEL[sourceOf(name)] }) : !dynamic && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[9px] uppercase tracking-wider text-red-400 border border-red-500/30 rounded px-1 py-px", children: t("missing") })
29503
29695
  ] }, name);
29504
29696
  }) })
29505
29697
  ] }),
@@ -29512,21 +29704,26 @@ function VariablesTab({ request: request2 }) {
29512
29704
  ")"
29513
29705
  ] })
29514
29706
  ] }),
29515
- allNames.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500", children: t("No variables are defined in the active environment, collection, or globals.") }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded border border-surface-800 overflow-hidden", children: allNames.map((name) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
29516
- "div",
29517
- {
29518
- className: `flex items-center gap-2 px-3 py-1.5 border-b border-surface-800 last:border-b-0 ${usedSet.has(name) ? "bg-surface-800/40" : ""}`,
29519
- children: [
29520
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "font-mono text-surface-200 shrink-0", children: name }),
29521
- usedSet.has(name) && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[9px] uppercase tracking-wider text-surface-400 border border-surface-700 rounded px-1 py-px", title: t("Referenced by this request"), children: t("used") }),
29522
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0 truncate text-surface-400 font-mono text-right", children: values[name] || /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "italic text-surface-600", children: t("(empty)") }) }),
29523
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-[9px] uppercase tracking-wider border rounded px-1 py-px ${SCOPE_BADGE[sourceOf(name)]}`, children: SCOPE_LABEL[sourceOf(name)] })
29524
- ]
29525
- },
29526
- name
29527
- )) })
29707
+ allNames.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500", children: t("No variables are defined in the active environment, collection, folder, or globals.") }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded border border-surface-800 overflow-hidden", children: allNames.map((name) => {
29708
+ const scope = sourceOf(name);
29709
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex items-center gap-2 px-3 py-1.5 border-b border-surface-800 last:border-b-0 ${usedSet.has(name) ? "bg-surface-800/40" : ""}`, children: [
29710
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "font-mono text-surface-200 shrink-0", children: name }),
29711
+ usedSet.has(name) && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "shrink-0 text-[9px] uppercase tracking-wider text-surface-400 border border-surface-700 rounded px-1 py-px", title: t("Referenced by this request"), children: t("used") }),
29712
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "flex-1 min-w-0 truncate text-surface-400 font-mono text-right", children: valueOf(name) || /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "italic text-surface-600", children: t("(empty)") }) }),
29713
+ scope !== "request" && /* @__PURE__ */ jsxRuntimeExports.jsx(
29714
+ "button",
29715
+ {
29716
+ onClick: () => overrideHere(name),
29717
+ className: "shrink-0 text-[9px] uppercase tracking-wider text-surface-500 hover:text-fuchsia-400 border border-surface-700 hover:border-fuchsia-500/40 rounded px-1 py-px",
29718
+ title: t("Override this variable for this request"),
29719
+ children: t("override")
29720
+ }
29721
+ ),
29722
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `shrink-0 text-[9px] uppercase tracking-wider border rounded px-1 py-px ${SCOPE_BADGE[scope]}`, children: SCOPE_LABEL[scope] })
29723
+ ] }, name);
29724
+ }) })
29528
29725
  ] }),
29529
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 leading-relaxed", children: t("Precedence: environment overrides collection overrides global. Secrets show as dots; environment-reference variables show as $NAME.") })
29726
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-600 leading-relaxed", children: t("Precedence (higher wins): global, collection, folder, environment, request. Data-row, runtime and CLI values override all. Secrets show as dots; environment-reference variables show as $NAME.") })
29530
29727
  ] });
29531
29728
  }
29532
29729
  function useRealtimeTest(runner) {
@@ -35927,7 +36124,7 @@ function WelcomeScreen() {
35927
36124
  )
35928
36125
  ] }),
35929
36126
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-400 text-sm max-w-sm", children: t("Local-first API testing with Robot Framework & Playwright code generation. Secrets stay on your machine.") }),
35930
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] mt-3", style: { color: "var(--text-muted)" }, children: t("version :version", { version: "0.8.3" }) })
36127
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] mt-3", style: { color: "var(--text-muted)" }, children: t("version :version", { version: "0.8.4" }) })
35931
36128
  ] }),
35932
36129
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 w-64", children: [
35933
36130
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -36227,6 +36424,25 @@ function EnvironmentEditor({ onClose }) {
36227
36424
  const next = Object.keys(useStore$1.getState().environments).at(-1);
36228
36425
  if (next) setSelectedId(next);
36229
36426
  }
36427
+ async function importFromPostman() {
36428
+ const imported = await electron$j.importPostmanEnvironment();
36429
+ if (!imported) return;
36430
+ const existingNames = Object.values(useStore$1.getState().environments).map((e) => e.data.name);
36431
+ const finalEnv = { ...imported, name: uniqueName$1(imported.name || t("Imported Environment"), existingNames) };
36432
+ const relPath = envRelPath(finalEnv.name, finalEnv.id);
36433
+ await electron$j.saveEnvironment(relPath, finalEnv);
36434
+ useStore$1.setState((s) => {
36435
+ s.environments[finalEnv.id] = { relPath, data: finalEnv };
36436
+ if (!s.activeEnvironmentId) s.activeEnvironmentId = finalEnv.id;
36437
+ if (s.workspace && !s.workspace.environments.includes(relPath)) {
36438
+ s.workspace.environments = [...s.workspace.environments, relPath];
36439
+ }
36440
+ return s;
36441
+ });
36442
+ const ws = useStore$1.getState().workspace;
36443
+ if (ws) await electron$j.saveWorkspace(ws);
36444
+ setSelectedId(finalEnv.id);
36445
+ }
36230
36446
  function updateVar(idx, patch) {
36231
36447
  if (!env2) return;
36232
36448
  const vars = env2.variables.map((v, i) => i === idx ? { ...v, ...patch } : v);
@@ -36386,6 +36602,14 @@ function EnvironmentEditor({ onClose }) {
36386
36602
  className: "px-3 py-2 text-xs text-surface-400 hover:text-white border-t border-surface-800 transition-colors text-left",
36387
36603
  children: t("+ Add environment")
36388
36604
  }
36605
+ ),
36606
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
36607
+ "button",
36608
+ {
36609
+ onClick: importFromPostman,
36610
+ className: "px-3 py-2 text-xs text-surface-400 hover:text-white border-t border-surface-800 transition-colors text-left",
36611
+ children: t("Import from Postman")
36612
+ }
36389
36613
  )
36390
36614
  ] }),
36391
36615
  env2 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col min-w-0", children: [
@@ -38103,7 +38327,7 @@ function parseCurl(command) {
38103
38327
  const { electron: electron$f } = window;
38104
38328
  const OPTIONS = [
38105
38329
  { id: "postman", label: "Postman", description: "Collection v2.1 JSON" },
38106
- { id: "postman-env", label: "Postman Env", description: "Environment export JSON" },
38330
+ { id: "wsdl", label: "WSDL", description: "SOAP service -> requests (all or selected operations)", supportsUrl: true },
38107
38331
  { id: "openapi", label: "OpenAPI", description: "JSON or YAML (v3.x)", supportsUrl: true },
38108
38332
  { id: "insomnia", label: "Insomnia", description: "Export v4 JSON" },
38109
38333
  { id: "bruno", label: "Bruno", description: "bruno.json collection file" },
@@ -38179,15 +38403,6 @@ function ImportModal({ onImport, onClose }) {
38179
38403
  setLoading(true);
38180
38404
  setError(null);
38181
38405
  try {
38182
- if (opt.id === "postman-env") {
38183
- const env2 = await electron$f.importPostmanEnvironment();
38184
- if (env2) {
38185
- await persistImportedEnvironment(env2);
38186
- onImport(null);
38187
- onClose();
38188
- }
38189
- return;
38190
- }
38191
38406
  if (opt.id === "arazzo") {
38192
38407
  const res = await electron$f.importArazzo();
38193
38408
  if (!res) return;
@@ -38207,11 +38422,12 @@ function ImportModal({ onImport, onClose }) {
38207
38422
  if (opt.id === "http") col = await electron$f.importHttpFile();
38208
38423
  if (opt.id === "har") col = await electron$f.importHar();
38209
38424
  if (opt.id === "spector") col = await electron$f.importSpectorCollection();
38425
+ if (opt.id === "wsdl") col = await electron$f.importWsdl();
38210
38426
  if (!col) {
38211
38427
  setLoading(false);
38212
38428
  return;
38213
38429
  }
38214
- if (opt.id === "openapi" || opt.id === "har") {
38430
+ if (opt.id === "openapi" || opt.id === "har" || opt.id === "wsdl") {
38215
38431
  enterPreview(col);
38216
38432
  } else {
38217
38433
  onImport(col);
@@ -38229,7 +38445,7 @@ function ImportModal({ onImport, onClose }) {
38229
38445
  setLoading(true);
38230
38446
  setError(null);
38231
38447
  try {
38232
- const col = await electron$f.importOpenApiFromUrl(trimmed);
38448
+ const col = selected2 === "wsdl" ? await electron$f.importWsdl({ url: trimmed }) : await electron$f.importOpenApiFromUrl(trimmed);
38233
38449
  if (col) enterPreview(col);
38234
38450
  } catch (err) {
38235
38451
  setError(err instanceof Error ? err.message : String(err));
@@ -38349,24 +38565,6 @@ function ImportModal({ onImport, onClose }) {
38349
38565
  setLoading(false);
38350
38566
  }
38351
38567
  }
38352
- async function persistImportedEnvironment(env2) {
38353
- const state = useStore$1.getState();
38354
- const existingNames = Object.values(state.environments).map((e) => e.data.name);
38355
- const finalName = uniqueEnvName(env2.name || t("Imported Environment"), existingNames);
38356
- const finalEnv = { ...env2, name: finalName };
38357
- const relPath = envRelPath(finalName, finalEnv.id);
38358
- await electron$f.saveEnvironment(relPath, finalEnv);
38359
- useStore$1.setState((s) => {
38360
- s.environments[finalEnv.id] = { relPath, data: finalEnv };
38361
- if (!s.activeEnvironmentId) s.activeEnvironmentId = finalEnv.id;
38362
- if (s.workspace && !s.workspace.environments.includes(relPath)) {
38363
- s.workspace.environments = [...s.workspace.environments, relPath];
38364
- }
38365
- return s;
38366
- });
38367
- const ws = useStore$1.getState().workspace;
38368
- if (ws) await electron$f.saveWorkspace(ws);
38369
- }
38370
38568
  async function persistBaseUrlVariable(name, value2) {
38371
38569
  const state = useStore$1.getState();
38372
38570
  if (envTarget === "__new__") {
@@ -38633,7 +38831,7 @@ function ImportModal({ onImport, onClose }) {
38633
38831
  },
38634
38832
  opt.id
38635
38833
  )) }),
38636
- selected2 === "openapi" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
38834
+ (selected2 === "openapi" || selected2 === "wsdl") && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-2", children: [
38637
38835
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[10px] text-surface-500 uppercase tracking-wider font-medium", children: t("Or import from URL") }),
38638
38836
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [
38639
38837
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -38648,7 +38846,7 @@ function ImportModal({ onImport, onClose }) {
38648
38846
  onKeyDown: (e) => {
38649
38847
  if (e.key === "Enter") importFromUrl();
38650
38848
  },
38651
- placeholder: "https://api.example.com/openapi.json",
38849
+ placeholder: selected2 === "wsdl" ? "http://www.dneonline.com/calculator.asmx?WSDL" : "https://api.example.com/openapi.json",
38652
38850
  className: "flex-1 text-xs bg-surface-800 border border-surface-700 rounded px-2.5 py-1.5 focus:outline-none focus:border-blue-500 placeholder-surface-600 font-mono"
38653
38851
  }
38654
38852
  ),
@@ -56085,7 +56283,7 @@ function App() {
56085
56283
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
56086
56284
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
56087
56285
  "v",
56088
- "0.8.3"
56286
+ "0.8.4"
56089
56287
  ] }),
56090
56288
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", children: "·" }),
56091
56289
  /* @__PURE__ */ jsxRuntimeExports.jsx(