@testsmith/api-spector 0.3.1 → 0.3.2

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.
@@ -2,18 +2,19 @@
2
2
  "use strict";
3
3
  const promises = require("fs/promises");
4
4
  const path = require("path");
5
- const undici = require("undici");
6
- const authBuilder = require("./chunks/auth-builder-CRQayp8x.js");
7
- const requestCollection = require("./chunks/request-collection-d2vgYls1.js");
5
+ const authBuilder = require("./chunks/auth-builder-CUs9yzOF.js");
6
+ const requestCollection = require("./chunks/request-collection-8TOVNXE0.js");
7
+ const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
8
8
  require("crypto");
9
9
  require("http");
10
+ require("./chunks/handle-C0IQL-Vl.js");
10
11
  require("dayjs");
11
12
  require("vm");
13
+ require("undici");
12
14
  require("tv4");
13
15
  require("jsonpath-plus");
14
16
  require("@xmldom/xmldom");
15
17
  require("ajv");
16
- require("./chunks/ipc-validate-CscN4HfG.js");
17
18
  function buildJsonReport(results, summary, meta = {}) {
18
19
  return JSON.stringify({
19
20
  timestamp: meta.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
@@ -337,267 +338,24 @@ ${failures.join("\n")}
337
338
  ];
338
339
  return lines.join("\n") + "\n";
339
340
  }
340
- const C = {
341
- reset: "\x1B[0m",
342
- bold: "\x1B[1m",
343
- dim: "\x1B[2m",
344
- green: "\x1B[32m",
345
- red: "\x1B[31m",
346
- yellow: "\x1B[33m",
347
- cyan: "\x1B[36m",
348
- gray: "\x1B[90m",
349
- white: "\x1B[97m"
350
- };
351
- function color(str, ...codes) {
352
- return process.stdout.isTTY ? codes.join("") + str + C.reset : str;
353
- }
354
- function parseArgs(argv) {
355
- const args = {};
356
- for (let i = 0; i < argv.length; i++) {
357
- const arg = argv[i];
358
- if (arg.startsWith("--")) {
359
- const key = arg.slice(2);
360
- const next = argv[i + 1];
361
- if (!next || next.startsWith("--")) {
362
- args[key] = true;
363
- } else {
364
- args[key] = next;
365
- i++;
366
- }
367
- }
368
- }
369
- return args;
370
- }
371
- async function resolveWorkspacePath(wsPath) {
372
- const s = await promises.stat(wsPath);
373
- if (!s.isDirectory()) return wsPath;
374
- const entries = await promises.readdir(wsPath);
375
- const spector = entries.find((e) => e.endsWith(".spector"));
376
- if (!spector) throw new Error(`No .spector workspace file found in directory: ${wsPath}`);
377
- return path.join(wsPath, spector);
378
- }
379
- async function loadWorkspace(wsPath) {
380
- const resolved = await resolveWorkspacePath(wsPath);
381
- const raw = await promises.readFile(resolved, "utf8");
382
- return { workspace: JSON.parse(raw), dir: path.dirname(path.resolve(resolved)) };
383
- }
384
- async function loadCollections(workspace, dir) {
385
- const cols = [];
386
- for (const relPath of workspace.collections) {
387
- try {
388
- const raw = await promises.readFile(path.join(dir, relPath), "utf8");
389
- cols.push(JSON.parse(raw));
390
- } catch (_e) {
391
- console.error(color(` [warn] Could not load collection: ${relPath}`, C.yellow));
392
- }
393
- }
394
- return cols;
395
- }
396
- async function loadEnvironments(workspace, dir) {
397
- const envs = [];
398
- for (const relPath of workspace.environments) {
399
- try {
400
- const raw = await promises.readFile(path.join(dir, relPath), "utf8");
401
- envs.push(JSON.parse(raw));
402
- } catch (_e) {
403
- }
404
- }
405
- return envs;
406
- }
407
- async function executeRequest(req, collectionVars, envVars, globals, localVars, verbose, tls, piiMaskPatterns = []) {
408
- if (!req.headers) req.headers = [];
409
- if (!req.params) req.params = [];
410
- if (!req.body) req.body = { mode: "none" };
411
- if (!req.auth) req.auth = { type: "none" };
412
- const base = {
413
- requestId: req.id,
414
- name: req.name,
415
- method: req.method,
416
- resolvedUrl: req.url,
417
- status: "running"
418
- };
419
- let updatedEnvVars = { ...envVars };
420
- let updatedCollectionVars = { ...collectionVars };
421
- let updatedGlobals = { ...globals };
422
- let preScriptError;
423
- const dynamicVars = await authBuilder.buildDynamicVars();
424
- let vars = authBuilder.mergeVars(envVars, collectionVars, globals, localVars, dynamicVars);
425
- if (req.preRequestScript?.trim()) {
426
- const r = await requestCollection.runScript(authBuilder.interpolate(req.preRequestScript, vars), {
427
- envVars: { ...envVars },
428
- collectionVars: { ...collectionVars },
429
- globals: { ...globals },
430
- localVars: { ...localVars },
431
- piiMaskPatterns
432
- });
433
- preScriptError = r.error;
434
- localVars = r.updatedLocalVars;
435
- updatedEnvVars = r.updatedEnvVars;
436
- updatedCollectionVars = r.updatedCollectionVars;
437
- updatedGlobals = r.updatedGlobals;
438
- requestCollection.patchGlobals(r.updatedGlobals);
439
- await requestCollection.persistGlobals();
440
- if (verbose && r.consoleOutput.length) r.consoleOutput.forEach((l) => console.log(color(` [pre] ${l}`, C.gray)));
441
- if (r.error) console.error(color(` [pre-script error] ${r.error}`, C.red));
442
- }
443
- vars = authBuilder.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars, dynamicVars);
444
- const resolvedUrl = authBuilder.buildUrl(req.url, req.params, vars);
445
- base.resolvedUrl = resolvedUrl;
446
- const start = Date.now();
447
- try {
448
- const authH = {};
449
- if (req.auth.type === "bearer") {
450
- let token = req.auth.token ?? "";
451
- if (!token && req.auth.tokenSecretRef) token = await authBuilder.getSecret(req.auth.tokenSecretRef) ?? "";
452
- if (token) authH["Authorization"] = `Bearer ${authBuilder.interpolate(token, vars)}`;
453
- }
454
- const headers = new undici.Headers();
455
- for (const h of req.headers) {
456
- if (h.enabled && h.key) headers.set(authBuilder.interpolate(h.key, vars), authBuilder.interpolate(h.value, vars));
457
- }
458
- for (const [k, v] of Object.entries(authH)) headers.set(k, v);
459
- let body;
460
- if (req.body.mode === "json" && req.body.json) {
461
- body = authBuilder.interpolate(req.body.json, vars);
462
- if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
463
- } else if (req.body.mode === "raw" && req.body.raw) {
464
- body = authBuilder.interpolate(req.body.raw, vars);
465
- if (!headers.has("content-type")) headers.set("Content-Type", req.body.rawContentType ?? "text/plain");
466
- } else if (req.body.mode === "graphql" && req.body.graphql) {
467
- const gql = req.body.graphql;
468
- const gqlBody = { query: authBuilder.interpolate(gql.query, vars) };
469
- const rawVars = gql.variables?.trim();
470
- if (rawVars) {
471
- try {
472
- gqlBody.variables = JSON.parse(authBuilder.interpolate(rawVars, vars));
473
- } catch {
474
- }
475
- }
476
- if (gql.operationName?.trim()) gqlBody.operationName = gql.operationName.trim();
477
- body = JSON.stringify(gqlBody);
478
- if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
479
- } else if (req.body.mode === "soap" && req.body.soap) {
480
- body = authBuilder.interpolate(req.body.soap.envelope, vars);
481
- if (!headers.has("content-type")) headers.set("Content-Type", "text/xml; charset=utf-8");
482
- if (req.body.soap.soapAction && !headers.has("soapaction")) {
483
- headers.set("SOAPAction", req.body.soap.soapAction);
484
- }
485
- }
486
- const dispatcher = await requestCollection.buildDispatcher(void 0, tls);
487
- const fetchResp = await undici.fetch(resolvedUrl, {
488
- method: req.method,
489
- headers,
490
- body: ["GET", "HEAD"].includes(req.method) ? void 0 : body,
491
- ...dispatcher ? { dispatcher } : {}
492
- });
493
- const responseBody = await fetchResp.text();
494
- const durationMs = Date.now() - start;
495
- const respHeaders = {};
496
- fetchResp.headers.forEach((v, k) => {
497
- respHeaders[k] = v;
498
- });
499
- const response = {
500
- status: fetchResp.status,
501
- statusText: fetchResp.statusText,
502
- headers: respHeaders,
503
- body: responseBody,
504
- bodySize: Buffer.byteLength(responseBody, "utf8"),
505
- durationMs
506
- };
507
- const protocolFaultTests = requestCollection.buildProtocolFaultTests(req.body.mode, responseBody);
508
- let testResults = [...protocolFaultTests];
509
- let consoleOutput = [];
510
- let postScriptError;
511
- if (req.postRequestScript?.trim()) {
512
- const r = await requestCollection.runScript(authBuilder.interpolate(req.postRequestScript, vars), {
513
- envVars: updatedEnvVars,
514
- collectionVars: updatedCollectionVars,
515
- globals: updatedGlobals,
516
- localVars,
517
- response,
518
- piiMaskPatterns
519
- });
520
- testResults = [...protocolFaultTests, ...r.testResults];
521
- consoleOutput = r.consoleOutput;
522
- postScriptError = r.error;
523
- updatedEnvVars = r.updatedEnvVars;
524
- updatedCollectionVars = r.updatedCollectionVars;
525
- updatedGlobals = r.updatedGlobals;
526
- localVars = r.updatedLocalVars;
527
- requestCollection.patchGlobals(r.updatedGlobals);
528
- await requestCollection.persistGlobals();
529
- if (verbose && r.consoleOutput.length) r.consoleOutput.forEach((l) => console.log(color(` [post] ${l}`, C.gray)));
530
- }
531
- const allPassed = testResults.every((t) => t.passed);
532
- const httpFailed = fetchResp.status >= 400;
533
- const hasTests = testResults.length > 0;
534
- const status = postScriptError ? "error" : hasTests ? allPassed ? "passed" : "failed" : httpFailed ? "failed" : "passed";
535
- if (httpFailed && testResults.length === 0) {
536
- testResults = [
537
- ...testResults,
538
- {
539
- name: `HTTP status ${fetchResp.status} ${fetchResp.statusText}`.trim(),
540
- passed: false,
541
- error: `Request returned ${fetchResp.status} — no assertion was defined to verify the status code.`
542
- }
543
- ];
544
- }
545
- const reqHeaders = {};
546
- headers.forEach((v, k) => {
547
- reqHeaders[k] = v;
548
- });
549
- return {
550
- result: {
551
- ...base,
552
- status,
553
- httpStatus: fetchResp.status,
554
- durationMs,
555
- testResults,
556
- consoleOutput,
557
- preScriptError,
558
- postScriptError,
559
- sentRequest: { headers: reqHeaders, body },
560
- receivedResponse: response
561
- },
562
- updatedEnvVars,
563
- updatedCollectionVars,
564
- updatedGlobals,
565
- updatedLocalVars: localVars
566
- };
567
- } catch (err) {
568
- return {
569
- result: {
570
- ...base,
571
- status: "error",
572
- durationMs: Date.now() - start,
573
- error: err instanceof Error ? err.message : String(err),
574
- preScriptError
575
- },
576
- updatedEnvVars,
577
- updatedCollectionVars,
578
- updatedGlobals,
579
- updatedLocalVars: localVars
580
- };
581
- }
582
- }
583
341
  function printResult(r, verbose) {
584
- const icon = r.status === "passed" ? color("✓", C.green, C.bold) : r.status === "failed" ? color("✗", C.red, C.bold) : r.status === "skipped" ? color("○", C.gray, C.bold) : color("⚠", C.yellow, C.bold);
585
- const http = r.httpStatus ? color(` ${r.httpStatus}`, r.httpStatus < 400 ? C.green : C.red) : "";
586
- const dur = r.durationMs !== void 0 ? color(` ${r.durationMs}ms`, C.gray) : "";
587
- const method = color(r.method.padEnd(7), C.cyan);
588
- const hookTag = r.isHook && r.hookType ? color(` [${r.hookType.toUpperCase()}]`, C.yellow) : "";
342
+ 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);
343
+ const http = r.httpStatus ? cliCommon.color(` ${r.httpStatus}`, r.httpStatus < 400 ? cliCommon.C.green : cliCommon.C.red) : "";
344
+ const dur = r.durationMs !== void 0 ? cliCommon.color(` ${r.durationMs}ms`, cliCommon.C.gray) : "";
345
+ const method = cliCommon.color(r.method.padEnd(7), cliCommon.C.cyan);
346
+ const hookTag = r.isHook && r.hookType ? cliCommon.color(` [${r.hookType.toUpperCase()}]`, cliCommon.C.yellow) : "";
589
347
  console.log(` ${icon} ${method} ${r.name}${hookTag}${http}${dur}`);
590
- if (verbose) console.log(color(` ${r.resolvedUrl}`, C.gray));
348
+ if (verbose) console.log(cliCommon.color(` ${r.resolvedUrl}`, cliCommon.C.gray));
591
349
  if (r.testResults?.length) {
592
350
  for (const t of r.testResults) {
593
- const ti = t.passed ? color(" ✓", C.green) : color(" ✗", C.red);
594
- console.log(`${ti} ${t.name}${t.error ? color(` — ${t.error}`, C.red) : ""}`);
351
+ const ti = t.passed ? cliCommon.color(" ✓", cliCommon.C.green) : cliCommon.color(" ✗", cliCommon.C.red);
352
+ console.log(`${ti} ${t.name}${t.error ? cliCommon.color(` — ${t.error}`, cliCommon.C.red) : ""}`);
595
353
  }
596
354
  }
597
- if (r.error) console.log(color(` Error: ${r.error}`, C.red));
355
+ if (r.error) console.log(cliCommon.color(` Error: ${r.error}`, cliCommon.C.red));
598
356
  }
599
357
  async function main() {
600
- const args = parseArgs(process.argv.slice(2));
358
+ const args = cliCommon.parseArgs(process.argv.slice(2));
601
359
  if (args.help) {
602
360
  console.log(
603
361
  "\nUsage:\n api-spector run --workspace <path> [--environment <name>] [--tags <a,b>]\n [--collection <name>] [--output <path>] [--format json|junit]\n [--verbose] [--bail]\n"
@@ -606,7 +364,7 @@ async function main() {
606
364
  }
607
365
  const wsPath = args.workspace;
608
366
  if (!wsPath) {
609
- console.error(color("Error: --workspace is required", C.red));
367
+ console.error(cliCommon.color("Error: --workspace is required", cliCommon.C.red));
610
368
  process.exit(1);
611
369
  }
612
370
  const filterTags = args.tags ? args.tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
@@ -621,24 +379,26 @@ async function main() {
621
379
  let workspace, wsDir;
622
380
  try {
623
381
  ;
624
- ({ workspace, dir: wsDir } = await loadWorkspace(wsPath));
382
+ ({ workspace, dir: wsDir } = await cliCommon.loadWorkspace(wsPath));
625
383
  } catch {
626
- console.error(color(`Error: could not read workspace file: ${wsPath}`, C.red));
384
+ console.error(cliCommon.color(`Error: could not read workspace file: ${wsPath}`, cliCommon.C.red));
627
385
  process.exit(1);
628
386
  }
629
387
  await requestCollection.loadGlobals(wsDir);
630
- const collections = await loadCollections(workspace, wsDir);
631
- const environments = await loadEnvironments(workspace, wsDir);
388
+ const collections = await cliCommon.loadCollections(workspace, wsDir, {
389
+ onError: (relPath) => console.error(cliCommon.color(` [warn] Could not load collection: ${relPath}`, cliCommon.C.yellow))
390
+ });
391
+ const environments = await cliCommon.loadEnvironments(workspace, wsDir);
632
392
  const env = envName ? environments.find((e) => e.name.toLowerCase() === envName.toLowerCase()) ?? null : null;
633
393
  if (envName && !env) {
634
- console.warn(color(`Warning: environment "${envName}" not found. Running without environment.`, C.yellow));
394
+ console.warn(cliCommon.color(`Warning: environment "${envName}" not found. Running without environment.`, cliCommon.C.yellow));
635
395
  }
636
- const version = `v${"0.3.1"}`;
396
+ const version = `v${"0.3.2"}`;
637
397
  console.log("");
638
- console.log(color(" API Test Runner" + (version ? ` ${version}` : ""), C.bold, C.white));
639
- console.log(color(` Workspace: ${wsPath}`, C.gray));
640
- console.log(color(` Environment: ${env?.name ?? "(none)"}`, C.gray));
641
- if (filterTags.length) console.log(color(` Tags: ${filterTags.join(", ")}`, C.gray));
398
+ console.log(cliCommon.color(" API Test Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
399
+ console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
400
+ console.log(cliCommon.color(` Environment: ${env?.name ?? "(none)"}`, cliCommon.C.gray));
401
+ if (filterTags.length) console.log(cliCommon.color(` Tags: ${filterTags.join(", ")}`, cliCommon.C.gray));
642
402
  console.log("");
643
403
  const envVarsSnapshot = await authBuilder.buildEnvVars(env);
644
404
  const secretValuesToMask = (env?.variables ?? []).filter((v) => v.secret && v.enabled).map((v) => envVarsSnapshot[v.key]).filter((v) => typeof v === "string" && v.length > 0);
@@ -679,7 +439,7 @@ async function main() {
679
439
  let runGlobals = requestCollection.getGlobals();
680
440
  let runCollectionVars = { ...col.collectionVariables ?? {} };
681
441
  let runLocalVars = {};
682
- console.log(color(` ┌ ${col.name}`, C.bold, C.white));
442
+ console.log(cliCommon.color(` ┌ ${col.name}`, cliCommon.C.bold, cliCommon.C.white));
683
443
  const workspaceTls = workspace.settings?.tls;
684
444
  const effectiveTls = col.tls ? { ...workspaceTls, ...col.tls } : workspaceTls;
685
445
  for (const item of items) {
@@ -695,32 +455,15 @@ async function main() {
695
455
  }
696
456
  let bailed = false;
697
457
  let lastPrintedScope = null;
698
- const failedScopes = /* @__PURE__ */ new Set();
699
- const skipRequests = /* @__PURE__ */ new Set();
458
+ const skipTracker = new requestCollection.HookSkipTracker();
459
+ const dispatcher = await requestCollection.buildDispatcher(void 0, effectiveTls);
460
+ const onScriptOutput = (phase, lines, error) => {
461
+ if (verbose && lines.length) lines.forEach((l) => console.log(cliCommon.color(` [${phase}] ${l}`, cliCommon.C.gray)));
462
+ if (phase === "pre" && error) console.error(cliCommon.color(` [pre-script error] ${error}`, cliCommon.C.red));
463
+ };
700
464
  for (const item of items) {
701
- const { isHook, hookType, scopeId, scopeAncestors, mainRequestId } = item;
702
- let skipReason;
703
- if (isHook) {
704
- if (hookType === "beforeAll") {
705
- if ((scopeAncestors ?? []).some((id) => failedScopes.has(id))) {
706
- skipReason = "Skipped — outer scope hook failed";
707
- }
708
- } else if (hookType === "before") {
709
- const allScopes = [...scopeAncestors ?? [], scopeId].filter(Boolean);
710
- if (allScopes.some((id) => failedScopes.has(id))) {
711
- skipReason = "Skipped — scope hook failed";
712
- } else if (mainRequestId && skipRequests.has(mainRequestId)) {
713
- skipReason = "Skipped — before hook failed";
714
- }
715
- }
716
- } else {
717
- const allScopes = [...scopeAncestors ?? [], scopeId].filter(Boolean);
718
- if (allScopes.some((id) => failedScopes.has(id))) {
719
- skipReason = "Skipped — beforeAll hook failed";
720
- } else if (skipRequests.has(item.request.id)) {
721
- skipReason = "Skipped — before hook failed";
722
- }
723
- }
465
+ const { isHook, hookType, scopeId } = item;
466
+ const skipReason = skipTracker.shouldSkip(item);
724
467
  let result;
725
468
  if (skipReason) {
726
469
  result = {
@@ -736,16 +479,17 @@ async function main() {
736
479
  scopePath: item.scopePath
737
480
  };
738
481
  } else {
739
- const out = await executeRequest(
740
- item.request,
741
- { ...item.collectionVars, ...runCollectionVars },
742
- runEnvVars,
743
- runGlobals,
744
- { ...runLocalVars },
745
- verbose,
746
- effectiveTls,
747
- piiPatterns
748
- );
482
+ const out = await requestCollection.executeRunnerRequest({
483
+ req: item.request,
484
+ collectionVars: { ...item.collectionVars, ...runCollectionVars },
485
+ envVars: runEnvVars,
486
+ globals: runGlobals,
487
+ localVars: { ...runLocalVars },
488
+ dispatcher,
489
+ piiMaskPatterns: piiPatterns,
490
+ tls: effectiveTls,
491
+ onScriptOutput
492
+ });
749
493
  result = out.result;
750
494
  runEnvVars = out.updatedEnvVars;
751
495
  runCollectionVars = out.updatedCollectionVars;
@@ -755,17 +499,11 @@ async function main() {
755
499
  result.hookType = hookType;
756
500
  result.scopeId = scopeId;
757
501
  result.scopePath = item.scopePath;
758
- if (result.status === "failed" || result.status === "error") {
759
- if (isHook && hookType === "beforeAll" && scopeId) {
760
- failedScopes.add(scopeId);
761
- } else if (isHook && hookType === "before" && mainRequestId) {
762
- skipRequests.add(mainRequestId);
763
- }
764
- }
502
+ skipTracker.recordResult(item, result.status);
765
503
  }
766
504
  const scopeKey = (item.scopePath ?? []).join(" / ");
767
505
  if (scopeKey !== lastPrintedScope) {
768
- if (scopeKey) console.log(color(` ${scopeKey}`, C.gray, C.bold));
506
+ if (scopeKey) console.log(cliCommon.color(` ${scopeKey}`, cliCommon.C.gray, cliCommon.C.bold));
769
507
  lastPrintedScope = scopeKey;
770
508
  }
771
509
  printResult(result, verbose);
@@ -776,7 +514,7 @@ async function main() {
776
514
  else if (result.status === "skipped") summary.skipped++;
777
515
  else summary.errors++;
778
516
  if (bail && (result.status === "failed" || result.status === "error")) {
779
- console.log(color("\n Bailing after first failure.", C.yellow));
517
+ console.log(cliCommon.color("\n Bailing after first failure.", cliCommon.C.yellow));
780
518
  bailed = true;
781
519
  break;
782
520
  }
@@ -785,11 +523,11 @@ async function main() {
785
523
  if (bailed) break;
786
524
  }
787
525
  summary.durationMs = Date.now() - totalStart;
788
- const passStr = color(`${summary.passed} passed`, C.green, C.bold);
789
- const failStr = summary.failed > 0 ? color(` · ${summary.failed} failed`, C.red, C.bold) : "";
790
- const errStr = summary.errors > 0 ? color(` · ${summary.errors} errors`, C.yellow, C.bold) : "";
791
- const skipStr = summary.skipped > 0 ? color(` · ${summary.skipped} skipped`, C.gray, C.bold) : "";
792
- const totalStr = color(` · ${summary.total} total · ${summary.durationMs}ms`, C.gray);
526
+ const passStr = cliCommon.color(`${summary.passed} passed`, cliCommon.C.green, cliCommon.C.bold);
527
+ const failStr = summary.failed > 0 ? cliCommon.color(` · ${summary.failed} failed`, cliCommon.C.red, cliCommon.C.bold) : "";
528
+ const errStr = summary.errors > 0 ? cliCommon.color(` · ${summary.errors} errors`, cliCommon.C.yellow, cliCommon.C.bold) : "";
529
+ const skipStr = summary.skipped > 0 ? cliCommon.color(` · ${summary.skipped} skipped`, cliCommon.C.gray, cliCommon.C.bold) : "";
530
+ const totalStr = cliCommon.color(` · ${summary.total} total · ${summary.durationMs}ms`, cliCommon.C.gray);
793
531
  console.log(` ${passStr}${failStr}${errStr}${skipStr}${totalStr}
794
532
  `);
795
533
  if (outputPath) {
@@ -797,12 +535,12 @@ async function main() {
797
535
  const maskedResults = allResults.map(maskResult);
798
536
  const report = outputFormat === "junit" ? buildJUnitReport(maskedResults, summary, meta) : outputFormat === "html" ? buildHtmlReport(maskedResults, summary, meta) : buildJsonReport(maskedResults, summary, meta);
799
537
  await promises.writeFile(path.resolve(outputPath), report, "utf8");
800
- console.log(color(` Report written: ${outputPath} (${outputFormat})
801
- `, C.gray));
538
+ console.log(cliCommon.color(` Report written: ${outputPath} (${outputFormat})
539
+ `, cliCommon.C.gray));
802
540
  }
803
541
  process.exit(summary.failed + summary.errors > 0 ? 1 : 0);
804
542
  }
805
543
  main().catch((err) => {
806
- console.error(color(`Fatal: ${err.message}`, C.red));
544
+ console.error(cliCommon.color(`Fatal: ${err.message}`, cliCommon.C.red));
807
545
  process.exit(2);
808
546
  });
package/out/main/wsdl.js CHANGED
@@ -4,27 +4,12 @@ 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-Cpj-JwyA.js");
8
- const _import = require("./chunks/import-C9qdkBCH.js");
7
+ const soapHandler = require("./chunks/soap-handler-B9x_YCtj.js");
8
+ const _import = require("./chunks/import-C5Ngq8hk.js");
9
+ const cliCommon = require("./chunks/cli-common-CDQY1erJ.js");
10
+ require("./chunks/handle-C0IQL-Vl.js");
9
11
  require("@xmldom/xmldom");
10
12
  require("uuid");
11
- function parseArgs(argv) {
12
- const args = {};
13
- for (let i = 0; i < argv.length; i++) {
14
- const arg = argv[i];
15
- if (arg.startsWith("--")) {
16
- const key = arg.slice(2);
17
- const next = argv[i + 1];
18
- if (!next || next.startsWith("--")) {
19
- args[key] = true;
20
- } else {
21
- args[key] = next;
22
- i++;
23
- }
24
- }
25
- }
26
- return args;
27
- }
28
13
  function fetchUrl(url) {
29
14
  return new Promise((resolveP, rejectP) => {
30
15
  const lib = url.startsWith("https") ? https : http;
@@ -41,19 +26,6 @@ function fetchUrl(url) {
41
26
  });
42
27
  });
43
28
  }
44
- async function resolveWorkspacePath(wsPath) {
45
- const s = await promises.stat(wsPath);
46
- if (!s.isDirectory()) return wsPath;
47
- const entries = await promises.readdir(wsPath);
48
- const spector = entries.find((e) => e.endsWith(".spector"));
49
- if (!spector) throw new Error(`No .spector workspace file found in directory: ${wsPath}`);
50
- return path.join(wsPath, spector);
51
- }
52
- async function loadWorkspace(wsPath) {
53
- const resolved = await resolveWorkspacePath(wsPath);
54
- const raw = await promises.readFile(resolved, "utf8");
55
- return { workspace: JSON.parse(raw), dir: path.dirname(path.resolve(resolved)), file: resolved };
56
- }
57
29
  async function ensureDir(dir) {
58
30
  await promises.mkdir(dir, { recursive: true });
59
31
  }
@@ -89,16 +61,8 @@ async function cmdDescribe(args) {
89
61
  console.log("");
90
62
  }
91
63
  async function loadExistingMockPorts(workspace, dir) {
92
- const ports = [];
93
- for (const relPath of workspace.mocks ?? []) {
94
- try {
95
- const raw = await promises.readFile(path.join(dir, relPath), "utf8");
96
- const m = JSON.parse(raw);
97
- if (typeof m.port === "number") ports.push(m.port);
98
- } catch {
99
- }
100
- }
101
- return ports;
64
+ const mocks = await cliCommon.loadMocks(workspace, dir);
65
+ return mocks.map((m) => m.port).filter((p) => typeof p === "number");
102
66
  }
103
67
  async function cmdImportCollection(args) {
104
68
  const url = typeof args["url"] === "string" ? args["url"] : void 0;
@@ -112,7 +76,7 @@ async function cmdImportCollection(args) {
112
76
  process.exit(2);
113
77
  }
114
78
  const wsdlText = await fetchUrl(url);
115
- const { workspace, dir, file } = await loadWorkspace(wsArg);
79
+ const { workspace, dir, file } = await cliCommon.loadWorkspace(wsArg);
116
80
  const name = typeof args["name"] === "string" ? args["name"] : void 0;
117
81
  const { collection } = _import.importWsdl(wsdlText, { name });
118
82
  const relPath = _import.defaultCollectionRelPath(workspace, collection);
@@ -135,7 +99,7 @@ async function cmdImportMock(args) {
135
99
  process.exit(2);
136
100
  }
137
101
  const wsdlText = await fetchUrl(url);
138
- const { workspace, dir, file } = await loadWorkspace(wsArg);
102
+ const { workspace, dir, file } = await cliCommon.loadWorkspace(wsArg);
139
103
  const existingPorts = await loadExistingMockPorts(workspace, dir);
140
104
  const name = typeof args["name"] === "string" ? args["name"] : void 0;
141
105
  const { mock } = _import.importWsdl(wsdlText, { name, existingMockPorts: existingPorts });
@@ -153,7 +117,7 @@ async function cmdImportMock(args) {
153
117
  }
154
118
  async function main() {
155
119
  const [, , sub, ...rest] = process.argv;
156
- const args = parseArgs(rest);
120
+ const args = cliCommon.parseArgs(rest);
157
121
  if (sub === "describe") return cmdDescribe(args);
158
122
  if (sub === "import-collection") return cmdImportCollection(args);
159
123
  if (sub === "import-mock") return cmdImportMock(args);