@testsmith/api-spector 0.4.1 → 0.4.3

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/out/main/index.js CHANGED
@@ -5174,7 +5174,7 @@ function isNewer(a, b) {
5174
5174
  return a2 > b2;
5175
5175
  }
5176
5176
  async function checkForUpdate() {
5177
- const current = "0.4.1";
5177
+ const current = "0.4.3";
5178
5178
  try {
5179
5179
  const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(4e3) });
5180
5180
  if (!res.ok) return null;
@@ -396,7 +396,7 @@ async function main() {
396
396
  } else if (!envName && workspace.settings?.defaultEnvironment && !env) {
397
397
  console.warn(cliCommon.color(`Warning: default environment "${workspace.settings.defaultEnvironment}" not found. Running without environment.`, cliCommon.C.yellow));
398
398
  }
399
- const version = `v${"0.4.1"}`;
399
+ const version = `v${"0.4.3"}`;
400
400
  console.log("");
401
401
  console.log(cliCommon.color(" API Test Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
402
402
  console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
@@ -13535,6 +13535,7 @@ const createTabsSlice = (set2, get2) => ({
13535
13535
  activeTabId: null,
13536
13536
  activeCollectionId: null,
13537
13537
  openInTab: (requestId, collectionId) => set2((s) => {
13538
+ s.collectionPanelOpen = false;
13538
13539
  const existing = s.tabs.find((t2) => t2.requestId === requestId);
13539
13540
  if (existing) {
13540
13541
  s.activeTabId = existing.id;
@@ -13568,6 +13569,7 @@ const createTabsSlice = (set2, get2) => ({
13568
13569
  }),
13569
13570
  setActiveTabId: (id2) => set2((s) => {
13570
13571
  s.activeTabId = id2;
13572
+ s.collectionPanelOpen = false;
13571
13573
  const tab = s.tabs.find((t2) => t2.id === id2);
13572
13574
  if (tab) s.activeCollectionId = tab.collectionId;
13573
13575
  }),
@@ -14144,6 +14146,7 @@ const createUiSlice = (set2) => ({
14144
14146
  activeGitDiff: null,
14145
14147
  quickInsertsOpen: true,
14146
14148
  sendSignal: 0,
14149
+ collectionPanelOpen: false,
14147
14150
  setShowGeneratorPanel: (v) => set2((s) => {
14148
14151
  s.showGeneratorPanel = v;
14149
14152
  }),
@@ -14194,6 +14197,9 @@ const createUiSlice = (set2) => ({
14194
14197
  }),
14195
14198
  requestSend: () => set2((s) => {
14196
14199
  s.sendSignal += 1;
14200
+ }),
14201
+ setCollectionPanelOpen: (open) => set2((s) => {
14202
+ s.collectionPanelOpen = open;
14197
14203
  })
14198
14204
  });
14199
14205
  const useStore = create()(
@@ -35826,6 +35832,144 @@ function BearerPanel({
35826
35832
  ] })
35827
35833
  ] });
35828
35834
  }
35835
+ function parseCSV(text) {
35836
+ const lines = text.trim().split(/\r?\n/).filter(Boolean);
35837
+ if (lines.length === 0) return { columns: [], rows: [] };
35838
+ function splitRow(line) {
35839
+ const cells = [];
35840
+ let cur2 = "";
35841
+ let inQuote = false;
35842
+ for (let i = 0; i < line.length; i++) {
35843
+ const ch = line[i];
35844
+ if (ch === '"') {
35845
+ inQuote = !inQuote;
35846
+ } else if (ch === "," && !inQuote) {
35847
+ cells.push(cur2.trim());
35848
+ cur2 = "";
35849
+ } else {
35850
+ cur2 += ch;
35851
+ }
35852
+ }
35853
+ cells.push(cur2.trim());
35854
+ return cells;
35855
+ }
35856
+ const columns = splitRow(lines[0]);
35857
+ const rows = lines.slice(1).map(splitRow);
35858
+ return { columns, rows };
35859
+ }
35860
+ function toCSV(ds) {
35861
+ const escape2 = (s) => s.includes(",") || s.includes('"') ? `"${s.replace(/"/g, '""')}"` : s;
35862
+ return [ds.columns, ...ds.rows].map((row) => row.map(escape2).join(",")).join("\n");
35863
+ }
35864
+ function DataSetEditor({ ds, onChange, exportName, scopeLabel = "collection" }) {
35865
+ const csvFileRef = reactExports.useRef(null);
35866
+ function addColumn() {
35867
+ const name2 = `var${ds.columns.length + 1}`;
35868
+ onChange({ columns: [...ds.columns, name2], rows: ds.rows.map((r) => [...r, ""]) });
35869
+ }
35870
+ function renameColumn(ci, name2) {
35871
+ onChange({ ...ds, columns: ds.columns.map((c, i) => i === ci ? name2 : c) });
35872
+ }
35873
+ function removeColumn(ci) {
35874
+ onChange({ columns: ds.columns.filter((_, i) => i !== ci), rows: ds.rows.map((r) => r.filter((_, i) => i !== ci)) });
35875
+ }
35876
+ function addRow() {
35877
+ onChange({ ...ds, rows: [...ds.rows, ds.columns.map(() => "")] });
35878
+ }
35879
+ function setCell(ri, ci, v) {
35880
+ onChange({ ...ds, rows: ds.rows.map((row, i) => i === ri ? row.map((c, j) => j === ci ? v : c) : row) });
35881
+ }
35882
+ function removeRow(ri) {
35883
+ onChange({ ...ds, rows: ds.rows.filter((_, i) => i !== ri) });
35884
+ }
35885
+ function importCSV(e) {
35886
+ const file = e.target.files?.[0];
35887
+ if (!file) return;
35888
+ const reader = new FileReader();
35889
+ reader.onload = (ev) => onChange(parseCSV(ev.target?.result));
35890
+ reader.readAsText(file);
35891
+ e.target.value = "";
35892
+ }
35893
+ function exportCSV() {
35894
+ const blob = new Blob([toCSV(ds)], { type: "text/csv" });
35895
+ const url = URL.createObjectURL(blob);
35896
+ const a = document.createElement("a");
35897
+ a.href = url;
35898
+ a.download = `${exportName.replace(/\s+/g, "_")}_data.csv`;
35899
+ a.click();
35900
+ URL.revokeObjectURL(url);
35901
+ }
35902
+ const hasColumns = ds.columns.length > 0;
35903
+ const iterCount = ds.rows.length;
35904
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 text-xs", children: [
35905
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-surface-600 text-[11px]", children: [
35906
+ "Define variables here - each row runs the entire ",
35907
+ scopeLabel,
35908
+ " once with those values injected. Columns become ",
35909
+ /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-500", children: "{{variable}}" }),
35910
+ " placeholders."
35911
+ ] }),
35912
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
35913
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: addColumn, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors", children: "+ Column" }),
35914
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: addRow, disabled: !hasColumns, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 disabled:opacity-40 rounded transition-colors", children: "+ Row" }),
35915
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1" }),
35916
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35917
+ "button",
35918
+ {
35919
+ onClick: () => csvFileRef.current?.click(),
35920
+ className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors",
35921
+ title: "Import CSV - first row is column headers",
35922
+ children: "↑ Import CSV"
35923
+ }
35924
+ ),
35925
+ hasColumns && iterCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: exportCSV, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors", children: "↓ Export CSV" }),
35926
+ /* @__PURE__ */ jsxRuntimeExports.jsx("input", { ref: csvFileRef, type: "file", accept: ".csv,text/csv", className: "hidden", onChange: importCSV })
35927
+ ] }),
35928
+ hasColumns && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500", children: iterCount === 0 ? "No rows yet - add rows or import a CSV." : `${iterCount} iteration${iterCount !== 1 ? "s" : ""} · columns: ${ds.columns.join(", ")}` }),
35929
+ hasColumns ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full border-collapse text-xs", children: [
35930
+ /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-surface-700", children: [
35931
+ /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "w-8 px-2 py-1 text-surface-600 font-normal text-left", children: "#" }),
35932
+ ds.columns.map((col, ci) => /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-1 py-1 font-normal text-left min-w-[120px]", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
35933
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35934
+ "input",
35935
+ {
35936
+ value: col,
35937
+ onChange: (e) => renameColumn(ci, e.target.value),
35938
+ className: "flex-1 bg-surface-800 border border-surface-700 rounded px-1.5 py-0.5 font-mono text-blue-400 focus:outline-none focus:border-blue-500",
35939
+ title: "Variable name"
35940
+ }
35941
+ ),
35942
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => removeColumn(ci), className: "text-surface-400 hover:text-red-400 transition-colors shrink-0", children: "×" })
35943
+ ] }) }, ci)),
35944
+ /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "w-6" })
35945
+ ] }) }),
35946
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("tbody", { children: [
35947
+ ds.rows.map((row, ri) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "group border-b border-surface-800/60 hover:bg-surface-800/30", children: [
35948
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-2 py-1 text-surface-600", children: ri + 1 }),
35949
+ ds.columns.map((_, ci) => /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-1 py-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35950
+ "input",
35951
+ {
35952
+ value: row[ci] ?? "",
35953
+ onChange: (e) => setCell(ri, ci, e.target.value),
35954
+ className: "w-full bg-surface-800 border border-transparent rounded px-1.5 py-0.5 font-mono focus:outline-none focus:border-blue-500 hover:border-surface-600"
35955
+ }
35956
+ ) }, ci)),
35957
+ /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-1 py-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => removeRow(ri), className: "text-surface-400 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-all", children: "×" }) })
35958
+ ] }, ri)),
35959
+ iterCount === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("td", { colSpan: ds.columns.length + 2, className: "px-2 py-3 text-surface-600 text-center", children: 'No rows - click "+ Row" or import a CSV' }) })
35960
+ ] })
35961
+ ] }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center gap-2 py-8 text-surface-600", children: [
35962
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "No columns defined." }),
35963
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px]", children: [
35964
+ "Click ",
35965
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "+ Column" }),
35966
+ " to add a variable, or ",
35967
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "↑ Import CSV" }),
35968
+ " to load from a file."
35969
+ ] })
35970
+ ] })
35971
+ ] });
35972
+ }
35829
35973
  function FolderSettingsModal({ collectionId, folder, onClose }) {
35830
35974
  const updateFolder = useStore((s) => s.updateFolder);
35831
35975
  const [activeTab, setActiveTab] = reactExports.useState("auth");
@@ -35834,6 +35978,7 @@ function FolderSettingsModal({ collectionId, folder, onClose }) {
35834
35978
  const [varRows, setVarRows] = reactExports.useState(
35835
35979
  Object.entries(folder.variables ?? {}).map(([key, value]) => ({ key, value, enabled: true }))
35836
35980
  );
35981
+ const [dataSet, setDataSet] = reactExports.useState(folder.dataSet ?? { columns: [], rows: [] });
35837
35982
  function patchAuth(patch) {
35838
35983
  setAuth((prev) => ({ ...prev, ...patch }));
35839
35984
  }
@@ -35841,7 +35986,8 @@ function FolderSettingsModal({ collectionId, folder, onClose }) {
35841
35986
  const variables = Object.fromEntries(
35842
35987
  varRows.filter((r) => r.key.trim()).map((r) => [r.key.trim(), r.value])
35843
35988
  );
35844
- updateFolder(collectionId, folder.id, { auth, headers, variables });
35989
+ const cleanData = dataSet.columns.length > 0 ? dataSet : void 0;
35990
+ updateFolder(collectionId, folder.id, { auth, headers, variables, dataSet: cleanData });
35845
35991
  onClose();
35846
35992
  }
35847
35993
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
@@ -35861,7 +36007,7 @@ function FolderSettingsModal({ collectionId, folder, onClose }) {
35861
36007
  ] }),
35862
36008
  /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: onClose, className: "text-surface-400 hover:text-white text-lg leading-none", children: "×" })
35863
36009
  ] }),
35864
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex border-b border-surface-800 px-4 shrink-0", children: ["auth", "headers", "variables"].map((t2) => /* @__PURE__ */ jsxRuntimeExports.jsx(
36010
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex border-b border-surface-800 px-4 shrink-0", children: ["auth", "headers", "variables", "data"].map((t2) => /* @__PURE__ */ jsxRuntimeExports.jsx(
35865
36011
  "button",
35866
36012
  {
35867
36013
  onClick: () => setActiveTab(t2),
@@ -35904,7 +36050,8 @@ function FolderSettingsModal({ collectionId, folder, onClose }) {
35904
36050
  valuePlaceholder: "value"
35905
36051
  }
35906
36052
  )
35907
- ] })
36053
+ ] }),
36054
+ activeTab === "data" && /* @__PURE__ */ jsxRuntimeExports.jsx(DataSetEditor, { ds: dataSet, onChange: setDataSet, exportName: folder.name, scopeLabel: "folder" })
35908
36055
  ] }),
35909
36056
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 px-4 py-3 border-t border-surface-800 shrink-0", children: [
35910
36057
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -36891,6 +37038,7 @@ function CollectionTree() {
36891
37038
  const tabs = useStore((s) => s.tabs);
36892
37039
  const openInTab = useStore((s) => s.openInTab);
36893
37040
  const setActiveCollection = useStore((s) => s.setActiveCollection);
37041
+ const setCollectionPanelOpen = useStore((s) => s.setCollectionPanelOpen);
36894
37042
  const activeRequestId = tabs.find((t2) => t2.id === activeTabId)?.requestId ?? null;
36895
37043
  const addCollection = useStore((s) => s.addCollection);
36896
37044
  const addRequest = useStore((s) => s.addRequest);
@@ -36948,7 +37096,10 @@ function CollectionTree() {
36948
37096
  isActive: col.id === activeCollectionId,
36949
37097
  activeRequestId,
36950
37098
  existingCollectionNames: colList.map((c) => c.data.name),
36951
- onSelectCollection: () => setActiveCollection(col.id),
37099
+ onSelectCollection: () => {
37100
+ setActiveCollection(col.id);
37101
+ setCollectionPanelOpen(true);
37102
+ },
36952
37103
  onSelectRequest: (reqId) => openInTab(reqId, col.id),
36953
37104
  newRequestId,
36954
37105
  onAddRequest: (folderId) => setNewRequestId(addRequest(col.id, folderId)),
@@ -66235,7 +66386,7 @@ function WelcomeScreen() {
66235
66386
  /* @__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." }),
66236
66387
  /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[11px] mt-3", style: { color: "var(--text-muted)" }, children: [
66237
66388
  "version ",
66238
- "0.4.1"
66389
+ "0.4.3"
66239
66390
  ] })
66240
66391
  ] }),
66241
66392
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 w-64", children: [
@@ -68992,7 +69143,7 @@ function RunnerModal() {
68992
69143
  const colEntry = collectionId ? collections[collectionId] : null;
68993
69144
  const colName = colEntry?.data.name ?? "Collection";
68994
69145
  const folderName = folderId && colEntry ? findFolder$1(colEntry.data.rootFolder, folderId)?.name ?? "Folder" : null;
68995
- const dataSet = colEntry?.data.dataSet ?? { columns: [], rows: [] };
69146
+ const dataSet = (folderId && colEntry ? findFolder$1(colEntry.data.rootFolder, folderId)?.dataSet : null) ?? colEntry?.data.dataSet ?? { columns: [], rows: [] };
68996
69147
  const iterCount = dataSet.rows.length;
68997
69148
  const availableTags = collectionId ? allTagsIn(collectionId, folderId) : [];
68998
69149
  const progressIdxRef = reactExports.useRef(0);
@@ -69008,7 +69159,7 @@ function RunnerModal() {
69008
69159
  }, [runnerModal.open]);
69009
69160
  const toggleTag = (tag) => setFilterTags((prev) => prev.includes(tag) ? prev.filter((t2) => t2 !== tag) : [...prev, tag]);
69010
69161
  const run = reactExports.useCallback(async () => {
69011
- const ds = colEntry?.data.dataSet ?? { columns: [], rows: [] };
69162
+ const ds = (folderId && colEntry ? findFolder$1(colEntry.data.rootFolder, folderId)?.dataSet : null) ?? colEntry?.data.dataSet ?? { columns: [], rows: [] };
69012
69163
  const baseItems = collectionId ? collectRequests(collectionId, folderId, filterTags) : [];
69013
69164
  if (baseItems.length === 0) return;
69014
69165
  let items2;
@@ -69313,42 +69464,12 @@ function RunnerModal() {
69313
69464
  }
69314
69465
  );
69315
69466
  }
69316
- function parseCSV(text) {
69317
- const lines = text.trim().split(/\r?\n/).filter(Boolean);
69318
- if (lines.length === 0) return { columns: [], rows: [] };
69319
- function splitRow(line) {
69320
- const cells = [];
69321
- let cur2 = "";
69322
- let inQuote = false;
69323
- for (let i = 0; i < line.length; i++) {
69324
- const ch = line[i];
69325
- if (ch === '"') {
69326
- inQuote = !inQuote;
69327
- } else if (ch === "," && !inQuote) {
69328
- cells.push(cur2.trim());
69329
- cur2 = "";
69330
- } else {
69331
- cur2 += ch;
69332
- }
69333
- }
69334
- cells.push(cur2.trim());
69335
- return cells;
69336
- }
69337
- const columns = splitRow(lines[0]);
69338
- const rows = lines.slice(1).map(splitRow);
69339
- return { columns, rows };
69340
- }
69341
- function toCSV(ds) {
69342
- const escape2 = (s) => s.includes(",") || s.includes('"') ? `"${s.replace(/"/g, '""')}"` : s;
69343
- return [ds.columns, ...ds.rows].map((row) => row.map(escape2).join(",")).join("\n");
69344
- }
69345
69467
  function CollectionPanel() {
69346
69468
  const activeCollectionId = useStore((s) => s.activeCollectionId);
69347
69469
  const collections = useStore((s) => s.collections);
69348
69470
  const updateCollectionDataSet = useStore((s) => s.updateCollectionDataSet);
69349
69471
  const openRunner = useStore((s) => s.openRunner);
69350
69472
  const [activeTab, setActiveTab] = reactExports.useState("data");
69351
- const csvFileRef = reactExports.useRef(null);
69352
69473
  if (!activeCollectionId) {
69353
69474
  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" });
69354
69475
  }
@@ -69358,43 +69479,6 @@ function CollectionPanel() {
69358
69479
  function setDs(next) {
69359
69480
  updateCollectionDataSet(activeCollectionId, next);
69360
69481
  }
69361
- function addColumn() {
69362
- const name2 = `var${ds.columns.length + 1}`;
69363
- setDs({ columns: [...ds.columns, name2], rows: ds.rows.map((r) => [...r, ""]) });
69364
- }
69365
- function renameColumn(ci, name2) {
69366
- setDs({ ...ds, columns: ds.columns.map((c, i) => i === ci ? name2 : c) });
69367
- }
69368
- function removeColumn(ci) {
69369
- setDs({ columns: ds.columns.filter((_, i) => i !== ci), rows: ds.rows.map((r) => r.filter((_, i) => i !== ci)) });
69370
- }
69371
- function addRow() {
69372
- setDs({ ...ds, rows: [...ds.rows, ds.columns.map(() => "")] });
69373
- }
69374
- function setCell(ri, ci, v) {
69375
- setDs({ ...ds, rows: ds.rows.map((row, i) => i === ri ? row.map((c, j) => j === ci ? v : c) : row) });
69376
- }
69377
- function removeRow(ri) {
69378
- setDs({ ...ds, rows: ds.rows.filter((_, i) => i !== ri) });
69379
- }
69380
- function importCSV(e) {
69381
- const file = e.target.files?.[0];
69382
- if (!file) return;
69383
- const reader = new FileReader();
69384
- reader.onload = (ev) => setDs(parseCSV(ev.target?.result));
69385
- reader.readAsText(file);
69386
- e.target.value = "";
69387
- }
69388
- function exportCSV() {
69389
- const blob = new Blob([toCSV(ds)], { type: "text/csv" });
69390
- const url = URL.createObjectURL(blob);
69391
- const a = document.createElement("a");
69392
- a.href = url;
69393
- a.download = `${col.name.replace(/\s+/g, "_")}_data.csv`;
69394
- a.click();
69395
- URL.revokeObjectURL(url);
69396
- }
69397
- const hasColumns = ds.columns.length > 0;
69398
69482
  const iterCount = ds.rows.length;
69399
69483
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full", children: [
69400
69484
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-6 py-4 border-b border-surface-800 flex-shrink-0 flex items-center justify-between", children: [
@@ -69434,72 +69518,7 @@ function CollectionPanel() {
69434
69518
  tab.id
69435
69519
  )) }),
69436
69520
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto px-6 py-4", children: [
69437
- activeTab === "data" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-3 text-xs", children: [
69438
- /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-surface-600 text-[11px]", children: [
69439
- "Define variables here - each row runs the entire collection once with those values injected. Columns become ",
69440
- /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-500", children: "{{variable}}" }),
69441
- " placeholders."
69442
- ] }),
69443
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
69444
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: addColumn, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors", children: "+ Column" }),
69445
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: addRow, disabled: !hasColumns, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 disabled:opacity-40 rounded transition-colors", children: "+ Row" }),
69446
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1" }),
69447
- /* @__PURE__ */ jsxRuntimeExports.jsx(
69448
- "button",
69449
- {
69450
- onClick: () => csvFileRef.current?.click(),
69451
- className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors",
69452
- title: "Import CSV - first row is column headers",
69453
- children: "↑ Import CSV"
69454
- }
69455
- ),
69456
- hasColumns && iterCount > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: exportCSV, className: "px-2.5 py-1 bg-surface-700 hover:bg-surface-600 rounded transition-colors", children: "↓ Export CSV" }),
69457
- /* @__PURE__ */ jsxRuntimeExports.jsx("input", { ref: csvFileRef, type: "file", accept: ".csv,text/csv", className: "hidden", onChange: importCSV })
69458
- ] }),
69459
- hasColumns && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-surface-500", children: iterCount === 0 ? "No rows yet - add rows or import a CSV." : `${iterCount} iteration${iterCount !== 1 ? "s" : ""} · columns: ${ds.columns.join(", ")}` }),
69460
- hasColumns ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full border-collapse text-xs", children: [
69461
- /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "border-b border-surface-700", children: [
69462
- /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "w-8 px-2 py-1 text-surface-600 font-normal text-left", children: "#" }),
69463
- ds.columns.map((col2, ci) => /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-1 py-1 font-normal text-left min-w-[120px]", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
69464
- /* @__PURE__ */ jsxRuntimeExports.jsx(
69465
- "input",
69466
- {
69467
- value: col2,
69468
- onChange: (e) => renameColumn(ci, e.target.value),
69469
- className: "flex-1 bg-surface-800 border border-surface-700 rounded px-1.5 py-0.5 font-mono text-blue-400 focus:outline-none focus:border-blue-500",
69470
- title: "Variable name"
69471
- }
69472
- ),
69473
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => removeColumn(ci), className: "text-surface-400 hover:text-red-400 transition-colors shrink-0", children: "×" })
69474
- ] }) }, ci)),
69475
- /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "w-6" })
69476
- ] }) }),
69477
- /* @__PURE__ */ jsxRuntimeExports.jsxs("tbody", { children: [
69478
- ds.rows.map((row, ri) => /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { className: "group border-b border-surface-800/60 hover:bg-surface-800/30", children: [
69479
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-2 py-1 text-surface-600", children: ri + 1 }),
69480
- ds.columns.map((_, ci) => /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-1 py-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
69481
- "input",
69482
- {
69483
- value: row[ci] ?? "",
69484
- onChange: (e) => setCell(ri, ci, e.target.value),
69485
- className: "w-full bg-surface-800 border border-transparent rounded px-1.5 py-0.5 font-mono focus:outline-none focus:border-blue-500 hover:border-surface-600"
69486
- }
69487
- ) }, ci)),
69488
- /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-1 py-1", children: /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => removeRow(ri), className: "text-surface-400 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-all", children: "×" }) })
69489
- ] }, ri)),
69490
- iterCount === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("td", { colSpan: ds.columns.length + 2, className: "px-2 py-3 text-surface-600 text-center", children: 'No rows - click "+ Row" or import a CSV' }) })
69491
- ] })
69492
- ] }) }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center gap-2 py-8 text-surface-600", children: [
69493
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "No columns defined." }),
69494
- /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px]", children: [
69495
- "Click ",
69496
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "+ Column" }),
69497
- " to add a variable, or ",
69498
- /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "↑ Import CSV" }),
69499
- " to load from a file."
69500
- ] })
69501
- ] })
69502
- ] }),
69521
+ activeTab === "data" && /* @__PURE__ */ jsxRuntimeExports.jsx(DataSetEditor, { ds, onChange: setDs, exportName: col.name, scopeLabel: "collection" }),
69503
69522
  activeTab === "variables" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col gap-2 text-xs text-surface-600", children: Object.keys(col.collectionVariables ?? {}).length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { children: [
69504
69523
  "No collection variables. Scripts can set them via ",
69505
69524
  /* @__PURE__ */ jsxRuntimeExports.jsx("code", { className: "text-surface-500", children: "sp.collectionVariables.set(…)" }),
@@ -72463,6 +72482,7 @@ function App() {
72463
72482
  const [tabContextMenu, setTabContextMenu] = reactExports.useState(null);
72464
72483
  const showGeneratorPanel = useStore((s) => s.showGeneratorPanel);
72465
72484
  const sidebarTab = useStore((s) => s.sidebarTab);
72485
+ const collectionPanelOpen = useStore((s) => s.collectionPanelOpen);
72466
72486
  const setSidebarTab = useStore((s) => s.setSidebarTab);
72467
72487
  const historyCount = useStore((s) => s.history.length);
72468
72488
  const addCollection = useStore((s) => s.addCollection);
@@ -72580,7 +72600,7 @@ function App() {
72580
72600
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "#6aa3c8" }, children: "Spector" }),
72581
72601
  /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "ml-2 text-[10px] font-normal opacity-50", children: [
72582
72602
  "v",
72583
- "0.4.1"
72603
+ "0.4.3"
72584
72604
  ] }),
72585
72605
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mx-2 opacity-30", children: "·" }),
72586
72606
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -72811,7 +72831,7 @@ function App() {
72811
72831
  }
72812
72832
  }
72813
72833
  }
72814
- ) }) : sidebarTab === "mocks" && activeMockId ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(MockDetailPanel, { mockId: activeMockId }) }) : activeRequest ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex min-h-0", children: [
72834
+ ) }) : sidebarTab === "mocks" && activeMockId ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(MockDetailPanel, { mockId: activeMockId }) }) : collectionPanelOpen ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(CollectionPanel, {}) }) : activeRequest ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex min-h-0", children: [
72815
72835
  /* @__PURE__ */ jsxRuntimeExports.jsx(
72816
72836
  "div",
72817
72837
  {
@@ -5,7 +5,7 @@
5
5
  <meta charset="UTF-8" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>API Spector</title>
8
- <script type="module" crossorigin src="./assets/index-DGOQFW5S.js"></script>
8
+ <script type="module" crossorigin src="./assets/index-nGZ7Xq6I.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="./assets/index-DkZ0bxof.css">
10
10
  </head>
11
11
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@testsmith/api-spector",
3
3
  "productName": "API Spector",
4
- "version": "0.4.1",
4
+ "version": "0.4.3",
5
5
  "description": "Local-first API testing tool to inspect, test and mock APIs",
6
6
  "repository": {
7
7
  "type": "git",
@@ -112,7 +112,23 @@
112
112
  }
113
113
  ],
114
114
  "mac": {
115
- "category": "public.app-category.developer-tools"
115
+ "category": "public.app-category.developer-tools",
116
+ "target": [
117
+ {
118
+ "target": "dmg",
119
+ "arch": [
120
+ "arm64",
121
+ "x64"
122
+ ]
123
+ },
124
+ {
125
+ "target": "zip",
126
+ "arch": [
127
+ "arm64",
128
+ "x64"
129
+ ]
130
+ }
131
+ ]
116
132
  },
117
133
  "win": {
118
134
  "target": "nsis"
@@ -124,21 +140,5 @@
124
140
  "provider": "github",
125
141
  "releaseType": "draft"
126
142
  }
127
- },
128
- "mac": {
129
- "category": "public.app-category.developer-tools",
130
- "target": [
131
- {
132
- "target": "dmg",
133
- "arch": [
134
- "arm64",
135
- "x64"
136
- ]
137
- }
138
- ],
139
- "identity": null
140
- },
141
- "win": {
142
- "target": "nsis"
143
143
  }
144
144
  }