@testsmith/api-spector 0.6.1 → 0.6.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.
Files changed (30) hide show
  1. package/out/main/agents.js +2 -1
  2. package/out/main/chunks/{snapshots-Y33M1SEy.js → broker-client-WEymdqAK.js} +119 -10
  3. package/out/main/chunks/{cli-common-BKYgsbGB.js → cli-common-vASoDfQH.js} +3 -2
  4. package/out/main/chunks/data-files-Br2h3Dt2.js +85 -0
  5. package/out/main/chunks/escape-BwPM4Tf6.js +9 -0
  6. package/out/main/chunks/{handle-BFaeFA0A.js → handle-CL-nWtYA.js} +11 -2
  7. package/out/main/chunks/{import-D9LH0isB.js → import-DdXhO56X.js} +2 -2
  8. package/out/main/chunks/{request-collection-BQIo1765.js → request-collection-GzK4QgvT.js} +78 -17
  9. package/out/main/chunks/{request-exec-5_3279w1.js → request-exec-DAFA6Fap.js} +19 -1
  10. package/out/main/chunks/{soap-handler-CWn3o6Py.js → soap-handler-BAPCALkN.js} +2 -2
  11. package/out/main/compare.js +2 -1
  12. package/out/main/contract.js +50 -147
  13. package/out/main/coverage.js +2 -1
  14. package/out/main/generate-tests.js +2 -1
  15. package/out/main/index.js +134 -36
  16. package/out/main/lib.js +2 -2
  17. package/out/main/mock.js +2 -1
  18. package/out/main/record.js +2 -1
  19. package/out/main/runner.js +117 -20
  20. package/out/main/wsdl.js +5 -4
  21. package/out/preload/index.js +21 -3
  22. package/out/renderer/assets/{GraphQLEditor-8wU1UeBL.js → GraphQLEditor-MImdgXb4.js} +3 -1
  23. package/out/renderer/assets/{SoapEditor-DQwLLqCX.js → SoapEditor-rxb3heue.js} +4 -2
  24. package/out/renderer/assets/ajv-2MAg6kGF.js +6153 -0
  25. package/out/renderer/assets/codemirror-F6m3Hm1M.js +26552 -0
  26. package/out/renderer/assets/index-B3mtLdU3.css +2 -0
  27. package/out/renderer/assets/{index-CdxTj7i6.js → index-ByGIqJ2H.js} +17526 -49312
  28. package/out/renderer/index.html +4 -2
  29. package/package.json +1 -1
  30. package/out/renderer/assets/index-CkHujZU3.css +0 -2
@@ -2,13 +2,14 @@
2
2
  "use strict";
3
3
  const promises = require("fs/promises");
4
4
  const path = require("path");
5
- const requestExec = require("./chunks/request-exec-5_3279w1.js");
6
- const requestCollection = require("./chunks/request-collection-BQIo1765.js");
5
+ const requestExec = require("./chunks/request-exec-DAFA6Fap.js");
6
+ const escape = require("./chunks/escape-BwPM4Tf6.js");
7
+ const requestCollection = require("./chunks/request-collection-GzK4QgvT.js");
7
8
  const environments = require("./chunks/environments-iM3SUM-4.js");
8
- const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
9
+ const cliCommon = require("./chunks/cli-common-vASoDfQH.js");
9
10
  require("undici");
10
11
  require("tls");
11
- require("./chunks/handle-BFaeFA0A.js");
12
+ require("./chunks/handle-CL-nWtYA.js");
12
13
  require("node:fs");
13
14
  require("node:os");
14
15
  require("node:path");
@@ -22,6 +23,7 @@ require("@xmldom/xmldom");
22
23
  require("http");
23
24
  require("ajv");
24
25
  require("uuid");
26
+ require("./chunks/data-files-Br2h3Dt2.js");
25
27
  function buildJsonReport(results, summary, meta = {}) {
26
28
  return JSON.stringify({
27
29
  timestamp: meta.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
@@ -34,6 +36,9 @@ function buildJsonReport(results, summary, meta = {}) {
34
36
  method: r.method,
35
37
  url: r.resolvedUrl,
36
38
  status: r.status,
39
+ // Tree position: which collection and folder path this request sits under.
40
+ collection: r.collection ?? meta.collection ?? null,
41
+ folderPath: r.scopePath ?? [],
37
42
  isHook: r.isHook ?? false,
38
43
  hookType: r.hookType ?? null,
39
44
  httpStatus: r.httpStatus ?? null,
@@ -50,7 +55,7 @@ function buildJsonReport(results, summary, meta = {}) {
50
55
  }, null, 2);
51
56
  }
52
57
  function buildHtmlReport(results, summary, meta = {}) {
53
- const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
58
+ const esc = escape.escapeHtml;
54
59
  function prettyJson(s) {
55
60
  try {
56
61
  return esc(JSON.stringify(JSON.parse(s), null, 2));
@@ -74,12 +79,26 @@ function buildHtmlReport(results, summary, meta = {}) {
74
79
  after: "AFTER",
75
80
  afterAll: "AFTER ALL"
76
81
  };
77
- let lastScopeKey = null;
82
+ let lastCollection = null;
83
+ let lastPath = [];
78
84
  const cards = results.map((r, idx) => {
79
- const scopeKey = (r.scopePath ?? []).join(" / ");
80
- const groupHeading = scopeKey && scopeKey !== lastScopeKey ? ` <div class="scope-heading">${esc(scopeKey)}</div>
81
- ` : "";
82
- lastScopeKey = scopeKey;
85
+ let groupHeading = "";
86
+ const coll = r.collection ?? meta.collection ?? "";
87
+ if (coll && coll !== lastCollection) {
88
+ groupHeading += ` <div class="scope-heading lvl-0">${esc(coll)}</div>
89
+ `;
90
+ lastCollection = coll;
91
+ lastPath = [];
92
+ }
93
+ const path2 = r.scopePath ?? [];
94
+ let diverge = 0;
95
+ while (diverge < path2.length && diverge < lastPath.length && path2[diverge] === lastPath[diverge]) diverge++;
96
+ for (let d = diverge; d < path2.length; d++) {
97
+ groupHeading += ` <div class="scope-heading lvl-${Math.min(d + 1, 6)}">${esc(path2[d])}</div>
98
+ `;
99
+ }
100
+ lastPath = path2;
101
+ const cardIndent = `lvl-${Math.min(path2.length + 1, 6)}`;
83
102
  const statusCls = r.status === "passed" ? "badge-pass" : r.status === "failed" ? "badge-fail" : r.status === "skipped" ? "badge-skip" : "badge-err";
84
103
  const httpCls = r.httpStatus && r.httpStatus < 300 ? "http-ok" : r.httpStatus && r.httpStatus < 400 ? "http-redir" : "http-err";
85
104
  const dur = r.durationMs != null ? `${r.durationMs} ms` : "—";
@@ -119,7 +138,7 @@ function buildHtmlReport(results, summary, meta = {}) {
119
138
  </div>` : "";
120
139
  const hookCls = hookLabel ? r.hookType?.startsWith("before") ? "card-hook-before" : "card-hook-after" : "";
121
140
  return `${groupHeading}
122
- <div class="card ${hookCls}" id="r${idx}">
141
+ <div class="card ${hookCls} ${cardIndent}" id="r${idx}">
123
142
  <div class="card-header" onclick="toggle(${idx})">
124
143
  <span class="chevron" id="ch${idx}">▶</span>
125
144
  <span class="badge ${statusCls}">${r.status}</span>
@@ -190,8 +209,18 @@ function buildHtmlReport(results, summary, meta = {}) {
190
209
  .stat-skip .stat-val { color: var(--text-muted); }
191
210
  /* Cards */
192
211
  .card { border: 1px solid var(--border); border-radius: 8px; margin-bottom: 8px; overflow: hidden; }
193
- .scope-heading { margin: 18px 0 6px; padding: 4px 2px; font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border); }
212
+ .scope-heading { margin: 14px 0 6px; padding: 4px 2px; font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border); }
194
213
  .scope-heading::before { content: "\\25B8 "; color: var(--text-dim); }
214
+ /* Nested tree indentation: collection (lvl-0) → folders (lvl-1..) → request card. */
215
+ .lvl-0 { margin-left: 0; }
216
+ .lvl-1 { margin-left: 16px; }
217
+ .lvl-2 { margin-left: 32px; }
218
+ .lvl-3 { margin-left: 48px; }
219
+ .lvl-4 { margin-left: 64px; }
220
+ .lvl-5 { margin-left: 80px; }
221
+ .lvl-6 { margin-left: 96px; }
222
+ .scope-heading.lvl-0 { font-size: 13px; color: var(--text); text-transform: none; letter-spacing: 0; margin-top: 22px; }
223
+ .scope-heading.lvl-0::before { content: "\\1F5C0 "; }
195
224
  .card-header { display: flex; align-items: baseline; gap: 8px; padding: 10px 14px; cursor: pointer; user-select: none; }
196
225
  .card-header:hover { background: var(--bg-surface); }
197
226
  .chevron { font-size: 10px; color: var(--text-muted); min-width: 10px; transition: transform .15s; }
@@ -295,7 +324,7 @@ function buildHtmlReport(results, summary, meta = {}) {
295
324
  `;
296
325
  }
297
326
  function buildJUnitReport(results, summary, meta = {}) {
298
- const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
327
+ const esc = escape.escapeHtml;
299
328
  const suiteName = esc(meta.collection ?? "API Tests");
300
329
  const totalSec = (summary.durationMs / 1e3).toFixed(3);
301
330
  const ts = meta.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
@@ -309,7 +338,7 @@ function buildJUnitReport(results, summary, meta = {}) {
309
338
  const label = r.iterationLabel ? ` #${r.iterationLabel}` : "";
310
339
  const hookPrefix = r.isHook && r.hookType ? `[${HOOK_JUNIT_LABELS[r.hookType] ?? "Hook"}] ` : "";
311
340
  const name = esc(hookPrefix + r.name + label);
312
- const classname = esc(`${r.method} ${r.resolvedUrl}`);
341
+ const classname = esc([r.collection ?? meta.collection, ...r.scopePath ?? []].filter(Boolean).map((s) => String(s).replace(/\./g, "_")).join(".") || "API Tests");
313
342
  const timeSec = ((r.durationMs ?? 0) / 1e3).toFixed(3);
314
343
  const failures = [];
315
344
  if (r.status === "error") {
@@ -345,6 +374,70 @@ ${failures.join("\n")}
345
374
  ];
346
375
  return lines.join("\n") + "\n";
347
376
  }
377
+ const OWASP_BY_TAG = {
378
+ BOLA: { id: "API1:2023", name: "Broken Object Level Authorization", ref: "https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/" },
379
+ AUTH: { id: "API2:2023", name: "Broken Authentication", ref: "https://owasp.org/API-Security/editions/2023/en/0xa2-broken-authentication/" },
380
+ "MASS-ASSIGN": { id: "API3:2023", name: "Broken Object Property Level Authorization", ref: "https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/" },
381
+ RESOURCE: { id: "API4:2023", name: "Unrestricted Resource Consumption", ref: "https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/" },
382
+ "FUNC-AUTH": { id: "API5:2023", name: "Broken Function Level Authorization", ref: "https://owasp.org/API-Security/editions/2023/en/0xa5-broken-function-level-authorization/" },
383
+ "BIZ-FLOW": { id: "API6:2023", name: "Unrestricted Access to Sensitive Business Flows", ref: "https://owasp.org/API-Security/editions/2023/en/0xa6-unrestricted-access-to-sensitive-business-flows/" },
384
+ SSRF: { id: "API7:2023", name: "Server Side Request Forgery", ref: "https://owasp.org/API-Security/editions/2023/en/0xa7-server-side-request-forgery/" },
385
+ CONFIG: { id: "API8:2023", name: "Security Misconfiguration", ref: "https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/" },
386
+ INVENTORY: { id: "API9:2023", name: "Improper Inventory Management", ref: "https://owasp.org/API-Security/editions/2023/en/0xa9-improper-inventory-management/" },
387
+ INJECTION: { id: "API10:2023", name: "Unsafe Consumption of APIs", ref: "https://owasp.org/API-Security/editions/2023/en/0xaa-unsafe-consumption-of-apis/" }
388
+ };
389
+ function owaspRuleForName(name) {
390
+ const m = /^\s*\[([A-Za-z-]+)\]/.exec(name ?? "");
391
+ return m ? OWASP_BY_TAG[m[1].toUpperCase()] ?? null : null;
392
+ }
393
+ function buildSarifReport(results, _summary, meta = {}) {
394
+ const rules = /* @__PURE__ */ new Map();
395
+ const addRule = (id, name, helpUri) => {
396
+ if (!rules.has(id)) rules.set(id, { id, name, ...helpUri ? { helpUri } : {} });
397
+ };
398
+ const sarifResults = [];
399
+ const emit = (rule, fallbackId, fallbackName, r, message) => {
400
+ const id = rule ? rule.id : fallbackId;
401
+ const name = rule ? rule.name : fallbackName;
402
+ addRule(id, name, rule?.ref);
403
+ sarifResults.push({
404
+ ruleId: id,
405
+ level: "error",
406
+ message: { text: `${r.method} ${r.resolvedUrl}${r.iterationLabel ? ` #${r.iterationLabel}` : ""} — ${message}` },
407
+ locations: [{ physicalLocation: { artifactLocation: { uri: r.resolvedUrl || r.name } } }]
408
+ });
409
+ };
410
+ for (const r of results) {
411
+ if (r.status === "error" && r.error) emit(owaspRuleForName(r.name), "request-error", "Request error", r, r.error);
412
+ if (r.preScriptError) emit(null, "pre-script-error", "Pre-request script error", r, r.preScriptError);
413
+ if (r.postScriptError) emit(null, "post-script-error", "Post-request script error", r, r.postScriptError);
414
+ for (const t of r.testResults ?? []) {
415
+ if (t.passed) continue;
416
+ const rule = owaspRuleForName(t.name) ?? owaspRuleForName(r.name);
417
+ emit(rule, "assertion-failed", "Assertion failed", r, `${t.name}${t.error ? `: ${t.error}` : ""}`);
418
+ }
419
+ }
420
+ return JSON.stringify({
421
+ version: "2.1.0",
422
+ $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
423
+ runs: [{
424
+ tool: {
425
+ driver: {
426
+ name: "API Spector",
427
+ informationUri: "https://github.com/testsmith-io/api-spector",
428
+ rules: [...rules.values()].map((r) => ({
429
+ id: r.id,
430
+ name: r.name,
431
+ shortDescription: { text: r.name },
432
+ ...r.helpUri ? { helpUri: r.helpUri } : {}
433
+ }))
434
+ }
435
+ },
436
+ ...meta.timestamp ? { invocations: [{ executionSuccessful: sarifResults.length === 0, endTimeUtc: meta.timestamp }] } : {},
437
+ results: sarifResults
438
+ }]
439
+ }, null, 2);
440
+ }
348
441
  function printResult(r, verbose) {
349
442
  const icon = r.status === "passed" ? cliCommon.color("✓", cliCommon.C.green, cliCommon.C.bold) : r.status === "failed" ? cliCommon.color("✗", cliCommon.C.red, cliCommon.C.bold) : r.status === "skipped" ? cliCommon.color("○", cliCommon.C.gray, cliCommon.C.bold) : cliCommon.color("⚠", cliCommon.C.yellow, cliCommon.C.bold);
350
443
  const http = r.httpStatus ? cliCommon.color(` ${r.httpStatus}`, r.httpStatus < 400 ? cliCommon.C.green : cliCommon.C.red) : "";
@@ -365,7 +458,7 @@ async function main() {
365
458
  const args = cliCommon.parseArgs(process.argv.slice(2));
366
459
  if (args.help) {
367
460
  console.log(
368
- "\nUsage:\n api-spector run --workspace <path> [--environment <name>] [--tags <a,b>]\n [--collection <name>] [--output <path>] [--format json|junit]\n [--verbose] [--bail]\n"
461
+ "\nUsage:\n api-spector run --workspace <path> [--environment <name>] [--tags <a,b>]\n [--collection <name>] [--output <path>] [--format json|junit|html|sarif]\n [--verbose] [--bail]\n"
369
462
  );
370
463
  process.exit(0);
371
464
  }
@@ -380,9 +473,9 @@ async function main() {
380
473
  const verbose = Boolean(args.verbose);
381
474
  const bail = Boolean(args.bail);
382
475
  const outputPath = args.output;
383
- const inferredFormat = outputPath ? path.extname(outputPath).toLowerCase() === ".xml" ? "junit" : path.extname(outputPath).toLowerCase() === ".html" ? "html" : "json" : "json";
476
+ const inferredFormat = outputPath ? path.extname(outputPath).toLowerCase() === ".xml" ? "junit" : path.extname(outputPath).toLowerCase() === ".html" ? "html" : path.extname(outputPath).toLowerCase() === ".sarif" ? "sarif" : "json" : "json";
384
477
  const explicitFormat = args.format?.toLowerCase();
385
- const outputFormat = explicitFormat === "junit" || explicitFormat === "html" ? explicitFormat : inferredFormat;
478
+ const outputFormat = explicitFormat === "junit" || explicitFormat === "html" || explicitFormat === "sarif" ? explicitFormat : inferredFormat;
386
479
  let workspace, wsDir;
387
480
  try {
388
481
  ;
@@ -403,7 +496,7 @@ async function main() {
403
496
  } else if (!envName && workspace.settings?.defaultEnvironment && !env) {
404
497
  console.warn(cliCommon.color(`Warning: default environment "${workspace.settings.defaultEnvironment}" not found. Running without environment.`, cliCommon.C.yellow));
405
498
  }
406
- const version = `v${"0.6.1"}`;
499
+ const version = `v${"0.6.3"}`;
407
500
  console.log("");
408
501
  console.log(cliCommon.color(" API Test Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
409
502
  console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
@@ -503,6 +596,7 @@ async function main() {
503
596
  isHook,
504
597
  hookType,
505
598
  scopeId,
599
+ collection: item.collection,
506
600
  scopePath: item.scopePath,
507
601
  iterationLabel: item.iterationLabel
508
602
  };
@@ -516,7 +610,9 @@ async function main() {
516
610
  dispatcher,
517
611
  piiMaskPatterns: piiPatterns,
518
612
  tls: effectiveTls,
519
- onScriptOutput
613
+ onScriptOutput,
614
+ expectStatus: item.expectStatus,
615
+ owaspTag: item.owaspTag
520
616
  });
521
617
  result = out.result;
522
618
  runEnvVars = out.updatedEnvVars;
@@ -526,6 +622,7 @@ async function main() {
526
622
  result.isHook = isHook;
527
623
  result.hookType = hookType;
528
624
  result.scopeId = scopeId;
625
+ result.collection = item.collection;
529
626
  result.scopePath = item.scopePath;
530
627
  result.iterationLabel = item.iterationLabel;
531
628
  skipTracker.recordResult(item, result.status);
@@ -562,7 +659,7 @@ async function main() {
562
659
  if (outputPath) {
563
660
  const meta = { workspace: wsPath, environment: env?.name ?? null, collection: firstColName, timestamp };
564
661
  const maskedResults = allResults.map(maskResult);
565
- const report = outputFormat === "junit" ? buildJUnitReport(maskedResults, summary, meta) : outputFormat === "html" ? buildHtmlReport(maskedResults, summary, meta) : buildJsonReport(maskedResults, summary, meta);
662
+ const report = outputFormat === "junit" ? buildJUnitReport(maskedResults, summary, meta) : outputFormat === "html" ? buildHtmlReport(maskedResults, summary, meta) : outputFormat === "sarif" ? buildSarifReport(maskedResults, summary, meta) : buildJsonReport(maskedResults, summary, meta);
566
663
  await promises.writeFile(path.resolve(outputPath), report, "utf8");
567
664
  console.log(cliCommon.color(` Report written: ${outputPath} (${outputFormat})
568
665
  `, cliCommon.C.gray));
package/out/main/wsdl.js CHANGED
@@ -4,12 +4,13 @@ const promises = require("fs/promises");
4
4
  const path = require("path");
5
5
  const https = require("https");
6
6
  const http = require("http");
7
- const soapHandler = require("./chunks/soap-handler-CWn3o6Py.js");
8
- const _import = require("./chunks/import-D9LH0isB.js");
9
- const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
10
- require("./chunks/handle-BFaeFA0A.js");
7
+ const soapHandler = require("./chunks/soap-handler-BAPCALkN.js");
8
+ const _import = require("./chunks/import-DdXhO56X.js");
9
+ const cliCommon = require("./chunks/cli-common-vASoDfQH.js");
10
+ require("./chunks/handle-CL-nWtYA.js");
11
11
  require("@xmldom/xmldom");
12
12
  require("uuid");
13
+ require("./chunks/data-files-Br2h3Dt2.js");
13
14
  require("crypto");
14
15
  function fetchUrl(url) {
15
16
  return new Promise((resolveP, rejectP) => {
@@ -40,7 +40,13 @@ const IPC = {
40
40
  secret: {
41
41
  checkMasterKey: "secret:checkMasterKey",
42
42
  setMasterKey: "secret:setMasterKey",
43
- set: "secret:set"
43
+ set: "secret:set",
44
+ has: "secret:has",
45
+ delete: "secret:delete"
46
+ },
47
+ // ─── AI (optional; key lives only in the OS keychain) ────────────────────────
48
+ ai: {
49
+ generateDocs: "ai:generateDocs"
44
50
  },
45
51
  // ─── Global variables ──────────────────────────────────────────────────────
46
52
  globals: {
@@ -164,6 +170,7 @@ const IPC = {
164
170
  branches: "git:branches",
165
171
  checkout: "git:checkout",
166
172
  deleteBranch: "git:deleteBranch",
173
+ fetch: "git:fetch",
167
174
  pull: "git:pull",
168
175
  push: "git:push",
169
176
  remotes: "git:remotes",
@@ -173,7 +180,9 @@ const IPC = {
173
180
  writeCiFile: "git:writeCiFile",
174
181
  resolveOurs: "git:resolveOurs",
175
182
  resolveTheirs: "git:resolveTheirs",
176
- markResolved: "git:markResolved"
183
+ markResolved: "git:markResolved",
184
+ readConflict: "git:readConflict",
185
+ writeResolved: "git:writeResolved"
177
186
  },
178
187
  // ─── Recorder ──────────────────────────────────────────────────────────────
179
188
  record: {
@@ -246,6 +255,12 @@ const api = {
246
255
  setMasterKey: (value) => electron.ipcRenderer.invoke(IPC.secret.setMasterKey, value),
247
256
  /** Save a named secret to the OS keychain (safeStorage). */
248
257
  setSecret: (ref, value) => electron.ipcRenderer.invoke(IPC.secret.set, ref, value),
258
+ /** Whether a secret exists (never returns the value itself). */
259
+ hasSecret: (ref) => electron.ipcRenderer.invoke(IPC.secret.has, ref),
260
+ /** Remove a secret from the OS keychain. */
261
+ deleteSecret: (ref) => electron.ipcRenderer.invoke(IPC.secret.delete, ref),
262
+ /** Optional: generate markdown docs for an item via the configured LLM. */
263
+ generateDocs: (input) => electron.ipcRenderer.invoke(IPC.ai.generateDocs, input),
249
264
  // ─── Globals ──────────────────────────────────────────────────────────────
250
265
  getGlobals: () => electron.ipcRenderer.invoke(IPC.globals.get),
251
266
  setGlobals: (globals) => electron.ipcRenderer.invoke(IPC.globals.set, globals),
@@ -376,7 +391,8 @@ const api = {
376
391
  gitBranches: () => electron.ipcRenderer.invoke(IPC.git.branches),
377
392
  gitCheckout: (branch, create) => electron.ipcRenderer.invoke(IPC.git.checkout, branch, create),
378
393
  gitDeleteBranch: (name, force = false) => electron.ipcRenderer.invoke(IPC.git.deleteBranch, name, force),
379
- gitPull: () => electron.ipcRenderer.invoke(IPC.git.pull),
394
+ gitFetch: () => electron.ipcRenderer.invoke(IPC.git.fetch),
395
+ gitPull: (rebase = false) => electron.ipcRenderer.invoke(IPC.git.pull, rebase),
380
396
  gitPush: (setUpstream) => electron.ipcRenderer.invoke(IPC.git.push, setUpstream),
381
397
  gitRemotes: () => electron.ipcRenderer.invoke(IPC.git.remotes),
382
398
  gitAddRemote: (name, url) => electron.ipcRenderer.invoke(IPC.git.addRemote, name, url),
@@ -386,6 +402,8 @@ const api = {
386
402
  gitResolveOurs: (filePath) => electron.ipcRenderer.invoke(IPC.git.resolveOurs, filePath),
387
403
  gitResolveTheirs: (filePath) => electron.ipcRenderer.invoke(IPC.git.resolveTheirs, filePath),
388
404
  gitMarkResolved: (filePath) => electron.ipcRenderer.invoke(IPC.git.markResolved, filePath),
405
+ gitReadConflict: (filePath) => electron.ipcRenderer.invoke(IPC.git.readConflict, filePath),
406
+ gitWriteResolved: (filePath, content) => electron.ipcRenderer.invoke(IPC.git.writeResolved, filePath, content),
389
407
  // ─── Recorder ─────────────────────────────────────────────────────────────
390
408
  recordStart: (config) => electron.ipcRenderer.invoke(IPC.record.start, config),
391
409
  recordStop: () => electron.ipcRenderer.invoke(IPC.record.stop),
@@ -1,4 +1,6 @@
1
- import { l as linter, E as EditorView, L as LanguageSupport, a as LRLanguage, s as styleTags, i as indentNodeProp, d as delimitedIndent, f as foldNodeProp, b as foldInside, S as StateField, c as LRParser, e as StateEffect, t as tags, u as useT, r as reactExports, g as useStore, h as useActiveEnvironment, j as jsxRuntimeExports, R as ReactCodeMirror, o as oneDark, k as json } from "./index-CdxTj7i6.js";
1
+ import { l as linter, E as EditorView, L as LanguageSupport, e as LRLanguage, s as styleTags, i as indentNodeProp, f as delimitedIndent, m as foldNodeProp, n as foldInside, S as StateField, p as LRParser, q as StateEffect, u as tags, r as reactExports, j as jsxRuntimeExports, c as ReactCodeMirror, o as oneDark, b as json } from "./codemirror-F6m3Hm1M.js";
2
+ import { u as useT, a as useStore, b as useActiveEnvironment } from "./index-ByGIqJ2H.js";
3
+ import "./ajv-2MAg6kGF.js";
2
4
  function devAssert(condition, message) {
3
5
  const booleanCondition = Boolean(condition);
4
6
  if (!booleanCondition) {
@@ -1,4 +1,6 @@
1
- import { u as useT, r as reactExports, j as jsxRuntimeExports, R as ReactCodeMirror, m as commentKeymap, o as oneDark, x as xml } from "./index-CdxTj7i6.js";
1
+ import { r as reactExports, j as jsxRuntimeExports, c as ReactCodeMirror, o as oneDark, x as xml } from "./codemirror-F6m3Hm1M.js";
2
+ import { u as useT, c as btnPrimaryCls, d as commentKeymap } from "./index-ByGIqJ2H.js";
3
+ import "./ajv-2MAg6kGF.js";
2
4
  const SOAP_11_CONTENT_TYPE = "text/xml; charset=utf-8";
3
5
  const SOAP_12_CONTENT_TYPE = "application/soap+xml; charset=utf-8";
4
6
  function contentTypeForSoap(version) {
@@ -120,7 +122,7 @@ function SoapEditor({ request, onChange }) {
120
122
  {
121
123
  onClick: fetchWsdl,
122
124
  disabled: fetching || !soap.wsdlUrl.trim(),
123
- 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",
125
+ className: `${btnPrimaryCls} whitespace-nowrap`,
124
126
  children: fetching ? t("Fetching…") : operations.length > 0 ? t("Refresh") : t("Fetch WSDL")
125
127
  }
126
128
  )