@testsmith/api-spector 0.4.6 → 0.4.8

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
@@ -22,12 +22,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  mod
23
23
  ));
24
24
  const electron = require("electron");
25
- const handle = require("./chunks/handle-BGYDylL2.js");
25
+ const handle = require("./chunks/handle-rimXXdJH.js");
26
26
  const path = require("path");
27
27
  const fs = require("fs");
28
28
  const promises = require("fs/promises");
29
29
  const crypto = require("crypto");
30
- const requestExec = require("./chunks/request-exec-CBi2kL6s.js");
30
+ const requestExec = require("./chunks/request-exec-BH-M3KqZ.js");
31
31
  const ipcValidate = require("./chunks/ipc-validate-k6KI8adf.js");
32
32
  const uuid = require("uuid");
33
33
  const jsYaml = require("js-yaml");
@@ -37,9 +37,9 @@ const requestCollection = require("./chunks/request-collection-CJoXpvOw.js");
37
37
  const mockServer = require("./chunks/mock-server-BxiXhP1m.js");
38
38
  const http = require("http");
39
39
  const WebSocket = require("ws");
40
- const soapHandler = require("./chunks/soap-handler-CAyUjzxB.js");
40
+ const soapHandler = require("./chunks/soap-handler-BXx842MN.js");
41
41
  const os = require("os");
42
- const snapshots = require("./chunks/snapshots-BDe-B5iQ.js");
42
+ const snapshots = require("./chunks/snapshots-CtYxkSLz.js");
43
43
  const simpleGit = require("simple-git");
44
44
  const recorder = require("./chunks/recorder-0Ij921El.js");
45
45
  require("tls");
@@ -544,7 +544,11 @@ function formatRequestError(err, context) {
544
544
  }
545
545
  return lines.join("\n");
546
546
  }
547
+ const streamControllers = /* @__PURE__ */ new Map();
547
548
  function registerRequestHandler(ipc) {
549
+ handle.handleIpc(ipc, handle.IPC.request.stopStream, async (_e, streamId) => {
550
+ streamControllers.get(streamId)?.abort();
551
+ });
548
552
  handle.handleIpc(ipc, handle.IPC.request.send, async (_e, payload) => {
549
553
  ipcValidate.validateSendRequestPayload(payload);
550
554
  const {
@@ -554,9 +558,23 @@ function registerRequestHandler(ipc) {
554
558
  globals: payloadGlobals,
555
559
  proxy,
556
560
  tls,
557
- piiMaskPatterns = []
561
+ piiMaskPatterns = [],
562
+ streamId,
563
+ forceStream
558
564
  } = payload;
559
565
  requestExec.applyRequestDefaults(req);
566
+ const maskEvent = (ev) => piiMaskPatterns.length ? { ...ev, data: requestExec.maskPii(ev.data, piiMaskPatterns), json: void 0 } : ev;
567
+ let abortController;
568
+ let flushTimer;
569
+ const pending = [];
570
+ const flushStream = () => {
571
+ if (pending.length) _e.sender.send(handle.IPC.request.streamEvent, { streamId, events: pending.splice(0) });
572
+ };
573
+ if (streamId) {
574
+ abortController = new AbortController();
575
+ streamControllers.set(streamId, abortController);
576
+ flushTimer = setInterval(flushStream, 40);
577
+ }
560
578
  const start = Date.now();
561
579
  const liveGlobals = requestExec.getGlobals();
562
580
  const mergedGlobals = { ...payloadGlobals, ...liveGlobals };
@@ -640,18 +658,27 @@ function registerRequestHandler(ipc) {
640
658
  tls,
641
659
  onSent: (sent) => {
642
660
  sentRequest = sent;
643
- }
661
+ },
662
+ forceStream,
663
+ signal: abortController?.signal,
664
+ onStreamEvent: streamId ? (ev) => {
665
+ pending.push(maskEvent(ev));
666
+ if (pending.length >= 50) flushStream();
667
+ } : void 0
644
668
  });
645
669
  const maskedBody = requestExec.maskPii(exchange.responseBody, piiMaskPatterns);
646
670
  const maskedHeaders = requestExec.maskHeaders(exchange.rawHeaders, piiMaskPatterns);
647
671
  const bodySize = Buffer.byteLength(exchange.responseBody, "utf8");
672
+ const maskedEvents = exchange.events?.map(maskEvent);
673
+ const streamFields = exchange.streamed ? { streamed: true, events: maskedEvents, streamClose: exchange.streamClose, firstEventMs: exchange.firstEventMs } : {};
648
674
  response = {
649
675
  status: exchange.status,
650
676
  statusText: exchange.statusText,
651
677
  headers: maskedHeaders,
652
678
  body: maskedBody,
653
679
  bodySize,
654
- durationMs: exchange.durationMs
680
+ durationMs: exchange.durationMs,
681
+ ...streamFields
655
682
  };
656
683
  scriptResponse = {
657
684
  status: exchange.status,
@@ -659,7 +686,8 @@ function registerRequestHandler(ipc) {
659
686
  headers: exchange.rawHeaders,
660
687
  body: exchange.responseBody,
661
688
  bodySize,
662
- durationMs: exchange.durationMs
689
+ durationMs: exchange.durationMs,
690
+ ...exchange.streamed ? { streamed: true, events: exchange.events } : {}
663
691
  };
664
692
  } catch (err) {
665
693
  const diagnostic = formatRequestError(err, {
@@ -680,6 +708,12 @@ function registerRequestHandler(ipc) {
680
708
  error: diagnostic
681
709
  };
682
710
  scriptResponse = response;
711
+ } finally {
712
+ if (streamId) {
713
+ if (flushTimer) clearInterval(flushTimer);
714
+ flushStream();
715
+ streamControllers.delete(streamId);
716
+ }
683
717
  }
684
718
  const schemaTestResults = !response.error ? requestExec.buildSchemaTestResults(req.schema, scriptResponse.body) : [];
685
719
  let postTestResults = [];
@@ -889,21 +923,71 @@ function translateMaybe(src, format) {
889
923
  }
890
924
  function parseRequest(item, collectionAuth) {
891
925
  const req = typeof item.request === "string" ? { url: item.request, method: "GET" } : item.request ?? {};
926
+ const method = (req.method ?? "GET").toUpperCase();
927
+ const url = parseUrl(req.url);
928
+ const headers = parseHeaders$1(req.header);
929
+ const params = parseParams$1(req.url);
930
+ const body = parseBody$1(req.body);
931
+ const examples = parseExamples(item, { method, url, headers, params, body });
892
932
  return {
893
933
  id: uuid.v4(),
894
934
  name: item.name ?? "Request",
895
- method: (req.method ?? "GET").toUpperCase(),
896
- url: parseUrl(req.url),
897
- headers: parseHeaders$1(req.header),
898
- params: parseParams$1(req.url),
935
+ method,
936
+ url,
937
+ headers,
938
+ params,
899
939
  auth: parseAuth$1(req.auth ?? collectionAuth),
900
- body: parseBody$1(req.body),
940
+ body,
901
941
  description: req.description ?? "",
902
942
  preRequestScript: translateMaybe(parseScript(item.event, "prerequest"), "postman"),
903
943
  postRequestScript: translateMaybe(parseScript(item.event, "test"), "postman"),
904
- meta: {}
944
+ meta: {},
945
+ ...examples ? { examples } : {}
905
946
  };
906
947
  }
948
+ function parseExamples(item, parent) {
949
+ const responses = item?.response;
950
+ if (!Array.isArray(responses) || responses.length === 0) return void 0;
951
+ const examples = [];
952
+ for (const r of responses) {
953
+ if (!r || typeof r !== "object") continue;
954
+ const orig = r.originalRequest;
955
+ const request = orig ? {
956
+ method: (orig.method ?? parent.method).toUpperCase(),
957
+ url: parseUrl(orig.url),
958
+ headers: parseHeaders$1(orig.header),
959
+ params: parseParams$1(orig.url),
960
+ body: parseBody$1(orig.body)
961
+ } : {
962
+ method: parent.method,
963
+ url: parent.url,
964
+ headers: parent.headers,
965
+ params: parent.params,
966
+ body: parent.body
967
+ };
968
+ const headers = {};
969
+ for (const h of Array.isArray(r.header) ? r.header : []) {
970
+ if (h && typeof h === "object" && h.key) headers[h.key] = h.value ?? "";
971
+ }
972
+ const body = typeof r.body === "string" ? r.body : "";
973
+ const code = typeof r.code === "number" ? r.code : 0;
974
+ examples.push({
975
+ id: uuid.v4(),
976
+ name: typeof r.name === "string" && r.name.trim() ? r.name : "Example",
977
+ request,
978
+ response: {
979
+ status: code,
980
+ statusText: typeof r.status === "string" ? r.status : "",
981
+ headers,
982
+ body,
983
+ bodySize: body.length,
984
+ durationMs: typeof r.responseTime === "number" ? r.responseTime : 0
985
+ },
986
+ source: "imported"
987
+ });
988
+ }
989
+ return examples.length ? examples : void 0;
990
+ }
907
991
  function parseScript(events, listen) {
908
992
  if (!Array.isArray(events)) return void 0;
909
993
  const event = events.find((e) => e.listen === listen);
@@ -1063,6 +1147,45 @@ function buildBody(operation, spec) {
1063
1147
  }
1064
1148
  return { mode: "none" };
1065
1149
  }
1150
+ function buildExamples(operation, spec, baseParams, baseHeaders, baseBody) {
1151
+ const json = resolve(spec, operation.requestBody?.content ?? {})["application/json"];
1152
+ const bodyExamples = {};
1153
+ if (json?.examples && typeof json.examples === "object") {
1154
+ for (const [name, ex] of Object.entries(json.examples)) {
1155
+ bodyExamples[name] = resolve(spec, ex)?.value;
1156
+ }
1157
+ }
1158
+ const paramExamples = {};
1159
+ for (const p of operation.parameters ?? []) {
1160
+ if (!p?.name || p.in !== "query" && p.in !== "path") continue;
1161
+ if (p.examples && typeof p.examples === "object") {
1162
+ paramExamples[p.name] = {};
1163
+ for (const [name, ex] of Object.entries(p.examples)) {
1164
+ paramExamples[p.name][name] = resolve(spec, ex)?.value;
1165
+ }
1166
+ }
1167
+ }
1168
+ const names = /* @__PURE__ */ new Set([
1169
+ ...Object.keys(bodyExamples),
1170
+ ...Object.values(paramExamples).flatMap((m) => Object.keys(m))
1171
+ ]);
1172
+ if (names.size === 0) return void 0;
1173
+ const examples = [];
1174
+ for (const name of names) {
1175
+ const body = name in bodyExamples && bodyExamples[name] !== void 0 ? { mode: "json", json: typeof bodyExamples[name] === "string" ? String(bodyExamples[name]) : JSON.stringify(bodyExamples[name], null, 2) } : baseBody;
1176
+ const params = baseParams.some((pr) => paramExamples[pr.key]?.[name] !== void 0) ? baseParams.map((pr) => {
1177
+ const v = paramExamples[pr.key]?.[name];
1178
+ return v !== void 0 ? { ...pr, value: v === null ? "" : String(v) } : pr;
1179
+ }) : baseParams;
1180
+ examples.push({
1181
+ id: uuid.v4(),
1182
+ name,
1183
+ request: { headers: baseHeaders, params, body },
1184
+ source: "imported"
1185
+ });
1186
+ }
1187
+ return examples.length ? examples : void 0;
1188
+ }
1066
1189
  function buildParams(operation) {
1067
1190
  return (operation.parameters ?? []).filter((p) => p.in === "query").map((p) => ({
1068
1191
  key: p.name,
@@ -1159,18 +1282,22 @@ function buildCollection(spec) {
1159
1282
  ];
1160
1283
  const rawOperation = pathItem[method];
1161
1284
  const responseSchema = buildResponseSchema(rawOperation, spec);
1285
+ const headers = buildHeaders(opWithParams);
1286
+ const body = buildBody(opWithParams, spec);
1287
+ const examples = buildExamples(opWithParams, spec, params, headers, body);
1162
1288
  const req = {
1163
1289
  id: uuid.v4(),
1164
1290
  name: operation.summary ?? operation.operationId ?? `${method.toUpperCase()} ${pathStr}`,
1165
1291
  method: method.toUpperCase(),
1166
1292
  url: rewritePathTemplate(`${baseUrl}${pathStr}`),
1167
- headers: buildHeaders(opWithParams),
1293
+ headers,
1168
1294
  params,
1169
1295
  auth: buildAuth(security, securitySchemes),
1170
- body: buildBody(opWithParams, spec),
1296
+ body,
1171
1297
  description: operation.description ?? "",
1172
1298
  meta: { tags },
1173
- ...responseSchema ? { schema: responseSchema } : {}
1299
+ ...responseSchema ? { schema: responseSchema } : {},
1300
+ ...examples ? { examples } : {}
1174
1301
  };
1175
1302
  requests[req.id] = req;
1176
1303
  if (!foldersByTag[tag]) {
@@ -4899,8 +5026,24 @@ async function resolveSnapshotSpec(relPath) {
4899
5026
  function registerContractHandlers(ipc) {
4900
5027
  handle.handleIpc(ipc, handle.IPC.contract.run, async (_e, payload) => {
4901
5028
  ipcValidate.validateContractRunPayload(payload);
4902
- const { mode, requests, envVars, collectionVars = {}, requestBaseUrl, providerBaseUrl, stateHandlerUrl } = payload;
5029
+ const { mode, envVars, collectionVars = {}, requestBaseUrl, providerBaseUrl, stateHandlerUrl } = payload;
5030
+ let requests = payload.requests;
4903
5031
  let { specUrl, specPath } = payload;
5032
+ if (mode !== "provider") {
5033
+ const designReqs = await snapshots.loadDesignContractRequests(
5034
+ { designContracts: payload.designContracts },
5035
+ getWorkspaceDir() ?? void 0
5036
+ );
5037
+ const keyOf = (r) => `${r.method} ${r.url} ${r.name}`;
5038
+ const seen = new Set(requests.map(keyOf));
5039
+ const fresh = designReqs.filter((r) => !seen.has(keyOf(r)));
5040
+ if (fresh.length) {
5041
+ requests = [...requests, ...fresh];
5042
+ if (envVars["baseUrl"] === void 0 && collectionVars["baseUrl"] === void 0) {
5043
+ collectionVars["baseUrl"] = "";
5044
+ }
5045
+ }
5046
+ }
4904
5047
  if (payload.specSnapshotRelPath) {
4905
5048
  const resolved = await resolveSnapshotSpec(payload.specSnapshotRelPath);
4906
5049
  specPath = resolved.specPath;
@@ -4931,6 +5074,16 @@ function registerContractHandlers(ipc) {
4931
5074
  await promises.writeFile(filePath, snapshots.reportToHtml(report, { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...meta }), "utf8");
4932
5075
  return true;
4933
5076
  });
5077
+ handle.handleIpc(ipc, handle.IPC.contract.exportDesignPact, async (_e, contract) => {
5078
+ const dir = getWorkspaceDir();
5079
+ if (!dir) throw new Error("No workspace open — open or save a workspace first.");
5080
+ const safe = (s) => (s || "unnamed").replace(/[^a-zA-Z0-9._-]+/g, "-");
5081
+ const relPath = path.join("pacts", `${safe(contract.consumer)}-${safe(contract.provider)}.pact.json`);
5082
+ const fullPath = path.join(dir, relPath);
5083
+ await promises.mkdir(path.join(dir, "pacts"), { recursive: true });
5084
+ await promises.writeFile(fullPath, JSON.stringify(snapshots.designContractToPact(contract), null, 2), "utf8");
5085
+ return relPath;
5086
+ });
4934
5087
  handle.handleIpc(ipc, handle.IPC.contract.captureSnapshot, async (_e, opts) => {
4935
5088
  const dir = getWorkspaceDir();
4936
5089
  if (!dir) throw new Error("No workspace open - cannot capture snapshot.");
@@ -5165,6 +5318,198 @@ function registerRecordHandlers(ipc, getWebContents) {
5165
5318
  return recorder.entriesToMockServer(entries, upstream, name, port);
5166
5319
  });
5167
5320
  }
5321
+ const CLOUD_TOKEN_REF = "cloud:token";
5322
+ const CLOUD_ENDPOINT = (process.env["API_SPECTOR_CLOUD_ENDPOINT"] || "https://api-spector.dev").replace(/\/+$/, "");
5323
+ async function cloudFetch(path2, method, body) {
5324
+ const token = await requestExec.getSecret(CLOUD_TOKEN_REF);
5325
+ if (!token) throw new Error("No cloud API token set. Add one in Settings → Cloud.");
5326
+ const dispatcher = await requestExec.buildDispatcher();
5327
+ const url = CLOUD_ENDPOINT + path2;
5328
+ let res;
5329
+ try {
5330
+ res = await undici.fetch(url, {
5331
+ method,
5332
+ headers: {
5333
+ "Content-Type": "application/json",
5334
+ Accept: "application/json",
5335
+ Authorization: `Bearer ${token}`
5336
+ },
5337
+ body: body === void 0 ? void 0 : JSON.stringify(body),
5338
+ dispatcher
5339
+ });
5340
+ } catch (err) {
5341
+ const msg = err.message;
5342
+ const hint = /ECONNREFUSED|fetch failed|connect|ENOTFOUND/i.test(msg) ? ` — is API Spector Cloud running and reachable at ${CLOUD_ENDPOINT}? (set API_SPECTOR_CLOUD_ENDPOINT or start the broker)` : "";
5343
+ throw new Error(`Could not reach ${url}: ${msg}${hint}`);
5344
+ }
5345
+ const text = await res.text();
5346
+ let json;
5347
+ try {
5348
+ json = text ? JSON.parse(text) : {};
5349
+ } catch {
5350
+ json = { raw: text };
5351
+ }
5352
+ if (!res.ok) {
5353
+ let detail = json?.error || json?.message || (res.status === 401 ? "Unauthorized (check your token)" : `HTTP ${res.status}`);
5354
+ if (res.status === 403 && json?.limit != null) detail += ` (plan limit: ${json.limit})`;
5355
+ if (res.status === 405) detail = `HTTP 405 from ${CLOUD_ENDPOINT} — this does not look like the API Spector Cloud API (set API_SPECTOR_CLOUD_ENDPOINT to your broker URL).`;
5356
+ throw new Error(detail);
5357
+ }
5358
+ return json;
5359
+ }
5360
+ function interpolateStatic(str, vars) {
5361
+ return str.replace(/\{\{([^}]+)\}\}/g, (m, key) => {
5362
+ const trimmed = String(key).trim();
5363
+ return Object.prototype.hasOwnProperty.call(vars, trimmed) ? vars[trimmed] : m;
5364
+ });
5365
+ }
5366
+ function resolveRequest(req, vars) {
5367
+ return {
5368
+ id: req.id,
5369
+ name: req.name,
5370
+ method: req.method,
5371
+ url: requestExec.buildUrl(req.url, req.params ?? [], vars),
5372
+ headers: resolveHeaders(req.headers, vars),
5373
+ params: [],
5374
+ // folded into url; empty so the runner doesn't re-append
5375
+ auth: req.auth ?? { type: "none" },
5376
+ body: resolveBody(req.body, vars),
5377
+ preRequestScript: req.preRequestScript || void 0,
5378
+ postRequestScript: req.postRequestScript || void 0
5379
+ };
5380
+ }
5381
+ function resolveHeaders(headers, vars) {
5382
+ return (headers ?? []).filter((h) => h.enabled !== false && h.key).map((h) => ({ key: interpolateStatic(h.key, vars), value: interpolateStatic(h.value ?? "", vars), enabled: true }));
5383
+ }
5384
+ function resolveBody(body, vars) {
5385
+ if (!body || !body.mode || body.mode === "none") return { mode: "none" };
5386
+ const b = { ...body };
5387
+ if (body.mode === "json" && body.json != null) b.json = interpolateStatic(body.json, vars);
5388
+ else if (body.mode === "raw" && body.raw != null) b.raw = interpolateStatic(body.raw, vars);
5389
+ else if (body.mode === "form" && body.form) {
5390
+ b.form = body.form.map((f) => ({ ...f, key: interpolateStatic(f.key, vars), value: interpolateStatic(f.value ?? "", vars) }));
5391
+ } else if (body.mode === "graphql" && body.graphql) {
5392
+ b.graphql = {
5393
+ ...body.graphql,
5394
+ query: interpolateStatic(body.graphql.query ?? "", vars),
5395
+ variables: interpolateStatic(body.graphql.variables ?? "", vars)
5396
+ };
5397
+ } else if (body.mode === "soap" && body.soap) {
5398
+ b.soap = { ...body.soap, envelope: interpolateStatic(body.soap.envelope ?? "", vars) };
5399
+ }
5400
+ return b;
5401
+ }
5402
+ function setVarNames(reqs) {
5403
+ const names = /* @__PURE__ */ new Set();
5404
+ const re = /sp\.(?:environment|collectionVariables|variables|globals)\.set\(\s*['"`]([^'"`]+)['"`]/g;
5405
+ for (const r of reqs) {
5406
+ for (const script of [r.preRequestScript, r.postRequestScript]) {
5407
+ if (!script) continue;
5408
+ let m;
5409
+ while ((m = re.exec(script)) !== null) names.add(m[1]);
5410
+ }
5411
+ }
5412
+ return names;
5413
+ }
5414
+ function omitKeys(vars, names) {
5415
+ if (!names.size) return vars;
5416
+ const out = {};
5417
+ for (const [k, v] of Object.entries(vars)) if (!names.has(k)) out[k] = v;
5418
+ return out;
5419
+ }
5420
+ function slugify(name) {
5421
+ return (name || "mock").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63) || "mock";
5422
+ }
5423
+ function registerCloudHandlers(ipc) {
5424
+ handle.handleIpc(ipc, handle.IPC.cloud.test, async () => {
5425
+ return cloudFetch("/api/me", "GET");
5426
+ });
5427
+ handle.handleIpc(ipc, handle.IPC.cloud.pushMock, async (_e, server) => {
5428
+ const payload = {
5429
+ name: server.name,
5430
+ slug: slugify(server.name),
5431
+ enabled: true,
5432
+ auth_type: "none",
5433
+ routes: (server.routes ?? []).map((r) => ({
5434
+ method: r.method,
5435
+ path: r.path,
5436
+ status: r.statusCode,
5437
+ headers: r.headers ?? {},
5438
+ body: r.body ?? "",
5439
+ delay: r.delay ?? 0,
5440
+ script: r.script ?? null
5441
+ }))
5442
+ };
5443
+ return cloudFetch("/api/mocks", "POST", payload);
5444
+ });
5445
+ handle.handleIpc(ipc, handle.IPC.cloud.getMock, async (_e, name) => {
5446
+ return cloudFetch("/api/mocks/" + encodeURIComponent(slugify(name)), "GET");
5447
+ });
5448
+ handle.handleIpc(ipc, handle.IPC.cloud.pushPact, async (_e, input) => {
5449
+ const pact = snapshots.exportPact(input.consumer, input.provider, input.requests ?? []);
5450
+ return cloudFetch("/api/contracts", "PUT", {
5451
+ consumer: input.consumer,
5452
+ consumerVersion: input.consumerVersion,
5453
+ provider: input.provider,
5454
+ content: pact
5455
+ });
5456
+ });
5457
+ handle.handleIpc(ipc, handle.IPC.cloud.pushDesignContract, async (_e, input) => {
5458
+ const pact = snapshots.designContractToPact(input.contract);
5459
+ return cloudFetch("/api/contracts", "PUT", {
5460
+ consumer: input.contract.consumer,
5461
+ consumerVersion: input.consumerVersion,
5462
+ provider: input.contract.provider,
5463
+ content: pact
5464
+ });
5465
+ });
5466
+ handle.handleIpc(ipc, handle.IPC.cloud.pushSpec, async (_e, input) => {
5467
+ let specText = input.spec;
5468
+ if (!specText && input.specUrl) {
5469
+ const res = await undici.fetch(input.specUrl, { dispatcher: await requestExec.buildDispatcher() });
5470
+ if (!res.ok) throw new Error(`Could not fetch spec from ${input.specUrl} (HTTP ${res.status})`);
5471
+ specText = await res.text();
5472
+ }
5473
+ if (!specText) throw new Error("Provide an OpenAPI spec (text or a spec URL).");
5474
+ let spec;
5475
+ try {
5476
+ spec = JSON.parse(specText);
5477
+ } catch {
5478
+ spec = jsYaml.load(specText);
5479
+ }
5480
+ if (!spec || typeof spec !== "object") {
5481
+ throw new Error("Could not parse the OpenAPI spec (expected JSON or YAML).");
5482
+ }
5483
+ return cloudFetch("/api/provider-contracts", "PUT", {
5484
+ pacticipant: input.pacticipant,
5485
+ version: input.version,
5486
+ spec
5487
+ });
5488
+ });
5489
+ handle.handleIpc(ipc, handle.IPC.cloud.openMatrix, async () => {
5490
+ await electron.shell.openExternal(CLOUD_ENDPOINT + "/matrix");
5491
+ });
5492
+ handle.handleIpc(ipc, handle.IPC.cloud.pushMonitor, async (_e, input) => {
5493
+ const { request, environment, collectionVars, globals } = input;
5494
+ const envVars = await requestExec.buildEnvVars(environment);
5495
+ const liveGlobals = { ...globals, ...requestExec.getGlobals() };
5496
+ const staticVars = requestExec.mergeVars(envVars, collectionVars ?? {}, liveGlobals, {}, {});
5497
+ const setup = input.setup ?? [];
5498
+ const resolveVars = omitKeys(staticVars, setVarNames(setup));
5499
+ const monitorRequest = resolveRequest(request, resolveVars);
5500
+ const setupRequests = setup.map((h) => resolveRequest(h, resolveVars));
5501
+ const payload = {
5502
+ name: input.name || request.name || "Monitor",
5503
+ target_url: monitorRequest.url,
5504
+ method: request.method,
5505
+ expected_status: input.expectedStatus ?? 200,
5506
+ interval_seconds: input.intervalSeconds ?? 300,
5507
+ request: monitorRequest,
5508
+ setup: setupRequests
5509
+ };
5510
+ return cloudFetch("/api/monitors", "POST", payload);
5511
+ });
5512
+ }
5168
5513
  const REGISTRY_URL = "https://registry.npmjs.org/@testsmith/api-spector/latest";
5169
5514
  function isNewer(a, b) {
5170
5515
  const core = (v) => v.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
@@ -5175,7 +5520,7 @@ function isNewer(a, b) {
5175
5520
  return a2 > b2;
5176
5521
  }
5177
5522
  async function checkForUpdate() {
5178
- const current = "0.4.6";
5523
+ const current = "0.4.8";
5179
5524
  try {
5180
5525
  const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(4e3) });
5181
5526
  if (!res.ok) return null;
@@ -5295,6 +5640,7 @@ electron.app.whenReady().then(async () => {
5295
5640
  registerContractHandlers(electron.ipcMain);
5296
5641
  registerGitHandlers(electron.ipcMain);
5297
5642
  registerRecordHandlers(electron.ipcMain, () => electron.BrowserWindow.getAllWindows()[0]?.webContents ?? null);
5643
+ registerCloudHandlers(electron.ipcMain);
5298
5644
  handle.handleIpc(electron.ipcMain, handle.IPC.shell.openExternal, (_e, url) => electron.shell.openExternal(url));
5299
5645
  handle.handleIpc(electron.ipcMain, handle.IPC.app.checkUpdate, () => checkForUpdate());
5300
5646
  createWindow();
package/out/main/lib.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const mockServer = require("./chunks/mock-server-BxiXhP1m.js");
4
- const requestExec = require("./chunks/request-exec-CBi2kL6s.js");
4
+ const requestExec = require("./chunks/request-exec-BH-M3KqZ.js");
5
5
  require("http");
6
6
  require("crypto");
7
7
  require("vm");
@@ -10,7 +10,7 @@ require("@xmldom/xmldom");
10
10
  require("undici");
11
11
  require("fs/promises");
12
12
  require("tls");
13
- require("./chunks/handle-BGYDylL2.js");
13
+ require("./chunks/handle-rimXXdJH.js");
14
14
  require("path");
15
15
  require("tv4");
16
16
  require("jsonpath-plus");
package/out/main/mock.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  const mockServer = require("./chunks/mock-server-BxiXhP1m.js");
4
- const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
4
+ const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
5
5
  require("http");
6
6
  require("crypto");
7
7
  require("vm");
@@ -3,7 +3,7 @@
3
3
  const promises = require("fs/promises");
4
4
  const path = require("path");
5
5
  const recorder = require("./chunks/recorder-0Ij921El.js");
6
- const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
6
+ const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
7
7
  require("http");
8
8
  require("crypto");
9
9
  require("undici");
@@ -2,13 +2,13 @@
2
2
  "use strict";
3
3
  const promises = require("fs/promises");
4
4
  const path = require("path");
5
- const requestExec = require("./chunks/request-exec-CBi2kL6s.js");
5
+ const requestExec = require("./chunks/request-exec-BH-M3KqZ.js");
6
6
  const requestCollection = require("./chunks/request-collection-CJoXpvOw.js");
7
7
  const environments = require("./chunks/environments-iM3SUM-4.js");
8
- const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
8
+ const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
9
9
  require("undici");
10
10
  require("tls");
11
- require("./chunks/handle-BGYDylL2.js");
11
+ require("./chunks/handle-rimXXdJH.js");
12
12
  require("crypto");
13
13
  require("dayjs");
14
14
  require("vm");
@@ -397,7 +397,7 @@ async function main() {
397
397
  } else if (!envName && workspace.settings?.defaultEnvironment && !env) {
398
398
  console.warn(cliCommon.color(`Warning: default environment "${workspace.settings.defaultEnvironment}" not found. Running without environment.`, cliCommon.C.yellow));
399
399
  }
400
- const version = `v${"0.4.6"}`;
400
+ const version = `v${"0.4.8"}`;
401
401
  console.log("");
402
402
  console.log(cliCommon.color(" API Test Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
403
403
  console.log(cliCommon.color(` Workspace: ${wsPath}`, 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-CAyUjzxB.js");
8
- const _import = require("./chunks/import-CUcjlmSK.js");
9
- const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
10
- require("./chunks/handle-BGYDylL2.js");
7
+ const soapHandler = require("./chunks/soap-handler-BXx842MN.js");
8
+ const _import = require("./chunks/import-DcenB5Q_.js");
9
+ const cliCommon = require("./chunks/cli-common-BKYgsbGB.js");
10
+ require("./chunks/handle-rimXXdJH.js");
11
11
  require("@xmldom/xmldom");
12
12
  require("uuid");
13
+ require("crypto");
13
14
  function fetchUrl(url) {
14
15
  return new Promise((resolveP, rejectP) => {
15
16
  const lib = url.startsWith("https") ? https : http;