@testsmith/api-spector 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  const undici = require("undici");
25
25
  const promises$1 = require("fs/promises");
26
26
  const nodeTls = require("tls");
27
- const handle = require("./handle-DDZ37J8G.js");
27
+ const handle = require("./handle-BY_jaVBT.js");
28
28
  const fs = require("node:fs");
29
29
  const os = require("node:os");
30
30
  const path = require("node:path");
@@ -1036,22 +1036,7 @@ function buildAt(ctx, testResults, consoleOutput) {
1036
1036
  }
1037
1037
  return sp;
1038
1038
  }
1039
- async function runScript(code, ctx, timeoutMs = 5e3) {
1040
- const faker = await getFaker();
1041
- const testResults = [];
1042
- const consoleOutput = [];
1043
- const envVarsCopy = { ...ctx.envVars };
1044
- const collectionCopy = { ...ctx.collectionVars };
1045
- const globalsCopy = { ...ctx.globals };
1046
- const localVarsCopy = { ...ctx.localVars };
1047
- const scriptCtx = {
1048
- envVars: envVarsCopy,
1049
- collectionVars: collectionCopy,
1050
- globals: globalsCopy,
1051
- localVars: localVarsCopy,
1052
- response: ctx.response,
1053
- piiMaskPatterns: ctx.piiMaskPatterns
1054
- };
1039
+ function buildSandbox(scriptCtx, testResults, consoleOutput, faker) {
1055
1040
  const sp = buildAt(scriptCtx, testResults, consoleOutput);
1056
1041
  const captureConsole = {
1057
1042
  log: (...args) => consoleOutput.push(args.map(String).join(" ")),
@@ -1059,8 +1044,9 @@ async function runScript(code, ctx, timeoutMs = 5e3) {
1059
1044
  error: (...args) => consoleOutput.push("[error] " + args.map(String).join(" ")),
1060
1045
  info: (...args) => consoleOutput.push("[info] " + args.map(String).join(" "))
1061
1046
  };
1062
- const sandbox = {
1047
+ return {
1063
1048
  sp,
1049
+ data: scriptCtx.data ?? {},
1064
1050
  DOMParser: makeSandboxDOMParser,
1065
1051
  dayjs,
1066
1052
  faker,
@@ -1082,6 +1068,24 @@ async function runScript(code, ctx, timeoutMs = 5e3) {
1082
1068
  // not available in sync vm context
1083
1069
  setInterval: void 0
1084
1070
  };
1071
+ }
1072
+ async function runScript(code, ctx, timeoutMs = 5e3) {
1073
+ const faker = await getFaker();
1074
+ const testResults = [];
1075
+ const consoleOutput = [];
1076
+ const envVarsCopy = { ...ctx.envVars };
1077
+ const collectionCopy = { ...ctx.collectionVars };
1078
+ const globalsCopy = { ...ctx.globals };
1079
+ const localVarsCopy = { ...ctx.localVars };
1080
+ const scriptCtx = {
1081
+ envVars: envVarsCopy,
1082
+ collectionVars: collectionCopy,
1083
+ globals: globalsCopy,
1084
+ localVars: localVarsCopy,
1085
+ response: ctx.response,
1086
+ piiMaskPatterns: ctx.piiMaskPatterns
1087
+ };
1088
+ const sandbox = buildSandbox(scriptCtx, testResults, consoleOutput, faker);
1085
1089
  try {
1086
1090
  vm__namespace.runInNewContext(code, sandbox, { timeout: timeoutMs, filename: "script.js" });
1087
1091
  } catch (err) {
@@ -1105,6 +1109,42 @@ async function runScript(code, ctx, timeoutMs = 5e3) {
1105
1109
  updatedLocalVars: localVarsCopy
1106
1110
  };
1107
1111
  }
1112
+ async function evaluateExpression(code, ctx, timeoutMs = 5e3) {
1113
+ const faker = await getFaker();
1114
+ const testResults = [];
1115
+ const consoleOutput = [];
1116
+ const envVarsCopy = { ...ctx.envVars };
1117
+ const collectionCopy = { ...ctx.collectionVars };
1118
+ const globalsCopy = { ...ctx.globals };
1119
+ const localVarsCopy = { ...ctx.localVars };
1120
+ const scriptCtx = {
1121
+ envVars: envVarsCopy,
1122
+ collectionVars: collectionCopy,
1123
+ globals: globalsCopy,
1124
+ localVars: localVarsCopy,
1125
+ response: ctx.response,
1126
+ piiMaskPatterns: ctx.piiMaskPatterns,
1127
+ data: ctx.data
1128
+ };
1129
+ const sandbox = buildSandbox(scriptCtx, testResults, consoleOutput, faker);
1130
+ const scopes = {
1131
+ updatedEnvVars: envVarsCopy,
1132
+ updatedCollectionVars: collectionCopy,
1133
+ updatedGlobals: globalsCopy,
1134
+ updatedLocalVars: localVarsCopy
1135
+ };
1136
+ try {
1137
+ let value;
1138
+ try {
1139
+ value = vm__namespace.runInNewContext(`(${code})`, sandbox, { timeout: timeoutMs, filename: "expression.js" });
1140
+ } catch {
1141
+ value = vm__namespace.runInNewContext(code, sandbox, { timeout: timeoutMs, filename: "expression.js" });
1142
+ }
1143
+ return { value, consoleOutput, ...scopes };
1144
+ } catch (err) {
1145
+ return { value: void 0, consoleOutput, ...scopes, error: err instanceof Error ? err.message : String(err) };
1146
+ }
1147
+ }
1108
1148
  function buildProxyUri(proxy) {
1109
1149
  const raw = proxy.url.trim();
1110
1150
  if (!raw) throw new Error("Proxy URL is empty");
@@ -2277,6 +2317,7 @@ exports.buildProxyUri = buildProxyUri;
2277
2317
  exports.buildSchemaTestResults = buildSchemaTestResults;
2278
2318
  exports.buildUrl = buildUrl;
2279
2319
  exports.deriveRunStatus = deriveRunStatus;
2320
+ exports.evaluateExpression = evaluateExpression;
2280
2321
  exports.executeRunnerRequest = executeRunnerRequest;
2281
2322
  exports.fetchOAuth2Token = fetchOAuth2Token;
2282
2323
  exports.getGlobals = getGlobals;
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const handle = require("./handle-DDZ37J8G.js");
2
+ const handle = require("./handle-BY_jaVBT.js");
3
3
  const https = require("node:https");
4
4
  const http = require("node:http");
5
5
  const xmldom = require("@xmldom/xmldom");
@@ -313,7 +313,7 @@ function registerSoapHandlers(ipc) {
313
313
  handle.handleIpc(ipc, handle.IPC.wsdl.import, async (_event, opts) => {
314
314
  const { validateWsdlImport } = await Promise.resolve().then(() => require("./ipc-validate-sqUFqRmE.js"));
315
315
  validateWsdlImport(opts);
316
- const { importWsdl } = await Promise.resolve().then(() => require("./import-CBAklepT.js"));
316
+ const { importWsdl } = await Promise.resolve().then(() => require("./import-DR_r1DUP.js"));
317
317
  const wsdlText = opts.xml ?? (opts.url ? await fetchUrl(opts.url) : "");
318
318
  if (!wsdlText) throw new Error("wsdl:import requires either `url` or `xml`");
319
319
  return importWsdl(wsdlText, { name: opts.name, existingMockPorts: opts.existingMockPorts });
@@ -4,7 +4,7 @@ const promises = require("node:fs/promises");
4
4
  const jsYaml = require("js-yaml");
5
5
  const undici = require("undici");
6
6
  const coverage = require("./chunks/coverage-C0U4QM83.js");
7
- const cliCommon = require("./chunks/cli-common-Cq_cphpZ.js");
7
+ const cliCommon = require("./chunks/cli-common-GnihVSjU.js");
8
8
  require("node:path");
9
9
  require("./chunks/data-files-BYqhsoPe.js");
10
10
  require("node:crypto");
@@ -2,19 +2,19 @@
2
2
  "use strict";
3
3
  const promises = require("node:fs/promises");
4
4
  const path = require("node:path");
5
- const brokerClient = require("./chunks/broker-client-BSTNMPtT.js");
5
+ const brokerClient = require("./chunks/broker-client-VwFF9sbp.js");
6
6
  const escape = require("./chunks/escape-DDVAlOvZ.js");
7
7
  const crypto = require("node:crypto");
8
8
  const undici = require("undici");
9
9
  const os = require("node:os");
10
- const cliCommon = require("./chunks/cli-common-Cq_cphpZ.js");
10
+ const cliCommon = require("./chunks/cli-common-GnihVSjU.js");
11
11
  const node_child_process = require("node:child_process");
12
12
  const jsYaml = require("js-yaml");
13
13
  const environments = require("./chunks/environments-iM3SUM-4.js");
14
- require("./chunks/request-exec-DLBjLZe1.js");
14
+ require("./chunks/request-exec-CcLHtkwG.js");
15
15
  require("fs/promises");
16
16
  require("tls");
17
- require("./chunks/handle-DDZ37J8G.js");
17
+ require("./chunks/handle-BY_jaVBT.js");
18
18
  require("node:fs");
19
19
  require("dayjs");
20
20
  require("node:vm");
@@ -4,7 +4,7 @@ const promises = require("node:fs/promises");
4
4
  const jsYaml = require("js-yaml");
5
5
  const undici = require("undici");
6
6
  const coverage = require("./chunks/coverage-C0U4QM83.js");
7
- const cliCommon = require("./chunks/cli-common-Cq_cphpZ.js");
7
+ const cliCommon = require("./chunks/cli-common-GnihVSjU.js");
8
8
  require("node:path");
9
9
  require("./chunks/data-files-BYqhsoPe.js");
10
10
  require("node:crypto");
@@ -0,0 +1,401 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const promises = require("node:fs/promises");
4
+ const path = require("node:path");
5
+ const requestExec = require("./chunks/request-exec-CcLHtkwG.js");
6
+ const requestCollection = require("./chunks/request-collection-KGPuvN7o.js");
7
+ const environments = require("./chunks/environments-iM3SUM-4.js");
8
+ const flowEngine = require("./chunks/flow-engine-BVzjz4lJ.js");
9
+ const cliCommon = require("./chunks/cli-common-GnihVSjU.js");
10
+ require("undici");
11
+ require("fs/promises");
12
+ require("tls");
13
+ require("./chunks/handle-BY_jaVBT.js");
14
+ require("node:fs");
15
+ require("node:os");
16
+ require("node:crypto");
17
+ require("dayjs");
18
+ require("node:vm");
19
+ require("tv4");
20
+ require("jsonpath-plus");
21
+ require("@xmldom/xmldom");
22
+ require("node:http");
23
+ require("ajv");
24
+ require("uuid");
25
+ require("./chunks/data-files-BYqhsoPe.js");
26
+ function esc(s) {
27
+ return String(s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
28
+ }
29
+ function pretty(v) {
30
+ if (typeof v === "string") {
31
+ try {
32
+ return esc(JSON.stringify(JSON.parse(v), null, 2));
33
+ } catch {
34
+ return esc(v);
35
+ }
36
+ }
37
+ try {
38
+ return esc(JSON.stringify(v, null, 2));
39
+ } catch {
40
+ return esc(String(v));
41
+ }
42
+ }
43
+ const STATUS_CLS = {
44
+ passed: "badge-pass",
45
+ failed: "badge-fail",
46
+ error: "badge-err",
47
+ done: "badge-done",
48
+ skipped: "badge-skip",
49
+ running: "badge-done"
50
+ };
51
+ function blockDetail(b) {
52
+ const parts = [];
53
+ if (b.error) parts.push(`<div class="err-row">&#x26a0; ${esc(b.error)}</div>`);
54
+ if (b.testResults?.length) {
55
+ const rows = b.testResults.map(
56
+ (tr) => `<div class="test-row ${tr.passed ? "test-pass" : "test-fail"}"><span class="dot">${tr.passed ? "✓" : "✗"}</span> ${esc(tr.name)}${!tr.passed && tr.error ? `<div class="test-err">${esc(tr.error)}</div>` : ""}</div>`
57
+ ).join("");
58
+ parts.push(`<div class="section-label">Tests</div><div class="tests-wrap">${rows}</div>`);
59
+ }
60
+ if (b.type === "request" && b.resolvedUrl) {
61
+ parts.push(`<div class="section-label">Request</div><div class="mono url">${esc(b.method ?? "")} ${esc(b.resolvedUrl)}</div>`);
62
+ }
63
+ if (b.type === "log" && b.message) {
64
+ parts.push(`<div class="section-label">Message</div><pre class="code-block">${esc(b.message)}</pre>`);
65
+ }
66
+ if (b.type === "display" && b.display !== void 0) {
67
+ parts.push(`<div class="section-label">Value</div><pre class="code-block">${pretty(b.display)}</pre>`);
68
+ }
69
+ return parts.join("");
70
+ }
71
+ function buildFlowHtmlReport(summary, meta = {}) {
72
+ const ts = meta.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
73
+ const flow = meta.flow ?? "Flow";
74
+ const env = meta.environment ?? "—";
75
+ const passRate = summary.total > 0 ? Math.round(summary.passed / summary.total * 100) : 0;
76
+ const cards = summary.blocks.map((b, idx) => {
77
+ const statusCls = STATUS_CLS[b.status] ?? "badge-done";
78
+ const httpCls = b.httpStatus == null ? "" : b.httpStatus < 400 ? "http-ok" : "http-err";
79
+ const dur = b.durationMs != null ? `${b.durationMs} ms` : "";
80
+ const at = b.atMs != null ? `+${b.atMs} ms` : "";
81
+ const detail = blockDetail(b);
82
+ return `
83
+ <div class="card" id="r${idx}">
84
+ <div class="card-header" ${detail ? `onclick="toggle(${idx})"` : ""}>
85
+ <span class="chevron" id="ch${idx}">${detail ? "▶" : ""}</span>
86
+ <span class="at mono muted">${esc(at)}</span>
87
+ <span class="badge ${statusCls}">${esc(b.status)}</span>
88
+ <span class="type mono">${esc(b.type)}</span>
89
+ <span class="card-name">${esc(b.label)}${b.iteration != null ? ` <span class="muted">#${b.iteration}</span>` : ""}</span>
90
+ <span class="card-url muted">${esc(b.resolvedUrl ?? "")}</span>
91
+ <span class="dur muted">${esc(dur)}</span>
92
+ ${b.httpStatus != null ? `<span class="http-badge ${httpCls}">${b.httpStatus}</span>` : ""}
93
+ </div>
94
+ ${detail ? `<div class="card-body" id="cb${idx}" style="display:none">${detail}</div>` : ""}
95
+ </div>`;
96
+ }).join("\n");
97
+ const vars = summary.variables;
98
+ const varRows = vars ? [
99
+ ...Object.entries(vars.localVars).map(([k, v]) => [k, v, "local"]),
100
+ ...Object.entries(vars.collectionVars).map(([k, v]) => [k, v, "collection"]),
101
+ ...Object.entries(vars.globals).map(([k, v]) => [k, v, "global"])
102
+ ].map(([k, v, scope]) => `<tr><td class="hk mono">${esc(k)}</td><td class="muted">${esc(scope)}</td><td class="hv mono">${esc(v)}</td></tr>`).join("") : "";
103
+ return `<!DOCTYPE html>
104
+ <html lang="en">
105
+ <head>
106
+ <meta charset="UTF-8">
107
+ <meta name="viewport" content="width=device-width, initial-scale=1">
108
+ <title>${esc(flow)} — Flow Report</title>
109
+ <style>
110
+ :root { --bg:#0f1117; --surface:#161b22; --border:#21262d; --text:#c9d1d9; --bright:#e6edf3; --muted:#8b949e;
111
+ --green:#3fb950; --red:#f85149; --amber:#d29922; --blue:#79c0ff;
112
+ --pass-bg:#0d3a1e; --fail-bg:#3d1014; --err-bg:#3d2a00; --skip-bg:#21262d; }
113
+ html.light { --bg:#fff; --surface:#f6f8fa; --border:#d0d7de; --text:#1f2328; --bright:#1f2328; --muted:#656d76;
114
+ --green:#1a7f37; --red:#cf222e; --amber:#9a6700; --blue:#0969da;
115
+ --pass-bg:#dafbe1; --fail-bg:#ffebe9; --err-bg:#fff8c5; --skip-bg:#f6f8fa; }
116
+ *,*::before,*::after { box-sizing:border-box; margin:0; padding:0; }
117
+ body { font-family:system-ui,sans-serif; background:var(--bg); color:var(--text); font-size:13px; line-height:1.5; }
118
+ .wrap { max-width:1100px; margin:0 auto; padding:32px 24px; }
119
+ h1 { font-size:20px; font-weight:600; color:var(--bright); margin-bottom:4px; }
120
+ .meta-line { color:var(--muted); font-size:11px; margin-bottom:24px; }
121
+ .mono { font-family:ui-monospace,Menlo,Consolas,monospace; }
122
+ .muted { color:var(--muted); }
123
+ .theme-toggle { position:fixed; top:16px; right:16px; background:var(--surface); border:1px solid var(--border); border-radius:6px; padding:4px 10px; font-size:16px; cursor:pointer; line-height:1; }
124
+ .summary { display:flex; gap:12px; margin-bottom:24px; flex-wrap:wrap; }
125
+ .stat { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:12px 20px; min-width:90px; }
126
+ .stat-val { font-size:22px; font-weight:700; color:var(--bright); }
127
+ .stat-lbl { font-size:11px; color:var(--muted); margin-top:2px; }
128
+ .stat-pass .stat-val { color:var(--green); } .stat-fail .stat-val { color:var(--red); } .stat-err .stat-val { color:var(--amber); }
129
+ .section-title { font-size:12px; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); font-weight:600; margin:24px 0 8px; }
130
+ .card { background:var(--surface); border:1px solid var(--border); border-radius:8px; margin-bottom:6px; overflow:hidden; }
131
+ .card-header { display:flex; align-items:center; gap:10px; padding:10px 14px; cursor:default; }
132
+ .card-header[onclick] { cursor:pointer; }
133
+ .chevron { width:10px; color:var(--muted); font-size:9px; transition:transform .15s; }
134
+ .chevron.open { transform:rotate(90deg); }
135
+ .at { width:74px; font-size:11px; text-align:right; flex-shrink:0; }
136
+ .badge { font-size:10px; font-weight:700; text-transform:uppercase; padding:2px 7px; border-radius:4px; letter-spacing:.04em; }
137
+ .badge-pass { background:var(--pass-bg); color:var(--green); } .badge-fail { background:var(--fail-bg); color:var(--red); }
138
+ .badge-err { background:var(--err-bg); color:var(--amber); } .badge-done,.badge-skip { background:var(--skip-bg); color:var(--muted); }
139
+ .type { font-size:11px; color:var(--blue); min-width:64px; }
140
+ .card-name { font-weight:500; color:var(--bright); }
141
+ .card-url { font-family:ui-monospace,monospace; font-size:11px; margin-left:auto; max-width:40%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
142
+ .dur { font-size:11px; }
143
+ .http-badge { font-family:ui-monospace,monospace; font-size:11px; font-weight:600; padding:1px 6px; border-radius:4px; background:var(--skip-bg); }
144
+ .http-ok { color:var(--green); } .http-err { color:var(--red); }
145
+ .card-body { padding:12px 16px 14px 34px; border-top:1px solid var(--border); }
146
+ .section-label { font-size:10px; text-transform:uppercase; letter-spacing:.05em; color:var(--muted); font-weight:600; margin:10px 0 4px; }
147
+ .code-block { background:var(--bg); border:1px solid var(--border); border-radius:6px; padding:8px 10px; font-family:ui-monospace,monospace; font-size:11px; white-space:pre-wrap; word-break:break-all; max-height:360px; overflow:auto; }
148
+ .url { font-size:11px; }
149
+ .err-row { color:var(--red); font-size:12px; margin:4px 0; }
150
+ .test-row { font-size:12px; padding:2px 0; } .test-pass .dot { color:var(--green); } .test-fail { color:var(--red); } .test-fail .dot { color:var(--red); }
151
+ .test-err { color:var(--red); font-family:ui-monospace,monospace; font-size:11px; margin-left:16px; }
152
+ table.vars { width:100%; border-collapse:collapse; background:var(--surface); border:1px solid var(--border); border-radius:8px; overflow:hidden; }
153
+ table.vars td { padding:5px 10px; border-bottom:1px solid var(--border); font-size:12px; }
154
+ .hk { color:var(--bright); } .hv { color:var(--text); word-break:break-all; }
155
+ </style>
156
+ </head>
157
+ <body>
158
+ <button class="theme-toggle" onclick="document.documentElement.classList.toggle('light')" title="Toggle theme">◐</button>
159
+ <div class="wrap">
160
+ <h1>${esc(flow)}</h1>
161
+ <div class="meta-line">Flow report · ${esc(env)} · ${esc(ts)}</div>
162
+ <div class="summary">
163
+ <div class="stat stat-pass"><div class="stat-val">${summary.passed}</div><div class="stat-lbl">Passed</div></div>
164
+ <div class="stat stat-fail"><div class="stat-val">${summary.failed}</div><div class="stat-lbl">Failed</div></div>
165
+ <div class="stat stat-err"><div class="stat-val">${summary.errors}</div><div class="stat-lbl">Errors</div></div>
166
+ <div class="stat"><div class="stat-val">${summary.total}</div><div class="stat-lbl">Blocks</div></div>
167
+ <div class="stat"><div class="stat-val">${passRate}%</div><div class="stat-lbl">Pass rate</div></div>
168
+ <div class="stat"><div class="stat-val">${summary.durationMs}<span style="font-size:13px">ms</span></div><div class="stat-lbl">Duration</div></div>
169
+ </div>
170
+ <div class="section-title">Run log</div>
171
+ ${cards}
172
+ ${varRows ? `<div class="section-title">Variables</div><table class="vars"><tbody>${varRows}</tbody></table>` : ""}
173
+ </div>
174
+ <script>
175
+ function toggle(i){ var b=document.getElementById('cb'+i), c=document.getElementById('ch'+i);
176
+ if(!b)return; var open=b.style.display!=='none'; b.style.display=open?'none':'block'; if(c)c.classList.toggle('open',!open); }
177
+ <\/script>
178
+ </body>
179
+ </html>`;
180
+ }
181
+ function buildJunit(flowName, summary) {
182
+ const cases = summary.blocks.filter((b) => b.type === "request" || b.type === "validate").map((b) => {
183
+ const name = `${b.label}${b.resolvedUrl ? ` ${b.resolvedUrl}` : ""}`;
184
+ const inner = b.status === "failed" || b.status === "error" ? `
185
+ <failure message="${escapeXml(b.error ?? "assertion failed")}"/>
186
+ ` : "";
187
+ return ` <testcase name="${escapeXml(name)}" classname="${escapeXml(flowName)}" time="${((b.durationMs ?? 0) / 1e3).toFixed(3)}">${inner}</testcase>`;
188
+ }).join("\n");
189
+ const failures = summary.failed;
190
+ const errors = summary.errors;
191
+ const tests = summary.blocks.filter((b) => b.type === "request" || b.type === "validate").length;
192
+ return `<?xml version="1.0" encoding="UTF-8"?>
193
+ <testsuites>
194
+ <testsuite name="${escapeXml(flowName)}" tests="${tests}" failures="${failures}" errors="${errors}" time="${(summary.durationMs / 1e3).toFixed(3)}">
195
+ ${cases}
196
+ </testsuite>
197
+ </testsuites>
198
+ `;
199
+ }
200
+ function escapeXml(s) {
201
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
202
+ }
203
+ async function main() {
204
+ const argv = process.argv.slice(2);
205
+ const positionals = [];
206
+ for (const a of argv) {
207
+ if (a.startsWith("--")) break;
208
+ positionals.push(a);
209
+ }
210
+ const sub = positionals[0];
211
+ const name = positionals[1];
212
+ const args = cliCommon.parseArgs(argv);
213
+ if (args.help || !sub || sub !== "run" && sub !== "list") {
214
+ console.log(
215
+ "\nUsage:\n api-spector flow list --workspace <path>\n api-spector flow run <name> --workspace <path> [--environment <name>]\n [--output <path>] [--format json|junit] [--verbose]\n"
216
+ );
217
+ process.exit(args.help ? 0 : 1);
218
+ }
219
+ const wsPath = args.workspace;
220
+ if (!wsPath) {
221
+ console.error(cliCommon.color("Error: --workspace is required", cliCommon.C.red));
222
+ process.exit(1);
223
+ }
224
+ let workspace, wsDir;
225
+ try {
226
+ ;
227
+ ({ workspace, dir: wsDir } = await cliCommon.loadWorkspace(wsPath));
228
+ } catch {
229
+ console.error(cliCommon.color(`Error: could not read workspace: ${wsPath}`, cliCommon.C.red));
230
+ process.exit(1);
231
+ }
232
+ await requestExec.loadGlobals(wsDir);
233
+ requestExec.setSecretsConfig(workspace.settings?.secrets);
234
+ const flows = (await cliCommon.loadFlows(
235
+ workspace,
236
+ wsDir,
237
+ (relPath) => console.error(cliCommon.color(` [warn] Could not load flow: ${relPath}`, cliCommon.C.yellow))
238
+ )).map(flowEngine.normalizeFlow);
239
+ if (sub === "list") {
240
+ if (flows.length === 0) {
241
+ console.log(cliCommon.color(" No flows in this workspace.", cliCommon.C.gray));
242
+ process.exit(0);
243
+ }
244
+ console.log("");
245
+ for (const f of flows) {
246
+ const steps = f.nodes.filter((n) => n.type !== "start" && n.type !== "end").length;
247
+ console.log(` ${cliCommon.color(f.name, cliCommon.C.bold, cliCommon.C.white)} ${cliCommon.color(`(${steps} blocks)`, cliCommon.C.gray)}`);
248
+ }
249
+ console.log("");
250
+ process.exit(0);
251
+ }
252
+ if (!name) {
253
+ console.error(cliCommon.color("Error: a flow name is required: api-spector flow run <name>", cliCommon.C.red));
254
+ process.exit(1);
255
+ }
256
+ const flow = flows.find((f) => f.name.toLowerCase() === name.toLowerCase());
257
+ if (!flow) {
258
+ console.error(cliCommon.color(`Error: flow "${name}" not found. Run \`api-spector flow list\` to see available flows.`, cliCommon.C.red));
259
+ process.exit(1);
260
+ }
261
+ const collections = await cliCommon.loadCollections(workspace, wsDir, {
262
+ onError: (relPath) => console.error(cliCommon.color(` [warn] Could not load collection: ${relPath}`, cliCommon.C.yellow))
263
+ });
264
+ const environments$1 = await cliCommon.loadEnvironments(workspace, wsDir);
265
+ const envName = args.environment ?? args.env;
266
+ const env = environments.selectEnvironment(workspace, environments$1, envName);
267
+ const verbose = Boolean(args.verbose);
268
+ const version = `v${"0.7.4"}`;
269
+ console.log("");
270
+ console.log(cliCommon.color(" API Flow Runner" + (version ? ` ${version}` : ""), cliCommon.C.bold, cliCommon.C.white));
271
+ console.log(cliCommon.color(` Flow: ${flow.name}`, cliCommon.C.gray));
272
+ console.log(cliCommon.color(` Workspace: ${wsPath}`, cliCommon.C.gray));
273
+ console.log(cliCommon.color(` Environment: ${env?.name ?? "(none)"}`, cliCommon.C.gray));
274
+ console.log("");
275
+ const envVars = await requestExec.buildEnvVars(env);
276
+ const globals = requestExec.getGlobals();
277
+ const dispatcher = await requestExec.buildDispatcher(workspace.settings?.proxy, workspace.settings?.tls);
278
+ const piiMaskPatterns = workspace.settings?.piiMaskPatterns ?? [];
279
+ const colById = new Map(collections.map((c) => [c.id, c]));
280
+ const printEvent = (evt) => {
281
+ const spec = flowEngine.BLOCK_SPECS[evt.blockType];
282
+ if (evt.kind === "log") {
283
+ console.log(cliCommon.color(` · ${evt.message}`, cliCommon.C.cyan));
284
+ return;
285
+ }
286
+ if (evt.kind === "display") {
287
+ const v = evt.display?.value;
288
+ console.log(cliCommon.color(` ▣ ${evt.display?.label ?? "display"}:`, cliCommon.C.magenta), typeof v === "string" ? v : JSON.stringify(v, null, 2));
289
+ return;
290
+ }
291
+ if (evt.kind !== "block-result") return;
292
+ const icon = evt.status === "passed" ? cliCommon.color("✓", cliCommon.C.green, cliCommon.C.bold) : evt.status === "failed" ? cliCommon.color("✗", cliCommon.C.red, cliCommon.C.bold) : evt.status === "error" ? cliCommon.color("⚠", cliCommon.C.yellow, cliCommon.C.bold) : cliCommon.color("•", cliCommon.C.gray);
293
+ const http = evt.httpStatus ? cliCommon.color(` ${evt.httpStatus}`, evt.httpStatus < 400 ? cliCommon.C.green : cliCommon.C.red) : "";
294
+ const dur = evt.durationMs !== void 0 ? cliCommon.color(` ${evt.durationMs}ms`, cliCommon.C.gray) : "";
295
+ const iter = evt.iteration !== void 0 ? cliCommon.color(` [#${evt.iteration}]`, cliCommon.C.gray) : "";
296
+ console.log(` ${icon} ${cliCommon.color((spec?.label ?? evt.blockType).padEnd(12), cliCommon.C.cyan)}${iter}${http}${dur}`);
297
+ if (verbose && evt.testResults?.length) {
298
+ for (const t of evt.testResults) {
299
+ console.log(` ${t.passed ? cliCommon.color("✓", cliCommon.C.green) : cliCommon.color("✗", cliCommon.C.red)} ${t.name}${t.error ? cliCommon.color(` - ${t.error}`, cliCommon.C.red) : ""}`);
300
+ }
301
+ }
302
+ if (evt.error) console.log(cliCommon.color(` Error: ${evt.error}`, cliCommon.C.red));
303
+ };
304
+ const deps = {
305
+ runRequest: async (ref, input) => {
306
+ const col = colById.get(ref.collectionId);
307
+ const original = col?.requests[ref.requestId];
308
+ if (!col || !original) return null;
309
+ const req = JSON.parse(JSON.stringify(original));
310
+ const inherited = requestCollection.resolveInheritedAuthAndHeaders(original.id, col);
311
+ if (!requestCollection.authIsConfigured(req.auth) && inherited.auth && inherited.auth.type !== "none") req.auth = inherited.auth;
312
+ const inheritedHeaders = inherited.headers.filter((h) => h.enabled && h.key);
313
+ if (inheritedHeaders.length) req.headers = [...inheritedHeaders, ...req.headers ?? []];
314
+ const { result, updatedEnvVars, updatedCollectionVars, updatedGlobals, updatedLocalVars } = await requestExec.executeRunnerRequest({
315
+ req,
316
+ collectionVars: { ...col.collectionVariables ?? {}, ...input.collectionVars },
317
+ envVars: input.envVars,
318
+ globals: input.globals,
319
+ localVars: input.localVars,
320
+ // Flows chain on the real response body (not the PII-masked report copy).
321
+ dispatcher,
322
+ piiMaskPatterns: [],
323
+ proxy: workspace.settings?.proxy,
324
+ tls: workspace.settings?.tls
325
+ });
326
+ const rr = result.receivedResponse;
327
+ const response = rr ? { status: rr.status, statusText: rr.statusText, headers: rr.headers, body: rr.body, bodySize: rr.body?.length ?? 0, durationMs: result.durationMs ?? 0 } : null;
328
+ return {
329
+ envVars: updatedEnvVars,
330
+ collectionVars: updatedCollectionVars,
331
+ globals: updatedGlobals,
332
+ localVars: updatedLocalVars,
333
+ response,
334
+ status: result.status,
335
+ httpStatus: result.httpStatus,
336
+ durationMs: result.durationMs,
337
+ error: result.error,
338
+ testResults: result.testResults,
339
+ name: result.name,
340
+ method: result.method,
341
+ resolvedUrl: result.resolvedUrl
342
+ };
343
+ },
344
+ runScript: async (code, input) => {
345
+ const out = await requestExec.runScript(code, { envVars: input.envVars, collectionVars: input.collectionVars, globals: input.globals, localVars: input.localVars, response: input.response ?? void 0, piiMaskPatterns, data: input.data });
346
+ if (verbose && out.consoleOutput?.length) for (const line of out.consoleOutput) console.log(cliCommon.color(` ${line}`, cliCommon.C.gray));
347
+ return { envVars: out.updatedEnvVars, collectionVars: out.updatedCollectionVars, globals: out.updatedGlobals, localVars: out.updatedLocalVars, testResults: out.testResults, consoleOutput: out.consoleOutput, error: out.error };
348
+ },
349
+ evalExpression: async (code, input) => {
350
+ const out = await requestExec.evaluateExpression(code, { envVars: input.envVars, collectionVars: input.collectionVars, globals: input.globals, localVars: input.localVars, response: input.response ?? void 0, piiMaskPatterns, data: input.data });
351
+ return { envVars: out.updatedEnvVars, collectionVars: out.updatedCollectionVars, globals: out.updatedGlobals, localVars: out.updatedLocalVars, value: out.value, consoleOutput: out.consoleOutput, error: out.error };
352
+ },
353
+ resolveFlow: (flowId) => flows.find((f) => f.id === flowId) ?? null,
354
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
355
+ interpolate: (str, vars) => requestExec.interpolate(str, vars),
356
+ onEvent: printEvent
357
+ };
358
+ let summary;
359
+ try {
360
+ summary = await flowEngine.runFlow(flow, deps, { envVars, globals });
361
+ } catch (err) {
362
+ console.error(cliCommon.color(`
363
+ Flow aborted: ${err instanceof Error ? err.message : String(err)}`, cliCommon.C.red));
364
+ process.exit(1);
365
+ }
366
+ console.log("");
367
+ console.log(
368
+ ` ${cliCommon.color(`${summary.passed} passed`, cliCommon.C.green)}` + (summary.failed ? ` ${cliCommon.color(`${summary.failed} failed`, cliCommon.C.red)}` : "") + (summary.errors ? ` ${cliCommon.color(`${summary.errors} errors`, cliCommon.C.yellow)}` : "") + cliCommon.color(` ${summary.total} blocks · ${summary.durationMs}ms`, cliCommon.C.gray)
369
+ );
370
+ console.log("");
371
+ if (verbose && summary.variables) {
372
+ const v = summary.variables;
373
+ const rows = [
374
+ ...Object.entries(v.localVars).map(([k, val]) => [k, val, "local"]),
375
+ ...Object.entries(v.collectionVars).map(([k, val]) => [k, val, "collection"]),
376
+ ...Object.entries(v.globals).map(([k, val]) => [k, val, "global"])
377
+ ];
378
+ if (rows.length) {
379
+ console.log(cliCommon.color(" Variables:", cliCommon.C.bold));
380
+ for (const [k, val, scope] of rows) {
381
+ console.log(` ${cliCommon.color(k, cliCommon.C.cyan)} ${cliCommon.color(`(${scope})`, cliCommon.C.gray)} = ${val.length > 80 ? val.slice(0, 80) + "…" : val}`);
382
+ }
383
+ console.log("");
384
+ }
385
+ }
386
+ const outputPath = args.output;
387
+ if (outputPath) {
388
+ const explicit = args.format?.toLowerCase();
389
+ const ext = path.extname(outputPath).toLowerCase();
390
+ const fmt = explicit ?? (ext === ".xml" ? "junit" : ext === ".html" ? "html" : "json");
391
+ const content = fmt === "junit" ? buildJunit(flow.name, summary) : fmt === "html" ? buildFlowHtmlReport(summary, { flow: flow.name, environment: env?.name ?? null }) : JSON.stringify(summary, null, 2);
392
+ await promises.writeFile(outputPath, content, "utf8");
393
+ console.log(cliCommon.color(` Report written to ${outputPath} (${fmt})`, cliCommon.C.gray));
394
+ console.log("");
395
+ }
396
+ process.exit(summary.failed + summary.errors > 0 ? 1 : 0);
397
+ }
398
+ main().catch((err) => {
399
+ console.error(err);
400
+ process.exit(1);
401
+ });
@@ -5,7 +5,7 @@ const jsYaml = require("js-yaml");
5
5
  const undici = require("undici");
6
6
  const uuid = require("uuid");
7
7
  const coverage = require("./chunks/coverage-C0U4QM83.js");
8
- const cliCommon = require("./chunks/cli-common-Cq_cphpZ.js");
8
+ const cliCommon = require("./chunks/cli-common-GnihVSjU.js");
9
9
  require("node:path");
10
10
  require("./chunks/data-files-BYqhsoPe.js");
11
11
  require("node:crypto");