@powerduck/openapi-cli 0.2.14 → 0.2.16

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/README.md CHANGED
@@ -238,7 +238,7 @@ MIT © [POWERDUCK LIMITED](https://www.powerduck.com)
238
238
 
239
239
  ## Transport and scenario contract (0.2.13)
240
240
 
241
- Requires Node.js 18.17 or newer. CLI arguments override JSON configuration only
241
+ Requires Node.js 22.17 or newer. CLI arguments override JSON configuration only
242
242
  when explicitly supplied; `openapi-cli --config config.json` accepts `specPath`
243
243
  from that file. Concurrency must be an integer from 1–100 and timeout from
244
244
  1–600000 milliseconds. Report versions match the installed package.
@@ -0,0 +1 @@
1
+ var e={name:"@powerduck/openapi-cli",version:"0.2.16",description:"CI-ready CLI for batch-testing OpenAPI documents across HTTP, SSE, WebSocket, GraphQL, gRPC and MCP",type:"module",main:"./dist/index.cjs",module:"./dist/index.js",types:"./dist/index.d.ts",exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/index.cjs"},"./package.json":"./package.json"},bin:{"openapi-cli":"./dist/cli.js"},files:["dist","README.md","LICENSE"],sideEffects:!1,engines:{node:">=22.0.0"},scripts:{build:"tsc --noEmit && tsup",typecheck:"tsc --noEmit",test:"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage",prepublishOnly:"npm run build",check:"tsc --noEmit && vitest run"},keywords:["openapi","cli","testing","grpc","mcp","graphql","sse","websocket","ci","api-testing","contract-testing"],license:"MIT",dependencies:{"@powerduck/openapi-parser":"^0.3.11","@powerduck/openapi-request":"0.2.20",commander:"^12.1.0","https-proxy-agent":"^5.0.1"},devDependencies:{"@types/node":"^20.14.0","@vitest/coverage-v8":"^3.2.7",terser:"^5.51.2",tsup:"^8.0.0",typescript:"^5.5.0",vitest:"^3.2.7",ws:"^8.21.3","@grpc/proto-loader":"^0.8.1","@grpc/grpc-js":"^1.14.6"},repository:{type:"git",url:"git+https://github.com/powerducklab/openapi-cli.git"},bugs:{url:"https://github.com/powerducklab/openapi-cli/issues"},author:"POWERDUCK LIMITED",homepage:"https://www.powerduck.com/",publishConfig:{access:"public"}};import t from"fs";import r from"path";import s from"http";import o from"https";import{dereference as n}from"@powerduck/openapi-parser";var a={outputDir:"./openapi-cli-report",formats:["json","cli","html"],concurrency:5,timeout:3e4,failOnError:!0,grpcReflection:!0,mcpTransport:"streamable-http"};function i(e){for(const[t,r,s]of[["concurrency",e.concurrency??5,100],["timeout",e.timeout??3e4,6e5]])if(!Number.isInteger(r)||r<1||r>s)throw new Error(`${t} must be an integer between 1 and ${s}.`);if(e.formats?.some(e=>!["json","cli","html"].includes(e)))throw new Error("Unsupported report format.");if(e.mcpTransport&&!["stdio","streamable-http"].includes(e.mcpTransport))throw new Error("Unsupported MCP transport.")}function c(e){const s=e.config?function(e){const s=r.resolve(e);if(!t.existsSync(s))throw new Error(`Config file not found: ${s}`);const o=t.readFileSync(s,"utf-8");try{const e=JSON.parse(o);if(!e||"object"!=typeof e||Array.isArray(e))throw new Error("Config must be an object.");return e}catch(e){throw new Error(`Invalid config file (must be JSON): ${e.message}`)}}(e.config):{},o={specPath:e.spec??s.specPath??"",serverUrl:e.server??s.serverUrl,outputDir:e.output??s.outputDir??a.outputDir,formats:p(e.format??s.formats),filter:d(e,s),concurrency:e.concurrency??s.concurrency??a.concurrency,timeout:e.timeout??s.timeout??a.timeout,proxy:e.proxy??s.proxy,tls:{caCert:e.ca??s.tls?.caCert,clientCert:e.cert??s.tls?.clientCert,clientKey:e.key??s.tls?.clientKey,pfx:s.tls?.pfx,passphrase:s.tls?.passphrase,strictSSL:void 0!==e.insecure?!e.insecure:s.tls?.strictSSL},requester:s.requester,maxEvents:s.maxEvents,maxStreamMs:s.maxStreamMs,maxResponseSize:s.maxResponseSize,headers:l(e.header,s.headers),auth:u(e,s),variables:f(e.variable,s.variables),failOnError:e.failOnError??s.failOnError??a.failOnError,envFile:e.env??s.envFile,grpcReflection:e.grpcReflection??s.grpcReflection,grpcProtoPaths:e.grpcProto??s.grpcProtoPaths,mcpTransport:e.mcpTransport??s.mcpTransport,mcpCommand:e.mcpCommand??s.mcpCommand,mcpArgs:e.mcpArgs?e.mcpArgs.split(/[\s,]+/).filter(Boolean):s.mcpArgs,mcpCwd:e.mcpCwd??s.mcpCwd};if(o.envFile&&function(e){const s=r.resolve(e);if(!t.existsSync(s))return;const o=t.readFileSync(s,"utf-8").split("\n");for(const e of o){const t=e.trim();if(!t||t.startsWith("#"))continue;const r=t.indexOf("=");if(r>0){const e=t.slice(0,r).trim(),s=t.slice(r+1).trim().replace(/^["']|["']$/g,"");e in process.env||(process.env[e]=s)}}}(o.envFile),function(e){const t=e=>"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,(e,t)=>process.env[t]??"");if(e.serverUrl=t(e.serverUrl),e.proxy=t(e.proxy),e.headers)for(const[r,s]of Object.entries(e.headers))e.headers[r]=t(s);e.auth?.token&&(e.auth.token=t(e.auth.token));if(e.variables)for(const[r,s]of Object.entries(e.variables))e.variables[r]=t(s)}(o),!o.specPath)throw new Error("No OpenAPI spec path provided. Use --spec <path> or set specPath in config.");if(o.proxy)try{new URL(o.proxy)}catch{throw new Error(`Invalid proxy URL: ${o.proxy}`)}return i(o),o}function p(e){return Array.isArray(e)?e:"string"==typeof e?e.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):a.formats}function d(e,t){const r=e.method?e.method.split(",").map(e=>e.trim().toLowerCase()):t.filter?.methods,s=e.path?e.path.split(",").map(e=>e.trim()):t.filter?.paths,o=e.tag?e.tag.split(",").map(e=>e.trim()):t.filter?.tags,n=e.operationId?e.operationId.split(",").map(e=>e.trim()):t.filter?.operationIds;if(r||s||o||n)return{methods:r,paths:s,tags:o,operationIds:n}}function l(e,t){const r={...t??{}};for(const t of e??[]){const e=t.indexOf(":");if(e>0){const s=t.slice(0,e).trim(),o=t.slice(e+1).trim();s&&(r[s]=o)}}return Object.keys(r).length?r:void 0}function u(e,t){return e.bearer?{type:"bearer",token:e.bearer}:t.auth}function f(e,t){const r={...t??{}};for(const t of e??[]){const e=t.indexOf("=");e>0&&(r[t.slice(0,e).trim()]=t.slice(e+1).trim())}return Object.keys(r).length?r:void 0}function m(e){return/^https?:\/\//i.test(e.trim())}async function h(e){const i=e.specPath.trim();let c;if(m(i))c=await function(e,t){let r=0;return new Promise((n,a)=>{const i=c=>{const p=("https:"===new URL(c).protocol?o:s).get(c,{timeout:t,headers:{Accept:"application/json, text/plain, */*","User-Agent":"@powerduck/openapi-cli"}},t=>{if(t.statusCode&&t.statusCode>=300&&t.statusCode<400&&t.headers.location){if(++r>5)return t.resume(),void a(new Error(`Too many redirects (>5) while fetching spec: ${e}`));const s=new URL(t.headers.location,c).toString();return t.resume(),void i(s)}if(200!==t.statusCode)return t.resume(),void a(new Error(`Failed to fetch spec from ${c}: HTTP ${t.statusCode} ${t.statusMessage??""}`.trim()));const s=[];t.on("data",e=>s.push(e)),t.on("end",()=>n(Buffer.concat(s).toString("utf-8"))),t.on("error",e=>a(new Error(`Error reading spec response: ${e.message}`)))});p.on("timeout",()=>{p.destroy(new Error(`Timed out after ${t}ms while fetching spec: ${e}`))}),p.on("error",t=>a(new Error(`Network error fetching spec ${e}: ${t.message}`)))};i(e)})}(i,e.timeout??a.timeout);else{const e=r.resolve(i);if(!t.existsSync(e))throw new Error(`OpenAPI spec not found: ${e}`);if(!t.statSync(e).isFile())throw new Error(`OpenAPI spec path is not a file: ${e}`);c=t.readFileSync(e,"utf-8")}const p=function(e,t){let r;try{r=JSON.parse(e)}catch(e){throw new Error(`Spec from ${t} is not valid JSON: ${e.message}`)}if(!r||"object"!=typeof r||Array.isArray(r))throw new Error(`Spec from ${t} is not a JSON object`);if(!r.paths||"object"!=typeof r.paths)throw new Error(`Spec from ${t} does not contain a valid 'paths' object`);return r}(c,i);return async function(e){try{const t=await n(e);return t.errors&&t.errors.length>0?e:t.schema??t.specification??e}catch(t){return e}}(p)}function g(e,t){return e.map(e=>{try{const r=function(e,t){switch(e.assert){case"status":if(void 0===e.value)throw new Error("status assertion requires 'value'");return t.status===e.value;case"header":{if(!e.key)throw new Error("header assertion requires 'key'");const r=function(e,t){const r=t.toLowerCase();for(const[t,s]of Object.entries(e))if(t.toLowerCase()===r)return s;return}(t.headers??{},e.key);return void 0!==r&&(void 0===e.contains||r.toLowerCase().includes(e.contains.toLowerCase()))}case"bodyContains":if(void 0===e.contains)throw new Error("bodyContains assertion requires 'contains'");return(t.bodyText??JSON.stringify(t.body??"")).includes(e.contains);case"bodyEquals":if(void 0===e.body)throw new Error("bodyEquals assertion requires 'body'");return(t.bodyText??JSON.stringify(t.body??""))===e.body;case"jsonPath":{if(!e.path)throw new Error("jsonPath assertion requires 'path'");const r=v(t.body,e.path);return void 0!==e.exists?e.exists?void 0!==r:void 0===r:void 0!==e.equals?b(r,e.equals):void 0!==r}case"responseTime":if(void 0===e.max)throw new Error("responseTime assertion requires 'max'");return t.durationMs<=e.max;default:throw new Error(`Unknown assertion type: ${e.assert}`)}}(e,t);return{name:e.name,passed:r}}catch(t){return{name:e.name,passed:!1,error:t.message}}})}function v(e,t){const r=t.replace(/^\$\.?/,"");if(!r)return e;const s=r.split(/\.|\[(\d+)\]/).filter(e=>void 0!==e&&""!==e);let o=e;for(const e of s){if(null==o)return;o=/^\d+$/.test(e)?o[Number(e)]:o[e]}return o}function b(e,t){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(null===e||null===t)return e===t;if("object"!=typeof e)return e===t;if(Array.isArray(e)!==Array.isArray(t))return!1;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,r)=>b(e,t[r]));const r=Object.keys(e),s=Object.keys(t);return r.length===s.length&&r.every(r=>b(e[r],t[r]))}function y(e,t){const r=e?.["x-tests"],s=t?.["x-tests"];return[...Array.isArray(s)?s:[],...Array.isArray(r)?r:[]].filter(e=>e&&"object"==typeof e&&e.assert&&e.name)}function w(e){const t=e?.["x-postman-scripts"];if(t&&"object"==typeof t&&"string"==typeof t.test)return t.test}import{operationEntries as x}from"@powerduck/openapi-parser/methods";import{createClient as $}from"@powerduck/openapi-request";import k from"fs";import{createPrivateKey as E}from"crypto";import S from"http";import C from"https";import O from"https-proxy-agent";var{HttpsProxyAgent:j}=O;function P(e){const t=e.tls;return{...t?.caCert?{ca:k.readFileSync(t.caCert)}:{},...t?.clientCert?{cert:k.readFileSync(t.clientCert)}:{},...t?.clientKey?{key:k.readFileSync(t.clientKey)}:{},...t?.pfx?{pfx:k.readFileSync(t.pfx)}:{},...t?.passphrase?{passphrase:t.passphrase}:{},rejectUnauthorized:!1!==t?.strictSSL}}function R(e){const t=P(e);if(e.proxy){const r=new URL(e.proxy);if(!["http:","https:"].includes(r.protocol))throw new Error("Only HTTP(S) CONNECT proxies are supported.");class s extends j{callback(e,r){return super.callback(e,{...r,...t})}}const o=new s({protocol:r.protocol,host:r.hostname,port:Number(r.port||("https:"===r.protocol?443:80)),...r.username||r.password?{auth:`${decodeURIComponent(r.username)}:${decodeURIComponent(r.password)}`}:{},ca:t.ca,rejectUnauthorized:t.rejectUnauthorized});return{http:o,https:o}}return{http:new S.Agent,https:new C.Agent(t)}}function T(e,t){if(e.proxy||Object.entries(e.tls||{}).some(([e,t])=>"strictSSL"===e?!1===t:Boolean(t)))throw new Error(`${t} custom proxy/TLS is not supported by the bundled request engine.`)}function A(e,t={}){return"string"==typeof e?e.replace(/\{\{([^{}]+)\}\}/g,(e,r)=>{if(!Object.prototype.hasOwnProperty.call(t,r))throw new Error(`Variable ${r} is not defined.`);return t[r]}):Array.isArray(e)?e.map(e=>A(e,t)):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,r])=>[e,A(r,t)])):e}function U(e,t,r){const s=e.auth,o=(e,r)=>{for(const r of Object.keys(t))r.toLowerCase()===e.toLowerCase()&&delete t[r];t[e]=r};if(!s||"none"===s.type)return r;if("basic"===s.type)o("Authorization",`Basic ${Buffer.from(`${s.username||""}:${s.password||""}`).toString("base64")}`);else if("bearer"===s.type)o("Authorization",`Bearer ${s.token||""}`);else{if("apikey"!==s.type)throw new Error(`Authentication ${s.type} is supported only by the HTTP/SSE runner.`);if(!s.key)throw new Error("API key name is required.");if("query"===s.in){if(!r)throw new Error("gRPC API keys require metadata headers.");const e=new URL(r);e.searchParams.set(s.key,s.value||""),r=e.href}else o(s.key,s.value||"")}return r}function q(e,t){const r={"grpc.enable_http_proxy":0};if(!e.proxy)return{address:t,channelOptions:r};const s=new URL(e.proxy);if("http:"!==s.protocol)throw new Error("gRPC supports only HTTP CONNECT proxies.");const o=new URL(`https://${t}`);return r["grpc.http_connect_target"]=`dns:///${t}`,r["grpc.default_authority"]=o.host,r["grpc.ssl_target_name_override"]=o.hostname,(s.username||s.password)&&(r["grpc.http_connect_creds"]=`${decodeURIComponent(s.username)}:${decodeURIComponent(s.password)}`),{address:`${s.hostname}:${s.port||80}`,channelOptions:r}}function L(e){return e.key&&e.passphrase?Buffer.from(E({key:e.key,passphrase:e.passphrase}).export({format:"pem",type:"pkcs8"})):e.key}function I(e,t){const r=new URL(e);for(const[e,s]of Object.entries(t||{}))if(null!=s){r.searchParams.delete(e);for(const t of Array.isArray(s)?s:[s])r.searchParams.append(e,String(t))}return r.href}var M=e.version;function z(e){if(!e)return;if("string"==typeof e)return e;if(e instanceof Error)return e.message;const t=e;if("string"==typeof t.message)return t.message;if("string"==typeof t.msg)return t.msg;try{return JSON.stringify(e)}catch{return String(e)}}function N(e,t){const r=[];if("http"===e||"sse"===e){const e=t?.status,s="number"==typeof e&&e>=200&&e<300;r.push({name:"HTTP status is 2xx (implicit)",passed:s,error:s?void 0:`expected 2xx, got ${e??"no status"}`})}else if("graphql"===e){const e=t?.status,s="number"==typeof e&&e>=200&&e<300,o=t?.body,n=Array.isArray(o?.errors)&&o.errors.length>0,a=s&&!n;let i;s?n&&(i=`GraphQL response contains ${o.errors.length} error(s)`):i=`expected HTTP 2xx, got ${e??"no status"}`,r.push({name:"GraphQL success (implicit)",passed:a,error:i})}else if("grpc"===e){const e=t?.statusCode??t?.code??t?.status,s=t?.statusName??t?.status,o=0===e;r.push({name:"gRPC status is OK (implicit)",passed:o,error:o?void 0:`expected code 0 (OK), got ${e??"unknown"}${s?` (${s})`:""}`})}else if("mcp"===e){const e=t?.body??{},s=void 0!==e.result,o=void 0!==e.error||!0===e.result?.isError||void 0!==t?.error,n=s&&!o;let a;o?a="MCP JSON-RPC response contains an error":s||(a="MCP JSON-RPC response missing 'result'"),r.push({name:"MCP JSON-RPC success (implicit)",passed:n,error:a})}else if("websocket"===e){const e=t?.events??[],s=e.some(e=>"open"===e.kind||"open"===e.state),o=e.some(e=>"error"===e.kind||"error"===e.state),n=s&&!o;r.push({name:"WebSocket connected (implicit)",passed:n,error:n?void 0:o?"WebSocket connection errored":"WebSocket did not reach open state"})}return r}async function F(e){i(e);const t=await h(e),r=$(),s=D(t,e),o=Date.now(),n=[];let a=0;const c=Math.min(e.concurrency??5,s.length),p=[];for(let o=0;o<c;o++)p.push((async()=>{for(;a<s.length;){if(e.signal?.aborted)throw new Error("Run cancelled.");const o=s[a++];if(!o)break;const i=await H(r,o,e,t);n.push(i)}})());await Promise.all(p);const d=Date.now()-o;return{summary:G(n,d),results:n.sort((e,t)=>e.path.localeCompare(t.path)||e.method.localeCompare(t.method)),config:e,generatedAt:(new Date).toISOString(),version:M}}function D(e,t){const r=[];for(const[s,o]of Object.entries(e.paths??{})){if(!o||"object"!=typeof o)continue;const e=o;for(const[o,n]of x(e)){if(!n||"object"!=typeof n)continue;const a={path:s,method:o,operation:n,pathItem:e,operationId:n.operationId??`${o.toUpperCase()} ${s}`,protocol:B(n,e)};J(a,t.filter)&&r.push(a)}}return r}function B(e,t){const r=e["x-protocol"]??t?.["x-protocol"];if(r)return String(r).toLowerCase();const s=e.responses??{};for(const e of Object.values(s)){if((e?.content??{})["text/event-stream"])return"sse"}return e["x-response-stream"]?"sse":"http"}function J(e,t){if(!t)return!0;if(t.methods?.length&&!t.methods.includes(e.method.toLowerCase()))return!1;if(t.operationIds?.length&&!t.operationIds.includes(e.operationId))return!1;if(t.tags?.length){const r=e.operation.tags??[];if(!t.tags.some(e=>r.includes(e)))return!1}if(t.paths?.length){if(!t.paths.some(t=>{try{return new RegExp(t).test(e.path)}catch{return e.path.includes(t)}}))return!1}return!0}async function H(e,t,r,s){return(await _(e,t,{config:r,spec:s,variables:r.variables})).result}async function _(e,t,r){let{config:s}=r;const{spec:o}=r,n=Date.now(),a={operationId:t.operationId,path:t.path,method:t.method.toUpperCase(),protocol:t.protocol};try{if((r.signal||s.signal)?.aborted)throw new Error("Run cancelled.");const i=r.variables||s.variables,c=t.operation["x-websocket"]||t.operation["x-ws"]||t.operation["x-grpc"]||t.operation["x-mcp"]||{},p=A(r.serverUrl??s.serverUrl??c.url??c.address??c.endpoint??o.servers?.[0]?.url,i);s.resolveTransport&&(s={...s,...await s.resolveTransport({protocol:t.protocol,url:p})});const d=["grpc","websocket","mcp"].includes(t.protocol),l={...s,serverUrl:A(s.serverUrl,i),auth:d?A(s.auth,i):s.auth,headers:d?A({...s.headers,...r.values?.header},i):s.headers,signal:r.signal||s.signal},u={...t,operation:d?{...t.operation,...Object.fromEntries(Object.entries(t.operation).filter(([e])=>["x-grpc","x-mcp","x-ws","x-websocket"].includes(e)).map(([e,t])=>[e,A(t,i)]))}:t.operation},f=d?A(r.values,i):r.values;if(!["http","sse","graphql","grpc","websocket","mcp"].includes(t.protocol))throw new Error(`Unsupported protocol: ${t.protocol}`);const m=[...y(t.operation,t.pathItem),...r.extraAssertions??[]],h=w(t.operation),v=[];let b,x,$={};switch(t.protocol){case"grpc":{const t=await async function(e,t,r,s,o){const n=t.operation["x-grpc"]??{},a=(s??r.serverUrl??n.address)?.replace(/^https?:\/\//,"");if(!a)throw new Error("gRPC operation requires x-grpc.address or --server");const i={...r.headers};U(r,i);const c=P(r);if(c.pfx)throw new Error("gRPC requires PEM certificates, not PFX.");const p=e.connect({kind:"grpc",deadlineMs:r.timeout??3e4,streamDeadlineMs:r.maxStreamMs??r.timeout??3e4,...q(r,a),tls:!(!0!==n.tls&&!/^https:/.test(s??r.serverUrl??""))&&{rootCerts:c.ca,privateKey:L(c),certChain:c.cert,rejectUnauthorized:c.rejectUnauthorized},service:n.service,method:n.method??t.operationId,reflection:r.grpcReflection??n.reflection??!(r.grpcProtoPaths??n.protoPaths)?.length,protoPaths:r.grpcProtoPaths??n.protoPaths,includeDirs:n.includeDirs,metadata:i}),d=()=>{p.close().catch(()=>{})};r.signal?.addEventListener("abort",d,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await p.open(),r.signal?.aborted)throw new Error("Run cancelled.");const e=p.kind,s=o?.body??function(e){const t=e["x-grpc"]??{};if(t.sample&&"object"==typeof t.sample)return t.sample;const r=e.requestBody?.content?.["application/json"]?.schema;if(r?.properties){const e={};for(const[t,s]of Object.entries(r.properties))e[t]=s.default??W(s.type);return e}return{}}(t.operation);"unary"===e||"server_streaming"===e?await p.send(s):(await p.send(s),await p.finishSending());const c=p.events??[],d=c.find(e=>"status"===e.kind),l=c.filter(e=>"data"===e.kind&&"in"===e.direction);"unary"!==e&&"server_streaming"!==e||await p.close();const u={status:d?.meta?.code??-1,statusText:d?.meta?.statusName,body:1===l.length?l[0].data:l.map(e=>e.data),events:c.map(e=>({kind:e.kind,direction:e.direction,data:e.data,meta:e.meta}))},f=n.method??t.operationId;return{response:u,request:{method:f,target:n.service?`${n.service}/${f}`:f,url:a,...Object.keys(s).length?{body:s}:{},...Object.keys(i).length?{headers:i}:{}}}}finally{r.signal?.removeEventListener("abort",d),await p.close().catch(()=>{})}}(e,u,l,A(r.serverUrl,i),f);b=t.response,x=t.request;break}case"websocket":{const t=await async function(e,t,r,s,o){const n=t.operation["x-websocket"]??t.operation["x-ws"]??{};let a=K(n.url,s??r.serverUrl,t.path)?.replace(/^http/,"ws");if(!a)throw new Error("WebSocket operation requires x-ws.url or --server");const i={...r.headers};a=U(r,i,I(a,o?.query));const c=R(r),p=e.connect({kind:"websocket",openTimeoutMs:r.timeout??3e4,signal:r.signal,clientOptions:{...P(r),agent:a.startsWith("wss:")?c.https:c.http},url:a,subprotocols:n.subprotocols,headers:i}),d=()=>{p.close().catch(()=>{})};r.signal?.addEventListener("abort",d,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await p.open(),r.signal?.aborted)throw new Error("Run cancelled.");const e=void 0!==o?.body?[o.body]:n.send??[];for(const t of e.slice(0,3))await p.send(t);await new Promise(e=>setTimeout(e,Math.min(500,r.timeout??500))),await p.close();const t=p.events??[],s=t.filter(e=>"in"===e.direction&&"open"!==e.kind);return{response:{status:101,statusText:"Switching Protocols",body:s.map(e=>e.data),events:t.map(e=>({kind:e.kind,direction:e.direction,data:e.data})),streaming:!0},request:{method:"WS",url:a,...e.length?{body:e.slice(0,3)}:{},...Object.keys(i).length?{headers:i}:{}}}}finally{r.signal?.removeEventListener("abort",d),await p.close().catch(()=>{});for(const e of new Set(Object.values(c)))e.destroy()}}(e,u,l,A(r.serverUrl,i),f);b=t.response,x=t.request;break}case"mcp":{const t=await async function(e,t,r,s,o){const n=t.operation["x-mcp"]??{},a=r.mcpTransport??n.transport??"streamable-http",i={...r.headers};let c=K(n.endpoint,s??r.serverUrl,t.path);"stdio"!==a&&(T(r,"MCP HTTP"),c=U(r,i,c?I(c,o?.query):c));const p=o?.body?.method??n.method??"tools/call",d=o?.body?.params??n.params??("tools/call"===p?{name:n.name,arguments:o?.body??n.arguments??{}}:{}),l=e.connect({kind:"mcp",transport:"stdio"===a?"stdio":"streamable-http",endpoint:c,headers:i,command:r.mcpCommand??n.command,args:r.mcpArgs??n.args,cwd:r.mcpCwd??n.cwd,timeoutMs:r.timeout??3e4}),u=()=>{l.close().catch(()=>{})};r.signal?.addEventListener("abort",u,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await l.open(),r.signal?.aborted)throw new Error("Run cancelled.");return{response:{status:200,statusText:"OK",body:{result:await l.request(p,d,{signal:r.signal,timeoutMs:r.timeout})}},request:{method:p,url:c,headers:i,body:d}}}finally{r.signal?.removeEventListener("abort",u),await l.close().catch(()=>{})}}(e,u,l,A(r.serverUrl,i),f);b=t.response,x=t.request;break}default:{const n=await async function(e,t,r,s,o){"graphql"===t.protocol&&T(r,"GraphQL");const n="graphql"===t.protocol?void 0:R(r),a={...r.httpRuntime,requester:{...r.requester,strictSSL:!1!==r.tls?.strictSSL,ignoreProxyEnvironmentVariables:!0,...n?{agents:n,...r.proxy||r.tls?.caCert||r.tls?.clientCert||r.tls?.pfx||r.httpRuntime?.certificates?{protocolVersion:"http1"}:{}}:{}}},i=o.values,c={...r.headers??{},...i?.header??{}},p={...r.headers?{header:r.headers}:{},...i??{},...Object.keys(c).length?{header:c}:{}};let d;try{d=await e.send({writeBack:!1,signal:o.signal,spec:s,target:{path:t.path,method:t.method},serverUrl:o.serverUrl??r.serverUrl,values:Object.keys(p).length?p:void 0,auth:r.auth,variables:o.variables,timeout:r.timeout,runner:Object.keys(a).length?a:void 0,scripts:o.postmanTest?{test:{exec:o.postmanTest},fromSpecExtensions:!1}:void 0,onAssertion:e=>{o.postmanAssertions.push({name:e.name??"assertion",passed:e.passed,error:z(e.error)})},maxStreamMs:r.maxStreamMs??r.timeout,maxEvents:r.maxEvents??50,maxResponseSize:r.maxResponseSize})}finally{for(const e of new Set(Object.values(n||{})))e.destroy()}if(o.signal?.aborted)throw new Error("Run cancelled.");if(d.error)throw new Error(d.error.message);const l={},u=d.scripts;if(u)for(const e of[...u.prerequest??[],...u.test??[]])e.environment&&Object.assign(l,e.environment);const f=d.request;return{response:{status:d.response?.status,statusText:d.response?.statusText,contentType:d.response?.contentType,sizeBytes:d.response?.sizeBytes,body:d.response?.body,text:d.response?.text,headers:d.response?.headers,events:d.response?.events,streaming:d.response?.streaming},request:f?{method:f.method,url:f.url,headers:f.headers,...void 0!==f.body?{body:f.body}:{}}:void 0,env:l}}(e,t,s,o,{postmanTest:h,postmanAssertions:v,variables:r.variables,values:r.values,serverUrl:r.serverUrl,signal:r.signal||s.signal});b=n.response,x=n.request,$=n.env;break}}if((r.signal||s.signal)?.aborted)throw new Error("Run cancelled.");const k=Date.now()-n,E=m.length>0||v.length>0?[]:N(t.protocol,b),S=m.length?g(m,{status:b?.status,headers:b?b.headers:void 0,body:b?.body,bodyText:b?b.text:void 0,durationMs:k}):[],C=new Set,O=v.filter(e=>{const t=`${e.name}|${e.passed}|${e.error??""}`;return!C.has(t)&&(C.add(t),!0)}),j=[...S,...O,...E],M=j.some(e=>!e.passed);return{result:{...a,status:M?"failed":"passed",durationMs:k,request:x,response:b,assertions:j.length?j:void 0,timestamp:(new Date).toISOString()},response:b,request:x,env:$}}catch(e){const o=Date.now()-n,i=r.values,c={...s.headers??{},...i?.header??{}},p=Object.fromEntries(Object.entries(c).map(([e,t])=>[e,null==t?"":String(t)])),d={method:t.method.toUpperCase(),url:r.serverUrl??s.serverUrl,...Object.keys(p).length?{headers:p}:{},...void 0!==i?.body?{body:i.body}:{}};return{result:{...a,status:"error",durationMs:o,request:d,error:e.message??String(e),timestamp:(new Date).toISOString()},response:void 0,request:d,env:{}}}}function K(e,t,r){if(!t)return e;const s=new URL(t),o=new URL(e||r,t);return o.host=s.host,o.protocol=s.protocol,e||"/"===s.pathname||(o.pathname=s.pathname.replace(/\/$/,"")+r),o.href}function W(e){switch(e){case"string":default:return"";case"number":case"integer":return 0;case"boolean":return!1;case"array":return[];case"object":return{}}}function G(e,t){const r=e.length,s=e.filter(e=>"passed"===e.status).length;return{total:r,passed:s,failed:e.filter(e=>"failed"===e.status).length,errors:e.filter(e=>"error"===e.status).length,skipped:e.filter(e=>"skipped"===e.status).length,durationMs:t,passRate:r>0?Math.round(s/r*1e3)/10:0}}import Q from"fs";import V from"path";function X(e,t){const r=V.resolve(t);Q.mkdirSync(r,{recursive:!0});const s=V.join(r,"report.json");return Q.writeFileSync(s,JSON.stringify(e,null,2),"utf-8"),s}var Y="",Z="",ee="",te="",re="",se="",oe="",ne="",ae="",ie="",ce="";function pe(e){if(null==e||!Number.isFinite(e))return`${ee}--ms${Y}`;return`${e>500?`${Z}${se}`:Z}${e}${Y}${ee}ms${Y}`}function de(e){const t=[],{summary:r,results:s}=e;t.push(""),t.push(`${Z}${oe}╔══════════════════════════════════════════════════════╗${Y}`),t.push(`${Z}${oe}║ Powerduck Test Report ║${Y}`),t.push(`${Z}${oe}╚══════════════════════════════════════════════════════╝${Y}`),t.push("");const o=r.passRate>=80?te:r.passRate>=50?se:re;t.push(`${Z} Total:${Y} ${r.total} ${te}Passed:${Y} ${r.passed} ${re}Failed:${Y} ${r.failed} ${se}Errors:${Y} ${r.errors} ${o}Rate:${Y} ${r.passRate}% ${ee}Time:${Y} ${pe(r.durationMs)}`),t.push("");const n=r.total>0?Math.round(r.passed/r.total*50):0,a=r.total>0?Math.round(r.failed/r.total*50):0,i=r.total>0?Math.round(r.errors/r.total*50):0,c=te+"█".repeat(n)+re+"█".repeat(a)+se+"█".repeat(i)+ne+"░".repeat(Math.max(0,50-n-a-i))+Y;t.push(` [${c}]`),t.push("");for(const e of s)t.push(le(e));return t.push(""),r.failed>0||r.errors>0?t.push(`${Z}${re} ✗ ${r.failed+r.errors} test(s) did not pass${Y}`):t.push(`${Z}${te} ✓ All ${r.total} test(s) passed${Y}`),t.push(` ${ee}Generated: ${e.generatedAt}${Y}`),t.push(""),t.join("\n")}function le(e){const t="passed"===e.status?`${te}✓${Y}`:"failed"===e.status?`${re}✗${Y}`:"error"===e.status?`${se}!${Y}`:`${ne}-${Y}`,r="passed"===e.status?`${ae}${Z} PASS ${Y}`:"failed"===e.status?`${ie}${Z} FAIL ${Y}`:"error"===e.status?`${ce}${Z} ERROR ${Y}`:`${ne} SKIP ${Y}`,s=e.protocol?` ${oe}[${e.protocol}]${Y}`:"",o=null!=e.response?.status?` ${ee}${e.response.status}${Y}`:"";let n=` ${t} ${r} ${Z}${e.method}${Y} ${e.path}${s}${o} ${pe(e.durationMs)}`;if(e.error&&(n+=`\n ${re}${e.error}${Y}`),e.assertions?.length)for(const t of e.assertions){n+=`\n ${t.passed?`${te} ✓${Y}`:`${re} ✗${Y}`} ${t.name}${t.error?` — ${re}${t.error}${Y}`:""}`}return n}function ue(e){const t=de(e);return console.log(t),t}import fe from"fs";import me from"path";function he(e,t){const r=me.resolve(t);fe.mkdirSync(r,{recursive:!0});const s=me.join(r,"report.html");return fe.writeFileSync(s,function(e){const{summary:t,results:r}=e,s=JSON.stringify({summary:t,results:r}).replace(/</g,"\\u003c");return`<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>Test Report — Powerduck</title>\n<style>\n *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n :root{\n --bg:#ffffff;--fg:#0f172a;--muted:#64748b;--border:#e2e8f0;\n --bg-subtle:#f8fafc;--bg-hover:#f1f5f9;\n --green:#16a34a;--green-bg:#f0fdf4;--green-border:#bbf7d0;\n --red:#dc2626;--red-bg:#fef2f2;--red-border:#fecaca;\n --amber:#d97706;--amber-bg:#fffbeb;--amber-border:#fde68a;\n --blue:#2563eb;--mono:ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,monospace;\n --sans:ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Inter,sans-serif;\n }\n @media (prefers-color-scheme:dark){\n :root{\n --bg:#0f172a;--fg:#f1f5f9;--muted:#94a3b8;--border:#1e293b;\n --bg-subtle:#1e293b;--bg-hover:#334155;\n --green:#4ade80;--green-bg:rgba(74,222,128,.1);--green-border:rgba(74,222,128,.25);\n --red:#f87171;--red-bg:rgba(248,113,113,.1);--red-border:rgba(248,113,113,.25);\n --amber:#fbbf24;--amber-bg:rgba(251,191,36,.1);--amber-border:rgba(251,191,36,.25);\n --blue:#60a5fa;\n }\n }\n body{font-family:var(--sans);background:var(--bg);color:var(--fg);line-height:1.5;font-size:14px;-webkit-font-smoothing:antialiased}\n .wrap{max-width:960px;margin:0 auto;padding:32px 24px 64px}\n\n /* Header */\n .hdr{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--border)}\n .hdr h1{font-size:18px;font-weight:600;letter-spacing:-.01em}\n .hdr .meta{font-size:12px;color:var(--muted);font-family:var(--mono)}\n\n /* Summary strip */\n .strip{display:grid;grid-template-columns:repeat(6,1fr);gap:0;border:1px solid var(--border);border-radius:8px;overflow:hidden;margin-bottom:24px}\n .stat{padding:14px 16px;border-right:1px solid var(--border);text-align:center}\n .stat:last-child{border-right:none}\n .stat .v{font-size:22px;font-weight:700;letter-spacing:-.02em;line-height:1.2}\n .stat .l{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-top:2px}\n .stat.total .v{color:var(--fg)}\n .stat.pass .v{color:var(--green)}\n .stat.fail .v{color:var(--red)}\n .stat.err .v{color:var(--amber)}\n .stat.rate .v{color:var(--blue)}\n .stat.time .v{font-size:16px;font-weight:600}\n\n /* Progress */\n .bar{height:4px;background:var(--border);border-radius:2px;overflow:hidden;margin-bottom:24px;display:flex}\n .bar>div{height:100%}\n .bar .p{background:var(--green)}\n .bar .f{background:var(--red)}\n .bar .e{background:var(--amber)}\n\n /* Filters */\n .filters{display:flex;gap:4px;margin-bottom:16px}\n .fbtn{padding:4px 12px;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--muted);cursor:pointer;font-size:12px;font-weight:500;transition:all .12s}\n .fbtn:hover{color:var(--fg);background:var(--bg-hover)}\n .fbtn.active{background:var(--bg-subtle);color:var(--fg);border-color:var(--border)}\n .fbtn .n{color:var(--muted);margin-left:4px;font-variant-numeric:tabular-nums}\n\n /* Test rows */\n .row{border:1px solid var(--border);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .12s}\n .row:hover{border-color:var(--muted)}\n .row-head{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;user-select:none}\n .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n .dot.pass{background:var(--green)}\n .dot.fail{background:var(--red)}\n .dot.err{background:var(--amber)}\n .method{font-family:var(--mono);font-size:11px;font-weight:600;color:var(--muted);min-width:40px;text-transform:uppercase}\n .path{font-family:var(--mono);font-size:13px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n .proto{font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);padding:2px 8px;border:1px solid var(--border);border-radius:4px}\n .dur{font-family:var(--mono);font-size:12px;color:var(--muted);min-width:56px;text-align:right;font-variant-numeric:tabular-nums}\n .chev{color:var(--muted);font-size:10px;transition:transform .15s;flex-shrink:0}\n .row.open .chev{transform:rotate(90deg)}\n\n /* Row body */\n .row-body{display:none;padding:0 14px 12px 32px;border-top:1px solid var(--border)}\n .row.open .row-body{display:block}\n .sect{margin-top:10px}\n .sect-t{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:600;margin-bottom:6px}\n .asrt{display:flex;align-items:center;gap:8px;padding:3px 0;font-size:13px}\n .asrt .ck{width:14px;height:14px;border-radius:3px;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;flex-shrink:0;color:#fff}\n .asrt.pass .ck{background:var(--green)}\n .asrt.fail .ck{background:var(--red)}\n .asrt .an{color:var(--fg)}\n .asrt .ae{color:var(--red);font-size:12px;margin-left:6px;font-family:var(--mono)}\n .rmeta{display:flex;gap:20px;flex-wrap:wrap;font-size:12px}\n .rmeta span{color:var(--muted)}\n .rmeta strong{color:var(--fg);font-weight:600;font-family:var(--mono)}\n .errbox{background:var(--red-bg);border:1px solid var(--red-border);border-radius:6px;padding:8px 12px;color:var(--red);font-size:12px;font-family:var(--mono);margin-top:10px}\n .bprev{background:var(--bg-subtle);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-family:var(--mono);font-size:11px;color:var(--muted);max-height:180px;overflow:auto;white-space:pre-wrap;word-break:break-all;margin-top:6px}\n\n footer{margin-top:32px;padding-top:16px;border-top:1px solid var(--border);font-size:11px;color:var(--muted);display:flex;justify-content:space-between}\n\n @media(max-width:640px){\n .strip{grid-template-columns:repeat(3,1fr)}\n .stat:nth-child(3){border-right:none}\n .stat:nth-child(4),.stat:nth-child(5){border-top:1px solid var(--border)}\n .path{font-size:11px}\n }\n</style>\n</head>\n<body>\n<div class="wrap">\n <div class="hdr">\n <h1>Test Report</h1>\n <span class="meta">${e.generatedAt} · v${e.version}</span>\n </div>\n\n <div class="strip">\n <div class="stat total"><div class="v">${t.total}</div><div class="l">Total</div></div>\n <div class="stat pass"><div class="v">${t.passed}</div><div class="l">Passed</div></div>\n <div class="stat fail"><div class="v">${t.failed}</div><div class="l">Failed</div></div>\n <div class="stat err"><div class="v">${t.errors}</div><div class="l">Errors</div></div>\n <div class="stat rate"><div class="v">${t.passRate}%</div><div class="l">Pass Rate</div></div>\n <div class="stat time"><div class="v">${(t.durationMs/1e3).toFixed(2)}s</div><div class="l">Duration</div></div>\n </div>\n\n <div class="bar">\n <div class="p" style="width:${t.total?t.passed/t.total*100:0}%"></div>\n <div class="f" style="width:${t.total?t.failed/t.total*100:0}%"></div>\n <div class="e" style="width:${t.total?t.errors/t.total*100:0}%"></div>\n </div>\n\n <div class="filters">\n <button class="fbtn active" data-f="all">All<span class="n">${t.total}</span></button>\n <button class="fbtn" data-f="passed">Passed<span class="n">${t.passed}</span></button>\n <button class="fbtn" data-f="failed">Failed<span class="n">${t.failed}</span></button>\n <button class="fbtn" data-f="error">Errors<span class="n">${t.errors}</span></button>\n </div>\n\n <div id="list"></div>\n\n <footer>\n <span>@powerduck/openapi-cli v${e.version}</span>\n <span>${t.passed}/${t.total} passed</span>\n </footer>\n</div>\n\n<script>\nconst D=${s};\nfunction esc(s){const d=document.createElement('div');d.textContent=String(s??'');return d.innerHTML}\nfunction render(f){\n const list=document.getElementById('list');\n const items=f==='all'?D.results:D.results.filter(r=>r.status===f);\n list.innerHTML=items.map(r=>{\n const as=r.assertions?r.assertions.map(a=>\`\n <div class="asrt \${a.passed?'pass':'fail'}">\n <div class="ck">\${a.passed?'✓':'✗'}</div>\n <span class="an">\${esc(a.name)}</span>\n \${a.error?'<span class="ae">'+esc(a.error)+'</span>':''}\n </div>\`).join(''):'';\n const bp=r.response?.body?'<div class="bprev">'+esc(JSON.stringify(r.response.body,null,2).slice(0,3000))+'</div>':'';\n const eb=r.error?'<div class="errbox">'+esc(r.error)+'</div>':'';\n const rm=r.response?\`\n <div class="rmeta">\n \${r.response.status!=null?'<span>Status <strong>'+r.response.status+(r.response.statusText?' '+esc(r.response.statusText):'')+'</strong></span>':''}\n \${r.response.contentType?'<span>Type <strong>'+esc(r.response.contentType)+'</strong></span>':''}\n \${r.response.sizeBytes!=null?'<span>Size <strong>'+r.response.sizeBytes+'B</strong></span>':''}\n \${r.response.streaming?'<span>Streaming <strong>yes</strong></span>':''}\n </div>\`:'';\n return \`\n <div class="row \${r.status}" data-s="\${r.status}">\n <div class="row-head" onclick="this.parentElement.classList.toggle('open')">\n <div class="dot \${r.status==='passed'?'pass':r.status==='failed'?'fail':'err'}"></div>\n <span class="method">\${esc(r.method)}</span>\n <span class="path">\${esc(r.path)}</span>\n \${r.protocol?'<span class="proto">'+esc(r.protocol)+'</span>':''}\n <span class="dur">\${r.durationMs}ms</span>\n <span class="chev">▶</span>\n </div>\n <div class="row-body">\n \${eb}\n \${rm}\n \${as?'<div class="sect"><div class="sect-t">Assertions</div>'+as+'</div>':''}\n \${bp?'<div class="sect"><div class="sect-t">Response</div>'+bp+'</div>':''}\n </div>\n </div>\`;\n }).join('');\n}\ndocument.querySelectorAll('.fbtn').forEach(b=>b.addEventListener('click',()=>{\n document.querySelectorAll('.fbtn').forEach(x=>x.classList.remove('active'));\n b.classList.add('active');render(b.dataset.f);\n}));\nrender('all');\n<\/script>\n</body>\n</html>`}(e),"utf-8"),s}export{e as package_default,a as DEFAULT_CONFIG,i as validateRunConfig,c as resolveConfig,m as isRemoteSpec,h as loadSpec,g as runDeclarativeAssertions,v as resolveJsonPath,y as extractAssertions,w as extractPostmanScripts,z as formatAssertionError,N as buildImplicitAssertions,F as runTests,D as collectOperations,_ as executeStep,G as buildSummary,X as generateJsonReport,de as generateCliReport,ue as printCliReport,he as generateHtmlReport};
package/dist/cli.cjs CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var e=Object.create,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,n=Object.prototype.hasOwnProperty,a=(a,i,c)=>(c=null!=a?e(s(a)):{},((e,s,a,i)=>{if(s&&"object"==typeof s||"function"==typeof s)for(let c of o(s))n.call(e,c)||c===a||t(e,c,{get:()=>s[c],enumerable:!(i=r(s,c))||i.enumerable});return e})(!i&&a&&a.__esModule?c:t(c,"default",{value:a,enumerable:!0}),a)),i=require("commander"),c=a(require("fs"),1),p=a(require("path"),1),d=a(require("http"),1),l=a(require("https"),1),u=require("@powerduck/openapi-parser"),f="./openapi-cli-report",m=["json","cli","html"],h=5,g=3e4,v=!0;function b(e){for(const[t,r,o]of[["concurrency",e.concurrency??5,100],["timeout",e.timeout??3e4,6e5]])if(!Number.isInteger(r)||r<1||r>o)throw new Error(`${t} must be an integer between 1 and ${o}.`);if(e.formats?.some(e=>!["json","cli","html"].includes(e)))throw new Error("Unsupported report format.");if(e.mcpTransport&&!["stdio","streamable-http"].includes(e.mcpTransport))throw new Error("Unsupported MCP transport.")}function y(e){const t=e.config?function(e){const t=p.default.resolve(e);if(!c.default.existsSync(t))throw new Error(`Config file not found: ${t}`);const r=c.default.readFileSync(t,"utf-8");try{const e=JSON.parse(r);if(!e||"object"!=typeof e||Array.isArray(e))throw new Error("Config must be an object.");return e}catch(e){throw new Error(`Invalid config file (must be JSON): ${e.message}`)}}(e.config):{},r={specPath:e.spec??t.specPath??"",serverUrl:e.server??t.serverUrl,outputDir:e.output??t.outputDir??f,formats:w(e.format??t.formats),filter:x(e,t),concurrency:e.concurrency??t.concurrency??h,timeout:e.timeout??t.timeout??g,proxy:e.proxy??t.proxy,tls:{caCert:e.ca??t.tls?.caCert,clientCert:e.cert??t.tls?.clientCert,clientKey:e.key??t.tls?.clientKey,pfx:t.tls?.pfx,passphrase:t.tls?.passphrase,strictSSL:void 0!==e.insecure?!e.insecure:t.tls?.strictSSL},requester:t.requester,maxEvents:t.maxEvents,maxStreamMs:t.maxStreamMs,maxResponseSize:t.maxResponseSize,headers:$(e.header,t.headers),auth:k(e,t),variables:S(e.variable,t.variables),failOnError:e.failOnError??t.failOnError??v,envFile:e.env??t.envFile,grpcReflection:e.grpcReflection??t.grpcReflection,grpcProtoPaths:e.grpcProto??t.grpcProtoPaths,mcpTransport:e.mcpTransport??t.mcpTransport,mcpCommand:e.mcpCommand??t.mcpCommand,mcpArgs:e.mcpArgs?e.mcpArgs.split(/[\s,]+/).filter(Boolean):t.mcpArgs,mcpCwd:e.mcpCwd??t.mcpCwd};if(r.envFile&&function(e){const t=p.default.resolve(e);if(!c.default.existsSync(t))return;const r=c.default.readFileSync(t,"utf-8").split("\n");for(const e of r){const t=e.trim();if(!t||t.startsWith("#"))continue;const r=t.indexOf("=");if(r>0){const e=t.slice(0,r).trim(),o=t.slice(r+1).trim().replace(/^["']|["']$/g,"");e in process.env||(process.env[e]=o)}}}(r.envFile),function(e){const t=e=>"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,(e,t)=>process.env[t]??"");if(e.serverUrl=t(e.serverUrl),e.proxy=t(e.proxy),e.headers)for(const[r,o]of Object.entries(e.headers))e.headers[r]=t(o);e.auth?.token&&(e.auth.token=t(e.auth.token));if(e.variables)for(const[r,o]of Object.entries(e.variables))e.variables[r]=t(o)}(r),!r.specPath)throw new Error("No OpenAPI spec path provided. Use --spec <path> or set specPath in config.");if(r.proxy)try{new URL(r.proxy)}catch{throw new Error(`Invalid proxy URL: ${r.proxy}`)}return b(r),r}function w(e){return Array.isArray(e)?e:"string"==typeof e?e.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):m}function x(e,t){const r=e.method?e.method.split(",").map(e=>e.trim().toLowerCase()):t.filter?.methods,o=e.path?e.path.split(",").map(e=>e.trim()):t.filter?.paths,s=e.tag?e.tag.split(",").map(e=>e.trim()):t.filter?.tags,n=e.operationId?e.operationId.split(",").map(e=>e.trim()):t.filter?.operationIds;if(r||o||s||n)return{methods:r,paths:o,tags:s,operationIds:n}}function $(e,t){const r={...t??{}};for(const t of e??[]){const e=t.indexOf(":");if(e>0){const o=t.slice(0,e).trim(),s=t.slice(e+1).trim();o&&(r[o]=s)}}return Object.keys(r).length?r:void 0}function k(e,t){return e.bearer?{type:"bearer",token:e.bearer}:t.auth}function S(e,t){const r={...t??{}};for(const t of e??[]){const e=t.indexOf("=");e>0&&(r[t.slice(0,e).trim()]=t.slice(e+1).trim())}return Object.keys(r).length?r:void 0}async function E(e){const t=e.specPath.trim();let r;if(/^https?:\/\//i.test(t.trim()))r=await function(e,t){let r=0;return new Promise((o,s)=>{const n=a=>{const i=("https:"===new URL(a).protocol?l.default:d.default).get(a,{timeout:t,headers:{Accept:"application/json, text/plain, */*","User-Agent":"@powerduck/openapi-cli"}},t=>{if(t.statusCode&&t.statusCode>=300&&t.statusCode<400&&t.headers.location){if(++r>5)return t.resume(),void s(new Error(`Too many redirects (>5) while fetching spec: ${e}`));const o=new URL(t.headers.location,a).toString();return t.resume(),void n(o)}if(200!==t.statusCode)return t.resume(),void s(new Error(`Failed to fetch spec from ${a}: HTTP ${t.statusCode} ${t.statusMessage??""}`.trim()));const i=[];t.on("data",e=>i.push(e)),t.on("end",()=>o(Buffer.concat(i).toString("utf-8"))),t.on("error",e=>s(new Error(`Error reading spec response: ${e.message}`)))});i.on("timeout",()=>{i.destroy(new Error(`Timed out after ${t}ms while fetching spec: ${e}`))}),i.on("error",t=>s(new Error(`Network error fetching spec ${e}: ${t.message}`)))};n(e)})}(t,e.timeout??g);else{const e=p.default.resolve(t);if(!c.default.existsSync(e))throw new Error(`OpenAPI spec not found: ${e}`);if(!c.default.statSync(e).isFile())throw new Error(`OpenAPI spec path is not a file: ${e}`);r=c.default.readFileSync(e,"utf-8")}const o=function(e,t){let r;try{r=JSON.parse(e)}catch(e){throw new Error(`Spec from ${t} is not valid JSON: ${e.message}`)}if(!r||"object"!=typeof r||Array.isArray(r))throw new Error(`Spec from ${t} is not a JSON object`);if(!r.paths||"object"!=typeof r.paths)throw new Error(`Spec from ${t} does not contain a valid 'paths' object`);return r}(r,t);return async function(e){try{const t=await(0,u.dereference)(e);return t.errors&&t.errors.length>0?e:t.schema??t.specification??e}catch(t){return e}}(o)}var P=require("@powerduck/openapi-parser/methods"),C=require("@powerduck/openapi-request"),O="0.2.14",j=a(require("fs"),1),T=require("crypto"),R=a(require("http"),1),q=a(require("https"),1),A=a(require("https-proxy-agent"),1),{HttpsProxyAgent:U}=A.default;function L(e){const t=e.tls;return{...t?.caCert?{ca:j.default.readFileSync(t.caCert)}:{},...t?.clientCert?{cert:j.default.readFileSync(t.clientCert)}:{},...t?.clientKey?{key:j.default.readFileSync(t.clientKey)}:{},...t?.pfx?{pfx:j.default.readFileSync(t.pfx)}:{},...t?.passphrase?{passphrase:t.passphrase}:{},rejectUnauthorized:!1!==t?.strictSSL}}function M(e){const t=L(e);if(e.proxy){const r=new URL(e.proxy);if(!["http:","https:"].includes(r.protocol))throw new Error("Only HTTP(S) CONNECT proxies are supported.");class o extends U{callback(e,r){return super.callback(e,{...r,...t})}}const s=new o({protocol:r.protocol,host:r.hostname,port:Number(r.port||("https:"===r.protocol?443:80)),...r.username||r.password?{auth:`${decodeURIComponent(r.username)}:${decodeURIComponent(r.password)}`}:{},ca:t.ca,rejectUnauthorized:t.rejectUnauthorized});return{http:s,https:s}}return{http:new R.default.Agent,https:new q.default.Agent(t)}}function I(e,t){if(e.proxy||Object.entries(e.tls||{}).some(([e,t])=>"strictSSL"===e?!1===t:Boolean(t)))throw new Error(`${t} custom proxy/TLS is not supported by the bundled request engine.`)}function z(e,t={}){return"string"==typeof e?e.replace(/\{\{([^{}]+)\}\}/g,(e,r)=>{if(!Object.prototype.hasOwnProperty.call(t,r))throw new Error(`Variable ${r} is not defined.`);return t[r]}):Array.isArray(e)?e.map(e=>z(e,t)):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,r])=>[e,z(r,t)])):e}function N(e,t,r){const o=e.auth,s=(e,r)=>{for(const r of Object.keys(t))r.toLowerCase()===e.toLowerCase()&&delete t[r];t[e]=r};if(!o||"none"===o.type)return r;if("basic"===o.type)s("Authorization",`Basic ${Buffer.from(`${o.username||""}:${o.password||""}`).toString("base64")}`);else if("bearer"===o.type)s("Authorization",`Bearer ${o.token||""}`);else{if("apikey"!==o.type)throw new Error(`Authentication ${o.type} is supported only by the HTTP/SSE runner.`);if(!o.key)throw new Error("API key name is required.");if("query"===o.in){if(!r)throw new Error("gRPC API keys require metadata headers.");const e=new URL(r);e.searchParams.set(o.key,o.value||""),r=e.href}else s(o.key,o.value||"")}return r}function F(e,t){const r={"grpc.enable_http_proxy":0};if(!e.proxy)return{address:t,channelOptions:r};const o=new URL(e.proxy);if("http:"!==o.protocol)throw new Error("gRPC supports only HTTP CONNECT proxies.");const s=new URL(`https://${t}`);return r["grpc.http_connect_target"]=`dns:///${t}`,r["grpc.default_authority"]=s.host,r["grpc.ssl_target_name_override"]=s.hostname,(o.username||o.password)&&(r["grpc.http_connect_creds"]=`${decodeURIComponent(o.username)}:${decodeURIComponent(o.password)}`),{address:`${o.hostname}:${o.port||80}`,channelOptions:r}}function D(e){return e.key&&e.passphrase?Buffer.from((0,T.createPrivateKey)({key:e.key,passphrase:e.passphrase}).export({format:"pem",type:"pkcs8"})):e.key}function B(e,t){const r=new URL(e);for(const[e,o]of Object.entries(t||{}))if(null!=o){r.searchParams.delete(e);for(const t of Array.isArray(o)?o:[o])r.searchParams.append(e,String(t))}return r.href}function J(e,t){return e.map(e=>{try{const r=function(e,t){switch(e.assert){case"status":if(void 0===e.value)throw new Error("status assertion requires 'value'");return t.status===e.value;case"header":{if(!e.key)throw new Error("header assertion requires 'key'");const r=function(e,t){const r=t.toLowerCase();for(const[t,o]of Object.entries(e))if(t.toLowerCase()===r)return o;return}(t.headers??{},e.key);return void 0!==r&&(void 0===e.contains||r.toLowerCase().includes(e.contains.toLowerCase()))}case"bodyContains":if(void 0===e.contains)throw new Error("bodyContains assertion requires 'contains'");return(t.bodyText??JSON.stringify(t.body??"")).includes(e.contains);case"bodyEquals":if(void 0===e.body)throw new Error("bodyEquals assertion requires 'body'");return(t.bodyText??JSON.stringify(t.body??""))===e.body;case"jsonPath":{if(!e.path)throw new Error("jsonPath assertion requires 'path'");const r=function(e,t){const r=t.replace(/^\$\.?/,"");if(!r)return e;const o=r.split(/\.|\[(\d+)\]/).filter(e=>void 0!==e&&""!==e);let s=e;for(const e of o){if(null==s)return;s=/^\d+$/.test(e)?s[Number(e)]:s[e]}return s}(t.body,e.path);return void 0!==e.exists?e.exists?void 0!==r:void 0===r:void 0!==e.equals?H(r,e.equals):void 0!==r}case"responseTime":if(void 0===e.max)throw new Error("responseTime assertion requires 'max'");return t.durationMs<=e.max;default:throw new Error(`Unknown assertion type: ${e.assert}`)}}(e,t);return{name:e.name,passed:r}}catch(t){return{name:e.name,passed:!1,error:t.message}}})}function H(e,t){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(null===e||null===t)return e===t;if("object"!=typeof e)return e===t;if(Array.isArray(e)!==Array.isArray(t))return!1;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,r)=>H(e,t[r]));const r=Object.keys(e),o=Object.keys(t);return r.length===o.length&&r.every(r=>H(e[r],t[r]))}var _=O;function K(e){if(!e)return;if("string"==typeof e)return e;if(e instanceof Error)return e.message;const t=e;if("string"==typeof t.message)return t.message;if("string"==typeof t.msg)return t.msg;try{return JSON.stringify(e)}catch{return String(e)}}async function W(e){b(e);const t=await E(e),r=(0,C.createClient)(),o=function(e,t){const r=[];for(const[o,s]of Object.entries(e.paths??{})){if(!s||"object"!=typeof s)continue;const e=s;for(const[s,n]of(0,P.operationEntries)(e)){if(!n||"object"!=typeof n)continue;const a={path:o,method:s,operation:n,pathItem:e,operationId:n.operationId??`${s.toUpperCase()} ${o}`,protocol:V(n,e)};G(a,t.filter)&&r.push(a)}}return r}(t,e),s=Date.now(),n=[];let a=0;const i=Math.min(e.concurrency??5,o.length),c=[];for(let s=0;s<i;s++)c.push((async()=>{for(;a<o.length;){if(e.signal?.aborted)throw new Error("Run cancelled.");const s=o[a++];if(!s)break;const i=await Q(r,s,e,t);n.push(i)}})());await Promise.all(c);const p=Date.now()-s,d=function(e,t){const r=e.length,o=e.filter(e=>"passed"===e.status).length,s=e.filter(e=>"failed"===e.status).length,n=e.filter(e=>"error"===e.status).length,a=e.filter(e=>"skipped"===e.status).length;return{total:r,passed:o,failed:s,errors:n,skipped:a,durationMs:t,passRate:r>0?Math.round(o/r*1e3)/10:0}}(n,p);return{summary:d,results:n.sort((e,t)=>e.path.localeCompare(t.path)||e.method.localeCompare(t.method)),config:e,generatedAt:(new Date).toISOString(),version:_}}function V(e,t){const r=e["x-protocol"]??t?.["x-protocol"];if(r)return String(r).toLowerCase();const o=e.responses??{};for(const e of Object.values(o)){if((e?.content??{})["text/event-stream"])return"sse"}return e["x-response-stream"]?"sse":"http"}function G(e,t){if(!t)return!0;if(t.methods?.length&&!t.methods.includes(e.method.toLowerCase()))return!1;if(t.operationIds?.length&&!t.operationIds.includes(e.operationId))return!1;if(t.tags?.length){const r=e.operation.tags??[];if(!t.tags.some(e=>r.includes(e)))return!1}if(t.paths?.length){if(!t.paths.some(t=>{try{return new RegExp(t).test(e.path)}catch{return e.path.includes(t)}}))return!1}return!0}async function Q(e,t,r,o){const s=await async function(e,t,r){let{config:o}=r;const{spec:s}=r,n=Date.now(),a={operationId:t.operationId,path:t.path,method:t.method.toUpperCase(),protocol:t.protocol};try{if((r.signal||o.signal)?.aborted)throw new Error("Run cancelled.");const i=r.variables||o.variables,c=t.operation["x-websocket"]||t.operation["x-ws"]||t.operation["x-grpc"]||t.operation["x-mcp"]||{},p=z(r.serverUrl??o.serverUrl??c.url??c.address??c.endpoint??s.servers?.[0]?.url,i);o.resolveTransport&&(o={...o,...await o.resolveTransport({protocol:t.protocol,url:p})});const d=["grpc","websocket","mcp"].includes(t.protocol),l={...o,serverUrl:z(o.serverUrl,i),auth:d?z(o.auth,i):o.auth,headers:d?z({...o.headers,...r.values?.header},i):o.headers,signal:r.signal||o.signal},u={...t,operation:d?{...t.operation,...Object.fromEntries(Object.entries(t.operation).filter(([e])=>["x-grpc","x-mcp","x-ws","x-websocket"].includes(e)).map(([e,t])=>[e,z(t,i)]))}:t.operation},f=d?z(r.values,i):r.values;if(!["http","sse","graphql","grpc","websocket","mcp"].includes(t.protocol))throw new Error(`Unsupported protocol: ${t.protocol}`);const m=[...function(e,t){const r=e?.["x-tests"],o=t?.["x-tests"];return[...Array.isArray(o)?o:[],...Array.isArray(r)?r:[]].filter(e=>e&&"object"==typeof e&&e.assert&&e.name)}(t.operation,t.pathItem),...r.extraAssertions??[]],h=function(e){const t=e?.["x-postman-scripts"];if(t&&"object"==typeof t&&"string"==typeof t.test)return t.test}(t.operation),g=[];let v,b,y={};switch(t.protocol){case"grpc":{const t=await async function(e,t,r,o,s){const n=t.operation["x-grpc"]??{},a=(o??r.serverUrl??n.address)?.replace(/^https?:\/\//,"");if(!a)throw new Error("gRPC operation requires x-grpc.address or --server");const i={...r.headers};N(r,i);const c=L(r);if(c.pfx)throw new Error("gRPC requires PEM certificates, not PFX.");const p=e.connect({kind:"grpc",deadlineMs:r.timeout??3e4,streamDeadlineMs:r.maxStreamMs??r.timeout??3e4,...F(r,a),tls:!(!0!==n.tls&&!/^https:/.test(o??r.serverUrl??""))&&{rootCerts:c.ca,privateKey:D(c),certChain:c.cert,rejectUnauthorized:c.rejectUnauthorized},service:n.service,method:n.method??t.operationId,reflection:r.grpcReflection??n.reflection??!(r.grpcProtoPaths??n.protoPaths)?.length,protoPaths:r.grpcProtoPaths??n.protoPaths,includeDirs:n.includeDirs,metadata:i}),d=()=>{p.close().catch(()=>{})};r.signal?.addEventListener("abort",d,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await p.open(),r.signal?.aborted)throw new Error("Run cancelled.");const e=p.kind,o=s?.body??function(e){const t=e["x-grpc"]??{};if(t.sample&&"object"==typeof t.sample)return t.sample;const r=e.requestBody?.content?.["application/json"]?.schema;if(r?.properties){const e={};for(const[t,o]of Object.entries(r.properties))e[t]=o.default??X(o.type);return e}return{}}(t.operation);"unary"===e||"server_streaming"===e?await p.send(o):(await p.send(o),await p.finishSending());const c=p.events??[],d=c.find(e=>"status"===e.kind),l=c.filter(e=>"data"===e.kind&&"in"===e.direction);"unary"!==e&&"server_streaming"!==e||await p.close();const u={status:d?.meta?.code??-1,statusText:d?.meta?.statusName,body:1===l.length?l[0].data:l.map(e=>e.data),events:c.map(e=>({kind:e.kind,direction:e.direction,data:e.data,meta:e.meta}))},f=n.method??t.operationId;return{response:u,request:{method:f,target:n.service?`${n.service}/${f}`:f,url:a,...Object.keys(o).length?{body:o}:{},...Object.keys(i).length?{headers:i}:{}}}}finally{r.signal?.removeEventListener("abort",d),await p.close().catch(()=>{})}}(e,u,l,z(r.serverUrl,i),f);v=t.response,b=t.request;break}case"websocket":{const t=await async function(e,t,r,o,s){const n=t.operation["x-websocket"]??t.operation["x-ws"]??{};let a=Y(n.url,o??r.serverUrl,t.path)?.replace(/^http/,"ws");if(!a)throw new Error("WebSocket operation requires x-ws.url or --server");const i={...r.headers};a=N(r,i,B(a,s?.query));const c=M(r),p=e.connect({kind:"websocket",openTimeoutMs:r.timeout??3e4,signal:r.signal,clientOptions:{...L(r),agent:a.startsWith("wss:")?c.https:c.http},url:a,subprotocols:n.subprotocols,headers:i}),d=()=>{p.close().catch(()=>{})};r.signal?.addEventListener("abort",d,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await p.open(),r.signal?.aborted)throw new Error("Run cancelled.");const e=void 0!==s?.body?[s.body]:n.send??[];for(const t of e.slice(0,3))await p.send(t);await new Promise(e=>setTimeout(e,Math.min(500,r.timeout??500))),await p.close();const t=p.events??[],o=t.filter(e=>"in"===e.direction&&"open"!==e.kind);return{response:{status:101,statusText:"Switching Protocols",body:o.map(e=>e.data),events:t.map(e=>({kind:e.kind,direction:e.direction,data:e.data})),streaming:!0},request:{method:"WS",url:a,...e.length?{body:e.slice(0,3)}:{},...Object.keys(i).length?{headers:i}:{}}}}finally{r.signal?.removeEventListener("abort",d),await p.close().catch(()=>{});for(const e of new Set(Object.values(c)))e.destroy()}}(e,u,l,z(r.serverUrl,i),f);v=t.response,b=t.request;break}case"mcp":{const t=await async function(e,t,r,o,s){const n=t.operation["x-mcp"]??{},a=r.mcpTransport??n.transport??"streamable-http",i={...r.headers};let c=Y(n.endpoint,o??r.serverUrl,t.path);"stdio"!==a&&(I(r,"MCP HTTP"),c=N(r,i,c?B(c,s?.query):c));const p=s?.body?.method??n.method??"tools/call",d=s?.body?.params??n.params??("tools/call"===p?{name:n.name,arguments:s?.body??n.arguments??{}}:{}),l=e.connect({kind:"mcp",transport:"stdio"===a?"stdio":"streamable-http",endpoint:c,headers:i,command:r.mcpCommand??n.command,args:r.mcpArgs??n.args,cwd:r.mcpCwd??n.cwd,timeoutMs:r.timeout??3e4}),u=()=>{l.close().catch(()=>{})};r.signal?.addEventListener("abort",u,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await l.open(),r.signal?.aborted)throw new Error("Run cancelled.");return{response:{status:200,statusText:"OK",body:{result:await l.request(p,d,{signal:r.signal,timeoutMs:r.timeout})}},request:{method:p,url:c,headers:i,body:d}}}finally{r.signal?.removeEventListener("abort",u),await l.close().catch(()=>{})}}(e,u,l,z(r.serverUrl,i),f);v=t.response,b=t.request;break}default:{const n=await async function(e,t,r,o,s){"graphql"===t.protocol&&I(r,"GraphQL");const n="graphql"===t.protocol?void 0:M(r),a={...r.httpRuntime,requester:{...r.requester,strictSSL:!1!==r.tls?.strictSSL,ignoreProxyEnvironmentVariables:!0,...n?{agents:n,...r.proxy||r.tls?.caCert||r.tls?.clientCert||r.tls?.pfx||r.httpRuntime?.certificates?{protocolVersion:"http1"}:{}}:{}}},i=s.values,c={...r.headers??{},...i?.header??{}},p={...r.headers?{header:r.headers}:{},...i??{},...Object.keys(c).length?{header:c}:{}};let d;try{d=await e.send({writeBack:!1,signal:s.signal,spec:o,target:{path:t.path,method:t.method},serverUrl:s.serverUrl??r.serverUrl,values:Object.keys(p).length?p:void 0,auth:r.auth,variables:s.variables,timeout:r.timeout,runner:Object.keys(a).length?a:void 0,scripts:s.postmanTest?{test:{exec:s.postmanTest},fromSpecExtensions:!1}:void 0,onAssertion:e=>{s.postmanAssertions.push({name:e.name??"assertion",passed:e.passed,error:K(e.error)})},maxStreamMs:r.maxStreamMs??r.timeout,maxEvents:r.maxEvents??50,maxResponseSize:r.maxResponseSize})}finally{for(const e of new Set(Object.values(n||{})))e.destroy()}if(s.signal?.aborted)throw new Error("Run cancelled.");if(d.error)throw new Error(d.error.message);const l={},u=d.scripts;if(u)for(const e of[...u.prerequest??[],...u.test??[]])e.environment&&Object.assign(l,e.environment);const f=d.request;return{response:{status:d.response?.status,statusText:d.response?.statusText,contentType:d.response?.contentType,sizeBytes:d.response?.sizeBytes,body:d.response?.body,text:d.response?.text,headers:d.response?.headers,events:d.response?.events,streaming:d.response?.streaming},request:f?{method:f.method,url:f.url,headers:f.headers,...void 0!==f.body?{body:f.body}:{}}:void 0,env:l}}(e,t,o,s,{postmanTest:h,postmanAssertions:g,variables:r.variables,values:r.values,serverUrl:r.serverUrl,signal:r.signal||o.signal});v=n.response,b=n.request,y=n.env;break}}if((r.signal||o.signal)?.aborted)throw new Error("Run cancelled.");const w=Date.now()-n,x=m.length>0||g.length>0?[]:function(e,t){const r=[];if("http"===e||"sse"===e){const e=t?.status,o="number"==typeof e&&e>=200&&e<300;r.push({name:"HTTP status is 2xx (implicit)",passed:o,error:o?void 0:`expected 2xx, got ${e??"no status"}`})}else if("graphql"===e){const e=t?.status,o="number"==typeof e&&e>=200&&e<300,s=t?.body,n=Array.isArray(s?.errors)&&s.errors.length>0,a=o&&!n;let i;o?n&&(i=`GraphQL response contains ${s.errors.length} error(s)`):i=`expected HTTP 2xx, got ${e??"no status"}`,r.push({name:"GraphQL success (implicit)",passed:a,error:i})}else if("grpc"===e){const e=t?.statusCode??t?.code??t?.status,o=t?.statusName??t?.status,s=0===e;r.push({name:"gRPC status is OK (implicit)",passed:s,error:s?void 0:`expected code 0 (OK), got ${e??"unknown"}${o?` (${o})`:""}`})}else if("mcp"===e){const e=t?.body??{},o=void 0!==e.result,s=void 0!==e.error||!0===e.result?.isError||void 0!==t?.error,n=o&&!s;let a;s?a="MCP JSON-RPC response contains an error":o||(a="MCP JSON-RPC response missing 'result'"),r.push({name:"MCP JSON-RPC success (implicit)",passed:n,error:a})}else if("websocket"===e){const e=t?.events??[],o=e.some(e=>"open"===e.kind||"open"===e.state),s=e.some(e=>"error"===e.kind||"error"===e.state),n=o&&!s;r.push({name:"WebSocket connected (implicit)",passed:n,error:n?void 0:s?"WebSocket connection errored":"WebSocket did not reach open state"})}return r}(t.protocol,v),$=m.length?J(m,{status:v?.status,headers:v?v.headers:void 0,body:v?.body,bodyText:v?v.text:void 0,durationMs:w}):[],k=new Set,S=g.filter(e=>{const t=`${e.name}|${e.passed}|${e.error??""}`;return!k.has(t)&&(k.add(t),!0)}),E=[...$,...S,...x],P=E.some(e=>!e.passed);return{result:{...a,status:P?"failed":"passed",durationMs:w,request:b,response:v,assertions:E.length?E:void 0,timestamp:(new Date).toISOString()},response:v,request:b,env:y}}catch(e){const s=Date.now()-n,i=r.values,c={...o.headers??{},...i?.header??{}},p=Object.fromEntries(Object.entries(c).map(([e,t])=>[e,null==t?"":String(t)])),d={method:t.method.toUpperCase(),url:r.serverUrl??o.serverUrl,...Object.keys(p).length?{headers:p}:{},...void 0!==i?.body?{body:i.body}:{}};return{result:{...a,status:"error",durationMs:s,request:d,error:e.message??String(e),timestamp:(new Date).toISOString()},response:void 0,request:d,env:{}}}}(e,t,{config:r,spec:o,variables:r.variables});return s.result}function Y(e,t,r){if(!t)return e;const o=new URL(t),s=new URL(e||r,t);return s.host=o.host,s.protocol=o.protocol,e||"/"===o.pathname||(s.pathname=o.pathname.replace(/\/$/,"")+r),s.href}function X(e){switch(e){case"string":default:return"";case"number":case"integer":return 0;case"boolean":return!1;case"array":return[];case"object":return{}}}var Z=a(require("fs"),1),ee=a(require("path"),1);var te="",re="",oe="",se="",ne="",ae="",ie="",ce="",pe="",de="",le="";function ue(e){if(null==e||!Number.isFinite(e))return`${oe}--ms${te}`;return`${e>500?`${re}${ae}`:re}${e}${te}${oe}ms${te}`}function fe(e){const t="passed"===e.status?`${se}✓${te}`:"failed"===e.status?`${ne}✗${te}`:"error"===e.status?`${ae}!${te}`:`${ce}-${te}`,r="passed"===e.status?`${pe}${re} PASS ${te}`:"failed"===e.status?`${de}${re} FAIL ${te}`:"error"===e.status?`${le}${re} ERROR ${te}`:`${ce} SKIP ${te}`,o=e.protocol?` ${ie}[${e.protocol}]${te}`:"",s=null!=e.response?.status?` ${oe}${e.response.status}${te}`:"";let n=` ${t} ${r} ${re}${e.method}${te} ${e.path}${o}${s} ${ue(e.durationMs)}`;if(e.error&&(n+=`\n ${ne}${e.error}${te}`),e.assertions?.length)for(const t of e.assertions){n+=`\n ${t.passed?`${se} ✓${te}`:`${ne} ✗${te}`} ${t.name}${t.error?` — ${ne}${t.error}${te}`:""}`}return n}function me(e){const t=function(e){const t=[],{summary:r,results:o}=e;t.push(""),t.push(`${re}${ie}╔══════════════════════════════════════════════════════╗${te}`),t.push(`${re}${ie}║ Powerduck Test Report ║${te}`),t.push(`${re}${ie}╚══════════════════════════════════════════════════════╝${te}`),t.push("");const s=r.passRate>=80?se:r.passRate>=50?ae:ne;t.push(`${re} Total:${te} ${r.total} ${se}Passed:${te} ${r.passed} ${ne}Failed:${te} ${r.failed} ${ae}Errors:${te} ${r.errors} ${s}Rate:${te} ${r.passRate}% ${oe}Time:${te} ${ue(r.durationMs)}`),t.push("");const n=r.total>0?Math.round(r.passed/r.total*50):0,a=r.total>0?Math.round(r.failed/r.total*50):0,i=r.total>0?Math.round(r.errors/r.total*50):0,c=se+"█".repeat(n)+ne+"█".repeat(a)+ae+"█".repeat(i)+ce+"░".repeat(Math.max(0,50-n-a-i))+te;t.push(` [${c}]`),t.push("");for(const e of o)t.push(fe(e));return t.push(""),r.failed>0||r.errors>0?t.push(`${re}${ne} ✗ ${r.failed+r.errors} test(s) did not pass${te}`):t.push(`${re}${se} ✓ All ${r.total} test(s) passed${te}`),t.push(` ${oe}Generated: ${e.generatedAt}${te}`),t.push(""),t.join("\n")}(e);return console.log(t),t}var he=a(require("fs"),1),ge=a(require("path"),1);function ve(e,t){const r=ge.default.resolve(t);he.default.mkdirSync(r,{recursive:!0});const o=ge.default.join(r,"report.html");return he.default.writeFileSync(o,function(e){const{summary:t,results:r}=e,o=JSON.stringify({summary:t,results:r});return`<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>Test Report — Powerduck</title>\n<style>\n *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n :root{\n --bg:#ffffff;--fg:#0f172a;--muted:#64748b;--border:#e2e8f0;\n --bg-subtle:#f8fafc;--bg-hover:#f1f5f9;\n --green:#16a34a;--green-bg:#f0fdf4;--green-border:#bbf7d0;\n --red:#dc2626;--red-bg:#fef2f2;--red-border:#fecaca;\n --amber:#d97706;--amber-bg:#fffbeb;--amber-border:#fde68a;\n --blue:#2563eb;--mono:ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,monospace;\n --sans:ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Inter,sans-serif;\n }\n @media (prefers-color-scheme:dark){\n :root{\n --bg:#0f172a;--fg:#f1f5f9;--muted:#94a3b8;--border:#1e293b;\n --bg-subtle:#1e293b;--bg-hover:#334155;\n --green:#4ade80;--green-bg:rgba(74,222,128,.1);--green-border:rgba(74,222,128,.25);\n --red:#f87171;--red-bg:rgba(248,113,113,.1);--red-border:rgba(248,113,113,.25);\n --amber:#fbbf24;--amber-bg:rgba(251,191,36,.1);--amber-border:rgba(251,191,36,.25);\n --blue:#60a5fa;\n }\n }\n body{font-family:var(--sans);background:var(--bg);color:var(--fg);line-height:1.5;font-size:14px;-webkit-font-smoothing:antialiased}\n .wrap{max-width:960px;margin:0 auto;padding:32px 24px 64px}\n\n /* Header */\n .hdr{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--border)}\n .hdr h1{font-size:18px;font-weight:600;letter-spacing:-.01em}\n .hdr .meta{font-size:12px;color:var(--muted);font-family:var(--mono)}\n\n /* Summary strip */\n .strip{display:grid;grid-template-columns:repeat(6,1fr);gap:0;border:1px solid var(--border);border-radius:8px;overflow:hidden;margin-bottom:24px}\n .stat{padding:14px 16px;border-right:1px solid var(--border);text-align:center}\n .stat:last-child{border-right:none}\n .stat .v{font-size:22px;font-weight:700;letter-spacing:-.02em;line-height:1.2}\n .stat .l{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-top:2px}\n .stat.total .v{color:var(--fg)}\n .stat.pass .v{color:var(--green)}\n .stat.fail .v{color:var(--red)}\n .stat.err .v{color:var(--amber)}\n .stat.rate .v{color:var(--blue)}\n .stat.time .v{font-size:16px;font-weight:600}\n\n /* Progress */\n .bar{height:4px;background:var(--border);border-radius:2px;overflow:hidden;margin-bottom:24px;display:flex}\n .bar>div{height:100%}\n .bar .p{background:var(--green)}\n .bar .f{background:var(--red)}\n .bar .e{background:var(--amber)}\n\n /* Filters */\n .filters{display:flex;gap:4px;margin-bottom:16px}\n .fbtn{padding:4px 12px;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--muted);cursor:pointer;font-size:12px;font-weight:500;transition:all .12s}\n .fbtn:hover{color:var(--fg);background:var(--bg-hover)}\n .fbtn.active{background:var(--bg-subtle);color:var(--fg);border-color:var(--border)}\n .fbtn .n{color:var(--muted);margin-left:4px;font-variant-numeric:tabular-nums}\n\n /* Test rows */\n .row{border:1px solid var(--border);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .12s}\n .row:hover{border-color:var(--muted)}\n .row-head{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;user-select:none}\n .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n .dot.pass{background:var(--green)}\n .dot.fail{background:var(--red)}\n .dot.err{background:var(--amber)}\n .method{font-family:var(--mono);font-size:11px;font-weight:600;color:var(--muted);min-width:40px;text-transform:uppercase}\n .path{font-family:var(--mono);font-size:13px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n .proto{font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);padding:2px 8px;border:1px solid var(--border);border-radius:4px}\n .dur{font-family:var(--mono);font-size:12px;color:var(--muted);min-width:56px;text-align:right;font-variant-numeric:tabular-nums}\n .chev{color:var(--muted);font-size:10px;transition:transform .15s;flex-shrink:0}\n .row.open .chev{transform:rotate(90deg)}\n\n /* Row body */\n .row-body{display:none;padding:0 14px 12px 32px;border-top:1px solid var(--border)}\n .row.open .row-body{display:block}\n .sect{margin-top:10px}\n .sect-t{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:600;margin-bottom:6px}\n .asrt{display:flex;align-items:center;gap:8px;padding:3px 0;font-size:13px}\n .asrt .ck{width:14px;height:14px;border-radius:3px;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;flex-shrink:0;color:#fff}\n .asrt.pass .ck{background:var(--green)}\n .asrt.fail .ck{background:var(--red)}\n .asrt .an{color:var(--fg)}\n .asrt .ae{color:var(--red);font-size:12px;margin-left:6px;font-family:var(--mono)}\n .rmeta{display:flex;gap:20px;flex-wrap:wrap;font-size:12px}\n .rmeta span{color:var(--muted)}\n .rmeta strong{color:var(--fg);font-weight:600;font-family:var(--mono)}\n .errbox{background:var(--red-bg);border:1px solid var(--red-border);border-radius:6px;padding:8px 12px;color:var(--red);font-size:12px;font-family:var(--mono);margin-top:10px}\n .bprev{background:var(--bg-subtle);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-family:var(--mono);font-size:11px;color:var(--muted);max-height:180px;overflow:auto;white-space:pre-wrap;word-break:break-all;margin-top:6px}\n\n footer{margin-top:32px;padding-top:16px;border-top:1px solid var(--border);font-size:11px;color:var(--muted);display:flex;justify-content:space-between}\n\n @media(max-width:640px){\n .strip{grid-template-columns:repeat(3,1fr)}\n .stat:nth-child(3){border-right:none}\n .stat:nth-child(4),.stat:nth-child(5){border-top:1px solid var(--border)}\n .path{font-size:11px}\n }\n</style>\n</head>\n<body>\n<div class="wrap">\n <div class="hdr">\n <h1>Test Report</h1>\n <span class="meta">${e.generatedAt} · v${e.version}</span>\n </div>\n\n <div class="strip">\n <div class="stat total"><div class="v">${t.total}</div><div class="l">Total</div></div>\n <div class="stat pass"><div class="v">${t.passed}</div><div class="l">Passed</div></div>\n <div class="stat fail"><div class="v">${t.failed}</div><div class="l">Failed</div></div>\n <div class="stat err"><div class="v">${t.errors}</div><div class="l">Errors</div></div>\n <div class="stat rate"><div class="v">${t.passRate}%</div><div class="l">Pass Rate</div></div>\n <div class="stat time"><div class="v">${(t.durationMs/1e3).toFixed(2)}s</div><div class="l">Duration</div></div>\n </div>\n\n <div class="bar">\n <div class="p" style="width:${t.total?t.passed/t.total*100:0}%"></div>\n <div class="f" style="width:${t.total?t.failed/t.total*100:0}%"></div>\n <div class="e" style="width:${t.total?t.errors/t.total*100:0}%"></div>\n </div>\n\n <div class="filters">\n <button class="fbtn active" data-f="all">All<span class="n">${t.total}</span></button>\n <button class="fbtn" data-f="passed">Passed<span class="n">${t.passed}</span></button>\n <button class="fbtn" data-f="failed">Failed<span class="n">${t.failed}</span></button>\n <button class="fbtn" data-f="error">Errors<span class="n">${t.errors}</span></button>\n </div>\n\n <div id="list"></div>\n\n <footer>\n <span>@powerduck/openapi-cli v${e.version}</span>\n <span>${t.passed}/${t.total} passed</span>\n </footer>\n</div>\n\n<script>\nconst D=${o};\nfunction esc(s){const d=document.createElement('div');d.textContent=String(s??'');return d.innerHTML}\nfunction render(f){\n const list=document.getElementById('list');\n const items=f==='all'?D.results:D.results.filter(r=>r.status===f);\n list.innerHTML=items.map(r=>{\n const as=r.assertions?r.assertions.map(a=>\`\n <div class="asrt \${a.passed?'pass':'fail'}">\n <div class="ck">\${a.passed?'✓':'✗'}</div>\n <span class="an">\${esc(a.name)}</span>\n \${a.error?'<span class="ae">'+esc(a.error)+'</span>':''}\n </div>\`).join(''):'';\n const bp=r.response?.body?'<div class="bprev">'+esc(JSON.stringify(r.response.body,null,2).slice(0,3000))+'</div>':'';\n const eb=r.error?'<div class="errbox">'+esc(r.error)+'</div>':'';\n const rm=r.response?\`\n <div class="rmeta">\n \${r.response.status!=null?'<span>Status <strong>'+r.response.status+(r.response.statusText?' '+r.response.statusText:'')+'</strong></span>':''}\n \${r.response.contentType?'<span>Type <strong>'+esc(r.response.contentType)+'</strong></span>':''}\n \${r.response.sizeBytes!=null?'<span>Size <strong>'+r.response.sizeBytes+'B</strong></span>':''}\n \${r.response.streaming?'<span>Streaming <strong>yes</strong></span>':''}\n </div>\`:'';\n return \`\n <div class="row \${r.status}" data-s="\${r.status}">\n <div class="row-head" onclick="this.parentElement.classList.toggle('open')">\n <div class="dot \${r.status==='passed'?'pass':r.status==='failed'?'fail':'err'}"></div>\n <span class="method">\${r.method}</span>\n <span class="path">\${esc(r.path)}</span>\n \${r.protocol?'<span class="proto">'+r.protocol+'</span>':''}\n <span class="dur">\${r.durationMs}ms</span>\n <span class="chev">▶</span>\n </div>\n <div class="row-body">\n \${eb}\n \${rm}\n \${as?'<div class="sect"><div class="sect-t">Assertions</div>'+as+'</div>':''}\n \${bp?'<div class="sect"><div class="sect-t">Response</div>'+bp+'</div>':''}\n </div>\n </div>\`;\n }).join('');\n}\ndocument.querySelectorAll('.fbtn').forEach(b=>b.addEventListener('click',()=>{\n document.querySelectorAll('.fbtn').forEach(x=>x.classList.remove('active'));\n b.classList.add('active');render(b.dataset.f);\n}));\nrender('all');\n<\/script>\n</body>\n</html>`}(e),"utf-8"),o}var be=new i.Command;be.name("openapi-cli").description("CI-ready CLI for batch-testing OpenAPI 3.2 documents across HTTP, SSE, WebSocket, GraphQL, gRPC and MCP").version(O).option("-s, --spec <path-or-url>","Path or URL (http/https) to OpenAPI 3.2 JSON spec").option("-S, --server <url>","Override server URL from the spec").option("-o, --output <dir>","Output directory for reports","./openapi-cli-report").option("-f, --format <formats>","Comma-separated report formats: json,cli,html","json,cli,html").option("-c, --config <path>","Path to JSON config file (CLI args take precedence)").option("--method <methods>","Filter by HTTP methods (comma-separated)").option("--path <patterns>","Filter by path regex patterns (comma-separated)").option("--tag <tags>","Filter by OpenAPI tags (comma-separated)").option("--operation-id <ids>","Filter by operationId (comma-separated)").option("-n, --concurrency <n>","Max concurrent requests","5").option("-t, --timeout <ms>","Per-request timeout in ms","30000").option("-p, --proxy <url>","HTTP(S) proxy URL").option("--ca <path>","CA certificate bundle (PEM)").option("--cert <path>","Client certificate (PEM) for mTLS").option("--key <path>","Client private key (PEM) for mTLS").option("-k, --insecure","Skip TLS certificate verification").option("-H, --header <headers...>","Extra headers (Key: Value, repeatable)").option("--bearer <token>","Bearer token for Authorization header").option("-v, --variable <vars...>","Postman variables (KEY=VALUE, repeatable)").option("--env <path>","Path to .env file").option("--no-fail-on-error","Exit 0 even if tests fail").option("--grpc-no-reflection","Disable gRPC server reflection (use proto files)").option("--grpc-proto <paths...>","Paths to .proto files or directories").option("--mcp-transport <transport>","MCP transport: streamable-http or stdio","streamable-http").option("--mcp-command <command>","MCP stdio command").option("--mcp-args <args>","MCP stdio arguments (space or comma separated)").option("--mcp-cwd <dir>","MCP stdio working directory"),be.parse(process.argv);var ye=be.opts();(async function(){const e=e=>"cli"===be.getOptionValueSource(e)?ye[e]:void 0,t={spec:ye.spec,server:ye.server,output:e("output"),format:e("format"),config:ye.config,method:ye.method,path:ye.path,tag:ye.tag,operationId:ye.operationId,concurrency:void 0===e("concurrency")?void 0:Number(ye.concurrency),timeout:void 0===e("timeout")?void 0:Number(ye.timeout),proxy:ye.proxy,ca:ye.ca,cert:ye.cert,key:ye.key,insecure:ye.insecure,header:ye.header,bearer:ye.bearer,variable:ye.variable,env:ye.env,failOnError:!process.argv.includes("--no-fail-on-error")&&void 0,grpcReflection:!ye.grpcNoReflection&&void 0,grpcProto:ye.grpcProto,mcpTransport:e("mcpTransport"),mcpCommand:ye.mcpCommand,mcpArgs:ye.mcpArgs,mcpCwd:ye.mcpCwd};let r,o;try{r=y(t)}catch(e){console.error(`Error: ${e.message}`),process.exit(2)}console.log(`openapi-cli v${O} — testing ${r.specPath}`),console.log(`Server: ${r.serverUrl??"(from spec)"} | Concurrency: ${r.concurrency} | Timeout: ${r.timeout}ms\n`);try{o=await W(r)}catch(e){console.error(`Fatal error: ${e.message}`),process.exit(2)}const s=r.formats??["json","cli","html"];if(s.includes("cli")&&me(o),s.includes("json")){const e=function(e,t){const r=ee.default.resolve(t);Z.default.mkdirSync(r,{recursive:!0});const o=ee.default.join(r,"report.json");return Z.default.writeFileSync(o,JSON.stringify(e,null,2),"utf-8"),o}(o,r.outputDir??"./openapi-cli-report");console.log(`JSON report: ${e}`)}if(s.includes("html")){const e=ve(o,r.outputDir??"./openapi-cli-report");console.log(`HTML report: ${e}`)}(o.summary.failed>0||o.summary.errors>0)&&!1!==r.failOnError&&process.exit(1)})().catch(e=>{console.error(`Unexpected error: ${e.message}`),console.error(e.stack),process.exit(2)});
2
+ "use strict";var e=Object.create,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,n=Object.prototype.hasOwnProperty,a=(a,i,c)=>(c=null!=a?e(s(a)):{},((e,s,a,i)=>{if(s&&"object"==typeof s||"function"==typeof s)for(let c of o(s))n.call(e,c)||c===a||t(e,c,{get:()=>s[c],enumerable:!(i=r(s,c))||i.enumerable});return e})(!i&&a&&a.__esModule?c:t(c,"default",{value:a,enumerable:!0}),a)),i=require("commander"),c=a(require("fs"),1),p=a(require("path"),1),d=a(require("http"),1),l=a(require("https"),1),u=require("@powerduck/openapi-parser"),f="./openapi-cli-report",m=["json","cli","html"],h=5,g=3e4,v=!0;function b(e){for(const[t,r,o]of[["concurrency",e.concurrency??5,100],["timeout",e.timeout??3e4,6e5]])if(!Number.isInteger(r)||r<1||r>o)throw new Error(`${t} must be an integer between 1 and ${o}.`);if(e.formats?.some(e=>!["json","cli","html"].includes(e)))throw new Error("Unsupported report format.");if(e.mcpTransport&&!["stdio","streamable-http"].includes(e.mcpTransport))throw new Error("Unsupported MCP transport.")}function y(e){const t=e.config?function(e){const t=p.default.resolve(e);if(!c.default.existsSync(t))throw new Error(`Config file not found: ${t}`);const r=c.default.readFileSync(t,"utf-8");try{const e=JSON.parse(r);if(!e||"object"!=typeof e||Array.isArray(e))throw new Error("Config must be an object.");return e}catch(e){throw new Error(`Invalid config file (must be JSON): ${e.message}`)}}(e.config):{},r={specPath:e.spec??t.specPath??"",serverUrl:e.server??t.serverUrl,outputDir:e.output??t.outputDir??f,formats:w(e.format??t.formats),filter:x(e,t),concurrency:e.concurrency??t.concurrency??h,timeout:e.timeout??t.timeout??g,proxy:e.proxy??t.proxy,tls:{caCert:e.ca??t.tls?.caCert,clientCert:e.cert??t.tls?.clientCert,clientKey:e.key??t.tls?.clientKey,pfx:t.tls?.pfx,passphrase:t.tls?.passphrase,strictSSL:void 0!==e.insecure?!e.insecure:t.tls?.strictSSL},requester:t.requester,maxEvents:t.maxEvents,maxStreamMs:t.maxStreamMs,maxResponseSize:t.maxResponseSize,headers:$(e.header,t.headers),auth:k(e,t),variables:S(e.variable,t.variables),failOnError:e.failOnError??t.failOnError??v,envFile:e.env??t.envFile,grpcReflection:e.grpcReflection??t.grpcReflection,grpcProtoPaths:e.grpcProto??t.grpcProtoPaths,mcpTransport:e.mcpTransport??t.mcpTransport,mcpCommand:e.mcpCommand??t.mcpCommand,mcpArgs:e.mcpArgs?e.mcpArgs.split(/[\s,]+/).filter(Boolean):t.mcpArgs,mcpCwd:e.mcpCwd??t.mcpCwd};if(r.envFile&&function(e){const t=p.default.resolve(e);if(!c.default.existsSync(t))return;const r=c.default.readFileSync(t,"utf-8").split("\n");for(const e of r){const t=e.trim();if(!t||t.startsWith("#"))continue;const r=t.indexOf("=");if(r>0){const e=t.slice(0,r).trim(),o=t.slice(r+1).trim().replace(/^["']|["']$/g,"");e in process.env||(process.env[e]=o)}}}(r.envFile),function(e){const t=e=>"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,(e,t)=>process.env[t]??"");if(e.serverUrl=t(e.serverUrl),e.proxy=t(e.proxy),e.headers)for(const[r,o]of Object.entries(e.headers))e.headers[r]=t(o);e.auth?.token&&(e.auth.token=t(e.auth.token));if(e.variables)for(const[r,o]of Object.entries(e.variables))e.variables[r]=t(o)}(r),!r.specPath)throw new Error("No OpenAPI spec path provided. Use --spec <path> or set specPath in config.");if(r.proxy)try{new URL(r.proxy)}catch{throw new Error(`Invalid proxy URL: ${r.proxy}`)}return b(r),r}function w(e){return Array.isArray(e)?e:"string"==typeof e?e.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):m}function x(e,t){const r=e.method?e.method.split(",").map(e=>e.trim().toLowerCase()):t.filter?.methods,o=e.path?e.path.split(",").map(e=>e.trim()):t.filter?.paths,s=e.tag?e.tag.split(",").map(e=>e.trim()):t.filter?.tags,n=e.operationId?e.operationId.split(",").map(e=>e.trim()):t.filter?.operationIds;if(r||o||s||n)return{methods:r,paths:o,tags:s,operationIds:n}}function $(e,t){const r={...t??{}};for(const t of e??[]){const e=t.indexOf(":");if(e>0){const o=t.slice(0,e).trim(),s=t.slice(e+1).trim();o&&(r[o]=s)}}return Object.keys(r).length?r:void 0}function k(e,t){return e.bearer?{type:"bearer",token:e.bearer}:t.auth}function S(e,t){const r={...t??{}};for(const t of e??[]){const e=t.indexOf("=");e>0&&(r[t.slice(0,e).trim()]=t.slice(e+1).trim())}return Object.keys(r).length?r:void 0}async function E(e){const t=e.specPath.trim();let r;if(/^https?:\/\//i.test(t.trim()))r=await function(e,t){let r=0;return new Promise((o,s)=>{const n=a=>{const i=("https:"===new URL(a).protocol?l.default:d.default).get(a,{timeout:t,headers:{Accept:"application/json, text/plain, */*","User-Agent":"@powerduck/openapi-cli"}},t=>{if(t.statusCode&&t.statusCode>=300&&t.statusCode<400&&t.headers.location){if(++r>5)return t.resume(),void s(new Error(`Too many redirects (>5) while fetching spec: ${e}`));const o=new URL(t.headers.location,a).toString();return t.resume(),void n(o)}if(200!==t.statusCode)return t.resume(),void s(new Error(`Failed to fetch spec from ${a}: HTTP ${t.statusCode} ${t.statusMessage??""}`.trim()));const i=[];t.on("data",e=>i.push(e)),t.on("end",()=>o(Buffer.concat(i).toString("utf-8"))),t.on("error",e=>s(new Error(`Error reading spec response: ${e.message}`)))});i.on("timeout",()=>{i.destroy(new Error(`Timed out after ${t}ms while fetching spec: ${e}`))}),i.on("error",t=>s(new Error(`Network error fetching spec ${e}: ${t.message}`)))};n(e)})}(t,e.timeout??g);else{const e=p.default.resolve(t);if(!c.default.existsSync(e))throw new Error(`OpenAPI spec not found: ${e}`);if(!c.default.statSync(e).isFile())throw new Error(`OpenAPI spec path is not a file: ${e}`);r=c.default.readFileSync(e,"utf-8")}const o=function(e,t){let r;try{r=JSON.parse(e)}catch(e){throw new Error(`Spec from ${t} is not valid JSON: ${e.message}`)}if(!r||"object"!=typeof r||Array.isArray(r))throw new Error(`Spec from ${t} is not a JSON object`);if(!r.paths||"object"!=typeof r.paths)throw new Error(`Spec from ${t} does not contain a valid 'paths' object`);return r}(r,t);return async function(e){try{const t=await(0,u.dereference)(e);return t.errors&&t.errors.length>0?e:t.schema??t.specification??e}catch(t){return e}}(o)}var P=require("@powerduck/openapi-parser/methods"),C=require("@powerduck/openapi-request"),O="0.2.16",j=a(require("fs"),1),T=require("crypto"),R=a(require("http"),1),q=a(require("https"),1),A=a(require("https-proxy-agent"),1),{HttpsProxyAgent:U}=A.default;function L(e){const t=e.tls;return{...t?.caCert?{ca:j.default.readFileSync(t.caCert)}:{},...t?.clientCert?{cert:j.default.readFileSync(t.clientCert)}:{},...t?.clientKey?{key:j.default.readFileSync(t.clientKey)}:{},...t?.pfx?{pfx:j.default.readFileSync(t.pfx)}:{},...t?.passphrase?{passphrase:t.passphrase}:{},rejectUnauthorized:!1!==t?.strictSSL}}function M(e){const t=L(e);if(e.proxy){const r=new URL(e.proxy);if(!["http:","https:"].includes(r.protocol))throw new Error("Only HTTP(S) CONNECT proxies are supported.");class o extends U{callback(e,r){return super.callback(e,{...r,...t})}}const s=new o({protocol:r.protocol,host:r.hostname,port:Number(r.port||("https:"===r.protocol?443:80)),...r.username||r.password?{auth:`${decodeURIComponent(r.username)}:${decodeURIComponent(r.password)}`}:{},ca:t.ca,rejectUnauthorized:t.rejectUnauthorized});return{http:s,https:s}}return{http:new R.default.Agent,https:new q.default.Agent(t)}}function I(e,t){if(e.proxy||Object.entries(e.tls||{}).some(([e,t])=>"strictSSL"===e?!1===t:Boolean(t)))throw new Error(`${t} custom proxy/TLS is not supported by the bundled request engine.`)}function z(e,t={}){return"string"==typeof e?e.replace(/\{\{([^{}]+)\}\}/g,(e,r)=>{if(!Object.prototype.hasOwnProperty.call(t,r))throw new Error(`Variable ${r} is not defined.`);return t[r]}):Array.isArray(e)?e.map(e=>z(e,t)):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,r])=>[e,z(r,t)])):e}function N(e,t,r){const o=e.auth,s=(e,r)=>{for(const r of Object.keys(t))r.toLowerCase()===e.toLowerCase()&&delete t[r];t[e]=r};if(!o||"none"===o.type)return r;if("basic"===o.type)s("Authorization",`Basic ${Buffer.from(`${o.username||""}:${o.password||""}`).toString("base64")}`);else if("bearer"===o.type)s("Authorization",`Bearer ${o.token||""}`);else{if("apikey"!==o.type)throw new Error(`Authentication ${o.type} is supported only by the HTTP/SSE runner.`);if(!o.key)throw new Error("API key name is required.");if("query"===o.in){if(!r)throw new Error("gRPC API keys require metadata headers.");const e=new URL(r);e.searchParams.set(o.key,o.value||""),r=e.href}else s(o.key,o.value||"")}return r}function F(e,t){const r={"grpc.enable_http_proxy":0};if(!e.proxy)return{address:t,channelOptions:r};const o=new URL(e.proxy);if("http:"!==o.protocol)throw new Error("gRPC supports only HTTP CONNECT proxies.");const s=new URL(`https://${t}`);return r["grpc.http_connect_target"]=`dns:///${t}`,r["grpc.default_authority"]=s.host,r["grpc.ssl_target_name_override"]=s.hostname,(o.username||o.password)&&(r["grpc.http_connect_creds"]=`${decodeURIComponent(o.username)}:${decodeURIComponent(o.password)}`),{address:`${o.hostname}:${o.port||80}`,channelOptions:r}}function D(e){return e.key&&e.passphrase?Buffer.from((0,T.createPrivateKey)({key:e.key,passphrase:e.passphrase}).export({format:"pem",type:"pkcs8"})):e.key}function B(e,t){const r=new URL(e);for(const[e,o]of Object.entries(t||{}))if(null!=o){r.searchParams.delete(e);for(const t of Array.isArray(o)?o:[o])r.searchParams.append(e,String(t))}return r.href}function J(e,t){return e.map(e=>{try{const r=function(e,t){switch(e.assert){case"status":if(void 0===e.value)throw new Error("status assertion requires 'value'");return t.status===e.value;case"header":{if(!e.key)throw new Error("header assertion requires 'key'");const r=function(e,t){const r=t.toLowerCase();for(const[t,o]of Object.entries(e))if(t.toLowerCase()===r)return o;return}(t.headers??{},e.key);return void 0!==r&&(void 0===e.contains||r.toLowerCase().includes(e.contains.toLowerCase()))}case"bodyContains":if(void 0===e.contains)throw new Error("bodyContains assertion requires 'contains'");return(t.bodyText??JSON.stringify(t.body??"")).includes(e.contains);case"bodyEquals":if(void 0===e.body)throw new Error("bodyEquals assertion requires 'body'");return(t.bodyText??JSON.stringify(t.body??""))===e.body;case"jsonPath":{if(!e.path)throw new Error("jsonPath assertion requires 'path'");const r=function(e,t){const r=t.replace(/^\$\.?/,"");if(!r)return e;const o=r.split(/\.|\[(\d+)\]/).filter(e=>void 0!==e&&""!==e);let s=e;for(const e of o){if(null==s)return;s=/^\d+$/.test(e)?s[Number(e)]:s[e]}return s}(t.body,e.path);return void 0!==e.exists?e.exists?void 0!==r:void 0===r:void 0!==e.equals?H(r,e.equals):void 0!==r}case"responseTime":if(void 0===e.max)throw new Error("responseTime assertion requires 'max'");return t.durationMs<=e.max;default:throw new Error(`Unknown assertion type: ${e.assert}`)}}(e,t);return{name:e.name,passed:r}}catch(t){return{name:e.name,passed:!1,error:t.message}}})}function H(e,t){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(null===e||null===t)return e===t;if("object"!=typeof e)return e===t;if(Array.isArray(e)!==Array.isArray(t))return!1;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,r)=>H(e,t[r]));const r=Object.keys(e),o=Object.keys(t);return r.length===o.length&&r.every(r=>H(e[r],t[r]))}var _=O;function K(e){if(!e)return;if("string"==typeof e)return e;if(e instanceof Error)return e.message;const t=e;if("string"==typeof t.message)return t.message;if("string"==typeof t.msg)return t.msg;try{return JSON.stringify(e)}catch{return String(e)}}async function W(e){b(e);const t=await E(e),r=(0,C.createClient)(),o=function(e,t){const r=[];for(const[o,s]of Object.entries(e.paths??{})){if(!s||"object"!=typeof s)continue;const e=s;for(const[s,n]of(0,P.operationEntries)(e)){if(!n||"object"!=typeof n)continue;const a={path:o,method:s,operation:n,pathItem:e,operationId:n.operationId??`${s.toUpperCase()} ${o}`,protocol:V(n,e)};G(a,t.filter)&&r.push(a)}}return r}(t,e),s=Date.now(),n=[];let a=0;const i=Math.min(e.concurrency??5,o.length),c=[];for(let s=0;s<i;s++)c.push((async()=>{for(;a<o.length;){if(e.signal?.aborted)throw new Error("Run cancelled.");const s=o[a++];if(!s)break;const i=await Q(r,s,e,t);n.push(i)}})());await Promise.all(c);const p=Date.now()-s,d=function(e,t){const r=e.length,o=e.filter(e=>"passed"===e.status).length,s=e.filter(e=>"failed"===e.status).length,n=e.filter(e=>"error"===e.status).length,a=e.filter(e=>"skipped"===e.status).length;return{total:r,passed:o,failed:s,errors:n,skipped:a,durationMs:t,passRate:r>0?Math.round(o/r*1e3)/10:0}}(n,p);return{summary:d,results:n.sort((e,t)=>e.path.localeCompare(t.path)||e.method.localeCompare(t.method)),config:e,generatedAt:(new Date).toISOString(),version:_}}function V(e,t){const r=e["x-protocol"]??t?.["x-protocol"];if(r)return String(r).toLowerCase();const o=e.responses??{};for(const e of Object.values(o)){if((e?.content??{})["text/event-stream"])return"sse"}return e["x-response-stream"]?"sse":"http"}function G(e,t){if(!t)return!0;if(t.methods?.length&&!t.methods.includes(e.method.toLowerCase()))return!1;if(t.operationIds?.length&&!t.operationIds.includes(e.operationId))return!1;if(t.tags?.length){const r=e.operation.tags??[];if(!t.tags.some(e=>r.includes(e)))return!1}if(t.paths?.length){if(!t.paths.some(t=>{try{return new RegExp(t).test(e.path)}catch{return e.path.includes(t)}}))return!1}return!0}async function Q(e,t,r,o){const s=await async function(e,t,r){let{config:o}=r;const{spec:s}=r,n=Date.now(),a={operationId:t.operationId,path:t.path,method:t.method.toUpperCase(),protocol:t.protocol};try{if((r.signal||o.signal)?.aborted)throw new Error("Run cancelled.");const i=r.variables||o.variables,c=t.operation["x-websocket"]||t.operation["x-ws"]||t.operation["x-grpc"]||t.operation["x-mcp"]||{},p=z(r.serverUrl??o.serverUrl??c.url??c.address??c.endpoint??s.servers?.[0]?.url,i);o.resolveTransport&&(o={...o,...await o.resolveTransport({protocol:t.protocol,url:p})});const d=["grpc","websocket","mcp"].includes(t.protocol),l={...o,serverUrl:z(o.serverUrl,i),auth:d?z(o.auth,i):o.auth,headers:d?z({...o.headers,...r.values?.header},i):o.headers,signal:r.signal||o.signal},u={...t,operation:d?{...t.operation,...Object.fromEntries(Object.entries(t.operation).filter(([e])=>["x-grpc","x-mcp","x-ws","x-websocket"].includes(e)).map(([e,t])=>[e,z(t,i)]))}:t.operation},f=d?z(r.values,i):r.values;if(!["http","sse","graphql","grpc","websocket","mcp"].includes(t.protocol))throw new Error(`Unsupported protocol: ${t.protocol}`);const m=[...function(e,t){const r=e?.["x-tests"],o=t?.["x-tests"];return[...Array.isArray(o)?o:[],...Array.isArray(r)?r:[]].filter(e=>e&&"object"==typeof e&&e.assert&&e.name)}(t.operation,t.pathItem),...r.extraAssertions??[]],h=function(e){const t=e?.["x-postman-scripts"];if(t&&"object"==typeof t&&"string"==typeof t.test)return t.test}(t.operation),g=[];let v,b,y={};switch(t.protocol){case"grpc":{const t=await async function(e,t,r,o,s){const n=t.operation["x-grpc"]??{},a=(o??r.serverUrl??n.address)?.replace(/^https?:\/\//,"");if(!a)throw new Error("gRPC operation requires x-grpc.address or --server");const i={...r.headers};N(r,i);const c=L(r);if(c.pfx)throw new Error("gRPC requires PEM certificates, not PFX.");const p=e.connect({kind:"grpc",deadlineMs:r.timeout??3e4,streamDeadlineMs:r.maxStreamMs??r.timeout??3e4,...F(r,a),tls:!(!0!==n.tls&&!/^https:/.test(o??r.serverUrl??""))&&{rootCerts:c.ca,privateKey:D(c),certChain:c.cert,rejectUnauthorized:c.rejectUnauthorized},service:n.service,method:n.method??t.operationId,reflection:r.grpcReflection??n.reflection??!(r.grpcProtoPaths??n.protoPaths)?.length,protoPaths:r.grpcProtoPaths??n.protoPaths,includeDirs:n.includeDirs,metadata:i}),d=()=>{p.close().catch(()=>{})};r.signal?.addEventListener("abort",d,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await p.open(),r.signal?.aborted)throw new Error("Run cancelled.");const e=p.kind,o=s?.body??function(e){const t=e["x-grpc"]??{};if(t.sample&&"object"==typeof t.sample)return t.sample;const r=e.requestBody?.content?.["application/json"]?.schema;if(r?.properties){const e={};for(const[t,o]of Object.entries(r.properties))e[t]=o.default??X(o.type);return e}return{}}(t.operation);"unary"===e||"server_streaming"===e?await p.send(o):(await p.send(o),await p.finishSending());const c=p.events??[],d=c.find(e=>"status"===e.kind),l=c.filter(e=>"data"===e.kind&&"in"===e.direction);"unary"!==e&&"server_streaming"!==e||await p.close();const u={status:d?.meta?.code??-1,statusText:d?.meta?.statusName,body:1===l.length?l[0].data:l.map(e=>e.data),events:c.map(e=>({kind:e.kind,direction:e.direction,data:e.data,meta:e.meta}))},f=n.method??t.operationId;return{response:u,request:{method:f,target:n.service?`${n.service}/${f}`:f,url:a,...Object.keys(o).length?{body:o}:{},...Object.keys(i).length?{headers:i}:{}}}}finally{r.signal?.removeEventListener("abort",d),await p.close().catch(()=>{})}}(e,u,l,z(r.serverUrl,i),f);v=t.response,b=t.request;break}case"websocket":{const t=await async function(e,t,r,o,s){const n=t.operation["x-websocket"]??t.operation["x-ws"]??{};let a=Y(n.url,o??r.serverUrl,t.path)?.replace(/^http/,"ws");if(!a)throw new Error("WebSocket operation requires x-ws.url or --server");const i={...r.headers};a=N(r,i,B(a,s?.query));const c=M(r),p=e.connect({kind:"websocket",openTimeoutMs:r.timeout??3e4,signal:r.signal,clientOptions:{...L(r),agent:a.startsWith("wss:")?c.https:c.http},url:a,subprotocols:n.subprotocols,headers:i}),d=()=>{p.close().catch(()=>{})};r.signal?.addEventListener("abort",d,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await p.open(),r.signal?.aborted)throw new Error("Run cancelled.");const e=void 0!==s?.body?[s.body]:n.send??[];for(const t of e.slice(0,3))await p.send(t);await new Promise(e=>setTimeout(e,Math.min(500,r.timeout??500))),await p.close();const t=p.events??[],o=t.filter(e=>"in"===e.direction&&"open"!==e.kind);return{response:{status:101,statusText:"Switching Protocols",body:o.map(e=>e.data),events:t.map(e=>({kind:e.kind,direction:e.direction,data:e.data})),streaming:!0},request:{method:"WS",url:a,...e.length?{body:e.slice(0,3)}:{},...Object.keys(i).length?{headers:i}:{}}}}finally{r.signal?.removeEventListener("abort",d),await p.close().catch(()=>{});for(const e of new Set(Object.values(c)))e.destroy()}}(e,u,l,z(r.serverUrl,i),f);v=t.response,b=t.request;break}case"mcp":{const t=await async function(e,t,r,o,s){const n=t.operation["x-mcp"]??{},a=r.mcpTransport??n.transport??"streamable-http",i={...r.headers};let c=Y(n.endpoint,o??r.serverUrl,t.path);"stdio"!==a&&(I(r,"MCP HTTP"),c=N(r,i,c?B(c,s?.query):c));const p=s?.body?.method??n.method??"tools/call",d=s?.body?.params??n.params??("tools/call"===p?{name:n.name,arguments:s?.body??n.arguments??{}}:{}),l=e.connect({kind:"mcp",transport:"stdio"===a?"stdio":"streamable-http",endpoint:c,headers:i,command:r.mcpCommand??n.command,args:r.mcpArgs??n.args,cwd:r.mcpCwd??n.cwd,timeoutMs:r.timeout??3e4}),u=()=>{l.close().catch(()=>{})};r.signal?.addEventListener("abort",u,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await l.open(),r.signal?.aborted)throw new Error("Run cancelled.");return{response:{status:200,statusText:"OK",body:{result:await l.request(p,d,{signal:r.signal,timeoutMs:r.timeout})}},request:{method:p,url:c,headers:i,body:d}}}finally{r.signal?.removeEventListener("abort",u),await l.close().catch(()=>{})}}(e,u,l,z(r.serverUrl,i),f);v=t.response,b=t.request;break}default:{const n=await async function(e,t,r,o,s){"graphql"===t.protocol&&I(r,"GraphQL");const n="graphql"===t.protocol?void 0:M(r),a={...r.httpRuntime,requester:{...r.requester,strictSSL:!1!==r.tls?.strictSSL,ignoreProxyEnvironmentVariables:!0,...n?{agents:n,...r.proxy||r.tls?.caCert||r.tls?.clientCert||r.tls?.pfx||r.httpRuntime?.certificates?{protocolVersion:"http1"}:{}}:{}}},i=s.values,c={...r.headers??{},...i?.header??{}},p={...r.headers?{header:r.headers}:{},...i??{},...Object.keys(c).length?{header:c}:{}};let d;try{d=await e.send({writeBack:!1,signal:s.signal,spec:o,target:{path:t.path,method:t.method},serverUrl:s.serverUrl??r.serverUrl,values:Object.keys(p).length?p:void 0,auth:r.auth,variables:s.variables,timeout:r.timeout,runner:Object.keys(a).length?a:void 0,scripts:s.postmanTest?{test:{exec:s.postmanTest},fromSpecExtensions:!1}:void 0,onAssertion:e=>{s.postmanAssertions.push({name:e.name??"assertion",passed:e.passed,error:K(e.error)})},maxStreamMs:r.maxStreamMs??r.timeout,maxEvents:r.maxEvents??50,maxResponseSize:r.maxResponseSize})}finally{for(const e of new Set(Object.values(n||{})))e.destroy()}if(s.signal?.aborted)throw new Error("Run cancelled.");if(d.error)throw new Error(d.error.message);const l={},u=d.scripts;if(u)for(const e of[...u.prerequest??[],...u.test??[]])e.environment&&Object.assign(l,e.environment);const f=d.request;return{response:{status:d.response?.status,statusText:d.response?.statusText,contentType:d.response?.contentType,sizeBytes:d.response?.sizeBytes,body:d.response?.body,text:d.response?.text,headers:d.response?.headers,events:d.response?.events,streaming:d.response?.streaming},request:f?{method:f.method,url:f.url,headers:f.headers,...void 0!==f.body?{body:f.body}:{}}:void 0,env:l}}(e,t,o,s,{postmanTest:h,postmanAssertions:g,variables:r.variables,values:r.values,serverUrl:r.serverUrl,signal:r.signal||o.signal});v=n.response,b=n.request,y=n.env;break}}if((r.signal||o.signal)?.aborted)throw new Error("Run cancelled.");const w=Date.now()-n,x=m.length>0||g.length>0?[]:function(e,t){const r=[];if("http"===e||"sse"===e){const e=t?.status,o="number"==typeof e&&e>=200&&e<300;r.push({name:"HTTP status is 2xx (implicit)",passed:o,error:o?void 0:`expected 2xx, got ${e??"no status"}`})}else if("graphql"===e){const e=t?.status,o="number"==typeof e&&e>=200&&e<300,s=t?.body,n=Array.isArray(s?.errors)&&s.errors.length>0,a=o&&!n;let i;o?n&&(i=`GraphQL response contains ${s.errors.length} error(s)`):i=`expected HTTP 2xx, got ${e??"no status"}`,r.push({name:"GraphQL success (implicit)",passed:a,error:i})}else if("grpc"===e){const e=t?.statusCode??t?.code??t?.status,o=t?.statusName??t?.status,s=0===e;r.push({name:"gRPC status is OK (implicit)",passed:s,error:s?void 0:`expected code 0 (OK), got ${e??"unknown"}${o?` (${o})`:""}`})}else if("mcp"===e){const e=t?.body??{},o=void 0!==e.result,s=void 0!==e.error||!0===e.result?.isError||void 0!==t?.error,n=o&&!s;let a;s?a="MCP JSON-RPC response contains an error":o||(a="MCP JSON-RPC response missing 'result'"),r.push({name:"MCP JSON-RPC success (implicit)",passed:n,error:a})}else if("websocket"===e){const e=t?.events??[],o=e.some(e=>"open"===e.kind||"open"===e.state),s=e.some(e=>"error"===e.kind||"error"===e.state),n=o&&!s;r.push({name:"WebSocket connected (implicit)",passed:n,error:n?void 0:s?"WebSocket connection errored":"WebSocket did not reach open state"})}return r}(t.protocol,v),$=m.length?J(m,{status:v?.status,headers:v?v.headers:void 0,body:v?.body,bodyText:v?v.text:void 0,durationMs:w}):[],k=new Set,S=g.filter(e=>{const t=`${e.name}|${e.passed}|${e.error??""}`;return!k.has(t)&&(k.add(t),!0)}),E=[...$,...S,...x],P=E.some(e=>!e.passed);return{result:{...a,status:P?"failed":"passed",durationMs:w,request:b,response:v,assertions:E.length?E:void 0,timestamp:(new Date).toISOString()},response:v,request:b,env:y}}catch(e){const s=Date.now()-n,i=r.values,c={...o.headers??{},...i?.header??{}},p=Object.fromEntries(Object.entries(c).map(([e,t])=>[e,null==t?"":String(t)])),d={method:t.method.toUpperCase(),url:r.serverUrl??o.serverUrl,...Object.keys(p).length?{headers:p}:{},...void 0!==i?.body?{body:i.body}:{}};return{result:{...a,status:"error",durationMs:s,request:d,error:e.message??String(e),timestamp:(new Date).toISOString()},response:void 0,request:d,env:{}}}}(e,t,{config:r,spec:o,variables:r.variables});return s.result}function Y(e,t,r){if(!t)return e;const o=new URL(t),s=new URL(e||r,t);return s.host=o.host,s.protocol=o.protocol,e||"/"===o.pathname||(s.pathname=o.pathname.replace(/\/$/,"")+r),s.href}function X(e){switch(e){case"string":default:return"";case"number":case"integer":return 0;case"boolean":return!1;case"array":return[];case"object":return{}}}var Z=a(require("fs"),1),ee=a(require("path"),1);var te="",re="",oe="",se="",ne="",ae="",ie="",ce="",pe="",de="",le="";function ue(e){if(null==e||!Number.isFinite(e))return`${oe}--ms${te}`;return`${e>500?`${re}${ae}`:re}${e}${te}${oe}ms${te}`}function fe(e){const t="passed"===e.status?`${se}✓${te}`:"failed"===e.status?`${ne}✗${te}`:"error"===e.status?`${ae}!${te}`:`${ce}-${te}`,r="passed"===e.status?`${pe}${re} PASS ${te}`:"failed"===e.status?`${de}${re} FAIL ${te}`:"error"===e.status?`${le}${re} ERROR ${te}`:`${ce} SKIP ${te}`,o=e.protocol?` ${ie}[${e.protocol}]${te}`:"",s=null!=e.response?.status?` ${oe}${e.response.status}${te}`:"";let n=` ${t} ${r} ${re}${e.method}${te} ${e.path}${o}${s} ${ue(e.durationMs)}`;if(e.error&&(n+=`\n ${ne}${e.error}${te}`),e.assertions?.length)for(const t of e.assertions){n+=`\n ${t.passed?`${se} ✓${te}`:`${ne} ✗${te}`} ${t.name}${t.error?` — ${ne}${t.error}${te}`:""}`}return n}function me(e){const t=function(e){const t=[],{summary:r,results:o}=e;t.push(""),t.push(`${re}${ie}╔══════════════════════════════════════════════════════╗${te}`),t.push(`${re}${ie}║ Powerduck Test Report ║${te}`),t.push(`${re}${ie}╚══════════════════════════════════════════════════════╝${te}`),t.push("");const s=r.passRate>=80?se:r.passRate>=50?ae:ne;t.push(`${re} Total:${te} ${r.total} ${se}Passed:${te} ${r.passed} ${ne}Failed:${te} ${r.failed} ${ae}Errors:${te} ${r.errors} ${s}Rate:${te} ${r.passRate}% ${oe}Time:${te} ${ue(r.durationMs)}`),t.push("");const n=r.total>0?Math.round(r.passed/r.total*50):0,a=r.total>0?Math.round(r.failed/r.total*50):0,i=r.total>0?Math.round(r.errors/r.total*50):0,c=se+"█".repeat(n)+ne+"█".repeat(a)+ae+"█".repeat(i)+ce+"░".repeat(Math.max(0,50-n-a-i))+te;t.push(` [${c}]`),t.push("");for(const e of o)t.push(fe(e));return t.push(""),r.failed>0||r.errors>0?t.push(`${re}${ne} ✗ ${r.failed+r.errors} test(s) did not pass${te}`):t.push(`${re}${se} ✓ All ${r.total} test(s) passed${te}`),t.push(` ${oe}Generated: ${e.generatedAt}${te}`),t.push(""),t.join("\n")}(e);return console.log(t),t}var he=a(require("fs"),1),ge=a(require("path"),1);function ve(e,t){const r=ge.default.resolve(t);he.default.mkdirSync(r,{recursive:!0});const o=ge.default.join(r,"report.html");return he.default.writeFileSync(o,function(e){const{summary:t,results:r}=e,o=JSON.stringify({summary:t,results:r}).replace(/</g,"\\u003c");return`<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>Test Report — Powerduck</title>\n<style>\n *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n :root{\n --bg:#ffffff;--fg:#0f172a;--muted:#64748b;--border:#e2e8f0;\n --bg-subtle:#f8fafc;--bg-hover:#f1f5f9;\n --green:#16a34a;--green-bg:#f0fdf4;--green-border:#bbf7d0;\n --red:#dc2626;--red-bg:#fef2f2;--red-border:#fecaca;\n --amber:#d97706;--amber-bg:#fffbeb;--amber-border:#fde68a;\n --blue:#2563eb;--mono:ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,monospace;\n --sans:ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Inter,sans-serif;\n }\n @media (prefers-color-scheme:dark){\n :root{\n --bg:#0f172a;--fg:#f1f5f9;--muted:#94a3b8;--border:#1e293b;\n --bg-subtle:#1e293b;--bg-hover:#334155;\n --green:#4ade80;--green-bg:rgba(74,222,128,.1);--green-border:rgba(74,222,128,.25);\n --red:#f87171;--red-bg:rgba(248,113,113,.1);--red-border:rgba(248,113,113,.25);\n --amber:#fbbf24;--amber-bg:rgba(251,191,36,.1);--amber-border:rgba(251,191,36,.25);\n --blue:#60a5fa;\n }\n }\n body{font-family:var(--sans);background:var(--bg);color:var(--fg);line-height:1.5;font-size:14px;-webkit-font-smoothing:antialiased}\n .wrap{max-width:960px;margin:0 auto;padding:32px 24px 64px}\n\n /* Header */\n .hdr{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--border)}\n .hdr h1{font-size:18px;font-weight:600;letter-spacing:-.01em}\n .hdr .meta{font-size:12px;color:var(--muted);font-family:var(--mono)}\n\n /* Summary strip */\n .strip{display:grid;grid-template-columns:repeat(6,1fr);gap:0;border:1px solid var(--border);border-radius:8px;overflow:hidden;margin-bottom:24px}\n .stat{padding:14px 16px;border-right:1px solid var(--border);text-align:center}\n .stat:last-child{border-right:none}\n .stat .v{font-size:22px;font-weight:700;letter-spacing:-.02em;line-height:1.2}\n .stat .l{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-top:2px}\n .stat.total .v{color:var(--fg)}\n .stat.pass .v{color:var(--green)}\n .stat.fail .v{color:var(--red)}\n .stat.err .v{color:var(--amber)}\n .stat.rate .v{color:var(--blue)}\n .stat.time .v{font-size:16px;font-weight:600}\n\n /* Progress */\n .bar{height:4px;background:var(--border);border-radius:2px;overflow:hidden;margin-bottom:24px;display:flex}\n .bar>div{height:100%}\n .bar .p{background:var(--green)}\n .bar .f{background:var(--red)}\n .bar .e{background:var(--amber)}\n\n /* Filters */\n .filters{display:flex;gap:4px;margin-bottom:16px}\n .fbtn{padding:4px 12px;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--muted);cursor:pointer;font-size:12px;font-weight:500;transition:all .12s}\n .fbtn:hover{color:var(--fg);background:var(--bg-hover)}\n .fbtn.active{background:var(--bg-subtle);color:var(--fg);border-color:var(--border)}\n .fbtn .n{color:var(--muted);margin-left:4px;font-variant-numeric:tabular-nums}\n\n /* Test rows */\n .row{border:1px solid var(--border);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .12s}\n .row:hover{border-color:var(--muted)}\n .row-head{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;user-select:none}\n .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n .dot.pass{background:var(--green)}\n .dot.fail{background:var(--red)}\n .dot.err{background:var(--amber)}\n .method{font-family:var(--mono);font-size:11px;font-weight:600;color:var(--muted);min-width:40px;text-transform:uppercase}\n .path{font-family:var(--mono);font-size:13px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n .proto{font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);padding:2px 8px;border:1px solid var(--border);border-radius:4px}\n .dur{font-family:var(--mono);font-size:12px;color:var(--muted);min-width:56px;text-align:right;font-variant-numeric:tabular-nums}\n .chev{color:var(--muted);font-size:10px;transition:transform .15s;flex-shrink:0}\n .row.open .chev{transform:rotate(90deg)}\n\n /* Row body */\n .row-body{display:none;padding:0 14px 12px 32px;border-top:1px solid var(--border)}\n .row.open .row-body{display:block}\n .sect{margin-top:10px}\n .sect-t{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:600;margin-bottom:6px}\n .asrt{display:flex;align-items:center;gap:8px;padding:3px 0;font-size:13px}\n .asrt .ck{width:14px;height:14px;border-radius:3px;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;flex-shrink:0;color:#fff}\n .asrt.pass .ck{background:var(--green)}\n .asrt.fail .ck{background:var(--red)}\n .asrt .an{color:var(--fg)}\n .asrt .ae{color:var(--red);font-size:12px;margin-left:6px;font-family:var(--mono)}\n .rmeta{display:flex;gap:20px;flex-wrap:wrap;font-size:12px}\n .rmeta span{color:var(--muted)}\n .rmeta strong{color:var(--fg);font-weight:600;font-family:var(--mono)}\n .errbox{background:var(--red-bg);border:1px solid var(--red-border);border-radius:6px;padding:8px 12px;color:var(--red);font-size:12px;font-family:var(--mono);margin-top:10px}\n .bprev{background:var(--bg-subtle);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-family:var(--mono);font-size:11px;color:var(--muted);max-height:180px;overflow:auto;white-space:pre-wrap;word-break:break-all;margin-top:6px}\n\n footer{margin-top:32px;padding-top:16px;border-top:1px solid var(--border);font-size:11px;color:var(--muted);display:flex;justify-content:space-between}\n\n @media(max-width:640px){\n .strip{grid-template-columns:repeat(3,1fr)}\n .stat:nth-child(3){border-right:none}\n .stat:nth-child(4),.stat:nth-child(5){border-top:1px solid var(--border)}\n .path{font-size:11px}\n }\n</style>\n</head>\n<body>\n<div class="wrap">\n <div class="hdr">\n <h1>Test Report</h1>\n <span class="meta">${e.generatedAt} · v${e.version}</span>\n </div>\n\n <div class="strip">\n <div class="stat total"><div class="v">${t.total}</div><div class="l">Total</div></div>\n <div class="stat pass"><div class="v">${t.passed}</div><div class="l">Passed</div></div>\n <div class="stat fail"><div class="v">${t.failed}</div><div class="l">Failed</div></div>\n <div class="stat err"><div class="v">${t.errors}</div><div class="l">Errors</div></div>\n <div class="stat rate"><div class="v">${t.passRate}%</div><div class="l">Pass Rate</div></div>\n <div class="stat time"><div class="v">${(t.durationMs/1e3).toFixed(2)}s</div><div class="l">Duration</div></div>\n </div>\n\n <div class="bar">\n <div class="p" style="width:${t.total?t.passed/t.total*100:0}%"></div>\n <div class="f" style="width:${t.total?t.failed/t.total*100:0}%"></div>\n <div class="e" style="width:${t.total?t.errors/t.total*100:0}%"></div>\n </div>\n\n <div class="filters">\n <button class="fbtn active" data-f="all">All<span class="n">${t.total}</span></button>\n <button class="fbtn" data-f="passed">Passed<span class="n">${t.passed}</span></button>\n <button class="fbtn" data-f="failed">Failed<span class="n">${t.failed}</span></button>\n <button class="fbtn" data-f="error">Errors<span class="n">${t.errors}</span></button>\n </div>\n\n <div id="list"></div>\n\n <footer>\n <span>@powerduck/openapi-cli v${e.version}</span>\n <span>${t.passed}/${t.total} passed</span>\n </footer>\n</div>\n\n<script>\nconst D=${o};\nfunction esc(s){const d=document.createElement('div');d.textContent=String(s??'');return d.innerHTML}\nfunction render(f){\n const list=document.getElementById('list');\n const items=f==='all'?D.results:D.results.filter(r=>r.status===f);\n list.innerHTML=items.map(r=>{\n const as=r.assertions?r.assertions.map(a=>\`\n <div class="asrt \${a.passed?'pass':'fail'}">\n <div class="ck">\${a.passed?'✓':'✗'}</div>\n <span class="an">\${esc(a.name)}</span>\n \${a.error?'<span class="ae">'+esc(a.error)+'</span>':''}\n </div>\`).join(''):'';\n const bp=r.response?.body?'<div class="bprev">'+esc(JSON.stringify(r.response.body,null,2).slice(0,3000))+'</div>':'';\n const eb=r.error?'<div class="errbox">'+esc(r.error)+'</div>':'';\n const rm=r.response?\`\n <div class="rmeta">\n \${r.response.status!=null?'<span>Status <strong>'+r.response.status+(r.response.statusText?' '+esc(r.response.statusText):'')+'</strong></span>':''}\n \${r.response.contentType?'<span>Type <strong>'+esc(r.response.contentType)+'</strong></span>':''}\n \${r.response.sizeBytes!=null?'<span>Size <strong>'+r.response.sizeBytes+'B</strong></span>':''}\n \${r.response.streaming?'<span>Streaming <strong>yes</strong></span>':''}\n </div>\`:'';\n return \`\n <div class="row \${r.status}" data-s="\${r.status}">\n <div class="row-head" onclick="this.parentElement.classList.toggle('open')">\n <div class="dot \${r.status==='passed'?'pass':r.status==='failed'?'fail':'err'}"></div>\n <span class="method">\${esc(r.method)}</span>\n <span class="path">\${esc(r.path)}</span>\n \${r.protocol?'<span class="proto">'+esc(r.protocol)+'</span>':''}\n <span class="dur">\${r.durationMs}ms</span>\n <span class="chev">▶</span>\n </div>\n <div class="row-body">\n \${eb}\n \${rm}\n \${as?'<div class="sect"><div class="sect-t">Assertions</div>'+as+'</div>':''}\n \${bp?'<div class="sect"><div class="sect-t">Response</div>'+bp+'</div>':''}\n </div>\n </div>\`;\n }).join('');\n}\ndocument.querySelectorAll('.fbtn').forEach(b=>b.addEventListener('click',()=>{\n document.querySelectorAll('.fbtn').forEach(x=>x.classList.remove('active'));\n b.classList.add('active');render(b.dataset.f);\n}));\nrender('all');\n<\/script>\n</body>\n</html>`}(e),"utf-8"),o}var be=new i.Command;be.name("openapi-cli").description("CI-ready CLI for batch-testing OpenAPI 3.2 documents across HTTP, SSE, WebSocket, GraphQL, gRPC and MCP").version(O).option("-s, --spec <path-or-url>","Path or URL (http/https) to OpenAPI 3.2 JSON spec").option("-S, --server <url>","Override server URL from the spec").option("-o, --output <dir>","Output directory for reports","./openapi-cli-report").option("-f, --format <formats>","Comma-separated report formats: json,cli,html","json,cli,html").option("-c, --config <path>","Path to JSON config file (CLI args take precedence)").option("--method <methods>","Filter by HTTP methods (comma-separated)").option("--path <patterns>","Filter by path regex patterns (comma-separated)").option("--tag <tags>","Filter by OpenAPI tags (comma-separated)").option("--operation-id <ids>","Filter by operationId (comma-separated)").option("-n, --concurrency <n>","Max concurrent requests","5").option("-t, --timeout <ms>","Per-request timeout in ms","30000").option("-p, --proxy <url>","HTTP(S) proxy URL").option("--ca <path>","CA certificate bundle (PEM)").option("--cert <path>","Client certificate (PEM) for mTLS").option("--key <path>","Client private key (PEM) for mTLS").option("-k, --insecure","Skip TLS certificate verification").option("-H, --header <headers...>","Extra headers (Key: Value, repeatable)").option("--bearer <token>","Bearer token for Authorization header").option("-v, --variable <vars...>","Postman variables (KEY=VALUE, repeatable)").option("--env <path>","Path to .env file").option("--no-fail-on-error","Exit 0 even if tests fail").option("--grpc-no-reflection","Disable gRPC server reflection (use proto files)").option("--grpc-proto <paths...>","Paths to .proto files or directories").option("--mcp-transport <transport>","MCP transport: streamable-http or stdio","streamable-http").option("--mcp-command <command>","MCP stdio command").option("--mcp-args <args>","MCP stdio arguments (space or comma separated)").option("--mcp-cwd <dir>","MCP stdio working directory"),be.parse(process.argv);var ye=be.opts();(async function(){const e=e=>"cli"===be.getOptionValueSource(e)?ye[e]:void 0,t={spec:ye.spec,server:ye.server,output:e("output"),format:e("format"),config:ye.config,method:ye.method,path:ye.path,tag:ye.tag,operationId:ye.operationId,concurrency:void 0===e("concurrency")?void 0:Number(ye.concurrency),timeout:void 0===e("timeout")?void 0:Number(ye.timeout),proxy:ye.proxy,ca:ye.ca,cert:ye.cert,key:ye.key,insecure:ye.insecure,header:ye.header,bearer:ye.bearer,variable:ye.variable,env:ye.env,failOnError:!process.argv.includes("--no-fail-on-error")&&void 0,grpcReflection:!ye.grpcNoReflection&&void 0,grpcProto:ye.grpcProto,mcpTransport:e("mcpTransport"),mcpCommand:ye.mcpCommand,mcpArgs:ye.mcpArgs,mcpCwd:ye.mcpCwd};let r,o;try{r=y(t)}catch(e){console.error(`Error: ${e.message}`),process.exit(2)}console.log(`openapi-cli v${O} — testing ${r.specPath}`),console.log(`Server: ${r.serverUrl??"(from spec)"} | Concurrency: ${r.concurrency} | Timeout: ${r.timeout}ms\n`);try{o=await W(r)}catch(e){console.error(`Fatal error: ${e.message}`),process.exit(2)}const s=r.formats??["json","cli","html"];if(s.includes("cli")&&me(o),s.includes("json")){const e=function(e,t){const r=ee.default.resolve(t);Z.default.mkdirSync(r,{recursive:!0});const o=ee.default.join(r,"report.json");return Z.default.writeFileSync(o,JSON.stringify(e,null,2),"utf-8"),o}(o,r.outputDir??"./openapi-cli-report");console.log(`JSON report: ${e}`)}if(s.includes("html")){const e=ve(o,r.outputDir??"./openapi-cli-report");console.log(`HTML report: ${e}`)}(o.summary.failed>0||o.summary.errors>0)&&!1!==r.failOnError&&process.exit(1)})().catch(e=>{console.error(`Unexpected error: ${e.message}`),console.error(e.stack),process.exit(2)});
package/dist/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{generateHtmlReport as o,generateJsonReport as e,package_default as r,printCliReport as t,resolveConfig as a,runTests as i}from"./chunk-HYCEXENJ.js";import{Command as n}from"commander";var p=new n;p.name("openapi-cli").description("CI-ready CLI for batch-testing OpenAPI 3.2 documents across HTTP, SSE, WebSocket, GraphQL, gRPC and MCP").version(r.version).option("-s, --spec <path-or-url>","Path or URL (http/https) to OpenAPI 3.2 JSON spec").option("-S, --server <url>","Override server URL from the spec").option("-o, --output <dir>","Output directory for reports","./openapi-cli-report").option("-f, --format <formats>","Comma-separated report formats: json,cli,html","json,cli,html").option("-c, --config <path>","Path to JSON config file (CLI args take precedence)").option("--method <methods>","Filter by HTTP methods (comma-separated)").option("--path <patterns>","Filter by path regex patterns (comma-separated)").option("--tag <tags>","Filter by OpenAPI tags (comma-separated)").option("--operation-id <ids>","Filter by operationId (comma-separated)").option("-n, --concurrency <n>","Max concurrent requests","5").option("-t, --timeout <ms>","Per-request timeout in ms","30000").option("-p, --proxy <url>","HTTP(S) proxy URL").option("--ca <path>","CA certificate bundle (PEM)").option("--cert <path>","Client certificate (PEM) for mTLS").option("--key <path>","Client private key (PEM) for mTLS").option("-k, --insecure","Skip TLS certificate verification").option("-H, --header <headers...>","Extra headers (Key: Value, repeatable)").option("--bearer <token>","Bearer token for Authorization header").option("-v, --variable <vars...>","Postman variables (KEY=VALUE, repeatable)").option("--env <path>","Path to .env file").option("--no-fail-on-error","Exit 0 even if tests fail").option("--grpc-no-reflection","Disable gRPC server reflection (use proto files)").option("--grpc-proto <paths...>","Paths to .proto files or directories").option("--mcp-transport <transport>","MCP transport: streamable-http or stdio","streamable-http").option("--mcp-command <command>","MCP stdio command").option("--mcp-args <args>","MCP stdio arguments (space or comma separated)").option("--mcp-cwd <dir>","MCP stdio working directory"),p.parse(process.argv);var c=p.opts();(async function(){const n=o=>"cli"===p.getOptionValueSource(o)?c[o]:void 0,s={spec:c.spec,server:c.server,output:n("output"),format:n("format"),config:c.config,method:c.method,path:c.path,tag:c.tag,operationId:c.operationId,concurrency:void 0===n("concurrency")?void 0:Number(c.concurrency),timeout:void 0===n("timeout")?void 0:Number(c.timeout),proxy:c.proxy,ca:c.ca,cert:c.cert,key:c.key,insecure:c.insecure,header:c.header,bearer:c.bearer,variable:c.variable,env:c.env,failOnError:!process.argv.includes("--no-fail-on-error")&&void 0,grpcReflection:!c.grpcNoReflection&&void 0,grpcProto:c.grpcProto,mcpTransport:n("mcpTransport"),mcpCommand:c.mcpCommand,mcpArgs:c.mcpArgs,mcpCwd:c.mcpCwd};let m,l;try{m=a(s)}catch(o){console.error(`Error: ${o.message}`),process.exit(2)}console.log(`openapi-cli v${r.version} — testing ${m.specPath}`),console.log(`Server: ${m.serverUrl??"(from spec)"} | Concurrency: ${m.concurrency} | Timeout: ${m.timeout}ms\n`);try{l=await i(m)}catch(o){console.error(`Fatal error: ${o.message}`),process.exit(2)}const d=m.formats??["json","cli","html"];if(d.includes("cli")&&t(l),d.includes("json")){const o=e(l,m.outputDir??"./openapi-cli-report");console.log(`JSON report: ${o}`)}if(d.includes("html")){const e=o(l,m.outputDir??"./openapi-cli-report");console.log(`HTML report: ${e}`)}(l.summary.failed>0||l.summary.errors>0)&&!1!==m.failOnError&&process.exit(1)})().catch(o=>{console.error(`Unexpected error: ${o.message}`),console.error(o.stack),process.exit(2)});
2
+ import{generateHtmlReport as o,generateJsonReport as e,package_default as r,printCliReport as t,resolveConfig as a,runTests as i}from"./chunk-V3S7H3A5.js";import{Command as n}from"commander";var p=new n;p.name("openapi-cli").description("CI-ready CLI for batch-testing OpenAPI 3.2 documents across HTTP, SSE, WebSocket, GraphQL, gRPC and MCP").version(r.version).option("-s, --spec <path-or-url>","Path or URL (http/https) to OpenAPI 3.2 JSON spec").option("-S, --server <url>","Override server URL from the spec").option("-o, --output <dir>","Output directory for reports","./openapi-cli-report").option("-f, --format <formats>","Comma-separated report formats: json,cli,html","json,cli,html").option("-c, --config <path>","Path to JSON config file (CLI args take precedence)").option("--method <methods>","Filter by HTTP methods (comma-separated)").option("--path <patterns>","Filter by path regex patterns (comma-separated)").option("--tag <tags>","Filter by OpenAPI tags (comma-separated)").option("--operation-id <ids>","Filter by operationId (comma-separated)").option("-n, --concurrency <n>","Max concurrent requests","5").option("-t, --timeout <ms>","Per-request timeout in ms","30000").option("-p, --proxy <url>","HTTP(S) proxy URL").option("--ca <path>","CA certificate bundle (PEM)").option("--cert <path>","Client certificate (PEM) for mTLS").option("--key <path>","Client private key (PEM) for mTLS").option("-k, --insecure","Skip TLS certificate verification").option("-H, --header <headers...>","Extra headers (Key: Value, repeatable)").option("--bearer <token>","Bearer token for Authorization header").option("-v, --variable <vars...>","Postman variables (KEY=VALUE, repeatable)").option("--env <path>","Path to .env file").option("--no-fail-on-error","Exit 0 even if tests fail").option("--grpc-no-reflection","Disable gRPC server reflection (use proto files)").option("--grpc-proto <paths...>","Paths to .proto files or directories").option("--mcp-transport <transport>","MCP transport: streamable-http or stdio","streamable-http").option("--mcp-command <command>","MCP stdio command").option("--mcp-args <args>","MCP stdio arguments (space or comma separated)").option("--mcp-cwd <dir>","MCP stdio working directory"),p.parse(process.argv);var c=p.opts();(async function(){const n=o=>"cli"===p.getOptionValueSource(o)?c[o]:void 0,s={spec:c.spec,server:c.server,output:n("output"),format:n("format"),config:c.config,method:c.method,path:c.path,tag:c.tag,operationId:c.operationId,concurrency:void 0===n("concurrency")?void 0:Number(c.concurrency),timeout:void 0===n("timeout")?void 0:Number(c.timeout),proxy:c.proxy,ca:c.ca,cert:c.cert,key:c.key,insecure:c.insecure,header:c.header,bearer:c.bearer,variable:c.variable,env:c.env,failOnError:!process.argv.includes("--no-fail-on-error")&&void 0,grpcReflection:!c.grpcNoReflection&&void 0,grpcProto:c.grpcProto,mcpTransport:n("mcpTransport"),mcpCommand:c.mcpCommand,mcpArgs:c.mcpArgs,mcpCwd:c.mcpCwd};let m,l;try{m=a(s)}catch(o){console.error(`Error: ${o.message}`),process.exit(2)}console.log(`openapi-cli v${r.version} — testing ${m.specPath}`),console.log(`Server: ${m.serverUrl??"(from spec)"} | Concurrency: ${m.concurrency} | Timeout: ${m.timeout}ms\n`);try{l=await i(m)}catch(o){console.error(`Fatal error: ${o.message}`),process.exit(2)}const d=m.formats??["json","cli","html"];if(d.includes("cli")&&t(l),d.includes("json")){const o=e(l,m.outputDir??"./openapi-cli-report");console.log(`JSON report: ${o}`)}if(d.includes("html")){const e=o(l,m.outputDir??"./openapi-cli-report");console.log(`HTML report: ${e}`)}(l.summary.failed>0||l.summary.errors>0)&&!1!==m.failOnError&&process.exit(1)})().catch(o=>{console.error(`Unexpected error: ${o.message}`),console.error(o.stack),process.exit(2)});
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t=Object.create,r=Object.defineProperty,s=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,o=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,i=(e,t,o,i)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of n(t))a.call(e,c)||c===o||r(e,c,{get:()=>t[c],enumerable:!(i=s(t,c))||i.enumerable});return e},c=(e,s,n)=>(n=null!=e?t(o(e)):{},i(!s&&e&&e.__esModule?n:r(n,"default",{value:e,enumerable:!0}),e)),p={};((e,t)=>{for(var s in t)r(e,s,{get:t[s],enumerable:!0})})(p,{DEFAULT_CONFIG:()=>A,applyExtracts:()=>ue,buildImplicitAssertions:()=>Q,buildSummary:()=>ne,collectOperations:()=>Y,executeStep:()=>te,extractAssertions:()=>H,extractPostmanScripts:()=>K,formatAssertionError:()=>W,generateCliReport:()=>we,generateHtmlReport:()=>Ee,generateJsonReport:()=>be,isRemoteSpec:()=>F,loadSpec:()=>D,printCliReport:()=>$e,resolveConfig:()=>U,resolveJsonPath:()=>_,resolveSteps:()=>pe,runDeclarativeAssertions:()=>J,runScenario:()=>me,runTests:()=>V,stepRef:()=>ce}),module.exports=(e=p,i(r({},"__esModule",{value:!0}),e));var l=require("@powerduck/openapi-parser/methods"),d=require("@powerduck/openapi-request"),u="0.2.14",f=c(require("fs"),1),m=require("crypto"),h=c(require("http"),1),g=c(require("https"),1),b=c(require("https-proxy-agent"),1),{HttpsProxyAgent:v}=b.default;function y(e){const t=e.tls;return{...t?.caCert?{ca:f.default.readFileSync(t.caCert)}:{},...t?.clientCert?{cert:f.default.readFileSync(t.clientCert)}:{},...t?.clientKey?{key:f.default.readFileSync(t.clientKey)}:{},...t?.pfx?{pfx:f.default.readFileSync(t.pfx)}:{},...t?.passphrase?{passphrase:t.passphrase}:{},rejectUnauthorized:!1!==t?.strictSSL}}function w(e){const t=y(e);if(e.proxy){const r=new URL(e.proxy);if(!["http:","https:"].includes(r.protocol))throw new Error("Only HTTP(S) CONNECT proxies are supported.");class s extends v{callback(e,r){return super.callback(e,{...r,...t})}}const n=new s({protocol:r.protocol,host:r.hostname,port:Number(r.port||("https:"===r.protocol?443:80)),...r.username||r.password?{auth:`${decodeURIComponent(r.username)}:${decodeURIComponent(r.password)}`}:{},ca:t.ca,rejectUnauthorized:t.rejectUnauthorized});return{http:n,https:n}}return{http:new h.default.Agent,https:new g.default.Agent(t)}}function x(e,t){if(e.proxy||Object.entries(e.tls||{}).some(([e,t])=>"strictSSL"===e?!1===t:Boolean(t)))throw new Error(`${t} custom proxy/TLS is not supported by the bundled request engine.`)}function $(e,t={}){return"string"==typeof e?e.replace(/\{\{([^{}]+)\}\}/g,(e,r)=>{if(!Object.prototype.hasOwnProperty.call(t,r))throw new Error(`Variable ${r} is not defined.`);return t[r]}):Array.isArray(e)?e.map(e=>$(e,t)):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,r])=>[e,$(r,t)])):e}function k(e,t,r){const s=e.auth,n=(e,r)=>{for(const r of Object.keys(t))r.toLowerCase()===e.toLowerCase()&&delete t[r];t[e]=r};if(!s||"none"===s.type)return r;if("basic"===s.type)n("Authorization",`Basic ${Buffer.from(`${s.username||""}:${s.password||""}`).toString("base64")}`);else if("bearer"===s.type)n("Authorization",`Bearer ${s.token||""}`);else{if("apikey"!==s.type)throw new Error(`Authentication ${s.type} is supported only by the HTTP/SSE runner.`);if(!s.key)throw new Error("API key name is required.");if("query"===s.in){if(!r)throw new Error("gRPC API keys require metadata headers.");const e=new URL(r);e.searchParams.set(s.key,s.value||""),r=e.href}else n(s.key,s.value||"")}return r}function S(e,t){const r={"grpc.enable_http_proxy":0};if(!e.proxy)return{address:t,channelOptions:r};const s=new URL(e.proxy);if("http:"!==s.protocol)throw new Error("gRPC supports only HTTP CONNECT proxies.");const n=new URL(`https://${t}`);return r["grpc.http_connect_target"]=`dns:///${t}`,r["grpc.default_authority"]=n.host,r["grpc.ssl_target_name_override"]=n.hostname,(s.username||s.password)&&(r["grpc.http_connect_creds"]=`${decodeURIComponent(s.username)}:${decodeURIComponent(s.password)}`),{address:`${s.hostname}:${s.port||80}`,channelOptions:r}}function E(e){return e.key&&e.passphrase?Buffer.from((0,m.createPrivateKey)({key:e.key,passphrase:e.passphrase}).export({format:"pem",type:"pkcs8"})):e.key}function O(e,t){const r=new URL(e);for(const[e,s]of Object.entries(t||{}))if(null!=s){r.searchParams.delete(e);for(const t of Array.isArray(s)?s:[s])r.searchParams.append(e,String(t))}return r.href}var C=c(require("fs"),1),q=c(require("path"),1),j=c(require("http"),1),P=c(require("https"),1),R=require("@powerduck/openapi-parser"),A={outputDir:"./openapi-cli-report",formats:["json","cli","html"],concurrency:5,timeout:3e4,failOnError:!0,grpcReflection:!0,mcpTransport:"streamable-http"};function T(e){for(const[t,r,s]of[["concurrency",e.concurrency??5,100],["timeout",e.timeout??3e4,6e5]])if(!Number.isInteger(r)||r<1||r>s)throw new Error(`${t} must be an integer between 1 and ${s}.`);if(e.formats?.some(e=>!["json","cli","html"].includes(e)))throw new Error("Unsupported report format.");if(e.mcpTransport&&!["stdio","streamable-http"].includes(e.mcpTransport))throw new Error("Unsupported MCP transport.")}function U(e){const t=e.config?function(e){const t=q.default.resolve(e);if(!C.default.existsSync(t))throw new Error(`Config file not found: ${t}`);const r=C.default.readFileSync(t,"utf-8");try{const e=JSON.parse(r);if(!e||"object"!=typeof e||Array.isArray(e))throw new Error("Config must be an object.");return e}catch(e){throw new Error(`Invalid config file (must be JSON): ${e.message}`)}}(e.config):{},r={specPath:e.spec??t.specPath??"",serverUrl:e.server??t.serverUrl,outputDir:e.output??t.outputDir??A.outputDir,formats:I(e.format??t.formats),filter:L(e,t),concurrency:e.concurrency??t.concurrency??A.concurrency,timeout:e.timeout??t.timeout??A.timeout,proxy:e.proxy??t.proxy,tls:{caCert:e.ca??t.tls?.caCert,clientCert:e.cert??t.tls?.clientCert,clientKey:e.key??t.tls?.clientKey,pfx:t.tls?.pfx,passphrase:t.tls?.passphrase,strictSSL:void 0!==e.insecure?!e.insecure:t.tls?.strictSSL},requester:t.requester,maxEvents:t.maxEvents,maxStreamMs:t.maxStreamMs,maxResponseSize:t.maxResponseSize,headers:M(e.header,t.headers),auth:z(e,t),variables:N(e.variable,t.variables),failOnError:e.failOnError??t.failOnError??A.failOnError,envFile:e.env??t.envFile,grpcReflection:e.grpcReflection??t.grpcReflection,grpcProtoPaths:e.grpcProto??t.grpcProtoPaths,mcpTransport:e.mcpTransport??t.mcpTransport,mcpCommand:e.mcpCommand??t.mcpCommand,mcpArgs:e.mcpArgs?e.mcpArgs.split(/[\s,]+/).filter(Boolean):t.mcpArgs,mcpCwd:e.mcpCwd??t.mcpCwd};if(r.envFile&&function(e){const t=q.default.resolve(e);if(!C.default.existsSync(t))return;const r=C.default.readFileSync(t,"utf-8").split("\n");for(const e of r){const t=e.trim();if(!t||t.startsWith("#"))continue;const r=t.indexOf("=");if(r>0){const e=t.slice(0,r).trim(),s=t.slice(r+1).trim().replace(/^["']|["']$/g,"");e in process.env||(process.env[e]=s)}}}(r.envFile),function(e){const t=e=>"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,(e,t)=>process.env[t]??"");if(e.serverUrl=t(e.serverUrl),e.proxy=t(e.proxy),e.headers)for(const[r,s]of Object.entries(e.headers))e.headers[r]=t(s);e.auth?.token&&(e.auth.token=t(e.auth.token));if(e.variables)for(const[r,s]of Object.entries(e.variables))e.variables[r]=t(s)}(r),!r.specPath)throw new Error("No OpenAPI spec path provided. Use --spec <path> or set specPath in config.");if(r.proxy)try{new URL(r.proxy)}catch{throw new Error(`Invalid proxy URL: ${r.proxy}`)}return T(r),r}function I(e){return Array.isArray(e)?e:"string"==typeof e?e.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):A.formats}function L(e,t){const r=e.method?e.method.split(",").map(e=>e.trim().toLowerCase()):t.filter?.methods,s=e.path?e.path.split(",").map(e=>e.trim()):t.filter?.paths,n=e.tag?e.tag.split(",").map(e=>e.trim()):t.filter?.tags,o=e.operationId?e.operationId.split(",").map(e=>e.trim()):t.filter?.operationIds;if(r||s||n||o)return{methods:r,paths:s,tags:n,operationIds:o}}function M(e,t){const r={...t??{}};for(const t of e??[]){const e=t.indexOf(":");if(e>0){const s=t.slice(0,e).trim(),n=t.slice(e+1).trim();s&&(r[s]=n)}}return Object.keys(r).length?r:void 0}function z(e,t){return e.bearer?{type:"bearer",token:e.bearer}:t.auth}function N(e,t){const r={...t??{}};for(const t of e??[]){const e=t.indexOf("=");e>0&&(r[t.slice(0,e).trim()]=t.slice(e+1).trim())}return Object.keys(r).length?r:void 0}function F(e){return/^https?:\/\//i.test(e.trim())}async function D(e){const t=e.specPath.trim();let r;if(F(t))r=await function(e,t){let r=0;return new Promise((s,n)=>{const o=a=>{const i=("https:"===new URL(a).protocol?P.default:j.default).get(a,{timeout:t,headers:{Accept:"application/json, text/plain, */*","User-Agent":"@powerduck/openapi-cli"}},t=>{if(t.statusCode&&t.statusCode>=300&&t.statusCode<400&&t.headers.location){if(++r>5)return t.resume(),void n(new Error(`Too many redirects (>5) while fetching spec: ${e}`));const s=new URL(t.headers.location,a).toString();return t.resume(),void o(s)}if(200!==t.statusCode)return t.resume(),void n(new Error(`Failed to fetch spec from ${a}: HTTP ${t.statusCode} ${t.statusMessage??""}`.trim()));const i=[];t.on("data",e=>i.push(e)),t.on("end",()=>s(Buffer.concat(i).toString("utf-8"))),t.on("error",e=>n(new Error(`Error reading spec response: ${e.message}`)))});i.on("timeout",()=>{i.destroy(new Error(`Timed out after ${t}ms while fetching spec: ${e}`))}),i.on("error",t=>n(new Error(`Network error fetching spec ${e}: ${t.message}`)))};o(e)})}(t,e.timeout??A.timeout);else{const e=q.default.resolve(t);if(!C.default.existsSync(e))throw new Error(`OpenAPI spec not found: ${e}`);if(!C.default.statSync(e).isFile())throw new Error(`OpenAPI spec path is not a file: ${e}`);r=C.default.readFileSync(e,"utf-8")}const s=function(e,t){let r;try{r=JSON.parse(e)}catch(e){throw new Error(`Spec from ${t} is not valid JSON: ${e.message}`)}if(!r||"object"!=typeof r||Array.isArray(r))throw new Error(`Spec from ${t} is not a JSON object`);if(!r.paths||"object"!=typeof r.paths)throw new Error(`Spec from ${t} does not contain a valid 'paths' object`);return r}(r,t);return async function(e){try{const t=await(0,R.dereference)(e);return t.errors&&t.errors.length>0?e:t.schema??t.specification??e}catch(t){return e}}(s)}function J(e,t){return e.map(e=>{try{const r=function(e,t){switch(e.assert){case"status":if(void 0===e.value)throw new Error("status assertion requires 'value'");return t.status===e.value;case"header":{if(!e.key)throw new Error("header assertion requires 'key'");const r=function(e,t){const r=t.toLowerCase();for(const[t,s]of Object.entries(e))if(t.toLowerCase()===r)return s;return}(t.headers??{},e.key);return void 0!==r&&(void 0===e.contains||r.toLowerCase().includes(e.contains.toLowerCase()))}case"bodyContains":if(void 0===e.contains)throw new Error("bodyContains assertion requires 'contains'");return(t.bodyText??JSON.stringify(t.body??"")).includes(e.contains);case"bodyEquals":if(void 0===e.body)throw new Error("bodyEquals assertion requires 'body'");return(t.bodyText??JSON.stringify(t.body??""))===e.body;case"jsonPath":{if(!e.path)throw new Error("jsonPath assertion requires 'path'");const r=_(t.body,e.path);return void 0!==e.exists?e.exists?void 0!==r:void 0===r:void 0!==e.equals?B(r,e.equals):void 0!==r}case"responseTime":if(void 0===e.max)throw new Error("responseTime assertion requires 'max'");return t.durationMs<=e.max;default:throw new Error(`Unknown assertion type: ${e.assert}`)}}(e,t);return{name:e.name,passed:r}}catch(t){return{name:e.name,passed:!1,error:t.message}}})}function _(e,t){const r=t.replace(/^\$\.?/,"");if(!r)return e;const s=r.split(/\.|\[(\d+)\]/).filter(e=>void 0!==e&&""!==e);let n=e;for(const e of s){if(null==n)return;n=/^\d+$/.test(e)?n[Number(e)]:n[e]}return n}function B(e,t){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(null===e||null===t)return e===t;if("object"!=typeof e)return e===t;if(Array.isArray(e)!==Array.isArray(t))return!1;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,r)=>B(e,t[r]));const r=Object.keys(e),s=Object.keys(t);return r.length===s.length&&r.every(r=>B(e[r],t[r]))}function H(e,t){const r=e?.["x-tests"],s=t?.["x-tests"];return[...Array.isArray(s)?s:[],...Array.isArray(r)?r:[]].filter(e=>e&&"object"==typeof e&&e.assert&&e.name)}function K(e){const t=e?.["x-postman-scripts"];if(t&&"object"==typeof t&&"string"==typeof t.test)return t.test}var G=u;function W(e){if(!e)return;if("string"==typeof e)return e;if(e instanceof Error)return e.message;const t=e;if("string"==typeof t.message)return t.message;if("string"==typeof t.msg)return t.msg;try{return JSON.stringify(e)}catch{return String(e)}}function Q(e,t){const r=[];if("http"===e||"sse"===e){const e=t?.status,s="number"==typeof e&&e>=200&&e<300;r.push({name:"HTTP status is 2xx (implicit)",passed:s,error:s?void 0:`expected 2xx, got ${e??"no status"}`})}else if("graphql"===e){const e=t?.status,s="number"==typeof e&&e>=200&&e<300,n=t?.body,o=Array.isArray(n?.errors)&&n.errors.length>0,a=s&&!o;let i;s?o&&(i=`GraphQL response contains ${n.errors.length} error(s)`):i=`expected HTTP 2xx, got ${e??"no status"}`,r.push({name:"GraphQL success (implicit)",passed:a,error:i})}else if("grpc"===e){const e=t?.statusCode??t?.code??t?.status,s=t?.statusName??t?.status,n=0===e;r.push({name:"gRPC status is OK (implicit)",passed:n,error:n?void 0:`expected code 0 (OK), got ${e??"unknown"}${s?` (${s})`:""}`})}else if("mcp"===e){const e=t?.body??{},s=void 0!==e.result,n=void 0!==e.error||!0===e.result?.isError||void 0!==t?.error,o=s&&!n;let a;n?a="MCP JSON-RPC response contains an error":s||(a="MCP JSON-RPC response missing 'result'"),r.push({name:"MCP JSON-RPC success (implicit)",passed:o,error:a})}else if("websocket"===e){const e=t?.events??[],s=e.some(e=>"open"===e.kind||"open"===e.state),n=e.some(e=>"error"===e.kind||"error"===e.state),o=s&&!n;r.push({name:"WebSocket connected (implicit)",passed:o,error:o?void 0:n?"WebSocket connection errored":"WebSocket did not reach open state"})}return r}async function V(e){T(e);const t=await D(e),r=(0,d.createClient)(),s=Y(t,e),n=Date.now(),o=[];let a=0;const i=Math.min(e.concurrency??5,s.length),c=[];for(let n=0;n<i;n++)c.push((async()=>{for(;a<s.length;){if(e.signal?.aborted)throw new Error("Run cancelled.");const n=s[a++];if(!n)break;const i=await ee(r,n,e,t);o.push(i)}})());await Promise.all(c);const p=Date.now()-n;return{summary:ne(o,p),results:o.sort((e,t)=>e.path.localeCompare(t.path)||e.method.localeCompare(t.method)),config:e,generatedAt:(new Date).toISOString(),version:G}}function Y(e,t){const r=[];for(const[s,n]of Object.entries(e.paths??{})){if(!n||"object"!=typeof n)continue;const e=n;for(const[n,o]of(0,l.operationEntries)(e)){if(!o||"object"!=typeof o)continue;const a={path:s,method:n,operation:o,pathItem:e,operationId:o.operationId??`${n.toUpperCase()} ${s}`,protocol:X(o,e)};Z(a,t.filter)&&r.push(a)}}return r}function X(e,t){const r=e["x-protocol"]??t?.["x-protocol"];if(r)return String(r).toLowerCase();const s=e.responses??{};for(const e of Object.values(s)){if((e?.content??{})["text/event-stream"])return"sse"}return e["x-response-stream"]?"sse":"http"}function Z(e,t){if(!t)return!0;if(t.methods?.length&&!t.methods.includes(e.method.toLowerCase()))return!1;if(t.operationIds?.length&&!t.operationIds.includes(e.operationId))return!1;if(t.tags?.length){const r=e.operation.tags??[];if(!t.tags.some(e=>r.includes(e)))return!1}if(t.paths?.length){if(!t.paths.some(t=>{try{return new RegExp(t).test(e.path)}catch{return e.path.includes(t)}}))return!1}return!0}async function ee(e,t,r,s){return(await te(e,t,{config:r,spec:s,variables:r.variables})).result}async function te(e,t,r){let{config:s}=r;const{spec:n}=r,o=Date.now(),a={operationId:t.operationId,path:t.path,method:t.method.toUpperCase(),protocol:t.protocol};try{if((r.signal||s.signal)?.aborted)throw new Error("Run cancelled.");const i=r.variables||s.variables,c=t.operation["x-websocket"]||t.operation["x-ws"]||t.operation["x-grpc"]||t.operation["x-mcp"]||{},p=$(r.serverUrl??s.serverUrl??c.url??c.address??c.endpoint??n.servers?.[0]?.url,i);s.resolveTransport&&(s={...s,...await s.resolveTransport({protocol:t.protocol,url:p})});const l=["grpc","websocket","mcp"].includes(t.protocol),d={...s,serverUrl:$(s.serverUrl,i),auth:l?$(s.auth,i):s.auth,headers:l?$({...s.headers,...r.values?.header},i):s.headers,signal:r.signal||s.signal},u={...t,operation:l?{...t.operation,...Object.fromEntries(Object.entries(t.operation).filter(([e])=>["x-grpc","x-mcp","x-ws","x-websocket"].includes(e)).map(([e,t])=>[e,$(t,i)]))}:t.operation},f=l?$(r.values,i):r.values;if(!["http","sse","graphql","grpc","websocket","mcp"].includes(t.protocol))throw new Error(`Unsupported protocol: ${t.protocol}`);const m=[...H(t.operation,t.pathItem),...r.extraAssertions??[]],h=K(t.operation),g=[];let b,v,C={};switch(t.protocol){case"grpc":{const t=await async function(e,t,r,s,n){const o=t.operation["x-grpc"]??{},a=(s??r.serverUrl??o.address)?.replace(/^https?:\/\//,"");if(!a)throw new Error("gRPC operation requires x-grpc.address or --server");const i={...r.headers};k(r,i);const c=y(r);if(c.pfx)throw new Error("gRPC requires PEM certificates, not PFX.");const p=e.connect({kind:"grpc",deadlineMs:r.timeout??3e4,streamDeadlineMs:r.maxStreamMs??r.timeout??3e4,...S(r,a),tls:!(!0!==o.tls&&!/^https:/.test(s??r.serverUrl??""))&&{rootCerts:c.ca,privateKey:E(c),certChain:c.cert,rejectUnauthorized:c.rejectUnauthorized},service:o.service,method:o.method??t.operationId,reflection:r.grpcReflection??o.reflection??!(r.grpcProtoPaths??o.protoPaths)?.length,protoPaths:r.grpcProtoPaths??o.protoPaths,includeDirs:o.includeDirs,metadata:i}),l=()=>{p.close().catch(()=>{})};r.signal?.addEventListener("abort",l,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await p.open(),r.signal?.aborted)throw new Error("Run cancelled.");const e=p.kind,s=n?.body??function(e){const t=e["x-grpc"]??{};if(t.sample&&"object"==typeof t.sample)return t.sample;const r=e.requestBody?.content?.["application/json"]?.schema;if(r?.properties){const e={};for(const[t,s]of Object.entries(r.properties))e[t]=s.default??se(s.type);return e}return{}}(t.operation);"unary"===e||"server_streaming"===e?await p.send(s):(await p.send(s),await p.finishSending());const c=p.events??[],l=c.find(e=>"status"===e.kind),d=c.filter(e=>"data"===e.kind&&"in"===e.direction);"unary"!==e&&"server_streaming"!==e||await p.close();const u={status:l?.meta?.code??-1,statusText:l?.meta?.statusName,body:1===d.length?d[0].data:d.map(e=>e.data),events:c.map(e=>({kind:e.kind,direction:e.direction,data:e.data,meta:e.meta}))},f=o.method??t.operationId;return{response:u,request:{method:f,target:o.service?`${o.service}/${f}`:f,url:a,...Object.keys(s).length?{body:s}:{},...Object.keys(i).length?{headers:i}:{}}}}finally{r.signal?.removeEventListener("abort",l),await p.close().catch(()=>{})}}(e,u,d,$(r.serverUrl,i),f);b=t.response,v=t.request;break}case"websocket":{const t=await async function(e,t,r,s,n){const o=t.operation["x-websocket"]??t.operation["x-ws"]??{};let a=re(o.url,s??r.serverUrl,t.path)?.replace(/^http/,"ws");if(!a)throw new Error("WebSocket operation requires x-ws.url or --server");const i={...r.headers};a=k(r,i,O(a,n?.query));const c=w(r),p=e.connect({kind:"websocket",openTimeoutMs:r.timeout??3e4,signal:r.signal,clientOptions:{...y(r),agent:a.startsWith("wss:")?c.https:c.http},url:a,subprotocols:o.subprotocols,headers:i}),l=()=>{p.close().catch(()=>{})};r.signal?.addEventListener("abort",l,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await p.open(),r.signal?.aborted)throw new Error("Run cancelled.");const e=void 0!==n?.body?[n.body]:o.send??[];for(const t of e.slice(0,3))await p.send(t);await new Promise(e=>setTimeout(e,Math.min(500,r.timeout??500))),await p.close();const t=p.events??[],s=t.filter(e=>"in"===e.direction&&"open"!==e.kind);return{response:{status:101,statusText:"Switching Protocols",body:s.map(e=>e.data),events:t.map(e=>({kind:e.kind,direction:e.direction,data:e.data})),streaming:!0},request:{method:"WS",url:a,...e.length?{body:e.slice(0,3)}:{},...Object.keys(i).length?{headers:i}:{}}}}finally{r.signal?.removeEventListener("abort",l),await p.close().catch(()=>{});for(const e of new Set(Object.values(c)))e.destroy()}}(e,u,d,$(r.serverUrl,i),f);b=t.response,v=t.request;break}case"mcp":{const t=await async function(e,t,r,s,n){const o=t.operation["x-mcp"]??{},a=r.mcpTransport??o.transport??"streamable-http",i={...r.headers};let c=re(o.endpoint,s??r.serverUrl,t.path);"stdio"!==a&&(x(r,"MCP HTTP"),c=k(r,i,c?O(c,n?.query):c));const p=n?.body?.method??o.method??"tools/call",l=n?.body?.params??o.params??("tools/call"===p?{name:o.name,arguments:n?.body??o.arguments??{}}:{}),d=e.connect({kind:"mcp",transport:"stdio"===a?"stdio":"streamable-http",endpoint:c,headers:i,command:r.mcpCommand??o.command,args:r.mcpArgs??o.args,cwd:r.mcpCwd??o.cwd,timeoutMs:r.timeout??3e4}),u=()=>{d.close().catch(()=>{})};r.signal?.addEventListener("abort",u,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await d.open(),r.signal?.aborted)throw new Error("Run cancelled.");return{response:{status:200,statusText:"OK",body:{result:await d.request(p,l,{signal:r.signal,timeoutMs:r.timeout})}},request:{method:p,url:c,headers:i,body:l}}}finally{r.signal?.removeEventListener("abort",u),await d.close().catch(()=>{})}}(e,u,d,$(r.serverUrl,i),f);b=t.response,v=t.request;break}default:{const o=await async function(e,t,r,s,n){"graphql"===t.protocol&&x(r,"GraphQL");const o="graphql"===t.protocol?void 0:w(r),a={...r.httpRuntime,requester:{...r.requester,strictSSL:!1!==r.tls?.strictSSL,ignoreProxyEnvironmentVariables:!0,...o?{agents:o,...r.proxy||r.tls?.caCert||r.tls?.clientCert||r.tls?.pfx||r.httpRuntime?.certificates?{protocolVersion:"http1"}:{}}:{}}},i=n.values,c={...r.headers??{},...i?.header??{}},p={...r.headers?{header:r.headers}:{},...i??{},...Object.keys(c).length?{header:c}:{}};let l;try{l=await e.send({writeBack:!1,signal:n.signal,spec:s,target:{path:t.path,method:t.method},serverUrl:n.serverUrl??r.serverUrl,values:Object.keys(p).length?p:void 0,auth:r.auth,variables:n.variables,timeout:r.timeout,runner:Object.keys(a).length?a:void 0,scripts:n.postmanTest?{test:{exec:n.postmanTest},fromSpecExtensions:!1}:void 0,onAssertion:e=>{n.postmanAssertions.push({name:e.name??"assertion",passed:e.passed,error:W(e.error)})},maxStreamMs:r.maxStreamMs??r.timeout,maxEvents:r.maxEvents??50,maxResponseSize:r.maxResponseSize})}finally{for(const e of new Set(Object.values(o||{})))e.destroy()}if(n.signal?.aborted)throw new Error("Run cancelled.");if(l.error)throw new Error(l.error.message);const d={},u=l.scripts;if(u)for(const e of[...u.prerequest??[],...u.test??[]])e.environment&&Object.assign(d,e.environment);const f=l.request;return{response:{status:l.response?.status,statusText:l.response?.statusText,contentType:l.response?.contentType,sizeBytes:l.response?.sizeBytes,body:l.response?.body,text:l.response?.text,headers:l.response?.headers,events:l.response?.events,streaming:l.response?.streaming},request:f?{method:f.method,url:f.url,headers:f.headers,...void 0!==f.body?{body:f.body}:{}}:void 0,env:d}}(e,t,s,n,{postmanTest:h,postmanAssertions:g,variables:r.variables,values:r.values,serverUrl:r.serverUrl,signal:r.signal||s.signal});b=o.response,v=o.request,C=o.env;break}}if((r.signal||s.signal)?.aborted)throw new Error("Run cancelled.");const q=Date.now()-o,j=m.length>0||g.length>0?[]:Q(t.protocol,b),P=m.length?J(m,{status:b?.status,headers:b?b.headers:void 0,body:b?.body,bodyText:b?b.text:void 0,durationMs:q}):[],R=new Set,A=g.filter(e=>{const t=`${e.name}|${e.passed}|${e.error??""}`;return!R.has(t)&&(R.add(t),!0)}),T=[...P,...A,...j],U=T.some(e=>!e.passed);return{result:{...a,status:U?"failed":"passed",durationMs:q,request:v,response:b,assertions:T.length?T:void 0,timestamp:(new Date).toISOString()},response:b,request:v,env:C}}catch(e){const n=Date.now()-o,i=r.values,c={...s.headers??{},...i?.header??{}},p=Object.fromEntries(Object.entries(c).map(([e,t])=>[e,null==t?"":String(t)])),l={method:t.method.toUpperCase(),url:r.serverUrl??s.serverUrl,...Object.keys(p).length?{headers:p}:{},...void 0!==i?.body?{body:i.body}:{}};return{result:{...a,status:"error",durationMs:n,request:l,error:e.message??String(e),timestamp:(new Date).toISOString()},response:void 0,request:l,env:{}}}}function re(e,t,r){if(!t)return e;const s=new URL(t),n=new URL(e||r,t);return n.host=s.host,n.protocol=s.protocol,e||"/"===s.pathname||(n.pathname=s.pathname.replace(/\/$/,"")+r),n.href}function se(e){switch(e){case"string":default:return"";case"number":case"integer":return 0;case"boolean":return!1;case"array":return[];case"object":return{}}}function ne(e,t){const r=e.length,s=e.filter(e=>"passed"===e.status).length;return{total:r,passed:s,failed:e.filter(e=>"failed"===e.status).length,errors:e.filter(e=>"error"===e.status).length,skipped:e.filter(e=>"skipped"===e.status).length,durationMs:t,passRate:r>0?Math.round(s/r*1e3)/10:0}}var oe=require("@powerduck/openapi-request"),ae=u,ie=1e3;function ce(e){if(e.method&&e.path)return`${e.method.toUpperCase()} ${e.path}`;const t=(e.ref??"").trim(),r=t.indexOf(" ");return r>0?`${t.slice(0,r).toUpperCase()} ${t.slice(r+1).trim()}`:t}function pe(e,t){const r=new Map,s=new Map;for(const e of t)r.set(`${e.method.toUpperCase()} ${e.path}`,e),s.set(e.operationId,e);const n=[],o=[];if(e.steps.forEach((e,t)=>{const a=ce(e),i=r.get(a)??(a?s.get(a):void 0);i?n.push({step:e,op:i,ref:`${i.method.toUpperCase()} ${i.path}`}):o.push(`step ${t} ("${e.name??a}") -> ${a||"<empty ref>"}`)}),o.length)throw new Error(`Scenario references operations not present in the spec: ${o.join("; ")}`);return n}function le(e,t){if(!e)return;const r=t.toLowerCase();for(const[t,s]of Object.entries(e))if(t.toLowerCase()===r)return s}function de(e){if(null==e)return"";if("string"==typeof e)return e;if("number"==typeof e||"boolean"==typeof e)return String(e);try{return JSON.stringify(e)}catch{return String(e)}}function ue(e,t){const r={};if(!e?.length)return r;for(const s of e){let e;e="status"===s.from?t?.status:"header"===s.from?s.key?le(t?.headers,s.key):void 0:_(t?.body,s.path??"$"),null!=e&&(r[s.name]=de(e))}return r}function fe(e,t,r,s,n){return{index:e,ref:t,name:r.name,operationId:t,path:t.includes(" ")?t.slice(t.indexOf(" ")+1):t,method:t.includes(" ")?t.slice(0,t.indexOf(" ")):"",protocol:"",status:"skipped",durationMs:0,timestamp:(new Date).toISOString(),notRun:!0,notRunReason:s,...n??{}}}async function me(e,t){const{config:r,onEvent:s,signal:n}=t;T(r);const o=Date.now(),a=new Date(o).toISOString(),i=e=>{try{s?.(e)}catch{}},c=t.spec??await D(r),p=pe(e,Y(c,{...r,filter:void 0})),l=(0,oe.createClient)(),d=e.data&&e.data.length?e.data:[],u=Math.trunc(Number(e.iterations)),f=Number.isFinite(u)&&u>0?Math.min(u,ie):1,m=Math.max(f,d.length),h=Array.from({length:m},(e,t)=>d[t]),g=p.map(({step:e})=>e.request?.data&&e.request.data.length?e.request.data.length:1),b={scenarioCount:h.length,stepCounts:g},v=!1!==e.stopOnFailure,y=[];let w="passed",x=!1,$=!1,k=null,S="failed",E={...r.variables??{},...e.variables??{}};i({type:"scenario:start",scenario:e.name,status:w,scenarioIterations:h.length});e:for(let t=0;t<h.length;t++){E={...r.variables??{},...e.variables??{},...h[t]??{}};for(let s=0;s<p.length;s++){const{step:o,op:a,ref:d}=p[s],u=o.request?.data&&o.request.data.length?o.request.data:[void 0],f={scenarioIteration:t,scenarioIterations:h.length,stepIterations:u.length};if(n?.aborted){w="cancelled",$=!0,x=!0,k={s:t,i:s,t:0};break e}if(o.skip)for(let t=0;t<u.length;t++){const r=fe(s,d,o,"Skipped by plan",{...f,stepIteration:t});y.push(r),i({type:"step:skip",scenario:e.name,stepIndex:s,step:r,...f,stepIteration:t})}else for(let p=0;p<u.length;p++){if(n?.aborted){w="cancelled",$=!0,x=!0,k={s:t,i:s,t:p};break e}i({type:"step:start",scenario:e.name,stepIndex:s,...f,stepIteration:p});const m={...E,...u[p]??{}},h=await te(l,a,{config:r,spec:c,signal:n,variables:m,values:o.request?.values,serverUrl:o.request?.serverUrl,extraAssertions:o.request?.assertions});n?.aborted&&($=!0);const g={};for(const[e,t]of Object.entries(h.env))m[e]!==t&&(g[e]=t);const b=ue(o.request?.extract,h.response),O={...g,...b},C=p===u.length-1;C&&Object.assign(E,h.env,b);const q={...h.result,index:s,ref:d,name:o.name,...f,stepIteration:p,extracted:Object.keys(O).length?O:void 0,variablesAfter:C?{...E}:{...E,...u[p]??{},...O}};if(y.push(q),i({type:"step:finish",scenario:e.name,stepIndex:s,step:q,...f,stepIteration:p}),"error"===q.status?w="error":"failed"===q.status&&"error"!==w&&(w="failed"),("failed"===q.status||"error"===q.status)&&v){x=!0,S=q.status,k={s:t,i:s,t:p};break e}}}}if(x&&k){const t=(e,t)=>$?"Scenario cancelled":e===k.s&&t===k.i?`Stopped after ${S} iteration ${k.t+1}`:e===k.s?`Stopped after ${S} step ${k.i+1}`:`Stopped after ${S} run ${k.s+1}`;for(let r=k.s;r<h.length;r++)for(let s=r===k.s?k.i:0;s<p.length;s++){const n=p[s],o=n.step.request?.data&&n.step.request.data.length?n.step.request.data.length:1;for(let a=r===k.s&&s===k.i?k.t+1:0;a<o;a++){const c={scenarioIteration:r,scenarioIterations:h.length,stepIteration:a,stepIterations:o},p=fe(s,n.ref,n.step,t(r,s),c);y.push(p),i({type:"step:skip",scenario:e.name,stepIndex:s,step:p,...c})}}}($||n?.aborted&&"error"!==w&&"failed"!==w)&&(w="cancelled");const O=Date.now()-o,C="cancelled"===w?"scenario:cancel":"scenario:finish",q={name:e.name,description:e.description,status:w,summary:ne(y,O),steps:y,variables:E,iterations:b,config:r,startedAt:a,durationMs:O,generatedAt:(new Date).toISOString(),version:ae};return i({type:C,scenario:e.name,status:w,message:x?"halted":void 0}),q}var he=c(require("fs"),1),ge=c(require("path"),1);function be(e,t){const r=ge.default.resolve(t);he.default.mkdirSync(r,{recursive:!0});const s=ge.default.join(r,"report.json");return he.default.writeFileSync(s,JSON.stringify(e,null,2),"utf-8"),s}var ve={reset:"",bold:"",dim:"",green:"",red:"",yellow:"",blue:"",cyan:"",gray:"",bgGreen:"",bgRed:"",bgYellow:""};function ye(e){if(null==e||!Number.isFinite(e))return`${ve.dim}--ms${ve.reset}`;return`${e>500?`${ve.bold}${ve.yellow}`:ve.bold}${e}${ve.reset}${ve.dim}ms${ve.reset}`}function we(e){const t=[],{summary:r,results:s}=e;t.push(""),t.push(`${ve.bold}${ve.cyan}╔══════════════════════════════════════════════════════╗${ve.reset}`),t.push(`${ve.bold}${ve.cyan}║ Powerduck Test Report ║${ve.reset}`),t.push(`${ve.bold}${ve.cyan}╚══════════════════════════════════════════════════════╝${ve.reset}`),t.push("");const n=r.passRate>=80?ve.green:r.passRate>=50?ve.yellow:ve.red;t.push(`${ve.bold} Total:${ve.reset} ${r.total} ${ve.green}Passed:${ve.reset} ${r.passed} ${ve.red}Failed:${ve.reset} ${r.failed} ${ve.yellow}Errors:${ve.reset} ${r.errors} ${n}Rate:${ve.reset} ${r.passRate}% ${ve.dim}Time:${ve.reset} ${ye(r.durationMs)}`),t.push("");const o=r.total>0?Math.round(r.passed/r.total*50):0,a=r.total>0?Math.round(r.failed/r.total*50):0,i=r.total>0?Math.round(r.errors/r.total*50):0,c=ve.green+"█".repeat(o)+ve.red+"█".repeat(a)+ve.yellow+"█".repeat(i)+ve.gray+"░".repeat(Math.max(0,50-o-a-i))+ve.reset;t.push(` [${c}]`),t.push("");for(const e of s)t.push(xe(e));return t.push(""),r.failed>0||r.errors>0?t.push(`${ve.bold}${ve.red} ✗ ${r.failed+r.errors} test(s) did not pass${ve.reset}`):t.push(`${ve.bold}${ve.green} ✓ All ${r.total} test(s) passed${ve.reset}`),t.push(` ${ve.dim}Generated: ${e.generatedAt}${ve.reset}`),t.push(""),t.join("\n")}function xe(e){const t="passed"===e.status?`${ve.green}✓${ve.reset}`:"failed"===e.status?`${ve.red}✗${ve.reset}`:"error"===e.status?`${ve.yellow}!${ve.reset}`:`${ve.gray}-${ve.reset}`,r="passed"===e.status?`${ve.bgGreen}${ve.bold} PASS ${ve.reset}`:"failed"===e.status?`${ve.bgRed}${ve.bold} FAIL ${ve.reset}`:"error"===e.status?`${ve.bgYellow}${ve.bold} ERROR ${ve.reset}`:`${ve.gray} SKIP ${ve.reset}`,s=e.protocol?` ${ve.cyan}[${e.protocol}]${ve.reset}`:"",n=null!=e.response?.status?` ${ve.dim}${e.response.status}${ve.reset}`:"";let o=` ${t} ${r} ${ve.bold}${e.method}${ve.reset} ${e.path}${s}${n} ${ye(e.durationMs)}`;if(e.error&&(o+=`\n ${ve.red}${e.error}${ve.reset}`),e.assertions?.length)for(const t of e.assertions){o+=`\n ${t.passed?`${ve.green} ✓${ve.reset}`:`${ve.red} ✗${ve.reset}`} ${t.name}${t.error?` — ${ve.red}${t.error}${ve.reset}`:""}`}return o}function $e(e){const t=we(e);return console.log(t),t}var ke=c(require("fs"),1),Se=c(require("path"),1);function Ee(e,t){const r=Se.default.resolve(t);ke.default.mkdirSync(r,{recursive:!0});const s=Se.default.join(r,"report.html");return ke.default.writeFileSync(s,function(e){const{summary:t,results:r}=e,s=JSON.stringify({summary:t,results:r});return`<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>Test Report — Powerduck</title>\n<style>\n *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n :root{\n --bg:#ffffff;--fg:#0f172a;--muted:#64748b;--border:#e2e8f0;\n --bg-subtle:#f8fafc;--bg-hover:#f1f5f9;\n --green:#16a34a;--green-bg:#f0fdf4;--green-border:#bbf7d0;\n --red:#dc2626;--red-bg:#fef2f2;--red-border:#fecaca;\n --amber:#d97706;--amber-bg:#fffbeb;--amber-border:#fde68a;\n --blue:#2563eb;--mono:ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,monospace;\n --sans:ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Inter,sans-serif;\n }\n @media (prefers-color-scheme:dark){\n :root{\n --bg:#0f172a;--fg:#f1f5f9;--muted:#94a3b8;--border:#1e293b;\n --bg-subtle:#1e293b;--bg-hover:#334155;\n --green:#4ade80;--green-bg:rgba(74,222,128,.1);--green-border:rgba(74,222,128,.25);\n --red:#f87171;--red-bg:rgba(248,113,113,.1);--red-border:rgba(248,113,113,.25);\n --amber:#fbbf24;--amber-bg:rgba(251,191,36,.1);--amber-border:rgba(251,191,36,.25);\n --blue:#60a5fa;\n }\n }\n body{font-family:var(--sans);background:var(--bg);color:var(--fg);line-height:1.5;font-size:14px;-webkit-font-smoothing:antialiased}\n .wrap{max-width:960px;margin:0 auto;padding:32px 24px 64px}\n\n /* Header */\n .hdr{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--border)}\n .hdr h1{font-size:18px;font-weight:600;letter-spacing:-.01em}\n .hdr .meta{font-size:12px;color:var(--muted);font-family:var(--mono)}\n\n /* Summary strip */\n .strip{display:grid;grid-template-columns:repeat(6,1fr);gap:0;border:1px solid var(--border);border-radius:8px;overflow:hidden;margin-bottom:24px}\n .stat{padding:14px 16px;border-right:1px solid var(--border);text-align:center}\n .stat:last-child{border-right:none}\n .stat .v{font-size:22px;font-weight:700;letter-spacing:-.02em;line-height:1.2}\n .stat .l{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-top:2px}\n .stat.total .v{color:var(--fg)}\n .stat.pass .v{color:var(--green)}\n .stat.fail .v{color:var(--red)}\n .stat.err .v{color:var(--amber)}\n .stat.rate .v{color:var(--blue)}\n .stat.time .v{font-size:16px;font-weight:600}\n\n /* Progress */\n .bar{height:4px;background:var(--border);border-radius:2px;overflow:hidden;margin-bottom:24px;display:flex}\n .bar>div{height:100%}\n .bar .p{background:var(--green)}\n .bar .f{background:var(--red)}\n .bar .e{background:var(--amber)}\n\n /* Filters */\n .filters{display:flex;gap:4px;margin-bottom:16px}\n .fbtn{padding:4px 12px;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--muted);cursor:pointer;font-size:12px;font-weight:500;transition:all .12s}\n .fbtn:hover{color:var(--fg);background:var(--bg-hover)}\n .fbtn.active{background:var(--bg-subtle);color:var(--fg);border-color:var(--border)}\n .fbtn .n{color:var(--muted);margin-left:4px;font-variant-numeric:tabular-nums}\n\n /* Test rows */\n .row{border:1px solid var(--border);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .12s}\n .row:hover{border-color:var(--muted)}\n .row-head{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;user-select:none}\n .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n .dot.pass{background:var(--green)}\n .dot.fail{background:var(--red)}\n .dot.err{background:var(--amber)}\n .method{font-family:var(--mono);font-size:11px;font-weight:600;color:var(--muted);min-width:40px;text-transform:uppercase}\n .path{font-family:var(--mono);font-size:13px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n .proto{font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);padding:2px 8px;border:1px solid var(--border);border-radius:4px}\n .dur{font-family:var(--mono);font-size:12px;color:var(--muted);min-width:56px;text-align:right;font-variant-numeric:tabular-nums}\n .chev{color:var(--muted);font-size:10px;transition:transform .15s;flex-shrink:0}\n .row.open .chev{transform:rotate(90deg)}\n\n /* Row body */\n .row-body{display:none;padding:0 14px 12px 32px;border-top:1px solid var(--border)}\n .row.open .row-body{display:block}\n .sect{margin-top:10px}\n .sect-t{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:600;margin-bottom:6px}\n .asrt{display:flex;align-items:center;gap:8px;padding:3px 0;font-size:13px}\n .asrt .ck{width:14px;height:14px;border-radius:3px;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;flex-shrink:0;color:#fff}\n .asrt.pass .ck{background:var(--green)}\n .asrt.fail .ck{background:var(--red)}\n .asrt .an{color:var(--fg)}\n .asrt .ae{color:var(--red);font-size:12px;margin-left:6px;font-family:var(--mono)}\n .rmeta{display:flex;gap:20px;flex-wrap:wrap;font-size:12px}\n .rmeta span{color:var(--muted)}\n .rmeta strong{color:var(--fg);font-weight:600;font-family:var(--mono)}\n .errbox{background:var(--red-bg);border:1px solid var(--red-border);border-radius:6px;padding:8px 12px;color:var(--red);font-size:12px;font-family:var(--mono);margin-top:10px}\n .bprev{background:var(--bg-subtle);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-family:var(--mono);font-size:11px;color:var(--muted);max-height:180px;overflow:auto;white-space:pre-wrap;word-break:break-all;margin-top:6px}\n\n footer{margin-top:32px;padding-top:16px;border-top:1px solid var(--border);font-size:11px;color:var(--muted);display:flex;justify-content:space-between}\n\n @media(max-width:640px){\n .strip{grid-template-columns:repeat(3,1fr)}\n .stat:nth-child(3){border-right:none}\n .stat:nth-child(4),.stat:nth-child(5){border-top:1px solid var(--border)}\n .path{font-size:11px}\n }\n</style>\n</head>\n<body>\n<div class="wrap">\n <div class="hdr">\n <h1>Test Report</h1>\n <span class="meta">${e.generatedAt} · v${e.version}</span>\n </div>\n\n <div class="strip">\n <div class="stat total"><div class="v">${t.total}</div><div class="l">Total</div></div>\n <div class="stat pass"><div class="v">${t.passed}</div><div class="l">Passed</div></div>\n <div class="stat fail"><div class="v">${t.failed}</div><div class="l">Failed</div></div>\n <div class="stat err"><div class="v">${t.errors}</div><div class="l">Errors</div></div>\n <div class="stat rate"><div class="v">${t.passRate}%</div><div class="l">Pass Rate</div></div>\n <div class="stat time"><div class="v">${(t.durationMs/1e3).toFixed(2)}s</div><div class="l">Duration</div></div>\n </div>\n\n <div class="bar">\n <div class="p" style="width:${t.total?t.passed/t.total*100:0}%"></div>\n <div class="f" style="width:${t.total?t.failed/t.total*100:0}%"></div>\n <div class="e" style="width:${t.total?t.errors/t.total*100:0}%"></div>\n </div>\n\n <div class="filters">\n <button class="fbtn active" data-f="all">All<span class="n">${t.total}</span></button>\n <button class="fbtn" data-f="passed">Passed<span class="n">${t.passed}</span></button>\n <button class="fbtn" data-f="failed">Failed<span class="n">${t.failed}</span></button>\n <button class="fbtn" data-f="error">Errors<span class="n">${t.errors}</span></button>\n </div>\n\n <div id="list"></div>\n\n <footer>\n <span>@powerduck/openapi-cli v${e.version}</span>\n <span>${t.passed}/${t.total} passed</span>\n </footer>\n</div>\n\n<script>\nconst D=${s};\nfunction esc(s){const d=document.createElement('div');d.textContent=String(s??'');return d.innerHTML}\nfunction render(f){\n const list=document.getElementById('list');\n const items=f==='all'?D.results:D.results.filter(r=>r.status===f);\n list.innerHTML=items.map(r=>{\n const as=r.assertions?r.assertions.map(a=>\`\n <div class="asrt \${a.passed?'pass':'fail'}">\n <div class="ck">\${a.passed?'✓':'✗'}</div>\n <span class="an">\${esc(a.name)}</span>\n \${a.error?'<span class="ae">'+esc(a.error)+'</span>':''}\n </div>\`).join(''):'';\n const bp=r.response?.body?'<div class="bprev">'+esc(JSON.stringify(r.response.body,null,2).slice(0,3000))+'</div>':'';\n const eb=r.error?'<div class="errbox">'+esc(r.error)+'</div>':'';\n const rm=r.response?\`\n <div class="rmeta">\n \${r.response.status!=null?'<span>Status <strong>'+r.response.status+(r.response.statusText?' '+r.response.statusText:'')+'</strong></span>':''}\n \${r.response.contentType?'<span>Type <strong>'+esc(r.response.contentType)+'</strong></span>':''}\n \${r.response.sizeBytes!=null?'<span>Size <strong>'+r.response.sizeBytes+'B</strong></span>':''}\n \${r.response.streaming?'<span>Streaming <strong>yes</strong></span>':''}\n </div>\`:'';\n return \`\n <div class="row \${r.status}" data-s="\${r.status}">\n <div class="row-head" onclick="this.parentElement.classList.toggle('open')">\n <div class="dot \${r.status==='passed'?'pass':r.status==='failed'?'fail':'err'}"></div>\n <span class="method">\${r.method}</span>\n <span class="path">\${esc(r.path)}</span>\n \${r.protocol?'<span class="proto">'+r.protocol+'</span>':''}\n <span class="dur">\${r.durationMs}ms</span>\n <span class="chev">▶</span>\n </div>\n <div class="row-body">\n \${eb}\n \${rm}\n \${as?'<div class="sect"><div class="sect-t">Assertions</div>'+as+'</div>':''}\n \${bp?'<div class="sect"><div class="sect-t">Response</div>'+bp+'</div>':''}\n </div>\n </div>\`;\n }).join('');\n}\ndocument.querySelectorAll('.fbtn').forEach(b=>b.addEventListener('click',()=>{\n document.querySelectorAll('.fbtn').forEach(x=>x.classList.remove('active'));\n b.classList.add('active');render(b.dataset.f);\n}));\nrender('all');\n<\/script>\n</body>\n</html>`}(e),"utf-8"),s}
1
+ "use strict";var e,t=Object.create,r=Object.defineProperty,s=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,o=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,i=(e,t,o,i)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of n(t))a.call(e,c)||c===o||r(e,c,{get:()=>t[c],enumerable:!(i=s(t,c))||i.enumerable});return e},c=(e,s,n)=>(n=null!=e?t(o(e)):{},i(!s&&e&&e.__esModule?n:r(n,"default",{value:e,enumerable:!0}),e)),p={};((e,t)=>{for(var s in t)r(e,s,{get:t[s],enumerable:!0})})(p,{DEFAULT_CONFIG:()=>A,applyExtracts:()=>ue,buildImplicitAssertions:()=>Q,buildSummary:()=>ne,collectOperations:()=>Y,executeStep:()=>te,extractAssertions:()=>H,extractPostmanScripts:()=>K,formatAssertionError:()=>W,generateCliReport:()=>we,generateHtmlReport:()=>Ee,generateJsonReport:()=>be,isRemoteSpec:()=>F,loadSpec:()=>D,printCliReport:()=>$e,resolveConfig:()=>U,resolveJsonPath:()=>_,resolveSteps:()=>pe,runDeclarativeAssertions:()=>J,runScenario:()=>me,runTests:()=>V,stepRef:()=>ce}),module.exports=(e=p,i(r({},"__esModule",{value:!0}),e));var l=require("@powerduck/openapi-parser/methods"),d=require("@powerduck/openapi-request"),u="0.2.16",f=c(require("fs"),1),m=require("crypto"),h=c(require("http"),1),g=c(require("https"),1),b=c(require("https-proxy-agent"),1),{HttpsProxyAgent:v}=b.default;function y(e){const t=e.tls;return{...t?.caCert?{ca:f.default.readFileSync(t.caCert)}:{},...t?.clientCert?{cert:f.default.readFileSync(t.clientCert)}:{},...t?.clientKey?{key:f.default.readFileSync(t.clientKey)}:{},...t?.pfx?{pfx:f.default.readFileSync(t.pfx)}:{},...t?.passphrase?{passphrase:t.passphrase}:{},rejectUnauthorized:!1!==t?.strictSSL}}function w(e){const t=y(e);if(e.proxy){const r=new URL(e.proxy);if(!["http:","https:"].includes(r.protocol))throw new Error("Only HTTP(S) CONNECT proxies are supported.");class s extends v{callback(e,r){return super.callback(e,{...r,...t})}}const n=new s({protocol:r.protocol,host:r.hostname,port:Number(r.port||("https:"===r.protocol?443:80)),...r.username||r.password?{auth:`${decodeURIComponent(r.username)}:${decodeURIComponent(r.password)}`}:{},ca:t.ca,rejectUnauthorized:t.rejectUnauthorized});return{http:n,https:n}}return{http:new h.default.Agent,https:new g.default.Agent(t)}}function x(e,t){if(e.proxy||Object.entries(e.tls||{}).some(([e,t])=>"strictSSL"===e?!1===t:Boolean(t)))throw new Error(`${t} custom proxy/TLS is not supported by the bundled request engine.`)}function $(e,t={}){return"string"==typeof e?e.replace(/\{\{([^{}]+)\}\}/g,(e,r)=>{if(!Object.prototype.hasOwnProperty.call(t,r))throw new Error(`Variable ${r} is not defined.`);return t[r]}):Array.isArray(e)?e.map(e=>$(e,t)):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,r])=>[e,$(r,t)])):e}function k(e,t,r){const s=e.auth,n=(e,r)=>{for(const r of Object.keys(t))r.toLowerCase()===e.toLowerCase()&&delete t[r];t[e]=r};if(!s||"none"===s.type)return r;if("basic"===s.type)n("Authorization",`Basic ${Buffer.from(`${s.username||""}:${s.password||""}`).toString("base64")}`);else if("bearer"===s.type)n("Authorization",`Bearer ${s.token||""}`);else{if("apikey"!==s.type)throw new Error(`Authentication ${s.type} is supported only by the HTTP/SSE runner.`);if(!s.key)throw new Error("API key name is required.");if("query"===s.in){if(!r)throw new Error("gRPC API keys require metadata headers.");const e=new URL(r);e.searchParams.set(s.key,s.value||""),r=e.href}else n(s.key,s.value||"")}return r}function S(e,t){const r={"grpc.enable_http_proxy":0};if(!e.proxy)return{address:t,channelOptions:r};const s=new URL(e.proxy);if("http:"!==s.protocol)throw new Error("gRPC supports only HTTP CONNECT proxies.");const n=new URL(`https://${t}`);return r["grpc.http_connect_target"]=`dns:///${t}`,r["grpc.default_authority"]=n.host,r["grpc.ssl_target_name_override"]=n.hostname,(s.username||s.password)&&(r["grpc.http_connect_creds"]=`${decodeURIComponent(s.username)}:${decodeURIComponent(s.password)}`),{address:`${s.hostname}:${s.port||80}`,channelOptions:r}}function E(e){return e.key&&e.passphrase?Buffer.from((0,m.createPrivateKey)({key:e.key,passphrase:e.passphrase}).export({format:"pem",type:"pkcs8"})):e.key}function O(e,t){const r=new URL(e);for(const[e,s]of Object.entries(t||{}))if(null!=s){r.searchParams.delete(e);for(const t of Array.isArray(s)?s:[s])r.searchParams.append(e,String(t))}return r.href}var C=c(require("fs"),1),q=c(require("path"),1),j=c(require("http"),1),P=c(require("https"),1),R=require("@powerduck/openapi-parser"),A={outputDir:"./openapi-cli-report",formats:["json","cli","html"],concurrency:5,timeout:3e4,failOnError:!0,grpcReflection:!0,mcpTransport:"streamable-http"};function T(e){for(const[t,r,s]of[["concurrency",e.concurrency??5,100],["timeout",e.timeout??3e4,6e5]])if(!Number.isInteger(r)||r<1||r>s)throw new Error(`${t} must be an integer between 1 and ${s}.`);if(e.formats?.some(e=>!["json","cli","html"].includes(e)))throw new Error("Unsupported report format.");if(e.mcpTransport&&!["stdio","streamable-http"].includes(e.mcpTransport))throw new Error("Unsupported MCP transport.")}function U(e){const t=e.config?function(e){const t=q.default.resolve(e);if(!C.default.existsSync(t))throw new Error(`Config file not found: ${t}`);const r=C.default.readFileSync(t,"utf-8");try{const e=JSON.parse(r);if(!e||"object"!=typeof e||Array.isArray(e))throw new Error("Config must be an object.");return e}catch(e){throw new Error(`Invalid config file (must be JSON): ${e.message}`)}}(e.config):{},r={specPath:e.spec??t.specPath??"",serverUrl:e.server??t.serverUrl,outputDir:e.output??t.outputDir??A.outputDir,formats:I(e.format??t.formats),filter:L(e,t),concurrency:e.concurrency??t.concurrency??A.concurrency,timeout:e.timeout??t.timeout??A.timeout,proxy:e.proxy??t.proxy,tls:{caCert:e.ca??t.tls?.caCert,clientCert:e.cert??t.tls?.clientCert,clientKey:e.key??t.tls?.clientKey,pfx:t.tls?.pfx,passphrase:t.tls?.passphrase,strictSSL:void 0!==e.insecure?!e.insecure:t.tls?.strictSSL},requester:t.requester,maxEvents:t.maxEvents,maxStreamMs:t.maxStreamMs,maxResponseSize:t.maxResponseSize,headers:M(e.header,t.headers),auth:z(e,t),variables:N(e.variable,t.variables),failOnError:e.failOnError??t.failOnError??A.failOnError,envFile:e.env??t.envFile,grpcReflection:e.grpcReflection??t.grpcReflection,grpcProtoPaths:e.grpcProto??t.grpcProtoPaths,mcpTransport:e.mcpTransport??t.mcpTransport,mcpCommand:e.mcpCommand??t.mcpCommand,mcpArgs:e.mcpArgs?e.mcpArgs.split(/[\s,]+/).filter(Boolean):t.mcpArgs,mcpCwd:e.mcpCwd??t.mcpCwd};if(r.envFile&&function(e){const t=q.default.resolve(e);if(!C.default.existsSync(t))return;const r=C.default.readFileSync(t,"utf-8").split("\n");for(const e of r){const t=e.trim();if(!t||t.startsWith("#"))continue;const r=t.indexOf("=");if(r>0){const e=t.slice(0,r).trim(),s=t.slice(r+1).trim().replace(/^["']|["']$/g,"");e in process.env||(process.env[e]=s)}}}(r.envFile),function(e){const t=e=>"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,(e,t)=>process.env[t]??"");if(e.serverUrl=t(e.serverUrl),e.proxy=t(e.proxy),e.headers)for(const[r,s]of Object.entries(e.headers))e.headers[r]=t(s);e.auth?.token&&(e.auth.token=t(e.auth.token));if(e.variables)for(const[r,s]of Object.entries(e.variables))e.variables[r]=t(s)}(r),!r.specPath)throw new Error("No OpenAPI spec path provided. Use --spec <path> or set specPath in config.");if(r.proxy)try{new URL(r.proxy)}catch{throw new Error(`Invalid proxy URL: ${r.proxy}`)}return T(r),r}function I(e){return Array.isArray(e)?e:"string"==typeof e?e.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):A.formats}function L(e,t){const r=e.method?e.method.split(",").map(e=>e.trim().toLowerCase()):t.filter?.methods,s=e.path?e.path.split(",").map(e=>e.trim()):t.filter?.paths,n=e.tag?e.tag.split(",").map(e=>e.trim()):t.filter?.tags,o=e.operationId?e.operationId.split(",").map(e=>e.trim()):t.filter?.operationIds;if(r||s||n||o)return{methods:r,paths:s,tags:n,operationIds:o}}function M(e,t){const r={...t??{}};for(const t of e??[]){const e=t.indexOf(":");if(e>0){const s=t.slice(0,e).trim(),n=t.slice(e+1).trim();s&&(r[s]=n)}}return Object.keys(r).length?r:void 0}function z(e,t){return e.bearer?{type:"bearer",token:e.bearer}:t.auth}function N(e,t){const r={...t??{}};for(const t of e??[]){const e=t.indexOf("=");e>0&&(r[t.slice(0,e).trim()]=t.slice(e+1).trim())}return Object.keys(r).length?r:void 0}function F(e){return/^https?:\/\//i.test(e.trim())}async function D(e){const t=e.specPath.trim();let r;if(F(t))r=await function(e,t){let r=0;return new Promise((s,n)=>{const o=a=>{const i=("https:"===new URL(a).protocol?P.default:j.default).get(a,{timeout:t,headers:{Accept:"application/json, text/plain, */*","User-Agent":"@powerduck/openapi-cli"}},t=>{if(t.statusCode&&t.statusCode>=300&&t.statusCode<400&&t.headers.location){if(++r>5)return t.resume(),void n(new Error(`Too many redirects (>5) while fetching spec: ${e}`));const s=new URL(t.headers.location,a).toString();return t.resume(),void o(s)}if(200!==t.statusCode)return t.resume(),void n(new Error(`Failed to fetch spec from ${a}: HTTP ${t.statusCode} ${t.statusMessage??""}`.trim()));const i=[];t.on("data",e=>i.push(e)),t.on("end",()=>s(Buffer.concat(i).toString("utf-8"))),t.on("error",e=>n(new Error(`Error reading spec response: ${e.message}`)))});i.on("timeout",()=>{i.destroy(new Error(`Timed out after ${t}ms while fetching spec: ${e}`))}),i.on("error",t=>n(new Error(`Network error fetching spec ${e}: ${t.message}`)))};o(e)})}(t,e.timeout??A.timeout);else{const e=q.default.resolve(t);if(!C.default.existsSync(e))throw new Error(`OpenAPI spec not found: ${e}`);if(!C.default.statSync(e).isFile())throw new Error(`OpenAPI spec path is not a file: ${e}`);r=C.default.readFileSync(e,"utf-8")}const s=function(e,t){let r;try{r=JSON.parse(e)}catch(e){throw new Error(`Spec from ${t} is not valid JSON: ${e.message}`)}if(!r||"object"!=typeof r||Array.isArray(r))throw new Error(`Spec from ${t} is not a JSON object`);if(!r.paths||"object"!=typeof r.paths)throw new Error(`Spec from ${t} does not contain a valid 'paths' object`);return r}(r,t);return async function(e){try{const t=await(0,R.dereference)(e);return t.errors&&t.errors.length>0?e:t.schema??t.specification??e}catch(t){return e}}(s)}function J(e,t){return e.map(e=>{try{const r=function(e,t){switch(e.assert){case"status":if(void 0===e.value)throw new Error("status assertion requires 'value'");return t.status===e.value;case"header":{if(!e.key)throw new Error("header assertion requires 'key'");const r=function(e,t){const r=t.toLowerCase();for(const[t,s]of Object.entries(e))if(t.toLowerCase()===r)return s;return}(t.headers??{},e.key);return void 0!==r&&(void 0===e.contains||r.toLowerCase().includes(e.contains.toLowerCase()))}case"bodyContains":if(void 0===e.contains)throw new Error("bodyContains assertion requires 'contains'");return(t.bodyText??JSON.stringify(t.body??"")).includes(e.contains);case"bodyEquals":if(void 0===e.body)throw new Error("bodyEquals assertion requires 'body'");return(t.bodyText??JSON.stringify(t.body??""))===e.body;case"jsonPath":{if(!e.path)throw new Error("jsonPath assertion requires 'path'");const r=_(t.body,e.path);return void 0!==e.exists?e.exists?void 0!==r:void 0===r:void 0!==e.equals?B(r,e.equals):void 0!==r}case"responseTime":if(void 0===e.max)throw new Error("responseTime assertion requires 'max'");return t.durationMs<=e.max;default:throw new Error(`Unknown assertion type: ${e.assert}`)}}(e,t);return{name:e.name,passed:r}}catch(t){return{name:e.name,passed:!1,error:t.message}}})}function _(e,t){const r=t.replace(/^\$\.?/,"");if(!r)return e;const s=r.split(/\.|\[(\d+)\]/).filter(e=>void 0!==e&&""!==e);let n=e;for(const e of s){if(null==n)return;n=/^\d+$/.test(e)?n[Number(e)]:n[e]}return n}function B(e,t){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(null===e||null===t)return e===t;if("object"!=typeof e)return e===t;if(Array.isArray(e)!==Array.isArray(t))return!1;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,r)=>B(e,t[r]));const r=Object.keys(e),s=Object.keys(t);return r.length===s.length&&r.every(r=>B(e[r],t[r]))}function H(e,t){const r=e?.["x-tests"],s=t?.["x-tests"];return[...Array.isArray(s)?s:[],...Array.isArray(r)?r:[]].filter(e=>e&&"object"==typeof e&&e.assert&&e.name)}function K(e){const t=e?.["x-postman-scripts"];if(t&&"object"==typeof t&&"string"==typeof t.test)return t.test}var G=u;function W(e){if(!e)return;if("string"==typeof e)return e;if(e instanceof Error)return e.message;const t=e;if("string"==typeof t.message)return t.message;if("string"==typeof t.msg)return t.msg;try{return JSON.stringify(e)}catch{return String(e)}}function Q(e,t){const r=[];if("http"===e||"sse"===e){const e=t?.status,s="number"==typeof e&&e>=200&&e<300;r.push({name:"HTTP status is 2xx (implicit)",passed:s,error:s?void 0:`expected 2xx, got ${e??"no status"}`})}else if("graphql"===e){const e=t?.status,s="number"==typeof e&&e>=200&&e<300,n=t?.body,o=Array.isArray(n?.errors)&&n.errors.length>0,a=s&&!o;let i;s?o&&(i=`GraphQL response contains ${n.errors.length} error(s)`):i=`expected HTTP 2xx, got ${e??"no status"}`,r.push({name:"GraphQL success (implicit)",passed:a,error:i})}else if("grpc"===e){const e=t?.statusCode??t?.code??t?.status,s=t?.statusName??t?.status,n=0===e;r.push({name:"gRPC status is OK (implicit)",passed:n,error:n?void 0:`expected code 0 (OK), got ${e??"unknown"}${s?` (${s})`:""}`})}else if("mcp"===e){const e=t?.body??{},s=void 0!==e.result,n=void 0!==e.error||!0===e.result?.isError||void 0!==t?.error,o=s&&!n;let a;n?a="MCP JSON-RPC response contains an error":s||(a="MCP JSON-RPC response missing 'result'"),r.push({name:"MCP JSON-RPC success (implicit)",passed:o,error:a})}else if("websocket"===e){const e=t?.events??[],s=e.some(e=>"open"===e.kind||"open"===e.state),n=e.some(e=>"error"===e.kind||"error"===e.state),o=s&&!n;r.push({name:"WebSocket connected (implicit)",passed:o,error:o?void 0:n?"WebSocket connection errored":"WebSocket did not reach open state"})}return r}async function V(e){T(e);const t=await D(e),r=(0,d.createClient)(),s=Y(t,e),n=Date.now(),o=[];let a=0;const i=Math.min(e.concurrency??5,s.length),c=[];for(let n=0;n<i;n++)c.push((async()=>{for(;a<s.length;){if(e.signal?.aborted)throw new Error("Run cancelled.");const n=s[a++];if(!n)break;const i=await ee(r,n,e,t);o.push(i)}})());await Promise.all(c);const p=Date.now()-n;return{summary:ne(o,p),results:o.sort((e,t)=>e.path.localeCompare(t.path)||e.method.localeCompare(t.method)),config:e,generatedAt:(new Date).toISOString(),version:G}}function Y(e,t){const r=[];for(const[s,n]of Object.entries(e.paths??{})){if(!n||"object"!=typeof n)continue;const e=n;for(const[n,o]of(0,l.operationEntries)(e)){if(!o||"object"!=typeof o)continue;const a={path:s,method:n,operation:o,pathItem:e,operationId:o.operationId??`${n.toUpperCase()} ${s}`,protocol:X(o,e)};Z(a,t.filter)&&r.push(a)}}return r}function X(e,t){const r=e["x-protocol"]??t?.["x-protocol"];if(r)return String(r).toLowerCase();const s=e.responses??{};for(const e of Object.values(s)){if((e?.content??{})["text/event-stream"])return"sse"}return e["x-response-stream"]?"sse":"http"}function Z(e,t){if(!t)return!0;if(t.methods?.length&&!t.methods.includes(e.method.toLowerCase()))return!1;if(t.operationIds?.length&&!t.operationIds.includes(e.operationId))return!1;if(t.tags?.length){const r=e.operation.tags??[];if(!t.tags.some(e=>r.includes(e)))return!1}if(t.paths?.length){if(!t.paths.some(t=>{try{return new RegExp(t).test(e.path)}catch{return e.path.includes(t)}}))return!1}return!0}async function ee(e,t,r,s){return(await te(e,t,{config:r,spec:s,variables:r.variables})).result}async function te(e,t,r){let{config:s}=r;const{spec:n}=r,o=Date.now(),a={operationId:t.operationId,path:t.path,method:t.method.toUpperCase(),protocol:t.protocol};try{if((r.signal||s.signal)?.aborted)throw new Error("Run cancelled.");const i=r.variables||s.variables,c=t.operation["x-websocket"]||t.operation["x-ws"]||t.operation["x-grpc"]||t.operation["x-mcp"]||{},p=$(r.serverUrl??s.serverUrl??c.url??c.address??c.endpoint??n.servers?.[0]?.url,i);s.resolveTransport&&(s={...s,...await s.resolveTransport({protocol:t.protocol,url:p})});const l=["grpc","websocket","mcp"].includes(t.protocol),d={...s,serverUrl:$(s.serverUrl,i),auth:l?$(s.auth,i):s.auth,headers:l?$({...s.headers,...r.values?.header},i):s.headers,signal:r.signal||s.signal},u={...t,operation:l?{...t.operation,...Object.fromEntries(Object.entries(t.operation).filter(([e])=>["x-grpc","x-mcp","x-ws","x-websocket"].includes(e)).map(([e,t])=>[e,$(t,i)]))}:t.operation},f=l?$(r.values,i):r.values;if(!["http","sse","graphql","grpc","websocket","mcp"].includes(t.protocol))throw new Error(`Unsupported protocol: ${t.protocol}`);const m=[...H(t.operation,t.pathItem),...r.extraAssertions??[]],h=K(t.operation),g=[];let b,v,C={};switch(t.protocol){case"grpc":{const t=await async function(e,t,r,s,n){const o=t.operation["x-grpc"]??{},a=(s??r.serverUrl??o.address)?.replace(/^https?:\/\//,"");if(!a)throw new Error("gRPC operation requires x-grpc.address or --server");const i={...r.headers};k(r,i);const c=y(r);if(c.pfx)throw new Error("gRPC requires PEM certificates, not PFX.");const p=e.connect({kind:"grpc",deadlineMs:r.timeout??3e4,streamDeadlineMs:r.maxStreamMs??r.timeout??3e4,...S(r,a),tls:!(!0!==o.tls&&!/^https:/.test(s??r.serverUrl??""))&&{rootCerts:c.ca,privateKey:E(c),certChain:c.cert,rejectUnauthorized:c.rejectUnauthorized},service:o.service,method:o.method??t.operationId,reflection:r.grpcReflection??o.reflection??!(r.grpcProtoPaths??o.protoPaths)?.length,protoPaths:r.grpcProtoPaths??o.protoPaths,includeDirs:o.includeDirs,metadata:i}),l=()=>{p.close().catch(()=>{})};r.signal?.addEventListener("abort",l,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await p.open(),r.signal?.aborted)throw new Error("Run cancelled.");const e=p.kind,s=n?.body??function(e){const t=e["x-grpc"]??{};if(t.sample&&"object"==typeof t.sample)return t.sample;const r=e.requestBody?.content?.["application/json"]?.schema;if(r?.properties){const e={};for(const[t,s]of Object.entries(r.properties))e[t]=s.default??se(s.type);return e}return{}}(t.operation);"unary"===e||"server_streaming"===e?await p.send(s):(await p.send(s),await p.finishSending());const c=p.events??[],l=c.find(e=>"status"===e.kind),d=c.filter(e=>"data"===e.kind&&"in"===e.direction);"unary"!==e&&"server_streaming"!==e||await p.close();const u={status:l?.meta?.code??-1,statusText:l?.meta?.statusName,body:1===d.length?d[0].data:d.map(e=>e.data),events:c.map(e=>({kind:e.kind,direction:e.direction,data:e.data,meta:e.meta}))},f=o.method??t.operationId;return{response:u,request:{method:f,target:o.service?`${o.service}/${f}`:f,url:a,...Object.keys(s).length?{body:s}:{},...Object.keys(i).length?{headers:i}:{}}}}finally{r.signal?.removeEventListener("abort",l),await p.close().catch(()=>{})}}(e,u,d,$(r.serverUrl,i),f);b=t.response,v=t.request;break}case"websocket":{const t=await async function(e,t,r,s,n){const o=t.operation["x-websocket"]??t.operation["x-ws"]??{};let a=re(o.url,s??r.serverUrl,t.path)?.replace(/^http/,"ws");if(!a)throw new Error("WebSocket operation requires x-ws.url or --server");const i={...r.headers};a=k(r,i,O(a,n?.query));const c=w(r),p=e.connect({kind:"websocket",openTimeoutMs:r.timeout??3e4,signal:r.signal,clientOptions:{...y(r),agent:a.startsWith("wss:")?c.https:c.http},url:a,subprotocols:o.subprotocols,headers:i}),l=()=>{p.close().catch(()=>{})};r.signal?.addEventListener("abort",l,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await p.open(),r.signal?.aborted)throw new Error("Run cancelled.");const e=void 0!==n?.body?[n.body]:o.send??[];for(const t of e.slice(0,3))await p.send(t);await new Promise(e=>setTimeout(e,Math.min(500,r.timeout??500))),await p.close();const t=p.events??[],s=t.filter(e=>"in"===e.direction&&"open"!==e.kind);return{response:{status:101,statusText:"Switching Protocols",body:s.map(e=>e.data),events:t.map(e=>({kind:e.kind,direction:e.direction,data:e.data})),streaming:!0},request:{method:"WS",url:a,...e.length?{body:e.slice(0,3)}:{},...Object.keys(i).length?{headers:i}:{}}}}finally{r.signal?.removeEventListener("abort",l),await p.close().catch(()=>{});for(const e of new Set(Object.values(c)))e.destroy()}}(e,u,d,$(r.serverUrl,i),f);b=t.response,v=t.request;break}case"mcp":{const t=await async function(e,t,r,s,n){const o=t.operation["x-mcp"]??{},a=r.mcpTransport??o.transport??"streamable-http",i={...r.headers};let c=re(o.endpoint,s??r.serverUrl,t.path);"stdio"!==a&&(x(r,"MCP HTTP"),c=k(r,i,c?O(c,n?.query):c));const p=n?.body?.method??o.method??"tools/call",l=n?.body?.params??o.params??("tools/call"===p?{name:o.name,arguments:n?.body??o.arguments??{}}:{}),d=e.connect({kind:"mcp",transport:"stdio"===a?"stdio":"streamable-http",endpoint:c,headers:i,command:r.mcpCommand??o.command,args:r.mcpArgs??o.args,cwd:r.mcpCwd??o.cwd,timeoutMs:r.timeout??3e4}),u=()=>{d.close().catch(()=>{})};r.signal?.addEventListener("abort",u,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await d.open(),r.signal?.aborted)throw new Error("Run cancelled.");return{response:{status:200,statusText:"OK",body:{result:await d.request(p,l,{signal:r.signal,timeoutMs:r.timeout})}},request:{method:p,url:c,headers:i,body:l}}}finally{r.signal?.removeEventListener("abort",u),await d.close().catch(()=>{})}}(e,u,d,$(r.serverUrl,i),f);b=t.response,v=t.request;break}default:{const o=await async function(e,t,r,s,n){"graphql"===t.protocol&&x(r,"GraphQL");const o="graphql"===t.protocol?void 0:w(r),a={...r.httpRuntime,requester:{...r.requester,strictSSL:!1!==r.tls?.strictSSL,ignoreProxyEnvironmentVariables:!0,...o?{agents:o,...r.proxy||r.tls?.caCert||r.tls?.clientCert||r.tls?.pfx||r.httpRuntime?.certificates?{protocolVersion:"http1"}:{}}:{}}},i=n.values,c={...r.headers??{},...i?.header??{}},p={...r.headers?{header:r.headers}:{},...i??{},...Object.keys(c).length?{header:c}:{}};let l;try{l=await e.send({writeBack:!1,signal:n.signal,spec:s,target:{path:t.path,method:t.method},serverUrl:n.serverUrl??r.serverUrl,values:Object.keys(p).length?p:void 0,auth:r.auth,variables:n.variables,timeout:r.timeout,runner:Object.keys(a).length?a:void 0,scripts:n.postmanTest?{test:{exec:n.postmanTest},fromSpecExtensions:!1}:void 0,onAssertion:e=>{n.postmanAssertions.push({name:e.name??"assertion",passed:e.passed,error:W(e.error)})},maxStreamMs:r.maxStreamMs??r.timeout,maxEvents:r.maxEvents??50,maxResponseSize:r.maxResponseSize})}finally{for(const e of new Set(Object.values(o||{})))e.destroy()}if(n.signal?.aborted)throw new Error("Run cancelled.");if(l.error)throw new Error(l.error.message);const d={},u=l.scripts;if(u)for(const e of[...u.prerequest??[],...u.test??[]])e.environment&&Object.assign(d,e.environment);const f=l.request;return{response:{status:l.response?.status,statusText:l.response?.statusText,contentType:l.response?.contentType,sizeBytes:l.response?.sizeBytes,body:l.response?.body,text:l.response?.text,headers:l.response?.headers,events:l.response?.events,streaming:l.response?.streaming},request:f?{method:f.method,url:f.url,headers:f.headers,...void 0!==f.body?{body:f.body}:{}}:void 0,env:d}}(e,t,s,n,{postmanTest:h,postmanAssertions:g,variables:r.variables,values:r.values,serverUrl:r.serverUrl,signal:r.signal||s.signal});b=o.response,v=o.request,C=o.env;break}}if((r.signal||s.signal)?.aborted)throw new Error("Run cancelled.");const q=Date.now()-o,j=m.length>0||g.length>0?[]:Q(t.protocol,b),P=m.length?J(m,{status:b?.status,headers:b?b.headers:void 0,body:b?.body,bodyText:b?b.text:void 0,durationMs:q}):[],R=new Set,A=g.filter(e=>{const t=`${e.name}|${e.passed}|${e.error??""}`;return!R.has(t)&&(R.add(t),!0)}),T=[...P,...A,...j],U=T.some(e=>!e.passed);return{result:{...a,status:U?"failed":"passed",durationMs:q,request:v,response:b,assertions:T.length?T:void 0,timestamp:(new Date).toISOString()},response:b,request:v,env:C}}catch(e){const n=Date.now()-o,i=r.values,c={...s.headers??{},...i?.header??{}},p=Object.fromEntries(Object.entries(c).map(([e,t])=>[e,null==t?"":String(t)])),l={method:t.method.toUpperCase(),url:r.serverUrl??s.serverUrl,...Object.keys(p).length?{headers:p}:{},...void 0!==i?.body?{body:i.body}:{}};return{result:{...a,status:"error",durationMs:n,request:l,error:e.message??String(e),timestamp:(new Date).toISOString()},response:void 0,request:l,env:{}}}}function re(e,t,r){if(!t)return e;const s=new URL(t),n=new URL(e||r,t);return n.host=s.host,n.protocol=s.protocol,e||"/"===s.pathname||(n.pathname=s.pathname.replace(/\/$/,"")+r),n.href}function se(e){switch(e){case"string":default:return"";case"number":case"integer":return 0;case"boolean":return!1;case"array":return[];case"object":return{}}}function ne(e,t){const r=e.length,s=e.filter(e=>"passed"===e.status).length;return{total:r,passed:s,failed:e.filter(e=>"failed"===e.status).length,errors:e.filter(e=>"error"===e.status).length,skipped:e.filter(e=>"skipped"===e.status).length,durationMs:t,passRate:r>0?Math.round(s/r*1e3)/10:0}}var oe=require("@powerduck/openapi-request"),ae=u,ie=1e3;function ce(e){if(e.method&&e.path)return`${e.method.toUpperCase()} ${e.path}`;const t=(e.ref??"").trim(),r=t.indexOf(" ");return r>0?`${t.slice(0,r).toUpperCase()} ${t.slice(r+1).trim()}`:t}function pe(e,t){const r=new Map,s=new Map;for(const e of t)r.set(`${e.method.toUpperCase()} ${e.path}`,e),s.set(e.operationId,e);const n=[],o=[];if(e.steps.forEach((e,t)=>{const a=ce(e),i=r.get(a)??(a?s.get(a):void 0);i?n.push({step:e,op:i,ref:`${i.method.toUpperCase()} ${i.path}`}):o.push(`step ${t} ("${e.name??a}") -> ${a||"<empty ref>"}`)}),o.length)throw new Error(`Scenario references operations not present in the spec: ${o.join("; ")}`);return n}function le(e,t){if(!e)return;const r=t.toLowerCase();for(const[t,s]of Object.entries(e))if(t.toLowerCase()===r)return s}function de(e){if(null==e)return"";if("string"==typeof e)return e;if("number"==typeof e||"boolean"==typeof e)return String(e);try{return JSON.stringify(e)}catch{return String(e)}}function ue(e,t){const r={};if(!e?.length)return r;for(const s of e){let e;e="status"===s.from?t?.status:"header"===s.from?s.key?le(t?.headers,s.key):void 0:_(t?.body,s.path??"$"),null!=e&&(r[s.name]=de(e))}return r}function fe(e,t,r,s,n){return{index:e,ref:t,name:r.name,operationId:t,path:t.includes(" ")?t.slice(t.indexOf(" ")+1):t,method:t.includes(" ")?t.slice(0,t.indexOf(" ")):"",protocol:"",status:"skipped",durationMs:0,timestamp:(new Date).toISOString(),notRun:!0,notRunReason:s,...n??{}}}async function me(e,t){const{config:r,onEvent:s,signal:n}=t;T(r);const o=Date.now(),a=new Date(o).toISOString(),i=e=>{try{s?.(e)}catch{}},c=t.spec??await D(r),p=pe(e,Y(c,{...r,filter:void 0})),l=(0,oe.createClient)(),d=e.data&&e.data.length?e.data:[],u=Math.trunc(Number(e.iterations)),f=Number.isFinite(u)&&u>0?Math.min(u,ie):1,m=Math.max(f,d.length),h=Array.from({length:m},(e,t)=>d[t]),g=p.map(({step:e})=>e.request?.data&&e.request.data.length?e.request.data.length:1),b={scenarioCount:h.length,stepCounts:g},v=!1!==e.stopOnFailure,y=[];let w="passed",x=!1,$=!1,k=null,S="failed",E={...r.variables??{},...e.variables??{}};i({type:"scenario:start",scenario:e.name,status:w,scenarioIterations:h.length});e:for(let t=0;t<h.length;t++){E={...r.variables??{},...e.variables??{},...h[t]??{}};for(let s=0;s<p.length;s++){const{step:o,op:a,ref:d}=p[s],u=o.request?.data&&o.request.data.length?o.request.data:[void 0],f={scenarioIteration:t,scenarioIterations:h.length,stepIterations:u.length};if(n?.aborted){w="cancelled",$=!0,x=!0,k={s:t,i:s,t:0};break e}if(o.skip)for(let t=0;t<u.length;t++){const r=fe(s,d,o,"Skipped by plan",{...f,stepIteration:t});y.push(r),i({type:"step:skip",scenario:e.name,stepIndex:s,step:r,...f,stepIteration:t})}else for(let p=0;p<u.length;p++){if(n?.aborted){w="cancelled",$=!0,x=!0,k={s:t,i:s,t:p};break e}i({type:"step:start",scenario:e.name,stepIndex:s,...f,stepIteration:p});const m={...E,...u[p]??{}},h=await te(l,a,{config:r,spec:c,signal:n,variables:m,values:o.request?.values,serverUrl:o.request?.serverUrl,extraAssertions:o.request?.assertions});n?.aborted&&($=!0);const g={};for(const[e,t]of Object.entries(h.env))m[e]!==t&&(g[e]=t);const b=ue(o.request?.extract,h.response),O={...g,...b},C=p===u.length-1;C&&Object.assign(E,h.env,b);const q={...h.result,index:s,ref:d,name:o.name,...f,stepIteration:p,extracted:Object.keys(O).length?O:void 0,variablesAfter:C?{...E}:{...E,...u[p]??{},...O}};if(y.push(q),i({type:"step:finish",scenario:e.name,stepIndex:s,step:q,...f,stepIteration:p}),"error"===q.status?w="error":"failed"===q.status&&"error"!==w&&(w="failed"),("failed"===q.status||"error"===q.status)&&v){x=!0,S=q.status,k={s:t,i:s,t:p};break e}}}}if(x&&k){const t=(e,t)=>$?"Scenario cancelled":e===k.s&&t===k.i?`Stopped after ${S} iteration ${k.t+1}`:e===k.s?`Stopped after ${S} step ${k.i+1}`:`Stopped after ${S} run ${k.s+1}`;for(let r=k.s;r<h.length;r++)for(let s=r===k.s?k.i:0;s<p.length;s++){const n=p[s],o=n.step.request?.data&&n.step.request.data.length?n.step.request.data.length:1;for(let a=r===k.s&&s===k.i?k.t+1:0;a<o;a++){const c={scenarioIteration:r,scenarioIterations:h.length,stepIteration:a,stepIterations:o},p=fe(s,n.ref,n.step,t(r,s),c);y.push(p),i({type:"step:skip",scenario:e.name,stepIndex:s,step:p,...c})}}}($||n?.aborted&&"error"!==w&&"failed"!==w)&&(w="cancelled");const O=Date.now()-o,C="cancelled"===w?"scenario:cancel":"scenario:finish",q={name:e.name,description:e.description,status:w,summary:ne(y,O),steps:y,variables:E,iterations:b,config:r,startedAt:a,durationMs:O,generatedAt:(new Date).toISOString(),version:ae};return i({type:C,scenario:e.name,status:w,message:x?"halted":void 0}),q}var he=c(require("fs"),1),ge=c(require("path"),1);function be(e,t){const r=ge.default.resolve(t);he.default.mkdirSync(r,{recursive:!0});const s=ge.default.join(r,"report.json");return he.default.writeFileSync(s,JSON.stringify(e,null,2),"utf-8"),s}var ve={reset:"",bold:"",dim:"",green:"",red:"",yellow:"",blue:"",cyan:"",gray:"",bgGreen:"",bgRed:"",bgYellow:""};function ye(e){if(null==e||!Number.isFinite(e))return`${ve.dim}--ms${ve.reset}`;return`${e>500?`${ve.bold}${ve.yellow}`:ve.bold}${e}${ve.reset}${ve.dim}ms${ve.reset}`}function we(e){const t=[],{summary:r,results:s}=e;t.push(""),t.push(`${ve.bold}${ve.cyan}╔══════════════════════════════════════════════════════╗${ve.reset}`),t.push(`${ve.bold}${ve.cyan}║ Powerduck Test Report ║${ve.reset}`),t.push(`${ve.bold}${ve.cyan}╚══════════════════════════════════════════════════════╝${ve.reset}`),t.push("");const n=r.passRate>=80?ve.green:r.passRate>=50?ve.yellow:ve.red;t.push(`${ve.bold} Total:${ve.reset} ${r.total} ${ve.green}Passed:${ve.reset} ${r.passed} ${ve.red}Failed:${ve.reset} ${r.failed} ${ve.yellow}Errors:${ve.reset} ${r.errors} ${n}Rate:${ve.reset} ${r.passRate}% ${ve.dim}Time:${ve.reset} ${ye(r.durationMs)}`),t.push("");const o=r.total>0?Math.round(r.passed/r.total*50):0,a=r.total>0?Math.round(r.failed/r.total*50):0,i=r.total>0?Math.round(r.errors/r.total*50):0,c=ve.green+"█".repeat(o)+ve.red+"█".repeat(a)+ve.yellow+"█".repeat(i)+ve.gray+"░".repeat(Math.max(0,50-o-a-i))+ve.reset;t.push(` [${c}]`),t.push("");for(const e of s)t.push(xe(e));return t.push(""),r.failed>0||r.errors>0?t.push(`${ve.bold}${ve.red} ✗ ${r.failed+r.errors} test(s) did not pass${ve.reset}`):t.push(`${ve.bold}${ve.green} ✓ All ${r.total} test(s) passed${ve.reset}`),t.push(` ${ve.dim}Generated: ${e.generatedAt}${ve.reset}`),t.push(""),t.join("\n")}function xe(e){const t="passed"===e.status?`${ve.green}✓${ve.reset}`:"failed"===e.status?`${ve.red}✗${ve.reset}`:"error"===e.status?`${ve.yellow}!${ve.reset}`:`${ve.gray}-${ve.reset}`,r="passed"===e.status?`${ve.bgGreen}${ve.bold} PASS ${ve.reset}`:"failed"===e.status?`${ve.bgRed}${ve.bold} FAIL ${ve.reset}`:"error"===e.status?`${ve.bgYellow}${ve.bold} ERROR ${ve.reset}`:`${ve.gray} SKIP ${ve.reset}`,s=e.protocol?` ${ve.cyan}[${e.protocol}]${ve.reset}`:"",n=null!=e.response?.status?` ${ve.dim}${e.response.status}${ve.reset}`:"";let o=` ${t} ${r} ${ve.bold}${e.method}${ve.reset} ${e.path}${s}${n} ${ye(e.durationMs)}`;if(e.error&&(o+=`\n ${ve.red}${e.error}${ve.reset}`),e.assertions?.length)for(const t of e.assertions){o+=`\n ${t.passed?`${ve.green} ✓${ve.reset}`:`${ve.red} ✗${ve.reset}`} ${t.name}${t.error?` — ${ve.red}${t.error}${ve.reset}`:""}`}return o}function $e(e){const t=we(e);return console.log(t),t}var ke=c(require("fs"),1),Se=c(require("path"),1);function Ee(e,t){const r=Se.default.resolve(t);ke.default.mkdirSync(r,{recursive:!0});const s=Se.default.join(r,"report.html");return ke.default.writeFileSync(s,function(e){const{summary:t,results:r}=e,s=JSON.stringify({summary:t,results:r}).replace(/</g,"\\u003c");return`<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>Test Report — Powerduck</title>\n<style>\n *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n :root{\n --bg:#ffffff;--fg:#0f172a;--muted:#64748b;--border:#e2e8f0;\n --bg-subtle:#f8fafc;--bg-hover:#f1f5f9;\n --green:#16a34a;--green-bg:#f0fdf4;--green-border:#bbf7d0;\n --red:#dc2626;--red-bg:#fef2f2;--red-border:#fecaca;\n --amber:#d97706;--amber-bg:#fffbeb;--amber-border:#fde68a;\n --blue:#2563eb;--mono:ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,monospace;\n --sans:ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Inter,sans-serif;\n }\n @media (prefers-color-scheme:dark){\n :root{\n --bg:#0f172a;--fg:#f1f5f9;--muted:#94a3b8;--border:#1e293b;\n --bg-subtle:#1e293b;--bg-hover:#334155;\n --green:#4ade80;--green-bg:rgba(74,222,128,.1);--green-border:rgba(74,222,128,.25);\n --red:#f87171;--red-bg:rgba(248,113,113,.1);--red-border:rgba(248,113,113,.25);\n --amber:#fbbf24;--amber-bg:rgba(251,191,36,.1);--amber-border:rgba(251,191,36,.25);\n --blue:#60a5fa;\n }\n }\n body{font-family:var(--sans);background:var(--bg);color:var(--fg);line-height:1.5;font-size:14px;-webkit-font-smoothing:antialiased}\n .wrap{max-width:960px;margin:0 auto;padding:32px 24px 64px}\n\n /* Header */\n .hdr{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--border)}\n .hdr h1{font-size:18px;font-weight:600;letter-spacing:-.01em}\n .hdr .meta{font-size:12px;color:var(--muted);font-family:var(--mono)}\n\n /* Summary strip */\n .strip{display:grid;grid-template-columns:repeat(6,1fr);gap:0;border:1px solid var(--border);border-radius:8px;overflow:hidden;margin-bottom:24px}\n .stat{padding:14px 16px;border-right:1px solid var(--border);text-align:center}\n .stat:last-child{border-right:none}\n .stat .v{font-size:22px;font-weight:700;letter-spacing:-.02em;line-height:1.2}\n .stat .l{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-top:2px}\n .stat.total .v{color:var(--fg)}\n .stat.pass .v{color:var(--green)}\n .stat.fail .v{color:var(--red)}\n .stat.err .v{color:var(--amber)}\n .stat.rate .v{color:var(--blue)}\n .stat.time .v{font-size:16px;font-weight:600}\n\n /* Progress */\n .bar{height:4px;background:var(--border);border-radius:2px;overflow:hidden;margin-bottom:24px;display:flex}\n .bar>div{height:100%}\n .bar .p{background:var(--green)}\n .bar .f{background:var(--red)}\n .bar .e{background:var(--amber)}\n\n /* Filters */\n .filters{display:flex;gap:4px;margin-bottom:16px}\n .fbtn{padding:4px 12px;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--muted);cursor:pointer;font-size:12px;font-weight:500;transition:all .12s}\n .fbtn:hover{color:var(--fg);background:var(--bg-hover)}\n .fbtn.active{background:var(--bg-subtle);color:var(--fg);border-color:var(--border)}\n .fbtn .n{color:var(--muted);margin-left:4px;font-variant-numeric:tabular-nums}\n\n /* Test rows */\n .row{border:1px solid var(--border);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .12s}\n .row:hover{border-color:var(--muted)}\n .row-head{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;user-select:none}\n .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n .dot.pass{background:var(--green)}\n .dot.fail{background:var(--red)}\n .dot.err{background:var(--amber)}\n .method{font-family:var(--mono);font-size:11px;font-weight:600;color:var(--muted);min-width:40px;text-transform:uppercase}\n .path{font-family:var(--mono);font-size:13px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n .proto{font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);padding:2px 8px;border:1px solid var(--border);border-radius:4px}\n .dur{font-family:var(--mono);font-size:12px;color:var(--muted);min-width:56px;text-align:right;font-variant-numeric:tabular-nums}\n .chev{color:var(--muted);font-size:10px;transition:transform .15s;flex-shrink:0}\n .row.open .chev{transform:rotate(90deg)}\n\n /* Row body */\n .row-body{display:none;padding:0 14px 12px 32px;border-top:1px solid var(--border)}\n .row.open .row-body{display:block}\n .sect{margin-top:10px}\n .sect-t{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:600;margin-bottom:6px}\n .asrt{display:flex;align-items:center;gap:8px;padding:3px 0;font-size:13px}\n .asrt .ck{width:14px;height:14px;border-radius:3px;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;flex-shrink:0;color:#fff}\n .asrt.pass .ck{background:var(--green)}\n .asrt.fail .ck{background:var(--red)}\n .asrt .an{color:var(--fg)}\n .asrt .ae{color:var(--red);font-size:12px;margin-left:6px;font-family:var(--mono)}\n .rmeta{display:flex;gap:20px;flex-wrap:wrap;font-size:12px}\n .rmeta span{color:var(--muted)}\n .rmeta strong{color:var(--fg);font-weight:600;font-family:var(--mono)}\n .errbox{background:var(--red-bg);border:1px solid var(--red-border);border-radius:6px;padding:8px 12px;color:var(--red);font-size:12px;font-family:var(--mono);margin-top:10px}\n .bprev{background:var(--bg-subtle);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-family:var(--mono);font-size:11px;color:var(--muted);max-height:180px;overflow:auto;white-space:pre-wrap;word-break:break-all;margin-top:6px}\n\n footer{margin-top:32px;padding-top:16px;border-top:1px solid var(--border);font-size:11px;color:var(--muted);display:flex;justify-content:space-between}\n\n @media(max-width:640px){\n .strip{grid-template-columns:repeat(3,1fr)}\n .stat:nth-child(3){border-right:none}\n .stat:nth-child(4),.stat:nth-child(5){border-top:1px solid var(--border)}\n .path{font-size:11px}\n }\n</style>\n</head>\n<body>\n<div class="wrap">\n <div class="hdr">\n <h1>Test Report</h1>\n <span class="meta">${e.generatedAt} · v${e.version}</span>\n </div>\n\n <div class="strip">\n <div class="stat total"><div class="v">${t.total}</div><div class="l">Total</div></div>\n <div class="stat pass"><div class="v">${t.passed}</div><div class="l">Passed</div></div>\n <div class="stat fail"><div class="v">${t.failed}</div><div class="l">Failed</div></div>\n <div class="stat err"><div class="v">${t.errors}</div><div class="l">Errors</div></div>\n <div class="stat rate"><div class="v">${t.passRate}%</div><div class="l">Pass Rate</div></div>\n <div class="stat time"><div class="v">${(t.durationMs/1e3).toFixed(2)}s</div><div class="l">Duration</div></div>\n </div>\n\n <div class="bar">\n <div class="p" style="width:${t.total?t.passed/t.total*100:0}%"></div>\n <div class="f" style="width:${t.total?t.failed/t.total*100:0}%"></div>\n <div class="e" style="width:${t.total?t.errors/t.total*100:0}%"></div>\n </div>\n\n <div class="filters">\n <button class="fbtn active" data-f="all">All<span class="n">${t.total}</span></button>\n <button class="fbtn" data-f="passed">Passed<span class="n">${t.passed}</span></button>\n <button class="fbtn" data-f="failed">Failed<span class="n">${t.failed}</span></button>\n <button class="fbtn" data-f="error">Errors<span class="n">${t.errors}</span></button>\n </div>\n\n <div id="list"></div>\n\n <footer>\n <span>@powerduck/openapi-cli v${e.version}</span>\n <span>${t.passed}/${t.total} passed</span>\n </footer>\n</div>\n\n<script>\nconst D=${s};\nfunction esc(s){const d=document.createElement('div');d.textContent=String(s??'');return d.innerHTML}\nfunction render(f){\n const list=document.getElementById('list');\n const items=f==='all'?D.results:D.results.filter(r=>r.status===f);\n list.innerHTML=items.map(r=>{\n const as=r.assertions?r.assertions.map(a=>\`\n <div class="asrt \${a.passed?'pass':'fail'}">\n <div class="ck">\${a.passed?'✓':'✗'}</div>\n <span class="an">\${esc(a.name)}</span>\n \${a.error?'<span class="ae">'+esc(a.error)+'</span>':''}\n </div>\`).join(''):'';\n const bp=r.response?.body?'<div class="bprev">'+esc(JSON.stringify(r.response.body,null,2).slice(0,3000))+'</div>':'';\n const eb=r.error?'<div class="errbox">'+esc(r.error)+'</div>':'';\n const rm=r.response?\`\n <div class="rmeta">\n \${r.response.status!=null?'<span>Status <strong>'+r.response.status+(r.response.statusText?' '+esc(r.response.statusText):'')+'</strong></span>':''}\n \${r.response.contentType?'<span>Type <strong>'+esc(r.response.contentType)+'</strong></span>':''}\n \${r.response.sizeBytes!=null?'<span>Size <strong>'+r.response.sizeBytes+'B</strong></span>':''}\n \${r.response.streaming?'<span>Streaming <strong>yes</strong></span>':''}\n </div>\`:'';\n return \`\n <div class="row \${r.status}" data-s="\${r.status}">\n <div class="row-head" onclick="this.parentElement.classList.toggle('open')">\n <div class="dot \${r.status==='passed'?'pass':r.status==='failed'?'fail':'err'}"></div>\n <span class="method">\${esc(r.method)}</span>\n <span class="path">\${esc(r.path)}</span>\n \${r.protocol?'<span class="proto">'+esc(r.protocol)+'</span>':''}\n <span class="dur">\${r.durationMs}ms</span>\n <span class="chev">▶</span>\n </div>\n <div class="row-body">\n \${eb}\n \${rm}\n \${as?'<div class="sect"><div class="sect-t">Assertions</div>'+as+'</div>':''}\n \${bp?'<div class="sect"><div class="sect-t">Response</div>'+bp+'</div>':''}\n </div>\n </div>\`;\n }).join('');\n}\ndocument.querySelectorAll('.fbtn').forEach(b=>b.addEventListener('click',()=>{\n document.querySelectorAll('.fbtn').forEach(x=>x.classList.remove('active'));\n b.classList.add('active');render(b.dataset.f);\n}));\nrender('all');\n<\/script>\n</body>\n</html>`}(e),"utf-8"),s}
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{DEFAULT_CONFIG as e,buildImplicitAssertions as t,buildSummary as n,collectOperations as s,executeStep as r,extractAssertions as a,extractPostmanScripts as o,formatAssertionError as i,generateCliReport as p,generateHtmlReport as c,generateJsonReport as l,isRemoteSpec as u,loadSpec as f,package_default as d,printCliReport as h,resolveConfig as g,resolveJsonPath as m,runDeclarativeAssertions as b,runTests as v,validateRunConfig as I}from"./chunk-HYCEXENJ.js";import{createClient as y}from"@powerduck/openapi-request";var $=d.version;function S(e){if(e.method&&e.path)return`${e.method.toUpperCase()} ${e.path}`;const t=(e.ref??"").trim(),n=t.indexOf(" ");return n>0?`${t.slice(0,n).toUpperCase()} ${t.slice(n+1).trim()}`:t}function q(e,t){const n=new Map,s=new Map;for(const e of t)n.set(`${e.method.toUpperCase()} ${e.path}`,e),s.set(e.operationId,e);const r=[],a=[];if(e.steps.forEach((e,t)=>{const o=S(e),i=n.get(o)??(o?s.get(o):void 0);i?r.push({step:e,op:i,ref:`${i.method.toUpperCase()} ${i.path}`}):a.push(`step ${t} ("${e.name??o}") -> ${o||"<empty ref>"}`)}),a.length)throw new Error(`Scenario references operations not present in the spec: ${a.join("; ")}`);return r}function w(e,t){if(!e)return;const n=t.toLowerCase();for(const[t,s]of Object.entries(e))if(t.toLowerCase()===n)return s}function x(e){if(null==e)return"";if("string"==typeof e)return e;if("number"==typeof e||"boolean"==typeof e)return String(e);try{return JSON.stringify(e)}catch{return String(e)}}function k(e,t){const n={};if(!e?.length)return n;for(const s of e){let e;e="status"===s.from?t?.status:"header"===s.from?s.key?w(t?.headers,s.key):void 0:m(t?.body,s.path??"$"),null!=e&&(n[s.name]=x(e))}return n}function O(e,t,n,s,r){return{index:e,ref:t,name:n.name,operationId:t,path:t.includes(" ")?t.slice(t.indexOf(" ")+1):t,method:t.includes(" ")?t.slice(0,t.indexOf(" ")):"",protocol:"",status:"skipped",durationMs:0,timestamp:(new Date).toISOString(),notRun:!0,notRunReason:s,...r??{}}}async function C(e,t){const{config:a,onEvent:o,signal:i}=t;I(a);const p=Date.now(),c=new Date(p).toISOString(),l=e=>{try{o?.(e)}catch{}},u=t.spec??await f(a),d=q(e,s(u,{...a,filter:void 0})),h=y(),g=e.data&&e.data.length?e.data:[],m=Math.trunc(Number(e.iterations)),b=Number.isFinite(m)&&m>0?Math.min(m,1e3):1,v=Math.max(b,g.length),S=Array.from({length:v},(e,t)=>g[t]),w=d.map(({step:e})=>e.request?.data&&e.request.data.length?e.request.data.length:1),x={scenarioCount:S.length,stepCounts:w},C=!1!==e.stopOnFailure,M=[];let j="passed",U=!1,A=!1,D=null,E="failed",N={...a.variables??{},...e.variables??{}};l({type:"scenario:start",scenario:e.name,status:j,scenarioIterations:S.length});e:for(let t=0;t<S.length;t++){N={...a.variables??{},...e.variables??{},...S[t]??{}};for(let n=0;n<d.length;n++){const{step:s,op:o,ref:p}=d[n],c=s.request?.data&&s.request.data.length?s.request.data:[void 0],f={scenarioIteration:t,scenarioIterations:S.length,stepIterations:c.length};if(i?.aborted){j="cancelled",A=!0,U=!0,D={s:t,i:n,t:0};break e}if(s.skip)for(let t=0;t<c.length;t++){const r=O(n,p,s,"Skipped by plan",{...f,stepIteration:t});M.push(r),l({type:"step:skip",scenario:e.name,stepIndex:n,step:r,...f,stepIteration:t})}else for(let d=0;d<c.length;d++){if(i?.aborted){j="cancelled",A=!0,U=!0,D={s:t,i:n,t:d};break e}l({type:"step:start",scenario:e.name,stepIndex:n,...f,stepIteration:d});const g={...N,...c[d]??{}},m=await r(h,o,{config:a,spec:u,signal:i,variables:g,values:s.request?.values,serverUrl:s.request?.serverUrl,extraAssertions:s.request?.assertions});i?.aborted&&(A=!0);const b={};for(const[e,t]of Object.entries(m.env))g[e]!==t&&(b[e]=t);const v=k(s.request?.extract,m.response),I={...b,...v},y=d===c.length-1;y&&Object.assign(N,m.env,v);const $={...m.result,index:n,ref:p,name:s.name,...f,stepIteration:d,extracted:Object.keys(I).length?I:void 0,variablesAfter:y?{...N}:{...N,...c[d]??{},...I}};if(M.push($),l({type:"step:finish",scenario:e.name,stepIndex:n,step:$,...f,stepIteration:d}),"error"===$.status?j="error":"failed"===$.status&&"error"!==j&&(j="failed"),("failed"===$.status||"error"===$.status)&&C){U=!0,E=$.status,D={s:t,i:n,t:d};break e}}}}if(U&&D){const t=(e,t)=>A?"Scenario cancelled":e===D.s&&t===D.i?`Stopped after ${E} iteration ${D.t+1}`:e===D.s?`Stopped after ${E} step ${D.i+1}`:`Stopped after ${E} run ${D.s+1}`;for(let n=D.s;n<S.length;n++)for(let s=n===D.s?D.i:0;s<d.length;s++){const r=d[s],a=r.step.request?.data&&r.step.request.data.length?r.step.request.data.length:1;for(let o=n===D.s&&s===D.i?D.t+1:0;o<a;o++){const i={scenarioIteration:n,scenarioIterations:S.length,stepIteration:o,stepIterations:a},p=O(s,r.ref,r.step,t(n,s),i);M.push(p),l({type:"step:skip",scenario:e.name,stepIndex:s,step:p,...i})}}}(A||i?.aborted&&"error"!==j&&"failed"!==j)&&(j="cancelled");const R=Date.now()-p,F="cancelled"===j?"scenario:cancel":"scenario:finish",J={name:e.name,description:e.description,status:j,summary:n(M,R),steps:M,variables:N,iterations:x,config:a,startedAt:c,durationMs:R,generatedAt:(new Date).toISOString(),version:$};return l({type:F,scenario:e.name,status:j,message:U?"halted":void 0}),J}export{e as DEFAULT_CONFIG,k as applyExtracts,t as buildImplicitAssertions,n as buildSummary,s as collectOperations,r as executeStep,a as extractAssertions,o as extractPostmanScripts,i as formatAssertionError,p as generateCliReport,c as generateHtmlReport,l as generateJsonReport,u as isRemoteSpec,f as loadSpec,h as printCliReport,g as resolveConfig,m as resolveJsonPath,q as resolveSteps,b as runDeclarativeAssertions,C as runScenario,v as runTests,S as stepRef};
1
+ import{DEFAULT_CONFIG as e,buildImplicitAssertions as t,buildSummary as n,collectOperations as s,executeStep as r,extractAssertions as a,extractPostmanScripts as o,formatAssertionError as i,generateCliReport as p,generateHtmlReport as c,generateJsonReport as l,isRemoteSpec as u,loadSpec as f,package_default as d,printCliReport as h,resolveConfig as g,resolveJsonPath as m,runDeclarativeAssertions as b,runTests as v,validateRunConfig as I}from"./chunk-V3S7H3A5.js";import{createClient as y}from"@powerduck/openapi-request";var $=d.version;function S(e){if(e.method&&e.path)return`${e.method.toUpperCase()} ${e.path}`;const t=(e.ref??"").trim(),n=t.indexOf(" ");return n>0?`${t.slice(0,n).toUpperCase()} ${t.slice(n+1).trim()}`:t}function q(e,t){const n=new Map,s=new Map;for(const e of t)n.set(`${e.method.toUpperCase()} ${e.path}`,e),s.set(e.operationId,e);const r=[],a=[];if(e.steps.forEach((e,t)=>{const o=S(e),i=n.get(o)??(o?s.get(o):void 0);i?r.push({step:e,op:i,ref:`${i.method.toUpperCase()} ${i.path}`}):a.push(`step ${t} ("${e.name??o}") -> ${o||"<empty ref>"}`)}),a.length)throw new Error(`Scenario references operations not present in the spec: ${a.join("; ")}`);return r}function w(e,t){if(!e)return;const n=t.toLowerCase();for(const[t,s]of Object.entries(e))if(t.toLowerCase()===n)return s}function x(e){if(null==e)return"";if("string"==typeof e)return e;if("number"==typeof e||"boolean"==typeof e)return String(e);try{return JSON.stringify(e)}catch{return String(e)}}function k(e,t){const n={};if(!e?.length)return n;for(const s of e){let e;e="status"===s.from?t?.status:"header"===s.from?s.key?w(t?.headers,s.key):void 0:m(t?.body,s.path??"$"),null!=e&&(n[s.name]=x(e))}return n}function O(e,t,n,s,r){return{index:e,ref:t,name:n.name,operationId:t,path:t.includes(" ")?t.slice(t.indexOf(" ")+1):t,method:t.includes(" ")?t.slice(0,t.indexOf(" ")):"",protocol:"",status:"skipped",durationMs:0,timestamp:(new Date).toISOString(),notRun:!0,notRunReason:s,...r??{}}}async function C(e,t){const{config:a,onEvent:o,signal:i}=t;I(a);const p=Date.now(),c=new Date(p).toISOString(),l=e=>{try{o?.(e)}catch{}},u=t.spec??await f(a),d=q(e,s(u,{...a,filter:void 0})),h=y(),g=e.data&&e.data.length?e.data:[],m=Math.trunc(Number(e.iterations)),b=Number.isFinite(m)&&m>0?Math.min(m,1e3):1,v=Math.max(b,g.length),S=Array.from({length:v},(e,t)=>g[t]),w=d.map(({step:e})=>e.request?.data&&e.request.data.length?e.request.data.length:1),x={scenarioCount:S.length,stepCounts:w},C=!1!==e.stopOnFailure,M=[];let j="passed",A=!1,U=!1,D=null,E="failed",N={...a.variables??{},...e.variables??{}};l({type:"scenario:start",scenario:e.name,status:j,scenarioIterations:S.length});e:for(let t=0;t<S.length;t++){N={...a.variables??{},...e.variables??{},...S[t]??{}};for(let n=0;n<d.length;n++){const{step:s,op:o,ref:p}=d[n],c=s.request?.data&&s.request.data.length?s.request.data:[void 0],f={scenarioIteration:t,scenarioIterations:S.length,stepIterations:c.length};if(i?.aborted){j="cancelled",U=!0,A=!0,D={s:t,i:n,t:0};break e}if(s.skip)for(let t=0;t<c.length;t++){const r=O(n,p,s,"Skipped by plan",{...f,stepIteration:t});M.push(r),l({type:"step:skip",scenario:e.name,stepIndex:n,step:r,...f,stepIteration:t})}else for(let d=0;d<c.length;d++){if(i?.aborted){j="cancelled",U=!0,A=!0,D={s:t,i:n,t:d};break e}l({type:"step:start",scenario:e.name,stepIndex:n,...f,stepIteration:d});const g={...N,...c[d]??{}},m=await r(h,o,{config:a,spec:u,signal:i,variables:g,values:s.request?.values,serverUrl:s.request?.serverUrl,extraAssertions:s.request?.assertions});i?.aborted&&(U=!0);const b={};for(const[e,t]of Object.entries(m.env))g[e]!==t&&(b[e]=t);const v=k(s.request?.extract,m.response),I={...b,...v},y=d===c.length-1;y&&Object.assign(N,m.env,v);const $={...m.result,index:n,ref:p,name:s.name,...f,stepIteration:d,extracted:Object.keys(I).length?I:void 0,variablesAfter:y?{...N}:{...N,...c[d]??{},...I}};if(M.push($),l({type:"step:finish",scenario:e.name,stepIndex:n,step:$,...f,stepIteration:d}),"error"===$.status?j="error":"failed"===$.status&&"error"!==j&&(j="failed"),("failed"===$.status||"error"===$.status)&&C){A=!0,E=$.status,D={s:t,i:n,t:d};break e}}}}if(A&&D){const t=(e,t)=>U?"Scenario cancelled":e===D.s&&t===D.i?`Stopped after ${E} iteration ${D.t+1}`:e===D.s?`Stopped after ${E} step ${D.i+1}`:`Stopped after ${E} run ${D.s+1}`;for(let n=D.s;n<S.length;n++)for(let s=n===D.s?D.i:0;s<d.length;s++){const r=d[s],a=r.step.request?.data&&r.step.request.data.length?r.step.request.data.length:1;for(let o=n===D.s&&s===D.i?D.t+1:0;o<a;o++){const i={scenarioIteration:n,scenarioIterations:S.length,stepIteration:o,stepIterations:a},p=O(s,r.ref,r.step,t(n,s),i);M.push(p),l({type:"step:skip",scenario:e.name,stepIndex:s,step:p,...i})}}}(U||i?.aborted&&"error"!==j&&"failed"!==j)&&(j="cancelled");const R=Date.now()-p,F="cancelled"===j?"scenario:cancel":"scenario:finish",L={name:e.name,description:e.description,status:j,summary:n(M,R),steps:M,variables:N,iterations:x,config:a,startedAt:c,durationMs:R,generatedAt:(new Date).toISOString(),version:$};return l({type:F,scenario:e.name,status:j,message:A?"halted":void 0}),L}export{e as DEFAULT_CONFIG,k as applyExtracts,t as buildImplicitAssertions,n as buildSummary,s as collectOperations,r as executeStep,a as extractAssertions,o as extractPostmanScripts,i as formatAssertionError,p as generateCliReport,c as generateHtmlReport,l as generateJsonReport,u as isRemoteSpec,f as loadSpec,h as printCliReport,g as resolveConfig,m as resolveJsonPath,q as resolveSteps,b as runDeclarativeAssertions,C as runScenario,v as runTests,S as stepRef};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerduck/openapi-cli",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "description": "CI-ready CLI for batch-testing OpenAPI documents across HTTP, SSE, WebSocket, GraphQL, gRPC and MCP",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -24,7 +24,7 @@
24
24
  ],
25
25
  "sideEffects": false,
26
26
  "engines": {
27
- "node": ">=18.17"
27
+ "node": ">=22.0.0"
28
28
  },
29
29
  "scripts": {
30
30
  "build": "tsc --noEmit && tsup",
@@ -50,8 +50,8 @@
50
50
  ],
51
51
  "license": "MIT",
52
52
  "dependencies": {
53
- "@powerduck/openapi-parser": "^0.3.9",
54
- "@powerduck/openapi-request": "^0.2.15",
53
+ "@powerduck/openapi-parser": "^0.3.11",
54
+ "@powerduck/openapi-request": "0.2.20",
55
55
  "commander": "^12.1.0",
56
56
  "https-proxy-agent": "^5.0.1"
57
57
  },
@@ -61,7 +61,10 @@
61
61
  "terser": "^5.51.2",
62
62
  "tsup": "^8.0.0",
63
63
  "typescript": "^5.5.0",
64
- "vitest": "^3.2.7"
64
+ "vitest": "^3.2.7",
65
+ "ws": "^8.21.3",
66
+ "@grpc/proto-loader": "^0.8.1",
67
+ "@grpc/grpc-js": "^1.14.6"
65
68
  },
66
69
  "repository": {
67
70
  "type": "git",
@@ -1 +0,0 @@
1
- var e={name:"@powerduck/openapi-cli",version:"0.2.14",description:"CI-ready CLI for batch-testing OpenAPI documents across HTTP, SSE, WebSocket, GraphQL, gRPC and MCP",type:"module",main:"./dist/index.cjs",module:"./dist/index.js",types:"./dist/index.d.ts",exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/index.cjs"},"./package.json":"./package.json"},bin:{"openapi-cli":"./dist/cli.js"},files:["dist","README.md","LICENSE"],sideEffects:!1,engines:{node:">=18.17"},scripts:{build:"tsc --noEmit && tsup",typecheck:"tsc --noEmit",test:"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage",prepublishOnly:"npm run build",check:"tsc --noEmit && vitest run"},keywords:["openapi","cli","testing","grpc","mcp","graphql","sse","websocket","ci","api-testing","contract-testing"],license:"MIT",dependencies:{"@powerduck/openapi-parser":"^0.3.9","@powerduck/openapi-request":"^0.2.15",commander:"^12.1.0","https-proxy-agent":"^5.0.1"},devDependencies:{"@types/node":"^20.14.0","@vitest/coverage-v8":"^3.2.7",terser:"^5.51.2",tsup:"^8.0.0",typescript:"^5.5.0",vitest:"^3.2.7"},repository:{type:"git",url:"git+https://github.com/powerducklab/openapi-cli.git"},bugs:{url:"https://github.com/powerducklab/openapi-cli/issues"},author:"POWERDUCK LIMITED",homepage:"https://www.powerduck.com/",publishConfig:{access:"public"}};import t from"fs";import r from"path";import s from"http";import o from"https";import{dereference as n}from"@powerduck/openapi-parser";var a={outputDir:"./openapi-cli-report",formats:["json","cli","html"],concurrency:5,timeout:3e4,failOnError:!0,grpcReflection:!0,mcpTransport:"streamable-http"};function i(e){for(const[t,r,s]of[["concurrency",e.concurrency??5,100],["timeout",e.timeout??3e4,6e5]])if(!Number.isInteger(r)||r<1||r>s)throw new Error(`${t} must be an integer between 1 and ${s}.`);if(e.formats?.some(e=>!["json","cli","html"].includes(e)))throw new Error("Unsupported report format.");if(e.mcpTransport&&!["stdio","streamable-http"].includes(e.mcpTransport))throw new Error("Unsupported MCP transport.")}function c(e){const s=e.config?function(e){const s=r.resolve(e);if(!t.existsSync(s))throw new Error(`Config file not found: ${s}`);const o=t.readFileSync(s,"utf-8");try{const e=JSON.parse(o);if(!e||"object"!=typeof e||Array.isArray(e))throw new Error("Config must be an object.");return e}catch(e){throw new Error(`Invalid config file (must be JSON): ${e.message}`)}}(e.config):{},o={specPath:e.spec??s.specPath??"",serverUrl:e.server??s.serverUrl,outputDir:e.output??s.outputDir??a.outputDir,formats:p(e.format??s.formats),filter:d(e,s),concurrency:e.concurrency??s.concurrency??a.concurrency,timeout:e.timeout??s.timeout??a.timeout,proxy:e.proxy??s.proxy,tls:{caCert:e.ca??s.tls?.caCert,clientCert:e.cert??s.tls?.clientCert,clientKey:e.key??s.tls?.clientKey,pfx:s.tls?.pfx,passphrase:s.tls?.passphrase,strictSSL:void 0!==e.insecure?!e.insecure:s.tls?.strictSSL},requester:s.requester,maxEvents:s.maxEvents,maxStreamMs:s.maxStreamMs,maxResponseSize:s.maxResponseSize,headers:l(e.header,s.headers),auth:u(e,s),variables:f(e.variable,s.variables),failOnError:e.failOnError??s.failOnError??a.failOnError,envFile:e.env??s.envFile,grpcReflection:e.grpcReflection??s.grpcReflection,grpcProtoPaths:e.grpcProto??s.grpcProtoPaths,mcpTransport:e.mcpTransport??s.mcpTransport,mcpCommand:e.mcpCommand??s.mcpCommand,mcpArgs:e.mcpArgs?e.mcpArgs.split(/[\s,]+/).filter(Boolean):s.mcpArgs,mcpCwd:e.mcpCwd??s.mcpCwd};if(o.envFile&&function(e){const s=r.resolve(e);if(!t.existsSync(s))return;const o=t.readFileSync(s,"utf-8").split("\n");for(const e of o){const t=e.trim();if(!t||t.startsWith("#"))continue;const r=t.indexOf("=");if(r>0){const e=t.slice(0,r).trim(),s=t.slice(r+1).trim().replace(/^["']|["']$/g,"");e in process.env||(process.env[e]=s)}}}(o.envFile),function(e){const t=e=>"string"!=typeof e?e:e.replace(/\$\{([^}]+)\}/g,(e,t)=>process.env[t]??"");if(e.serverUrl=t(e.serverUrl),e.proxy=t(e.proxy),e.headers)for(const[r,s]of Object.entries(e.headers))e.headers[r]=t(s);e.auth?.token&&(e.auth.token=t(e.auth.token));if(e.variables)for(const[r,s]of Object.entries(e.variables))e.variables[r]=t(s)}(o),!o.specPath)throw new Error("No OpenAPI spec path provided. Use --spec <path> or set specPath in config.");if(o.proxy)try{new URL(o.proxy)}catch{throw new Error(`Invalid proxy URL: ${o.proxy}`)}return i(o),o}function p(e){return Array.isArray(e)?e:"string"==typeof e?e.split(",").map(e=>e.trim().toLowerCase()).filter(Boolean):a.formats}function d(e,t){const r=e.method?e.method.split(",").map(e=>e.trim().toLowerCase()):t.filter?.methods,s=e.path?e.path.split(",").map(e=>e.trim()):t.filter?.paths,o=e.tag?e.tag.split(",").map(e=>e.trim()):t.filter?.tags,n=e.operationId?e.operationId.split(",").map(e=>e.trim()):t.filter?.operationIds;if(r||s||o||n)return{methods:r,paths:s,tags:o,operationIds:n}}function l(e,t){const r={...t??{}};for(const t of e??[]){const e=t.indexOf(":");if(e>0){const s=t.slice(0,e).trim(),o=t.slice(e+1).trim();s&&(r[s]=o)}}return Object.keys(r).length?r:void 0}function u(e,t){return e.bearer?{type:"bearer",token:e.bearer}:t.auth}function f(e,t){const r={...t??{}};for(const t of e??[]){const e=t.indexOf("=");e>0&&(r[t.slice(0,e).trim()]=t.slice(e+1).trim())}return Object.keys(r).length?r:void 0}function m(e){return/^https?:\/\//i.test(e.trim())}async function h(e){const i=e.specPath.trim();let c;if(m(i))c=await function(e,t){let r=0;return new Promise((n,a)=>{const i=c=>{const p=("https:"===new URL(c).protocol?o:s).get(c,{timeout:t,headers:{Accept:"application/json, text/plain, */*","User-Agent":"@powerduck/openapi-cli"}},t=>{if(t.statusCode&&t.statusCode>=300&&t.statusCode<400&&t.headers.location){if(++r>5)return t.resume(),void a(new Error(`Too many redirects (>5) while fetching spec: ${e}`));const s=new URL(t.headers.location,c).toString();return t.resume(),void i(s)}if(200!==t.statusCode)return t.resume(),void a(new Error(`Failed to fetch spec from ${c}: HTTP ${t.statusCode} ${t.statusMessage??""}`.trim()));const s=[];t.on("data",e=>s.push(e)),t.on("end",()=>n(Buffer.concat(s).toString("utf-8"))),t.on("error",e=>a(new Error(`Error reading spec response: ${e.message}`)))});p.on("timeout",()=>{p.destroy(new Error(`Timed out after ${t}ms while fetching spec: ${e}`))}),p.on("error",t=>a(new Error(`Network error fetching spec ${e}: ${t.message}`)))};i(e)})}(i,e.timeout??a.timeout);else{const e=r.resolve(i);if(!t.existsSync(e))throw new Error(`OpenAPI spec not found: ${e}`);if(!t.statSync(e).isFile())throw new Error(`OpenAPI spec path is not a file: ${e}`);c=t.readFileSync(e,"utf-8")}const p=function(e,t){let r;try{r=JSON.parse(e)}catch(e){throw new Error(`Spec from ${t} is not valid JSON: ${e.message}`)}if(!r||"object"!=typeof r||Array.isArray(r))throw new Error(`Spec from ${t} is not a JSON object`);if(!r.paths||"object"!=typeof r.paths)throw new Error(`Spec from ${t} does not contain a valid 'paths' object`);return r}(c,i);return async function(e){try{const t=await n(e);return t.errors&&t.errors.length>0?e:t.schema??t.specification??e}catch(t){return e}}(p)}function g(e,t){return e.map(e=>{try{const r=function(e,t){switch(e.assert){case"status":if(void 0===e.value)throw new Error("status assertion requires 'value'");return t.status===e.value;case"header":{if(!e.key)throw new Error("header assertion requires 'key'");const r=function(e,t){const r=t.toLowerCase();for(const[t,s]of Object.entries(e))if(t.toLowerCase()===r)return s;return}(t.headers??{},e.key);return void 0!==r&&(void 0===e.contains||r.toLowerCase().includes(e.contains.toLowerCase()))}case"bodyContains":if(void 0===e.contains)throw new Error("bodyContains assertion requires 'contains'");return(t.bodyText??JSON.stringify(t.body??"")).includes(e.contains);case"bodyEquals":if(void 0===e.body)throw new Error("bodyEquals assertion requires 'body'");return(t.bodyText??JSON.stringify(t.body??""))===e.body;case"jsonPath":{if(!e.path)throw new Error("jsonPath assertion requires 'path'");const r=v(t.body,e.path);return void 0!==e.exists?e.exists?void 0!==r:void 0===r:void 0!==e.equals?b(r,e.equals):void 0!==r}case"responseTime":if(void 0===e.max)throw new Error("responseTime assertion requires 'max'");return t.durationMs<=e.max;default:throw new Error(`Unknown assertion type: ${e.assert}`)}}(e,t);return{name:e.name,passed:r}}catch(t){return{name:e.name,passed:!1,error:t.message}}})}function v(e,t){const r=t.replace(/^\$\.?/,"");if(!r)return e;const s=r.split(/\.|\[(\d+)\]/).filter(e=>void 0!==e&&""!==e);let o=e;for(const e of s){if(null==o)return;o=/^\d+$/.test(e)?o[Number(e)]:o[e]}return o}function b(e,t){if(e===t)return!0;if(typeof e!=typeof t)return!1;if(null===e||null===t)return e===t;if("object"!=typeof e)return e===t;if(Array.isArray(e)!==Array.isArray(t))return!1;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,r)=>b(e,t[r]));const r=Object.keys(e),s=Object.keys(t);return r.length===s.length&&r.every(r=>b(e[r],t[r]))}function y(e,t){const r=e?.["x-tests"],s=t?.["x-tests"];return[...Array.isArray(s)?s:[],...Array.isArray(r)?r:[]].filter(e=>e&&"object"==typeof e&&e.assert&&e.name)}function w(e){const t=e?.["x-postman-scripts"];if(t&&"object"==typeof t&&"string"==typeof t.test)return t.test}import{operationEntries as x}from"@powerduck/openapi-parser/methods";import{createClient as $}from"@powerduck/openapi-request";import k from"fs";import{createPrivateKey as E}from"crypto";import S from"http";import C from"https";import O from"https-proxy-agent";var{HttpsProxyAgent:j}=O;function P(e){const t=e.tls;return{...t?.caCert?{ca:k.readFileSync(t.caCert)}:{},...t?.clientCert?{cert:k.readFileSync(t.clientCert)}:{},...t?.clientKey?{key:k.readFileSync(t.clientKey)}:{},...t?.pfx?{pfx:k.readFileSync(t.pfx)}:{},...t?.passphrase?{passphrase:t.passphrase}:{},rejectUnauthorized:!1!==t?.strictSSL}}function R(e){const t=P(e);if(e.proxy){const r=new URL(e.proxy);if(!["http:","https:"].includes(r.protocol))throw new Error("Only HTTP(S) CONNECT proxies are supported.");class s extends j{callback(e,r){return super.callback(e,{...r,...t})}}const o=new s({protocol:r.protocol,host:r.hostname,port:Number(r.port||("https:"===r.protocol?443:80)),...r.username||r.password?{auth:`${decodeURIComponent(r.username)}:${decodeURIComponent(r.password)}`}:{},ca:t.ca,rejectUnauthorized:t.rejectUnauthorized});return{http:o,https:o}}return{http:new S.Agent,https:new C.Agent(t)}}function T(e,t){if(e.proxy||Object.entries(e.tls||{}).some(([e,t])=>"strictSSL"===e?!1===t:Boolean(t)))throw new Error(`${t} custom proxy/TLS is not supported by the bundled request engine.`)}function A(e,t={}){return"string"==typeof e?e.replace(/\{\{([^{}]+)\}\}/g,(e,r)=>{if(!Object.prototype.hasOwnProperty.call(t,r))throw new Error(`Variable ${r} is not defined.`);return t[r]}):Array.isArray(e)?e.map(e=>A(e,t)):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,r])=>[e,A(r,t)])):e}function U(e,t,r){const s=e.auth,o=(e,r)=>{for(const r of Object.keys(t))r.toLowerCase()===e.toLowerCase()&&delete t[r];t[e]=r};if(!s||"none"===s.type)return r;if("basic"===s.type)o("Authorization",`Basic ${Buffer.from(`${s.username||""}:${s.password||""}`).toString("base64")}`);else if("bearer"===s.type)o("Authorization",`Bearer ${s.token||""}`);else{if("apikey"!==s.type)throw new Error(`Authentication ${s.type} is supported only by the HTTP/SSE runner.`);if(!s.key)throw new Error("API key name is required.");if("query"===s.in){if(!r)throw new Error("gRPC API keys require metadata headers.");const e=new URL(r);e.searchParams.set(s.key,s.value||""),r=e.href}else o(s.key,s.value||"")}return r}function q(e,t){const r={"grpc.enable_http_proxy":0};if(!e.proxy)return{address:t,channelOptions:r};const s=new URL(e.proxy);if("http:"!==s.protocol)throw new Error("gRPC supports only HTTP CONNECT proxies.");const o=new URL(`https://${t}`);return r["grpc.http_connect_target"]=`dns:///${t}`,r["grpc.default_authority"]=o.host,r["grpc.ssl_target_name_override"]=o.hostname,(s.username||s.password)&&(r["grpc.http_connect_creds"]=`${decodeURIComponent(s.username)}:${decodeURIComponent(s.password)}`),{address:`${s.hostname}:${s.port||80}`,channelOptions:r}}function L(e){return e.key&&e.passphrase?Buffer.from(E({key:e.key,passphrase:e.passphrase}).export({format:"pem",type:"pkcs8"})):e.key}function I(e,t){const r=new URL(e);for(const[e,s]of Object.entries(t||{}))if(null!=s){r.searchParams.delete(e);for(const t of Array.isArray(s)?s:[s])r.searchParams.append(e,String(t))}return r.href}var M=e.version;function z(e){if(!e)return;if("string"==typeof e)return e;if(e instanceof Error)return e.message;const t=e;if("string"==typeof t.message)return t.message;if("string"==typeof t.msg)return t.msg;try{return JSON.stringify(e)}catch{return String(e)}}function N(e,t){const r=[];if("http"===e||"sse"===e){const e=t?.status,s="number"==typeof e&&e>=200&&e<300;r.push({name:"HTTP status is 2xx (implicit)",passed:s,error:s?void 0:`expected 2xx, got ${e??"no status"}`})}else if("graphql"===e){const e=t?.status,s="number"==typeof e&&e>=200&&e<300,o=t?.body,n=Array.isArray(o?.errors)&&o.errors.length>0,a=s&&!n;let i;s?n&&(i=`GraphQL response contains ${o.errors.length} error(s)`):i=`expected HTTP 2xx, got ${e??"no status"}`,r.push({name:"GraphQL success (implicit)",passed:a,error:i})}else if("grpc"===e){const e=t?.statusCode??t?.code??t?.status,s=t?.statusName??t?.status,o=0===e;r.push({name:"gRPC status is OK (implicit)",passed:o,error:o?void 0:`expected code 0 (OK), got ${e??"unknown"}${s?` (${s})`:""}`})}else if("mcp"===e){const e=t?.body??{},s=void 0!==e.result,o=void 0!==e.error||!0===e.result?.isError||void 0!==t?.error,n=s&&!o;let a;o?a="MCP JSON-RPC response contains an error":s||(a="MCP JSON-RPC response missing 'result'"),r.push({name:"MCP JSON-RPC success (implicit)",passed:n,error:a})}else if("websocket"===e){const e=t?.events??[],s=e.some(e=>"open"===e.kind||"open"===e.state),o=e.some(e=>"error"===e.kind||"error"===e.state),n=s&&!o;r.push({name:"WebSocket connected (implicit)",passed:n,error:n?void 0:o?"WebSocket connection errored":"WebSocket did not reach open state"})}return r}async function F(e){i(e);const t=await h(e),r=$(),s=D(t,e),o=Date.now(),n=[];let a=0;const c=Math.min(e.concurrency??5,s.length),p=[];for(let o=0;o<c;o++)p.push((async()=>{for(;a<s.length;){if(e.signal?.aborted)throw new Error("Run cancelled.");const o=s[a++];if(!o)break;const i=await H(r,o,e,t);n.push(i)}})());await Promise.all(p);const d=Date.now()-o;return{summary:G(n,d),results:n.sort((e,t)=>e.path.localeCompare(t.path)||e.method.localeCompare(t.method)),config:e,generatedAt:(new Date).toISOString(),version:M}}function D(e,t){const r=[];for(const[s,o]of Object.entries(e.paths??{})){if(!o||"object"!=typeof o)continue;const e=o;for(const[o,n]of x(e)){if(!n||"object"!=typeof n)continue;const a={path:s,method:o,operation:n,pathItem:e,operationId:n.operationId??`${o.toUpperCase()} ${s}`,protocol:B(n,e)};J(a,t.filter)&&r.push(a)}}return r}function B(e,t){const r=e["x-protocol"]??t?.["x-protocol"];if(r)return String(r).toLowerCase();const s=e.responses??{};for(const e of Object.values(s)){if((e?.content??{})["text/event-stream"])return"sse"}return e["x-response-stream"]?"sse":"http"}function J(e,t){if(!t)return!0;if(t.methods?.length&&!t.methods.includes(e.method.toLowerCase()))return!1;if(t.operationIds?.length&&!t.operationIds.includes(e.operationId))return!1;if(t.tags?.length){const r=e.operation.tags??[];if(!t.tags.some(e=>r.includes(e)))return!1}if(t.paths?.length){if(!t.paths.some(t=>{try{return new RegExp(t).test(e.path)}catch{return e.path.includes(t)}}))return!1}return!0}async function H(e,t,r,s){return(await _(e,t,{config:r,spec:s,variables:r.variables})).result}async function _(e,t,r){let{config:s}=r;const{spec:o}=r,n=Date.now(),a={operationId:t.operationId,path:t.path,method:t.method.toUpperCase(),protocol:t.protocol};try{if((r.signal||s.signal)?.aborted)throw new Error("Run cancelled.");const i=r.variables||s.variables,c=t.operation["x-websocket"]||t.operation["x-ws"]||t.operation["x-grpc"]||t.operation["x-mcp"]||{},p=A(r.serverUrl??s.serverUrl??c.url??c.address??c.endpoint??o.servers?.[0]?.url,i);s.resolveTransport&&(s={...s,...await s.resolveTransport({protocol:t.protocol,url:p})});const d=["grpc","websocket","mcp"].includes(t.protocol),l={...s,serverUrl:A(s.serverUrl,i),auth:d?A(s.auth,i):s.auth,headers:d?A({...s.headers,...r.values?.header},i):s.headers,signal:r.signal||s.signal},u={...t,operation:d?{...t.operation,...Object.fromEntries(Object.entries(t.operation).filter(([e])=>["x-grpc","x-mcp","x-ws","x-websocket"].includes(e)).map(([e,t])=>[e,A(t,i)]))}:t.operation},f=d?A(r.values,i):r.values;if(!["http","sse","graphql","grpc","websocket","mcp"].includes(t.protocol))throw new Error(`Unsupported protocol: ${t.protocol}`);const m=[...y(t.operation,t.pathItem),...r.extraAssertions??[]],h=w(t.operation),v=[];let b,x,$={};switch(t.protocol){case"grpc":{const t=await async function(e,t,r,s,o){const n=t.operation["x-grpc"]??{},a=(s??r.serverUrl??n.address)?.replace(/^https?:\/\//,"");if(!a)throw new Error("gRPC operation requires x-grpc.address or --server");const i={...r.headers};U(r,i);const c=P(r);if(c.pfx)throw new Error("gRPC requires PEM certificates, not PFX.");const p=e.connect({kind:"grpc",deadlineMs:r.timeout??3e4,streamDeadlineMs:r.maxStreamMs??r.timeout??3e4,...q(r,a),tls:!(!0!==n.tls&&!/^https:/.test(s??r.serverUrl??""))&&{rootCerts:c.ca,privateKey:L(c),certChain:c.cert,rejectUnauthorized:c.rejectUnauthorized},service:n.service,method:n.method??t.operationId,reflection:r.grpcReflection??n.reflection??!(r.grpcProtoPaths??n.protoPaths)?.length,protoPaths:r.grpcProtoPaths??n.protoPaths,includeDirs:n.includeDirs,metadata:i}),d=()=>{p.close().catch(()=>{})};r.signal?.addEventListener("abort",d,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await p.open(),r.signal?.aborted)throw new Error("Run cancelled.");const e=p.kind,s=o?.body??function(e){const t=e["x-grpc"]??{};if(t.sample&&"object"==typeof t.sample)return t.sample;const r=e.requestBody?.content?.["application/json"]?.schema;if(r?.properties){const e={};for(const[t,s]of Object.entries(r.properties))e[t]=s.default??W(s.type);return e}return{}}(t.operation);"unary"===e||"server_streaming"===e?await p.send(s):(await p.send(s),await p.finishSending());const c=p.events??[],d=c.find(e=>"status"===e.kind),l=c.filter(e=>"data"===e.kind&&"in"===e.direction);"unary"!==e&&"server_streaming"!==e||await p.close();const u={status:d?.meta?.code??-1,statusText:d?.meta?.statusName,body:1===l.length?l[0].data:l.map(e=>e.data),events:c.map(e=>({kind:e.kind,direction:e.direction,data:e.data,meta:e.meta}))},f=n.method??t.operationId;return{response:u,request:{method:f,target:n.service?`${n.service}/${f}`:f,url:a,...Object.keys(s).length?{body:s}:{},...Object.keys(i).length?{headers:i}:{}}}}finally{r.signal?.removeEventListener("abort",d),await p.close().catch(()=>{})}}(e,u,l,A(r.serverUrl,i),f);b=t.response,x=t.request;break}case"websocket":{const t=await async function(e,t,r,s,o){const n=t.operation["x-websocket"]??t.operation["x-ws"]??{};let a=K(n.url,s??r.serverUrl,t.path)?.replace(/^http/,"ws");if(!a)throw new Error("WebSocket operation requires x-ws.url or --server");const i={...r.headers};a=U(r,i,I(a,o?.query));const c=R(r),p=e.connect({kind:"websocket",openTimeoutMs:r.timeout??3e4,signal:r.signal,clientOptions:{...P(r),agent:a.startsWith("wss:")?c.https:c.http},url:a,subprotocols:n.subprotocols,headers:i}),d=()=>{p.close().catch(()=>{})};r.signal?.addEventListener("abort",d,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await p.open(),r.signal?.aborted)throw new Error("Run cancelled.");const e=void 0!==o?.body?[o.body]:n.send??[];for(const t of e.slice(0,3))await p.send(t);await new Promise(e=>setTimeout(e,Math.min(500,r.timeout??500))),await p.close();const t=p.events??[],s=t.filter(e=>"in"===e.direction&&"open"!==e.kind);return{response:{status:101,statusText:"Switching Protocols",body:s.map(e=>e.data),events:t.map(e=>({kind:e.kind,direction:e.direction,data:e.data})),streaming:!0},request:{method:"WS",url:a,...e.length?{body:e.slice(0,3)}:{},...Object.keys(i).length?{headers:i}:{}}}}finally{r.signal?.removeEventListener("abort",d),await p.close().catch(()=>{});for(const e of new Set(Object.values(c)))e.destroy()}}(e,u,l,A(r.serverUrl,i),f);b=t.response,x=t.request;break}case"mcp":{const t=await async function(e,t,r,s,o){const n=t.operation["x-mcp"]??{},a=r.mcpTransport??n.transport??"streamable-http",i={...r.headers};let c=K(n.endpoint,s??r.serverUrl,t.path);"stdio"!==a&&(T(r,"MCP HTTP"),c=U(r,i,c?I(c,o?.query):c));const p=o?.body?.method??n.method??"tools/call",d=o?.body?.params??n.params??("tools/call"===p?{name:n.name,arguments:o?.body??n.arguments??{}}:{}),l=e.connect({kind:"mcp",transport:"stdio"===a?"stdio":"streamable-http",endpoint:c,headers:i,command:r.mcpCommand??n.command,args:r.mcpArgs??n.args,cwd:r.mcpCwd??n.cwd,timeoutMs:r.timeout??3e4}),u=()=>{l.close().catch(()=>{})};r.signal?.addEventListener("abort",u,{once:!0});try{if(r.signal?.aborted)throw new Error("Run cancelled.");if(await l.open(),r.signal?.aborted)throw new Error("Run cancelled.");return{response:{status:200,statusText:"OK",body:{result:await l.request(p,d,{signal:r.signal,timeoutMs:r.timeout})}},request:{method:p,url:c,headers:i,body:d}}}finally{r.signal?.removeEventListener("abort",u),await l.close().catch(()=>{})}}(e,u,l,A(r.serverUrl,i),f);b=t.response,x=t.request;break}default:{const n=await async function(e,t,r,s,o){"graphql"===t.protocol&&T(r,"GraphQL");const n="graphql"===t.protocol?void 0:R(r),a={...r.httpRuntime,requester:{...r.requester,strictSSL:!1!==r.tls?.strictSSL,ignoreProxyEnvironmentVariables:!0,...n?{agents:n,...r.proxy||r.tls?.caCert||r.tls?.clientCert||r.tls?.pfx||r.httpRuntime?.certificates?{protocolVersion:"http1"}:{}}:{}}},i=o.values,c={...r.headers??{},...i?.header??{}},p={...r.headers?{header:r.headers}:{},...i??{},...Object.keys(c).length?{header:c}:{}};let d;try{d=await e.send({writeBack:!1,signal:o.signal,spec:s,target:{path:t.path,method:t.method},serverUrl:o.serverUrl??r.serverUrl,values:Object.keys(p).length?p:void 0,auth:r.auth,variables:o.variables,timeout:r.timeout,runner:Object.keys(a).length?a:void 0,scripts:o.postmanTest?{test:{exec:o.postmanTest},fromSpecExtensions:!1}:void 0,onAssertion:e=>{o.postmanAssertions.push({name:e.name??"assertion",passed:e.passed,error:z(e.error)})},maxStreamMs:r.maxStreamMs??r.timeout,maxEvents:r.maxEvents??50,maxResponseSize:r.maxResponseSize})}finally{for(const e of new Set(Object.values(n||{})))e.destroy()}if(o.signal?.aborted)throw new Error("Run cancelled.");if(d.error)throw new Error(d.error.message);const l={},u=d.scripts;if(u)for(const e of[...u.prerequest??[],...u.test??[]])e.environment&&Object.assign(l,e.environment);const f=d.request;return{response:{status:d.response?.status,statusText:d.response?.statusText,contentType:d.response?.contentType,sizeBytes:d.response?.sizeBytes,body:d.response?.body,text:d.response?.text,headers:d.response?.headers,events:d.response?.events,streaming:d.response?.streaming},request:f?{method:f.method,url:f.url,headers:f.headers,...void 0!==f.body?{body:f.body}:{}}:void 0,env:l}}(e,t,s,o,{postmanTest:h,postmanAssertions:v,variables:r.variables,values:r.values,serverUrl:r.serverUrl,signal:r.signal||s.signal});b=n.response,x=n.request,$=n.env;break}}if((r.signal||s.signal)?.aborted)throw new Error("Run cancelled.");const k=Date.now()-n,E=m.length>0||v.length>0?[]:N(t.protocol,b),S=m.length?g(m,{status:b?.status,headers:b?b.headers:void 0,body:b?.body,bodyText:b?b.text:void 0,durationMs:k}):[],C=new Set,O=v.filter(e=>{const t=`${e.name}|${e.passed}|${e.error??""}`;return!C.has(t)&&(C.add(t),!0)}),j=[...S,...O,...E],M=j.some(e=>!e.passed);return{result:{...a,status:M?"failed":"passed",durationMs:k,request:x,response:b,assertions:j.length?j:void 0,timestamp:(new Date).toISOString()},response:b,request:x,env:$}}catch(e){const o=Date.now()-n,i=r.values,c={...s.headers??{},...i?.header??{}},p=Object.fromEntries(Object.entries(c).map(([e,t])=>[e,null==t?"":String(t)])),d={method:t.method.toUpperCase(),url:r.serverUrl??s.serverUrl,...Object.keys(p).length?{headers:p}:{},...void 0!==i?.body?{body:i.body}:{}};return{result:{...a,status:"error",durationMs:o,request:d,error:e.message??String(e),timestamp:(new Date).toISOString()},response:void 0,request:d,env:{}}}}function K(e,t,r){if(!t)return e;const s=new URL(t),o=new URL(e||r,t);return o.host=s.host,o.protocol=s.protocol,e||"/"===s.pathname||(o.pathname=s.pathname.replace(/\/$/,"")+r),o.href}function W(e){switch(e){case"string":default:return"";case"number":case"integer":return 0;case"boolean":return!1;case"array":return[];case"object":return{}}}function G(e,t){const r=e.length,s=e.filter(e=>"passed"===e.status).length;return{total:r,passed:s,failed:e.filter(e=>"failed"===e.status).length,errors:e.filter(e=>"error"===e.status).length,skipped:e.filter(e=>"skipped"===e.status).length,durationMs:t,passRate:r>0?Math.round(s/r*1e3)/10:0}}import Q from"fs";import V from"path";function X(e,t){const r=V.resolve(t);Q.mkdirSync(r,{recursive:!0});const s=V.join(r,"report.json");return Q.writeFileSync(s,JSON.stringify(e,null,2),"utf-8"),s}var Y="",Z="",ee="",te="",re="",se="",oe="",ne="",ae="",ie="",ce="";function pe(e){if(null==e||!Number.isFinite(e))return`${ee}--ms${Y}`;return`${e>500?`${Z}${se}`:Z}${e}${Y}${ee}ms${Y}`}function de(e){const t=[],{summary:r,results:s}=e;t.push(""),t.push(`${Z}${oe}╔══════════════════════════════════════════════════════╗${Y}`),t.push(`${Z}${oe}║ Powerduck Test Report ║${Y}`),t.push(`${Z}${oe}╚══════════════════════════════════════════════════════╝${Y}`),t.push("");const o=r.passRate>=80?te:r.passRate>=50?se:re;t.push(`${Z} Total:${Y} ${r.total} ${te}Passed:${Y} ${r.passed} ${re}Failed:${Y} ${r.failed} ${se}Errors:${Y} ${r.errors} ${o}Rate:${Y} ${r.passRate}% ${ee}Time:${Y} ${pe(r.durationMs)}`),t.push("");const n=r.total>0?Math.round(r.passed/r.total*50):0,a=r.total>0?Math.round(r.failed/r.total*50):0,i=r.total>0?Math.round(r.errors/r.total*50):0,c=te+"█".repeat(n)+re+"█".repeat(a)+se+"█".repeat(i)+ne+"░".repeat(Math.max(0,50-n-a-i))+Y;t.push(` [${c}]`),t.push("");for(const e of s)t.push(le(e));return t.push(""),r.failed>0||r.errors>0?t.push(`${Z}${re} ✗ ${r.failed+r.errors} test(s) did not pass${Y}`):t.push(`${Z}${te} ✓ All ${r.total} test(s) passed${Y}`),t.push(` ${ee}Generated: ${e.generatedAt}${Y}`),t.push(""),t.join("\n")}function le(e){const t="passed"===e.status?`${te}✓${Y}`:"failed"===e.status?`${re}✗${Y}`:"error"===e.status?`${se}!${Y}`:`${ne}-${Y}`,r="passed"===e.status?`${ae}${Z} PASS ${Y}`:"failed"===e.status?`${ie}${Z} FAIL ${Y}`:"error"===e.status?`${ce}${Z} ERROR ${Y}`:`${ne} SKIP ${Y}`,s=e.protocol?` ${oe}[${e.protocol}]${Y}`:"",o=null!=e.response?.status?` ${ee}${e.response.status}${Y}`:"";let n=` ${t} ${r} ${Z}${e.method}${Y} ${e.path}${s}${o} ${pe(e.durationMs)}`;if(e.error&&(n+=`\n ${re}${e.error}${Y}`),e.assertions?.length)for(const t of e.assertions){n+=`\n ${t.passed?`${te} ✓${Y}`:`${re} ✗${Y}`} ${t.name}${t.error?` — ${re}${t.error}${Y}`:""}`}return n}function ue(e){const t=de(e);return console.log(t),t}import fe from"fs";import me from"path";function he(e,t){const r=me.resolve(t);fe.mkdirSync(r,{recursive:!0});const s=me.join(r,"report.html");return fe.writeFileSync(s,function(e){const{summary:t,results:r}=e,s=JSON.stringify({summary:t,results:r});return`<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>Test Report — Powerduck</title>\n<style>\n *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\n :root{\n --bg:#ffffff;--fg:#0f172a;--muted:#64748b;--border:#e2e8f0;\n --bg-subtle:#f8fafc;--bg-hover:#f1f5f9;\n --green:#16a34a;--green-bg:#f0fdf4;--green-border:#bbf7d0;\n --red:#dc2626;--red-bg:#fef2f2;--red-border:#fecaca;\n --amber:#d97706;--amber-bg:#fffbeb;--amber-border:#fde68a;\n --blue:#2563eb;--mono:ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,monospace;\n --sans:ui-sans-serif,system-ui,-apple-system,'Segoe UI',Roboto,Inter,sans-serif;\n }\n @media (prefers-color-scheme:dark){\n :root{\n --bg:#0f172a;--fg:#f1f5f9;--muted:#94a3b8;--border:#1e293b;\n --bg-subtle:#1e293b;--bg-hover:#334155;\n --green:#4ade80;--green-bg:rgba(74,222,128,.1);--green-border:rgba(74,222,128,.25);\n --red:#f87171;--red-bg:rgba(248,113,113,.1);--red-border:rgba(248,113,113,.25);\n --amber:#fbbf24;--amber-bg:rgba(251,191,36,.1);--amber-border:rgba(251,191,36,.25);\n --blue:#60a5fa;\n }\n }\n body{font-family:var(--sans);background:var(--bg);color:var(--fg);line-height:1.5;font-size:14px;-webkit-font-smoothing:antialiased}\n .wrap{max-width:960px;margin:0 auto;padding:32px 24px 64px}\n\n /* Header */\n .hdr{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--border)}\n .hdr h1{font-size:18px;font-weight:600;letter-spacing:-.01em}\n .hdr .meta{font-size:12px;color:var(--muted);font-family:var(--mono)}\n\n /* Summary strip */\n .strip{display:grid;grid-template-columns:repeat(6,1fr);gap:0;border:1px solid var(--border);border-radius:8px;overflow:hidden;margin-bottom:24px}\n .stat{padding:14px 16px;border-right:1px solid var(--border);text-align:center}\n .stat:last-child{border-right:none}\n .stat .v{font-size:22px;font-weight:700;letter-spacing:-.02em;line-height:1.2}\n .stat .l{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-top:2px}\n .stat.total .v{color:var(--fg)}\n .stat.pass .v{color:var(--green)}\n .stat.fail .v{color:var(--red)}\n .stat.err .v{color:var(--amber)}\n .stat.rate .v{color:var(--blue)}\n .stat.time .v{font-size:16px;font-weight:600}\n\n /* Progress */\n .bar{height:4px;background:var(--border);border-radius:2px;overflow:hidden;margin-bottom:24px;display:flex}\n .bar>div{height:100%}\n .bar .p{background:var(--green)}\n .bar .f{background:var(--red)}\n .bar .e{background:var(--amber)}\n\n /* Filters */\n .filters{display:flex;gap:4px;margin-bottom:16px}\n .fbtn{padding:4px 12px;border-radius:6px;border:1px solid transparent;background:transparent;color:var(--muted);cursor:pointer;font-size:12px;font-weight:500;transition:all .12s}\n .fbtn:hover{color:var(--fg);background:var(--bg-hover)}\n .fbtn.active{background:var(--bg-subtle);color:var(--fg);border-color:var(--border)}\n .fbtn .n{color:var(--muted);margin-left:4px;font-variant-numeric:tabular-nums}\n\n /* Test rows */\n .row{border:1px solid var(--border);border-radius:8px;margin-bottom:6px;overflow:hidden;transition:border-color .12s}\n .row:hover{border-color:var(--muted)}\n .row-head{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;user-select:none}\n .dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n .dot.pass{background:var(--green)}\n .dot.fail{background:var(--red)}\n .dot.err{background:var(--amber)}\n .method{font-family:var(--mono);font-size:11px;font-weight:600;color:var(--muted);min-width:40px;text-transform:uppercase}\n .path{font-family:var(--mono);font-size:13px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n .proto{font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);padding:2px 8px;border:1px solid var(--border);border-radius:4px}\n .dur{font-family:var(--mono);font-size:12px;color:var(--muted);min-width:56px;text-align:right;font-variant-numeric:tabular-nums}\n .chev{color:var(--muted);font-size:10px;transition:transform .15s;flex-shrink:0}\n .row.open .chev{transform:rotate(90deg)}\n\n /* Row body */\n .row-body{display:none;padding:0 14px 12px 32px;border-top:1px solid var(--border)}\n .row.open .row-body{display:block}\n .sect{margin-top:10px}\n .sect-t{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:600;margin-bottom:6px}\n .asrt{display:flex;align-items:center;gap:8px;padding:3px 0;font-size:13px}\n .asrt .ck{width:14px;height:14px;border-radius:3px;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:700;flex-shrink:0;color:#fff}\n .asrt.pass .ck{background:var(--green)}\n .asrt.fail .ck{background:var(--red)}\n .asrt .an{color:var(--fg)}\n .asrt .ae{color:var(--red);font-size:12px;margin-left:6px;font-family:var(--mono)}\n .rmeta{display:flex;gap:20px;flex-wrap:wrap;font-size:12px}\n .rmeta span{color:var(--muted)}\n .rmeta strong{color:var(--fg);font-weight:600;font-family:var(--mono)}\n .errbox{background:var(--red-bg);border:1px solid var(--red-border);border-radius:6px;padding:8px 12px;color:var(--red);font-size:12px;font-family:var(--mono);margin-top:10px}\n .bprev{background:var(--bg-subtle);border:1px solid var(--border);border-radius:6px;padding:10px 12px;font-family:var(--mono);font-size:11px;color:var(--muted);max-height:180px;overflow:auto;white-space:pre-wrap;word-break:break-all;margin-top:6px}\n\n footer{margin-top:32px;padding-top:16px;border-top:1px solid var(--border);font-size:11px;color:var(--muted);display:flex;justify-content:space-between}\n\n @media(max-width:640px){\n .strip{grid-template-columns:repeat(3,1fr)}\n .stat:nth-child(3){border-right:none}\n .stat:nth-child(4),.stat:nth-child(5){border-top:1px solid var(--border)}\n .path{font-size:11px}\n }\n</style>\n</head>\n<body>\n<div class="wrap">\n <div class="hdr">\n <h1>Test Report</h1>\n <span class="meta">${e.generatedAt} · v${e.version}</span>\n </div>\n\n <div class="strip">\n <div class="stat total"><div class="v">${t.total}</div><div class="l">Total</div></div>\n <div class="stat pass"><div class="v">${t.passed}</div><div class="l">Passed</div></div>\n <div class="stat fail"><div class="v">${t.failed}</div><div class="l">Failed</div></div>\n <div class="stat err"><div class="v">${t.errors}</div><div class="l">Errors</div></div>\n <div class="stat rate"><div class="v">${t.passRate}%</div><div class="l">Pass Rate</div></div>\n <div class="stat time"><div class="v">${(t.durationMs/1e3).toFixed(2)}s</div><div class="l">Duration</div></div>\n </div>\n\n <div class="bar">\n <div class="p" style="width:${t.total?t.passed/t.total*100:0}%"></div>\n <div class="f" style="width:${t.total?t.failed/t.total*100:0}%"></div>\n <div class="e" style="width:${t.total?t.errors/t.total*100:0}%"></div>\n </div>\n\n <div class="filters">\n <button class="fbtn active" data-f="all">All<span class="n">${t.total}</span></button>\n <button class="fbtn" data-f="passed">Passed<span class="n">${t.passed}</span></button>\n <button class="fbtn" data-f="failed">Failed<span class="n">${t.failed}</span></button>\n <button class="fbtn" data-f="error">Errors<span class="n">${t.errors}</span></button>\n </div>\n\n <div id="list"></div>\n\n <footer>\n <span>@powerduck/openapi-cli v${e.version}</span>\n <span>${t.passed}/${t.total} passed</span>\n </footer>\n</div>\n\n<script>\nconst D=${s};\nfunction esc(s){const d=document.createElement('div');d.textContent=String(s??'');return d.innerHTML}\nfunction render(f){\n const list=document.getElementById('list');\n const items=f==='all'?D.results:D.results.filter(r=>r.status===f);\n list.innerHTML=items.map(r=>{\n const as=r.assertions?r.assertions.map(a=>\`\n <div class="asrt \${a.passed?'pass':'fail'}">\n <div class="ck">\${a.passed?'✓':'✗'}</div>\n <span class="an">\${esc(a.name)}</span>\n \${a.error?'<span class="ae">'+esc(a.error)+'</span>':''}\n </div>\`).join(''):'';\n const bp=r.response?.body?'<div class="bprev">'+esc(JSON.stringify(r.response.body,null,2).slice(0,3000))+'</div>':'';\n const eb=r.error?'<div class="errbox">'+esc(r.error)+'</div>':'';\n const rm=r.response?\`\n <div class="rmeta">\n \${r.response.status!=null?'<span>Status <strong>'+r.response.status+(r.response.statusText?' '+r.response.statusText:'')+'</strong></span>':''}\n \${r.response.contentType?'<span>Type <strong>'+esc(r.response.contentType)+'</strong></span>':''}\n \${r.response.sizeBytes!=null?'<span>Size <strong>'+r.response.sizeBytes+'B</strong></span>':''}\n \${r.response.streaming?'<span>Streaming <strong>yes</strong></span>':''}\n </div>\`:'';\n return \`\n <div class="row \${r.status}" data-s="\${r.status}">\n <div class="row-head" onclick="this.parentElement.classList.toggle('open')">\n <div class="dot \${r.status==='passed'?'pass':r.status==='failed'?'fail':'err'}"></div>\n <span class="method">\${r.method}</span>\n <span class="path">\${esc(r.path)}</span>\n \${r.protocol?'<span class="proto">'+r.protocol+'</span>':''}\n <span class="dur">\${r.durationMs}ms</span>\n <span class="chev">▶</span>\n </div>\n <div class="row-body">\n \${eb}\n \${rm}\n \${as?'<div class="sect"><div class="sect-t">Assertions</div>'+as+'</div>':''}\n \${bp?'<div class="sect"><div class="sect-t">Response</div>'+bp+'</div>':''}\n </div>\n </div>\`;\n }).join('');\n}\ndocument.querySelectorAll('.fbtn').forEach(b=>b.addEventListener('click',()=>{\n document.querySelectorAll('.fbtn').forEach(x=>x.classList.remove('active'));\n b.classList.add('active');render(b.dataset.f);\n}));\nrender('all');\n<\/script>\n</body>\n</html>`}(e),"utf-8"),s}export{e as package_default,a as DEFAULT_CONFIG,i as validateRunConfig,c as resolveConfig,m as isRemoteSpec,h as loadSpec,g as runDeclarativeAssertions,v as resolveJsonPath,y as extractAssertions,w as extractPostmanScripts,z as formatAssertionError,N as buildImplicitAssertions,F as runTests,D as collectOperations,_ as executeStep,G as buildSummary,X as generateJsonReport,de as generateCliReport,ue as printCliReport,he as generateHtmlReport};