@curl-runner/cli 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- var h=Object.create;var{getPrototypeOf:b,defineProperty:N,getOwnPropertyNames:C}=Object;var v=Object.prototype.hasOwnProperty;var V=($,w,K)=>{K=$!=null?h(b($)):{};let X=w||!$||!$.__esModule?N(K,"default",{value:$,enumerable:!0}):K;for(let Q of C($))if(!v.call(X,Q))N(X,Q,{get:()=>$[Q],enumerable:!0});return X};var j=import.meta.require;var{Glob:d}=globalThis.Bun;class L{static buildCommand($){let w=["curl"];if(w.push("-X",$.method||"GET"),w.push("-w",'"\\n__CURL_METRICS_START__%{json}__CURL_METRICS_END__"'),$.headers)for(let[X,Q]of Object.entries($.headers))w.push("-H",`"${X}: ${Q}"`);if($.auth){if($.auth.type==="basic"&&$.auth.username&&$.auth.password)w.push("-u",`"${$.auth.username}:${$.auth.password}"`);else if($.auth.type==="bearer"&&$.auth.token)w.push("-H",`"Authorization: Bearer ${$.auth.token}"`)}if($.body){let X=typeof $.body==="string"?$.body:JSON.stringify($.body);if(w.push("-d",`'${X.replace(/'/g,"'\\''")}'`),!$.headers?.["Content-Type"])w.push("-H",'"Content-Type: application/json"')}if($.timeout)w.push("--max-time",$.timeout.toString());if($.followRedirects!==!1){if(w.push("-L"),$.maxRedirects)w.push("--max-redirs",$.maxRedirects.toString())}if($.proxy)w.push("-x",$.proxy);if($.insecure)w.push("-k");if($.output)w.push("-o",$.output);w.push("-s","-S");let K=$.url;if($.params&&Object.keys($.params).length>0){let X=new URLSearchParams($.params).toString();K+=(K.includes("?")?"&":"?")+X}return w.push(`"${K}"`),w.join(" ")}static async executeCurl($){try{let w=Bun.spawn(["sh","-c",$],{stdout:"pipe",stderr:"pipe"}),K=await new Response(w.stdout).text(),X=await new Response(w.stderr).text();if(await w.exited,w.exitCode!==0&&!K)return{success:!1,error:X||`Command failed with exit code ${w.exitCode}`};let Q=K,Z={},W=K.match(/__CURL_METRICS_START__(.+?)__CURL_METRICS_END__/);if(W){Q=K.replace(/__CURL_METRICS_START__.+?__CURL_METRICS_END__/,"").trim();try{Z=JSON.parse(W[1])}catch(D){}}let z={};if(Z.response_code){let D=X.split(`
4
- `).filter((G)=>G.includes(":"));for(let G of D){let[O,...J]=G.split(":");if(O&&J.length>0)z[O.trim()]=J.join(":").trim()}}return{success:!0,status:Z.response_code||Z.http_code,headers:z,body:Q,metrics:{duration:(Z.time_total||0)*1000,size:Z.size_download,dnsLookup:(Z.time_namelookup||0)*1000,tcpConnection:(Z.time_connect||0)*1000,tlsHandshake:(Z.time_appconnect||0)*1000,firstByte:(Z.time_starttransfer||0)*1000,download:(Z.time_total||0)*1000}}}catch(w){return{success:!1,error:w instanceof Error?w.message:String(w)}}}}class Y{config;colors={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",underscore:"\x1B[4m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",bgBlack:"\x1B[40m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m",bgMagenta:"\x1B[45m",bgCyan:"\x1B[46m",bgWhite:"\x1B[47m"};constructor($={}){this.config={verbose:!1,showHeaders:!1,showBody:!0,showMetrics:!1,format:"pretty",prettyLevel:"standard",...$}}color($,w){return`${this.colors[w]}${$}${this.colors.reset}`}getShortFilename($){return $.replace(/.*\//,"").replace(".yaml","")}shouldShowOutput(){if(this.config.format==="raw")return!1;if(this.config.format==="pretty")return!0;return this.config.verbose!==!1}shouldShowHeaders(){if(this.config.format!=="pretty")return this.config.showHeaders||!1;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return this.config.showHeaders||!1;case"detailed":return!0;default:return this.config.showHeaders||!1}}shouldShowBody(){if(this.config.format!=="pretty")return this.config.showBody!==!1;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return this.config.showBody!==!1;case"detailed":return!0;default:return this.config.showBody!==!1}}shouldShowMetrics(){if(this.config.format!=="pretty")return this.config.showMetrics||!1;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return this.config.showMetrics||!1;case"detailed":return!0;default:return this.config.showMetrics||!1}}shouldShowRequestDetails(){if(this.config.format!=="pretty")return this.config.verbose||!1;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return this.config.verbose||!1;case"detailed":return!0;default:return this.config.verbose||!1}}shouldShowSeparators(){if(this.config.format!=="pretty")return!0;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return!0;case"detailed":return!0;default:return!0}}colorStatusCode($){return this.color($,"yellow")}logValidationErrors($){let w=$.split("; ");if(w.length===1){let K=w[0].trim(),X=K.match(/^Expected status (.+?), got (.+)$/);if(X){let[,Q,Z]=X,W=this.colorStatusCode(Q.replace(" or ","|")),z=this.color(Z,"red");console.log(` ${this.color("\u2717","red")} ${this.color("Error:","red")} Expected ${this.color("status","yellow")} ${W}, got ${z}`)}else console.log(` ${this.color("\u2717","red")} ${this.color("Error:","red")} ${K}`)}else{console.log(` ${this.color("\u2717","red")} ${this.color("Validation Errors:","red")}`);for(let K of w){let X=K.trim();if(X)if(X.startsWith("Expected ")){let Q=X.match(/^Expected status (.+?), got (.+)$/);if(Q){let[,Z,W]=Q,z=this.colorStatusCode(Z.replace(" or ","|")),D=this.color(W,"red");console.log(` ${this.color("\u2022","red")} ${this.color("status","yellow")}: expected ${z}, got ${D}`)}else{let Z=X.match(/^Expected (.+?) to be (.+?), got (.+)$/);if(Z){let[,W,z,D]=Z;console.log(` ${this.color("\u2022","red")} ${this.color(W,"yellow")}: expected ${this.color(z,"green")}, got ${this.color(D,"red")}`)}else console.log(` ${this.color("\u2022","red")} ${X}`)}}else console.log(` ${this.color("\u2022","red")} ${X}`)}}}formatJson($){if(this.config.format==="raw")return typeof $==="string"?$:JSON.stringify($);if(this.config.format==="json")return JSON.stringify($);return JSON.stringify($,null,2)}formatDuration($){if($<1000)return`${$.toFixed(0)}ms`;return`${($/1000).toFixed(2)}s`}formatSize($){if(!$)return"0 B";let w=["B","KB","MB","GB"],K=Math.floor(Math.log($)/Math.log(1024));return`${($/1024**K).toFixed(2)} ${w[K]}`}printSeparator($="\u2500",w=60){console.log(this.color($.repeat(w),"dim"))}logExecutionStart($,w){if(!this.shouldShowOutput())return;if(this.shouldShowSeparators())this.printSeparator("\u2550"),console.log(this.color("\uD83D\uDE80 CURL RUNNER","bright")),console.log(this.color(`Executing ${$} request(s) in ${w} mode`,"cyan")),this.printSeparator("\u2550"),console.log()}logRequestStart($,w){if(!this.shouldShowOutput())return;let K=$.name||`Request #${w}`,X=$.sourceFile?` ${this.color(`[${this.getShortFilename($.sourceFile)}]`,"cyan")}`:"";if(console.log(this.color(`\u25B6 ${K}`,"bright")+X),console.log(` ${this.color($.method||"GET","yellow")} ${this.color($.url,"blue")}`),this.shouldShowRequestDetails()&&$.headers&&Object.keys($.headers).length>0){console.log(this.color(" Headers:","dim"));for(let[Q,Z]of Object.entries($.headers))console.log(` ${Q}: ${Z}`)}if(this.shouldShowRequestDetails()&&$.body){console.log(this.color(" Body:","dim"));let Q=this.formatJson($.body);for(let Z of Q.split(`
5
- `))console.log(` ${Z}`)}}logCommand($){if(this.shouldShowRequestDetails())console.log(this.color(" Command:","dim")),console.log(this.color(` ${$}`,"dim"))}logRetry($,w){console.log(this.color(` \u21BB Retry ${$}/${w}...`,"yellow"))}logRequestComplete($){if(this.config.format==="raw"){if($.success&&this.config.showBody&&$.body){let X=this.formatJson($.body);console.log(X)}return}if(this.config.format==="json"){let X={request:{name:$.request.name,url:$.request.url,method:$.request.method||"GET"},success:$.success,status:$.status,...this.shouldShowHeaders()&&$.headers?{headers:$.headers}:{},...this.shouldShowBody()&&$.body?{body:$.body}:{},...$.error?{error:$.error}:{},...this.shouldShowMetrics()&&$.metrics?{metrics:$.metrics}:{}};console.log(JSON.stringify(X,null,2));return}if(!this.shouldShowOutput())return;let w=$.success?"green":"red",K=$.success?"\u2713":"\u2717";if(console.log(` ${this.color(K,w)} Status: ${this.color(String($.status||"ERROR"),w)}`),$.error)this.logValidationErrors($.error);if(this.shouldShowMetrics()&&$.metrics){let X=$.metrics,Q=[`Duration: ${this.color(this.formatDuration(X.duration),"cyan")}`];if(X.size!==void 0)Q.push(`Size: ${this.color(this.formatSize(X.size),"cyan")}`);if(this.shouldShowRequestDetails()){if(X.dnsLookup)Q.push(`DNS: ${this.formatDuration(X.dnsLookup)}`);if(X.tcpConnection)Q.push(`TCP: ${this.formatDuration(X.tcpConnection)}`);if(X.tlsHandshake)Q.push(`TLS: ${this.formatDuration(X.tlsHandshake)}`);if(X.firstByte)Q.push(`TTFB: ${this.formatDuration(X.firstByte)}`)}console.log(` ${Q.join(" | ")}`)}if(this.shouldShowHeaders()&&$.headers&&Object.keys($.headers).length>0){console.log(this.color(" Response Headers:","dim"));for(let[X,Q]of Object.entries($.headers))console.log(` ${X}: ${Q}`)}if(this.shouldShowBody()&&$.body){console.log(this.color(" Response Body:","dim"));let Q=this.formatJson($.body).split(`
6
- `),Z=this.shouldShowRequestDetails()?1/0:10;for(let W of Q.slice(0,Z))console.log(` ${W}`);if(Q.length>Z)console.log(this.color(` ... (${Q.length-Z} more lines)`,"dim"))}console.log()}logSummary($,w=!1){if(this.config.format==="raw")return;if(this.config.format==="json"){let Q={summary:{total:$.total,successful:$.successful,failed:$.failed,duration:$.duration},results:$.results.map((Z)=>({request:{name:Z.request.name,url:Z.request.url,method:Z.request.method||"GET"},success:Z.success,status:Z.status,...this.shouldShowHeaders()&&Z.headers?{headers:Z.headers}:{},...this.shouldShowBody()&&Z.body?{body:Z.body}:{},...Z.error?{error:Z.error}:{},...this.shouldShowMetrics()&&Z.metrics?{metrics:Z.metrics}:{}}))};console.log(JSON.stringify(Q,null,2));return}if(!this.shouldShowOutput())return;if(this.shouldShowSeparators()){this.printSeparator("\u2550");let Q=w?"\uD83C\uDFAF OVERALL SUMMARY":"\uD83D\uDCCA EXECUTION SUMMARY";console.log(this.color(Q,"bright")),this.printSeparator()}let K=($.successful/$.total*100).toFixed(1),X=$.failed===0?"green":$.successful===0?"red":"yellow";if(console.log(` Total Requests: ${this.color(String($.total),"cyan")}`),console.log(` Successful: ${this.color(String($.successful),"green")}`),console.log(` Failed: ${this.color(String($.failed),"red")}`),console.log(` Success Rate: ${this.color(`${K}%`,X)}`),console.log(` Total Duration: ${this.color(this.formatDuration($.duration),"cyan")}`),$.failed>0&&this.shouldShowRequestDetails())console.log(),console.log(this.color(" Failed Requests:","red")),$.results.filter((Q)=>!Q.success).forEach((Q)=>{let Z=Q.request.name||Q.request.url;console.log(` \u2022 ${Z}: ${Q.error}`)});if(this.shouldShowSeparators())this.printSeparator("\u2550")}logError($){console.error(this.color(`\u274C ${$}`,"red"))}logWarning($){console.warn(this.color(`\u26A0\uFE0F ${$}`,"yellow"))}logInfo($){console.log(this.color(`\u2139\uFE0F ${$}`,"blue"))}logSuccess($){console.log(this.color(`\u2705 ${$}`,"green"))}logFileHeader($,w){if(!this.shouldShowOutput()||this.config.format!=="pretty")return;let K=$.replace(/.*\//,"").replace(".yaml","");console.log(),this.printSeparator("\u2500"),console.log(this.color(`\uD83D\uDCC4 ${K}.yaml`,"bright")+this.color(` (${w} request${w===1?"":"s"})`,"dim")),this.printSeparator("\u2500")}}class A{logger;globalConfig;constructor($={}){this.globalConfig=$,this.logger=new Y($.output)}mergeOutputConfig($){return{...this.globalConfig.output,...$.sourceOutputConfig}}async executeRequest($,w=0){let K=performance.now(),X=this.mergeOutputConfig($),Q=new Y(X);Q.logRequestStart($,w);let Z=L.buildCommand($);Q.logCommand(Z);let W=0,z,D=($.retry?.count||0)+1;while(W<D){if(W>0){if(Q.logRetry(W,D-1),$.retry?.delay)await Bun.sleep($.retry.delay)}let O=await L.executeCurl(Z);if(O.success){let J=O.body;try{if(O.headers?.["content-type"]?.includes("application/json")||J&&(J.trim().startsWith("{")||J.trim().startsWith("[")))J=JSON.parse(J)}catch(T){}let _={request:$,success:!0,status:O.status,headers:O.headers,body:J,metrics:{...O.metrics,duration:performance.now()-K}};if($.expect){let T=this.validateResponse(_,$.expect);if(!T.success)_.success=!1,_.error=T.error}return Q.logRequestComplete(_),_}z=O.error,W++}let G={request:$,success:!1,error:z,metrics:{duration:performance.now()-K}};return Q.logRequestComplete(G),G}validateResponse($,w){if(!w)return{success:!0};let K=[];if(w.status!==void 0){let Q=Array.isArray(w.status)?w.status:[w.status];if(!Q.includes($.status||0))K.push(`Expected status ${Q.join(" or ")}, got ${$.status}`)}if(w.headers)for(let[Q,Z]of Object.entries(w.headers)){let W=$.headers?.[Q]||$.headers?.[Q.toLowerCase()];if(W!==Z)K.push(`Expected header ${Q}="${Z}", got "${W}"`)}if(w.body!==void 0){let Q=this.validateBodyProperties($.body,w.body,"");if(Q.length>0)K.push(...Q)}if(w.responseTime!==void 0&&$.metrics){let Q=$.metrics.duration;if(!this.validateRangePattern(Q,w.responseTime))K.push(`Expected response time to match ${w.responseTime}ms, got ${Q.toFixed(2)}ms`)}let X=K.length>0;if(w.failure===!0){if(X)return{success:!1,error:K.join("; ")};let Q=$.status||0;if(Q>=400)return{success:!0};else return{success:!1,error:`Expected request to fail (4xx/5xx) but got status ${Q}`}}else if(X)return{success:!1,error:K.join("; ")};else return{success:!0}}validateBodyProperties($,w,K){let X=[];if(typeof w!=="object"||w===null){let Q=this.validateValue($,w,K||"body");if(!Q.isValid)X.push(Q.error);return X}if(Array.isArray(w)){let Q=this.validateValue($,w,K||"body");if(!Q.isValid)X.push(Q.error);return X}for(let[Q,Z]of Object.entries(w)){let W=K?`${K}.${Q}`:Q,z;if(Array.isArray($)&&this.isArraySelector(Q))z=this.getArrayValue($,Q);else z=$?.[Q];if(typeof Z==="object"&&Z!==null&&!Array.isArray(Z)){let D=this.validateBodyProperties(z,Z,W);X.push(...D)}else{let D=this.validateValue(z,Z,W);if(!D.isValid)X.push(D.error)}}return X}validateValue($,w,K){if(w==="*")return{isValid:!0};if(Array.isArray(w)){if(!w.some((Q)=>{if(Q==="*")return!0;if(typeof Q==="string"&&this.isRegexPattern(Q))return this.validateRegexPattern($,Q);if(typeof Q==="string"&&this.isRangePattern(Q))return this.validateRangePattern($,Q);return $===Q}))return{isValid:!1,error:`Expected ${K} to match one of ${JSON.stringify(w)}, got ${JSON.stringify($)}`};return{isValid:!0}}if(typeof w==="string"&&this.isRegexPattern(w)){if(!this.validateRegexPattern($,w))return{isValid:!1,error:`Expected ${K} to match pattern ${w}, got ${JSON.stringify($)}`};return{isValid:!0}}if(typeof w==="string"&&this.isRangePattern(w)){if(!this.validateRangePattern($,w))return{isValid:!1,error:`Expected ${K} to match range ${w}, got ${JSON.stringify($)}`};return{isValid:!0}}if(w==="null"||w===null){if($!==null)return{isValid:!1,error:`Expected ${K} to be null, got ${JSON.stringify($)}`};return{isValid:!0}}if($!==w)return{isValid:!1,error:`Expected ${K} to be ${JSON.stringify(w)}, got ${JSON.stringify($)}`};return{isValid:!0}}isRegexPattern($){return $.startsWith("^")||$.endsWith("$")||$.includes("\\d")||$.includes("\\w")||$.includes("\\s")||$.includes("[")||$.includes("*")||$.includes("+")||$.includes("?")}validateRegexPattern($,w){let K=String($);try{return new RegExp(w).test(K)}catch{return!1}}isRangePattern($){return/^(>=?|<=?|>|<)\s*[\d.-]+(\s*,\s*(>=?|<=?|>|<)\s*[\d.-]+)*$/.test($)}validateRangePattern($,w){let K=Number($);if(Number.isNaN(K))return!1;let X=w.match(/^([\d.-]+)\s*-\s*([\d.-]+)$/);if(X){let Z=Number(X[1]),W=Number(X[2]);return K>=Z&&K<=W}return w.split(",").map((Z)=>Z.trim()).every((Z)=>{let W=Z.match(/^(>=?|<=?|>|<)\s*([\d.-]+)$/);if(!W)return!1;let z=W[1],D=Number(W[2]);switch(z){case">":return K>D;case">=":return K>=D;case"<":return K<D;case"<=":return K<=D;default:return!1}})}isArraySelector($){return/^\[.*\]$/.test($)||$==="*"||$.startsWith("slice(")}getArrayValue($,w){if(w==="*")return $;if(w.startsWith("[")&&w.endsWith("]")){let K=w.slice(1,-1);if(K==="*")return $;let X=Number(K);if(!Number.isNaN(X))return X>=0?$[X]:$[$.length+X]}if(w.startsWith("slice(")){let K=w.match(/slice\((\d+)(?:,(\d+))?\)/);if(K){let X=Number(K[1]),Q=K[2]?Number(K[2]):void 0;return $.slice(X,Q)}}return}async executeSequential($){let w=performance.now(),K=[];for(let X=0;X<$.length;X++){let Q=await this.executeRequest($[X],X+1);if(K.push(Q),!Q.success&&!this.globalConfig.continueOnError){this.logger.logError("Stopping execution due to error");break}}return this.createSummary(K,performance.now()-w)}async executeParallel($){let w=performance.now(),K=$.map((Q,Z)=>this.executeRequest(Q,Z+1)),X=await Promise.all(K);return this.createSummary(X,performance.now()-w)}async execute($){this.logger.logExecutionStart($.length,this.globalConfig.execution||"sequential");let w=this.globalConfig.execution==="parallel"?await this.executeParallel($):await this.executeSequential($);if(this.logger.logSummary(w),this.globalConfig.output?.saveToFile)await this.saveSummaryToFile(w);return w}createSummary($,w){let K=$.filter((Q)=>Q.success).length,X=$.filter((Q)=>!Q.success).length;return{total:$.length,successful:K,failed:X,duration:w,results:$}}async saveSummaryToFile($){let w=this.globalConfig.output?.saveToFile;if(!w)return;let K=JSON.stringify($,null,2);await Bun.write(w,K),this.logger.logInfo(`Results saved to ${w}`)}}var{YAML:R}=globalThis.Bun;class H{static async parseFile($){let K=await Bun.file($).text();return R.parse(K)}static parse($){return R.parse($)}static interpolateVariables($,w){if(typeof $==="string"){let K=$.match(/^\$\{([^}]+)\}$/);if(K){let X=K[1],Q=H.resolveDynamicVariable(X);return Q!==null?Q:w[X]||$}return $.replace(/\$\{([^}]+)\}/g,(X,Q)=>{let Z=H.resolveDynamicVariable(Q);return Z!==null?Z:w[Q]||X})}if(Array.isArray($))return $.map((K)=>H.interpolateVariables(K,w));if($&&typeof $==="object"){let K={};for(let[X,Q]of Object.entries($))K[X]=H.interpolateVariables(Q,w);return K}return $}static resolveDynamicVariable($){if($==="UUID")return crypto.randomUUID();if($==="CURRENT_TIME"||$==="TIMESTAMP")return Date.now().toString();if($.startsWith("DATE:")){let w=$.slice(5);return H.formatDate(new Date,w)}if($.startsWith("TIME:")){let w=$.slice(5);return H.formatTime(new Date,w)}return null}static formatDate($,w){let K=$.getFullYear(),X=String($.getMonth()+1).padStart(2,"0"),Q=String($.getDate()).padStart(2,"0");return w.replace("YYYY",K.toString()).replace("MM",X).replace("DD",Q)}static formatTime($,w){let K=String($.getHours()).padStart(2,"0"),X=String($.getMinutes()).padStart(2,"0"),Q=String($.getSeconds()).padStart(2,"0");return w.replace("HH",K).replace("mm",X).replace("ss",Q)}static mergeConfigs($,w){return{...$,...w,headers:{...$.headers,...w.headers},params:{...$.params,...w.params},variables:{...$.variables,...w.variables}}}}function M(){if(typeof BUILD_VERSION!=="undefined")return BUILD_VERSION;if(process.env.CURL_RUNNER_VERSION)return process.env.CURL_RUNNER_VERSION;try{let $=["../package.json","./package.json","../../package.json"];for(let w of $)try{let K=j(w);if(K.name==="@curl-runner/cli"&&K.version)return K.version}catch{}return"0.0.0"}catch{return"0.0.0"}}var P={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",underscore:"\x1B[4m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",bgBlack:"\x1B[40m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m",bgMagenta:"\x1B[45m",bgCyan:"\x1B[46m",bgWhite:"\x1B[47m"};function U($,w){return`${P[w]}${$}${P.reset}`}var k=`${process.env.HOME}/.curl-runner-version-cache.json`,x=86400000,y="https://registry.npmjs.org/@curl-runner/cli/latest";class B{async checkForUpdates($=!1){try{if(process.env.CI)return;let w=M();if(w==="0.0.0")return;if(!$){let X=await this.getCachedVersion();if(X&&Date.now()-X.lastCheck<x){this.compareVersions(w,X.latestVersion);return}}let K=await this.fetchLatestVersion();if(K)await this.setCachedVersion(K),this.compareVersions(w,K)}catch{}}async fetchLatestVersion(){try{let $=await fetch(y,{signal:AbortSignal.timeout(3000)});if(!$.ok)return null;return(await $.json()).version}catch{return null}}compareVersions($,w){if(this.isNewerVersion($,w))console.log(),console.log(U("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E","yellow")),console.log(U("\u2502","yellow")+" "+U("\u2502","yellow")),console.log(U("\u2502","yellow")+" "+U("\uD83D\uDCE6 New version available!","bright")+` ${U($,"red")} \u2192 ${U(w,"green")} `+U("\u2502","yellow")),console.log(U("\u2502","yellow")+" "+U("\u2502","yellow")),console.log(U("\u2502","yellow")+" Update with: "+U("npm install -g @curl-runner/cli","cyan")+" "+U("\u2502","yellow")),console.log(U("\u2502","yellow")+" or: "+U("bun install -g @curl-runner/cli","cyan")+" "+U("\u2502","yellow")),console.log(U("\u2502","yellow")+" "+U("\u2502","yellow")),console.log(U("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F","yellow")),console.log()}isNewerVersion($,w){try{let K=$.replace(/^v/,""),X=w.replace(/^v/,""),Q=K.split(".").map(Number),Z=X.split(".").map(Number);for(let W=0;W<Math.max(Q.length,Z.length);W++){let z=Q[W]||0,D=Z[W]||0;if(D>z)return!0;if(D<z)return!1}return!1}catch{return!1}}async getCachedVersion(){try{let $=Bun.file(k);if(await $.exists())return JSON.parse(await $.text())}catch{}return null}async setCachedVersion($){try{let w={lastCheck:Date.now(),latestVersion:$};await Bun.write(k,JSON.stringify(w))}catch{}}}class q{logger=new Y;async loadConfigFile(){let $=["curl-runner.yaml","curl-runner.yml",".curl-runner.yaml",".curl-runner.yml"];for(let w of $)try{if(await Bun.file(w).exists()){let X=await H.parseFile(w),Q=X.global||X;return this.logger.logInfo(`Loaded configuration from ${w}`),Q}}catch(K){this.logger.logWarning(`Failed to load configuration from ${w}: ${K}`)}return{}}loadEnvironmentVariables(){let $={};if(process.env.CURL_RUNNER_TIMEOUT)$.defaults={...$.defaults,timeout:Number.parseInt(process.env.CURL_RUNNER_TIMEOUT,10)};if(process.env.CURL_RUNNER_RETRIES)$.defaults={...$.defaults,retry:{...$.defaults?.retry,count:Number.parseInt(process.env.CURL_RUNNER_RETRIES,10)}};if(process.env.CURL_RUNNER_RETRY_DELAY)$.defaults={...$.defaults,retry:{...$.defaults?.retry,delay:Number.parseInt(process.env.CURL_RUNNER_RETRY_DELAY,10)}};if(process.env.CURL_RUNNER_VERBOSE)$.output={...$.output,verbose:process.env.CURL_RUNNER_VERBOSE.toLowerCase()==="true"};if(process.env.CURL_RUNNER_EXECUTION)$.execution=process.env.CURL_RUNNER_EXECUTION;if(process.env.CURL_RUNNER_CONTINUE_ON_ERROR)$.continueOnError=process.env.CURL_RUNNER_CONTINUE_ON_ERROR.toLowerCase()==="true";if(process.env.CURL_RUNNER_OUTPUT_FORMAT){let w=process.env.CURL_RUNNER_OUTPUT_FORMAT;if(["json","pretty","raw"].includes(w))$.output={...$.output,format:w}}if(process.env.CURL_RUNNER_PRETTY_LEVEL){let w=process.env.CURL_RUNNER_PRETTY_LEVEL;if(["minimal","standard","detailed"].includes(w))$.output={...$.output,prettyLevel:w}}if(process.env.CURL_RUNNER_OUTPUT_FILE)$.output={...$.output,saveToFile:process.env.CURL_RUNNER_OUTPUT_FILE};return $}async run($){try{let{files:w,options:K}=this.parseArguments($);if(!K.version&&!K.help)new B().checkForUpdates().catch(()=>{});if(K.help){this.showHelp();return}if(K.version){console.log(`curl-runner v${M()}`);return}let X=this.loadEnvironmentVariables(),Q=await this.loadConfigFile(),Z=await this.findYamlFiles(w,K);if(Z.length===0)this.logger.logError("No YAML files found"),process.exit(1);this.logger.logInfo(`Found ${Z.length} YAML file(s)`);let W=this.mergeGlobalConfigs(X,Q),z=[],D=[];for(let J of Z){this.logger.logInfo(`Processing: ${J}`);let{requests:_,config:T}=await this.processYamlFile(J),S=T?.output||{},F=_.map((I)=>({...I,sourceOutputConfig:S,sourceFile:J}));if(T){let{...I}=T;W=this.mergeGlobalConfigs(W,I)}D.push({file:J,requests:F,config:T}),z.push(...F)}if(K.execution)W.execution=K.execution;if(K.continueOnError!==void 0)W.continueOnError=K.continueOnError;if(K.verbose!==void 0)W.output={...W.output,verbose:K.verbose};if(K.quiet!==void 0)W.output={...W.output,verbose:!1};if(K.output)W.output={...W.output,saveToFile:K.output};if(K.outputFormat)W.output={...W.output,format:K.outputFormat};if(K.prettyLevel)W.output={...W.output,prettyLevel:K.prettyLevel};if(K.showHeaders!==void 0)W.output={...W.output,showHeaders:K.showHeaders};if(K.showBody!==void 0)W.output={...W.output,showBody:K.showBody};if(K.showMetrics!==void 0)W.output={...W.output,showMetrics:K.showMetrics};if(K.timeout)W.defaults={...W.defaults,timeout:K.timeout};if(K.retries||K.noRetry){let J=K.noRetry?0:K.retries||0;W.defaults={...W.defaults,retry:{...W.defaults?.retry,count:J}}}if(K.retryDelay)W.defaults={...W.defaults,retry:{...W.defaults?.retry,delay:K.retryDelay}};if(z.length===0)this.logger.logError("No requests found in YAML files"),process.exit(1);let G=new A(W),O;if(D.length>1){let J=[],_=0;for(let F=0;F<D.length;F++){let I=D[F];this.logger.logFileHeader(I.file,I.requests.length);let E=await G.execute(I.requests);if(J.push(...E.results),_+=E.duration,F<D.length-1)console.log()}let T=J.filter((F)=>F.success).length,S=J.filter((F)=>!F.success).length;O={total:J.length,successful:T,failed:S,duration:_,results:J},G.logger.logSummary(O,!0)}else O=await G.execute(z);process.exit(O.failed>0&&!W.continueOnError?1:0)}catch(w){this.logger.logError(w instanceof Error?w.message:String(w)),process.exit(1)}}parseArguments($){let w={},K=[];for(let X=0;X<$.length;X++){let Q=$[X];if(Q.startsWith("--")){let Z=Q.slice(2),W=$[X+1];if(Z==="help"||Z==="version")w[Z]=!0;else if(Z==="no-retry")w.noRetry=!0;else if(Z==="quiet")w.quiet=!0;else if(Z==="show-headers")w.showHeaders=!0;else if(Z==="show-body")w.showBody=!0;else if(Z==="show-metrics")w.showMetrics=!0;else if(W&&!W.startsWith("--")){if(Z==="continue-on-error")w.continueOnError=W==="true";else if(Z==="verbose")w.verbose=W==="true";else if(Z==="timeout")w.timeout=Number.parseInt(W,10);else if(Z==="retries")w.retries=Number.parseInt(W,10);else if(Z==="retry-delay")w.retryDelay=Number.parseInt(W,10);else if(Z==="output-format"){if(["json","pretty","raw"].includes(W))w.outputFormat=W}else if(Z==="pretty-level"){if(["minimal","standard","detailed"].includes(W))w.prettyLevel=W}else w[Z]=W;X++}else w[Z]=!0}else if(Q.startsWith("-")){let Z=Q.slice(1);for(let W of Z)switch(W){case"h":w.help=!0;break;case"v":w.verbose=!0;break;case"p":w.execution="parallel";break;case"c":w.continueOnError=!0;break;case"q":w.quiet=!0;break;case"o":{let z=$[X+1];if(z&&!z.startsWith("-"))w.output=z,X++;break}}}else K.push(Q)}return{files:K,options:w}}async findYamlFiles($,w){let K=new Set,X=[];if($.length===0)X=w.all?["**/*.yaml","**/*.yml"]:["*.yaml","*.yml"];else for(let Q of $)try{let W=await(await import("fs/promises")).stat(Q);if(W.isDirectory()){if(X.push(`${Q}/*.yaml`,`${Q}/*.yml`),w.all)X.push(`${Q}/**/*.yaml`,`${Q}/**/*.yml`)}else if(W.isFile())X.push(Q)}catch{X.push(Q)}for(let Q of X){let Z=new d(Q);for await(let W of Z.scan("."))if(W.endsWith(".yaml")||W.endsWith(".yml"))K.add(W)}return Array.from(K).sort()}async processYamlFile($){let w=await H.parseFile($),K=[],X;if(w.global)X=w.global;let Q={...w.global?.variables,...w.collection?.variables},Z={...w.global?.defaults,...w.collection?.defaults};if(w.request){let W=this.prepareRequest(w.request,Q,Z);K.push(W)}if(w.requests)for(let W of w.requests){let z=this.prepareRequest(W,Q,Z);K.push(z)}if(w.collection?.requests)for(let W of w.collection.requests){let z=this.prepareRequest(W,Q,Z);K.push(z)}return{requests:K,config:X}}prepareRequest($,w,K){let X=H.interpolateVariables($,w);return H.mergeConfigs(K,X)}mergeGlobalConfigs($,w){return{...$,...w,variables:{...$.variables,...w.variables},output:{...$.output,...w.output},defaults:{...$.defaults,...w.defaults}}}showHelp(){console.log(`
3
+ var v=Object.create;var{getPrototypeOf:b,defineProperty:k,getOwnPropertyNames:V}=Object;var C=Object.prototype.hasOwnProperty;var x=($,w,K)=>{K=$!=null?v(b($)):{};let X=w||!$||!$.__esModule?k(K,"default",{value:$,enumerable:!0}):K;for(let Q of V($))if(!C.call(X,Q))k(X,Q,{get:()=>$[Q],enumerable:!0});return X};var A=import.meta.require;var{Glob:g}=globalThis.Bun;class T{static buildCommand($){let w=["curl"];if(w.push("-X",$.method||"GET"),w.push("-w",'"\\n__CURL_METRICS_START__%{json}__CURL_METRICS_END__"'),$.headers)for(let[X,Q]of Object.entries($.headers))w.push("-H",`"${X}: ${Q}"`);if($.auth){if($.auth.type==="basic"&&$.auth.username&&$.auth.password)w.push("-u",`"${$.auth.username}:${$.auth.password}"`);else if($.auth.type==="bearer"&&$.auth.token)w.push("-H",`"Authorization: Bearer ${$.auth.token}"`)}if($.body){let X=typeof $.body==="string"?$.body:JSON.stringify($.body);if(w.push("-d",`'${X.replace(/'/g,"'\\''")}'`),!$.headers?.["Content-Type"])w.push("-H",'"Content-Type: application/json"')}if($.timeout)w.push("--max-time",$.timeout.toString());if($.followRedirects!==!1){if(w.push("-L"),$.maxRedirects)w.push("--max-redirs",$.maxRedirects.toString())}if($.proxy)w.push("-x",$.proxy);if($.insecure)w.push("-k");if($.output)w.push("-o",$.output);w.push("-s","-S");let K=$.url;if($.params&&Object.keys($.params).length>0){let X=new URLSearchParams($.params).toString();K+=(K.includes("?")?"&":"?")+X}return w.push(`"${K}"`),w.join(" ")}static async executeCurl($){try{let w=Bun.spawn(["sh","-c",$],{stdout:"pipe",stderr:"pipe"}),K=await new Response(w.stdout).text(),X=await new Response(w.stderr).text();if(await w.exited,w.exitCode!==0&&!K)return{success:!1,error:X||`Command failed with exit code ${w.exitCode}`};let Q=K,W={},Z=K.match(/__CURL_METRICS_START__(.+?)__CURL_METRICS_END__/);if(Z){Q=K.replace(/__CURL_METRICS_START__.+?__CURL_METRICS_END__/,"").trim();try{W=JSON.parse(Z[1])}catch(D){}}let z={};if(W.response_code){let D=X.split(`
4
+ `).filter((U)=>U.includes(":"));for(let U of D){let[O,...J]=U.split(":");if(O&&J.length>0)z[O.trim()]=J.join(":").trim()}}return{success:!0,status:W.response_code||W.http_code,headers:z,body:Q,metrics:{duration:(W.time_total||0)*1000,size:W.size_download,dnsLookup:(W.time_namelookup||0)*1000,tcpConnection:(W.time_connect||0)*1000,tlsHandshake:(W.time_appconnect||0)*1000,firstByte:(W.time_starttransfer||0)*1000,download:(W.time_total||0)*1000}}}catch(w){return{success:!1,error:w instanceof Error?w.message:String(w)}}}}class B{colors;constructor($){this.colors=$}color($,w){if(!w||!this.colors[w])return $;return`${this.colors[w]}${$}${this.colors.reset}`}render($,w=" "){$.forEach((K,X)=>{let Q=X===$.length-1,W=Q?`${w}\u2514\u2500`:`${w}\u251C\u2500`;if(K.label&&K.value){let Z=K.color?this.color(K.value,K.color):K.value,z=Z.split(`
5
+ `);if(z.length===1)console.log(`${W} ${K.label}: ${Z}`);else{console.log(`${W} ${K.label}:`);let D=Q?`${w} `:`${w}\u2502 `;z.forEach((U)=>{console.log(`${D}${U}`)})}}else if(K.label&&!K.value)console.log(`${W} ${K.label}:`);else if(!K.label&&K.value){let Z=Q?`${w} `:`${w}\u2502 `;console.log(`${Z}${K.value}`)}if(K.children&&K.children.length>0){let Z=Q?`${w} `:`${w}\u2502 `;this.render(K.children,Z)}})}}class M{config;colors={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",underscore:"\x1B[4m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",bgBlack:"\x1B[40m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m",bgMagenta:"\x1B[45m",bgCyan:"\x1B[46m",bgWhite:"\x1B[47m"};constructor($={}){this.config={verbose:!1,showHeaders:!1,showBody:!0,showMetrics:!1,format:"pretty",prettyLevel:"minimal",...$}}color($,w){return`${this.colors[w]}${$}${this.colors.reset}`}getShortFilename($){return $.replace(/.*\//,"").replace(".yaml","")}shouldShowOutput(){if(this.config.format==="raw")return!1;if(this.config.format==="pretty")return!0;return this.config.verbose!==!1}shouldShowHeaders(){if(this.config.format!=="pretty")return this.config.showHeaders||!1;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return this.config.showHeaders||!1;case"detailed":return!0;default:return this.config.showHeaders||!1}}shouldShowBody(){if(this.config.format!=="pretty")return this.config.showBody!==!1;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return this.config.showBody!==!1;case"detailed":return!0;default:return this.config.showBody!==!1}}shouldShowMetrics(){if(this.config.format!=="pretty")return this.config.showMetrics||!1;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return this.config.showMetrics||!1;case"detailed":return!0;default:return this.config.showMetrics||!1}}shouldShowRequestDetails(){if(this.config.format!=="pretty")return this.config.verbose||!1;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return this.config.verbose||!1;case"detailed":return!0;default:return this.config.verbose||!1}}shouldShowSeparators(){if(this.config.format!=="pretty")return!0;switch(this.config.prettyLevel||"standard"){case"minimal":return!1;case"standard":return!0;case"detailed":return!0;default:return!0}}colorStatusCode($){return this.color($,"yellow")}logValidationErrors($){let w=$.split("; ");if(w.length===1){let K=w[0].trim(),X=K.match(/^Expected status (.+?), got (.+)$/);if(X){let[,Q,W]=X,Z=this.colorStatusCode(Q.replace(" or ","|")),z=this.color(W,"red");console.log(` ${this.color("\u2717","red")} ${this.color("Error:","red")} Expected ${this.color("status","yellow")} ${Z}, got ${z}`)}else console.log(` ${this.color("\u2717","red")} ${this.color("Error:","red")} ${K}`)}else{console.log(` ${this.color("\u2717","red")} ${this.color("Validation Errors:","red")}`);for(let K of w){let X=K.trim();if(X)if(X.startsWith("Expected ")){let Q=X.match(/^Expected status (.+?), got (.+)$/);if(Q){let[,W,Z]=Q,z=this.colorStatusCode(W.replace(" or ","|")),D=this.color(Z,"red");console.log(` ${this.color("\u2022","red")} ${this.color("status","yellow")}: expected ${z}, got ${D}`)}else{let W=X.match(/^Expected (.+?) to be (.+?), got (.+)$/);if(W){let[,Z,z,D]=W;console.log(` ${this.color("\u2022","red")} ${this.color(Z,"yellow")}: expected ${this.color(z,"green")}, got ${this.color(D,"red")}`)}else console.log(` ${this.color("\u2022","red")} ${X}`)}}else console.log(` ${this.color("\u2022","red")} ${X}`)}}}formatJson($){if(this.config.format==="raw")return typeof $==="string"?$:JSON.stringify($);if(this.config.format==="json")return JSON.stringify($);return JSON.stringify($,null,2)}formatDuration($){if($<1000)return`${$.toFixed(0)}ms`;return`${($/1000).toFixed(2)}s`}formatSize($){if(!$)return"0 B";let w=["B","KB","MB","GB"],K=Math.floor(Math.log($)/Math.log(1024));return`${($/1024**K).toFixed(2)} ${w[K]}`}logExecutionStart($,w){if(!this.shouldShowOutput())return;if(this.shouldShowSeparators())console.log(),console.log(this.color(`Executing ${$} request(s) in ${w} mode`,"dim")),console.log();else console.log()}logRequestStart($,w){return}logCommand($){if(this.shouldShowRequestDetails())console.log(this.color(" Command:","dim")),console.log(this.color(` ${$}`,"dim"))}logRetry($,w){console.log(this.color(` \u21BB Retry ${$}/${w}...`,"yellow"))}logRequestComplete($){if(this.config.format==="raw"){if($.success&&this.config.showBody&&$.body){let z=this.formatJson($.body);console.log(z)}return}if(this.config.format==="json"){let z={request:{name:$.request.name,url:$.request.url,method:$.request.method||"GET"},success:$.success,status:$.status,...this.shouldShowHeaders()&&$.headers?{headers:$.headers}:{},...this.shouldShowBody()&&$.body?{body:$.body}:{},...$.error?{error:$.error}:{},...this.shouldShowMetrics()&&$.metrics?{metrics:$.metrics}:{}};console.log(JSON.stringify(z,null,2));return}if(!this.shouldShowOutput())return;let w=this.config.prettyLevel||"minimal",K=$.success?"green":"red",X=$.success?"\u2713":"x",Q=$.request.name||"Request";if(w==="minimal"){let z=$.request.sourceFile?this.getShortFilename($.request.sourceFile):"inline";console.log(`${this.color(X,K)} ${this.color(Q,"bright")} [${z}]`);let D=[],U=new B(this.colors);D.push({label:$.request.method||"GET",value:$.request.url,color:"blue"});let O=$.status?`${$.status}`:"ERROR";if(D.push({label:`${X} Status`,value:O,color:K}),$.metrics){let J=`${this.formatDuration($.metrics.duration)} | ${this.formatSize($.metrics.size)}`;D.push({label:"Duration",value:J,color:"cyan"})}if(U.render(D),$.error)console.log(),this.logValidationErrors($.error);console.log();return}console.log(`${this.color(X,K)} ${this.color(Q,"bright")}`);let W=[],Z=new B(this.colors);if(W.push({label:"URL",value:$.request.url,color:"blue"}),W.push({label:"Method",value:$.request.method||"GET",color:"yellow"}),W.push({label:"Status",value:String($.status||"ERROR"),color:K}),$.metrics)W.push({label:"Duration",value:this.formatDuration($.metrics.duration),color:"cyan"});if(this.shouldShowHeaders()&&$.headers&&Object.keys($.headers).length>0){let z=Object.entries($.headers).map(([D,U])=>({label:this.color(D,"dim"),value:String(U)}));W.push({label:"Headers",children:z})}if(this.shouldShowBody()&&$.body){let D=this.formatJson($.body).split(`
6
+ `),U=this.shouldShowRequestDetails()?1/0:10,O=D.slice(0,U);if(D.length>U)O.push(this.color(`... (${D.length-U} more lines)`,"dim"));W.push({label:"Response Body",value:O.join(`
7
+ `)})}if(this.shouldShowMetrics()&&$.metrics&&w==="detailed"){let z=$.metrics,D=[];if(D.push({label:"Request Duration",value:this.formatDuration(z.duration),color:"cyan"}),z.size!==void 0)D.push({label:"Response Size",value:this.formatSize(z.size),color:"cyan"});if(z.dnsLookup)D.push({label:"DNS Lookup",value:this.formatDuration(z.dnsLookup),color:"cyan"});if(z.tcpConnection)D.push({label:"TCP Connection",value:this.formatDuration(z.tcpConnection),color:"cyan"});if(z.tlsHandshake)D.push({label:"TLS Handshake",value:this.formatDuration(z.tlsHandshake),color:"cyan"});if(z.firstByte)D.push({label:"Time to First Byte",value:this.formatDuration(z.firstByte),color:"cyan"});W.push({label:"Metrics",children:D})}if(Z.render(W),$.error)console.log(),this.logValidationErrors($.error);console.log()}logSummary($,w=!1){if(this.config.format==="raw")return;if(this.config.format==="json"){let z={summary:{total:$.total,successful:$.successful,failed:$.failed,duration:$.duration},results:$.results.map((D)=>({request:{name:D.request.name,url:D.request.url,method:D.request.method||"GET"},success:D.success,status:D.status,...this.shouldShowHeaders()&&D.headers?{headers:D.headers}:{},...this.shouldShowBody()&&D.body?{body:D.body}:{},...D.error?{error:D.error}:{},...this.shouldShowMetrics()&&D.metrics?{metrics:D.metrics}:{}}))};console.log(JSON.stringify(z,null,2));return}if(!this.shouldShowOutput())return;let K=this.config.prettyLevel||"minimal";if(w)console.log();if(K==="minimal"){let z=$.failed===0?"green":"red",D=$.failed===0?`${$.total} request${$.total===1?"":"s"} completed successfully`:`${$.successful}/${$.total} request${$.total===1?"":"s"} completed, ${$.failed} failed`;console.log(`${w?"\u25C6 Global Summary":"Summary"}: ${this.color(D,z)}`);return}let X=($.successful/$.total*100).toFixed(1),Q=$.failed===0?"green":"red",W=$.failed===0?`${$.total} request${$.total===1?"":"s"} completed successfully`:`${$.successful}/${$.total} request${$.total===1?"":"s"} completed, ${$.failed} failed`,Z=w?"\u25C6 Global Summary":"Summary";if(console.log(),console.log(`${Z}: ${this.color(W,Q)} (${this.color(this.formatDuration($.duration),"cyan")})`),$.failed>0&&this.shouldShowRequestDetails())$.results.filter((z)=>!z.success).forEach((z)=>{let D=z.request.name||z.request.url;console.log(` ${this.color("\u2022","red")} ${D}: ${z.error}`)})}logError($){console.error(this.color(`\u2717 ${$}`,"red"))}logWarning($){console.warn(this.color(`\u26A0 ${$}`,"yellow"))}logInfo($){console.log(this.color(`\u2139 ${$}`,"blue"))}logSuccess($){console.log(this.color(`\u2713 ${$}`,"green"))}logFileHeader($,w){if(!this.shouldShowOutput()||this.config.format!=="pretty")return;let K=$.replace(/.*\//,"").replace(".yaml","");console.log(),console.log(this.color(`\u25B6 ${K}.yaml`,"bright")+this.color(` (${w} request${w===1?"":"s"})`,"dim"))}}class E{logger;globalConfig;constructor($={}){this.globalConfig=$,this.logger=new M($.output)}mergeOutputConfig($){return{...this.globalConfig.output,...$.sourceOutputConfig}}async executeRequest($,w=0){let K=performance.now(),X=this.mergeOutputConfig($),Q=new M(X);Q.logRequestStart($,w);let W=T.buildCommand($);Q.logCommand(W);let Z=0,z,D=($.retry?.count||0)+1;while(Z<D){if(Z>0){if(Q.logRetry(Z,D-1),$.retry?.delay)await Bun.sleep($.retry.delay)}let O=await T.executeCurl(W);if(O.success){let J=O.body;try{if(O.headers?.["content-type"]?.includes("application/json")||J&&(J.trim().startsWith("{")||J.trim().startsWith("[")))J=JSON.parse(J)}catch(I){}let G={request:$,success:!0,status:O.status,headers:O.headers,body:J,metrics:{...O.metrics,duration:performance.now()-K}};if($.expect){let I=this.validateResponse(G,$.expect);if(!I.success)G.success=!1,G.error=I.error}return Q.logRequestComplete(G),G}z=O.error,Z++}let U={request:$,success:!1,error:z,metrics:{duration:performance.now()-K}};return Q.logRequestComplete(U),U}validateResponse($,w){if(!w)return{success:!0};let K=[];if(w.status!==void 0){let Q=Array.isArray(w.status)?w.status:[w.status];if(!Q.includes($.status||0))K.push(`Expected status ${Q.join(" or ")}, got ${$.status}`)}if(w.headers)for(let[Q,W]of Object.entries(w.headers)){let Z=$.headers?.[Q]||$.headers?.[Q.toLowerCase()];if(Z!==W)K.push(`Expected header ${Q}="${W}", got "${Z}"`)}if(w.body!==void 0){let Q=this.validateBodyProperties($.body,w.body,"");if(Q.length>0)K.push(...Q)}if(w.responseTime!==void 0&&$.metrics){let Q=$.metrics.duration;if(!this.validateRangePattern(Q,w.responseTime))K.push(`Expected response time to match ${w.responseTime}ms, got ${Q.toFixed(2)}ms`)}let X=K.length>0;if(w.failure===!0){if(X)return{success:!1,error:K.join("; ")};let Q=$.status||0;if(Q>=400)return{success:!0};else return{success:!1,error:`Expected request to fail (4xx/5xx) but got status ${Q}`}}else if(X)return{success:!1,error:K.join("; ")};else return{success:!0}}validateBodyProperties($,w,K){let X=[];if(typeof w!=="object"||w===null){let Q=this.validateValue($,w,K||"body");if(!Q.isValid)X.push(Q.error);return X}if(Array.isArray(w)){let Q=this.validateValue($,w,K||"body");if(!Q.isValid)X.push(Q.error);return X}for(let[Q,W]of Object.entries(w)){let Z=K?`${K}.${Q}`:Q,z;if(Array.isArray($)&&this.isArraySelector(Q))z=this.getArrayValue($,Q);else z=$?.[Q];if(typeof W==="object"&&W!==null&&!Array.isArray(W)){let D=this.validateBodyProperties(z,W,Z);X.push(...D)}else{let D=this.validateValue(z,W,Z);if(!D.isValid)X.push(D.error)}}return X}validateValue($,w,K){if(w==="*")return{isValid:!0};if(Array.isArray(w)){if(!w.some((Q)=>{if(Q==="*")return!0;if(typeof Q==="string"&&this.isRegexPattern(Q))return this.validateRegexPattern($,Q);if(typeof Q==="string"&&this.isRangePattern(Q))return this.validateRangePattern($,Q);return $===Q}))return{isValid:!1,error:`Expected ${K} to match one of ${JSON.stringify(w)}, got ${JSON.stringify($)}`};return{isValid:!0}}if(typeof w==="string"&&this.isRegexPattern(w)){if(!this.validateRegexPattern($,w))return{isValid:!1,error:`Expected ${K} to match pattern ${w}, got ${JSON.stringify($)}`};return{isValid:!0}}if(typeof w==="string"&&this.isRangePattern(w)){if(!this.validateRangePattern($,w))return{isValid:!1,error:`Expected ${K} to match range ${w}, got ${JSON.stringify($)}`};return{isValid:!0}}if(w==="null"||w===null){if($!==null)return{isValid:!1,error:`Expected ${K} to be null, got ${JSON.stringify($)}`};return{isValid:!0}}if($!==w)return{isValid:!1,error:`Expected ${K} to be ${JSON.stringify(w)}, got ${JSON.stringify($)}`};return{isValid:!0}}isRegexPattern($){return $.startsWith("^")||$.endsWith("$")||$.includes("\\d")||$.includes("\\w")||$.includes("\\s")||$.includes("[")||$.includes("*")||$.includes("+")||$.includes("?")}validateRegexPattern($,w){let K=String($);try{return new RegExp(w).test(K)}catch{return!1}}isRangePattern($){return/^(>=?|<=?|>|<)\s*[\d.-]+(\s*,\s*(>=?|<=?|>|<)\s*[\d.-]+)*$/.test($)}validateRangePattern($,w){let K=Number($);if(Number.isNaN(K))return!1;let X=w.match(/^([\d.-]+)\s*-\s*([\d.-]+)$/);if(X){let W=Number(X[1]),Z=Number(X[2]);return K>=W&&K<=Z}return w.split(",").map((W)=>W.trim()).every((W)=>{let Z=W.match(/^(>=?|<=?|>|<)\s*([\d.-]+)$/);if(!Z)return!1;let z=Z[1],D=Number(Z[2]);switch(z){case">":return K>D;case">=":return K>=D;case"<":return K<D;case"<=":return K<=D;default:return!1}})}isArraySelector($){return/^\[.*\]$/.test($)||$==="*"||$.startsWith("slice(")}getArrayValue($,w){if(w==="*")return $;if(w.startsWith("[")&&w.endsWith("]")){let K=w.slice(1,-1);if(K==="*")return $;let X=Number(K);if(!Number.isNaN(X))return X>=0?$[X]:$[$.length+X]}if(w.startsWith("slice(")){let K=w.match(/slice\((\d+)(?:,(\d+))?\)/);if(K){let X=Number(K[1]),Q=K[2]?Number(K[2]):void 0;return $.slice(X,Q)}}return}async executeSequential($){let w=performance.now(),K=[];for(let X=0;X<$.length;X++){let Q=await this.executeRequest($[X],X+1);if(K.push(Q),!Q.success&&!this.globalConfig.continueOnError){this.logger.logError("Stopping execution due to error");break}}return this.createSummary(K,performance.now()-w)}async executeParallel($){let w=performance.now(),K=$.map((Q,W)=>this.executeRequest(Q,W+1)),X=await Promise.all(K);return this.createSummary(X,performance.now()-w)}async execute($){this.logger.logExecutionStart($.length,this.globalConfig.execution||"sequential");let w=this.globalConfig.execution==="parallel"?await this.executeParallel($):await this.executeSequential($);if(this.logger.logSummary(w),this.globalConfig.output?.saveToFile)await this.saveSummaryToFile(w);return w}createSummary($,w){let K=$.filter((Q)=>Q.success).length,X=$.filter((Q)=>!Q.success).length;return{total:$.length,successful:K,failed:X,duration:w,results:$}}async saveSummaryToFile($){let w=this.globalConfig.output?.saveToFile;if(!w)return;let K=JSON.stringify($,null,2);await Bun.write(w,K),this.logger.logInfo(`Results saved to ${w}`)}}var{YAML:N}=globalThis.Bun;class H{static async parseFile($){let K=await Bun.file($).text();return N.parse(K)}static parse($){return N.parse($)}static interpolateVariables($,w){if(typeof $==="string"){let K=$.match(/^\$\{([^}]+)\}$/);if(K){let X=K[1],Q=H.resolveDynamicVariable(X);return Q!==null?Q:w[X]||$}return $.replace(/\$\{([^}]+)\}/g,(X,Q)=>{let W=H.resolveDynamicVariable(Q);return W!==null?W:w[Q]||X})}if(Array.isArray($))return $.map((K)=>H.interpolateVariables(K,w));if($&&typeof $==="object"){let K={};for(let[X,Q]of Object.entries($))K[X]=H.interpolateVariables(Q,w);return K}return $}static resolveDynamicVariable($){if($==="UUID")return crypto.randomUUID();if($==="CURRENT_TIME"||$==="TIMESTAMP")return Date.now().toString();if($.startsWith("DATE:")){let w=$.slice(5);return H.formatDate(new Date,w)}if($.startsWith("TIME:")){let w=$.slice(5);return H.formatTime(new Date,w)}return null}static formatDate($,w){let K=$.getFullYear(),X=String($.getMonth()+1).padStart(2,"0"),Q=String($.getDate()).padStart(2,"0");return w.replace("YYYY",K.toString()).replace("MM",X).replace("DD",Q)}static formatTime($,w){let K=String($.getHours()).padStart(2,"0"),X=String($.getMinutes()).padStart(2,"0"),Q=String($.getSeconds()).padStart(2,"0");return w.replace("HH",K).replace("mm",X).replace("ss",Q)}static mergeConfigs($,w){return{...$,...w,headers:{...$.headers,...w.headers},params:{...$.params,...w.params},variables:{...$.variables,...w.variables}}}}function S(){if(typeof BUILD_VERSION<"u")return BUILD_VERSION;if(process.env.CURL_RUNNER_VERSION)return process.env.CURL_RUNNER_VERSION;try{let $=["../package.json","./package.json","../../package.json"];for(let w of $)try{let K=A(w);if(K.name==="@curl-runner/cli"&&K.version)return K.version}catch{}return"0.0.0"}catch{return"0.0.0"}}var q={reset:"\x1B[0m",bright:"\x1B[1m",dim:"\x1B[2m",underscore:"\x1B[4m",black:"\x1B[30m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",blue:"\x1B[34m",magenta:"\x1B[35m",cyan:"\x1B[36m",white:"\x1B[37m",bgBlack:"\x1B[40m",bgRed:"\x1B[41m",bgGreen:"\x1B[42m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m",bgMagenta:"\x1B[45m",bgCyan:"\x1B[46m",bgWhite:"\x1B[47m"};function F($,w){return`${q[w]}${$}${q.reset}`}var P=`${process.env.HOME}/.curl-runner-version-cache.json`,y=86400000,f="https://registry.npmjs.org/@curl-runner/cli/latest";class L{async checkForUpdates($=!1){try{if(process.env.CI)return;let w=S();if(w==="0.0.0")return;if(!$){let X=await this.getCachedVersion();if(X&&Date.now()-X.lastCheck<y){this.compareVersions(w,X.latestVersion);return}}let K=await this.fetchLatestVersion();if(K)await this.setCachedVersion(K),this.compareVersions(w,K)}catch{}}async fetchLatestVersion(){try{let $=await fetch(f,{signal:AbortSignal.timeout(3000)});if(!$.ok)return null;return(await $.json()).version}catch{return null}}compareVersions($,w){if(this.isNewerVersion($,w))console.log(),console.log(F("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E","yellow")),console.log(F("\u2502","yellow")+" "+F("\u2502","yellow")),console.log(F("\u2502","yellow")+" "+F("\uD83D\uDCE6 New version available!","bright")+` ${F($,"red")} \u2192 ${F(w,"green")} `+F("\u2502","yellow")),console.log(F("\u2502","yellow")+" "+F("\u2502","yellow")),console.log(F("\u2502","yellow")+" Update with: "+F("npm install -g @curl-runner/cli","cyan")+" "+F("\u2502","yellow")),console.log(F("\u2502","yellow")+" or: "+F("bun install -g @curl-runner/cli","cyan")+" "+F("\u2502","yellow")),console.log(F("\u2502","yellow")+" "+F("\u2502","yellow")),console.log(F("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F","yellow")),console.log()}isNewerVersion($,w){try{let K=$.replace(/^v/,""),X=w.replace(/^v/,""),Q=K.split(".").map(Number),W=X.split(".").map(Number);for(let Z=0;Z<Math.max(Q.length,W.length);Z++){let z=Q[Z]||0,D=W[Z]||0;if(D>z)return!0;if(D<z)return!1}return!1}catch{return!1}}async getCachedVersion(){try{let $=Bun.file(P);if(await $.exists())return JSON.parse(await $.text())}catch{}return null}async setCachedVersion($){try{let w={lastCheck:Date.now(),latestVersion:$};await Bun.write(P,JSON.stringify(w))}catch{}}}class h{logger=new M;async loadConfigFile(){let $=["curl-runner.yaml","curl-runner.yml",".curl-runner.yaml",".curl-runner.yml"];for(let w of $)try{if(await Bun.file(w).exists()){let X=await H.parseFile(w),Q=X.global||X;return this.logger.logInfo(`Loaded configuration from ${w}`),Q}}catch(K){this.logger.logWarning(`Failed to load configuration from ${w}: ${K}`)}return{}}loadEnvironmentVariables(){let $={};if(process.env.CURL_RUNNER_TIMEOUT)$.defaults={...$.defaults,timeout:Number.parseInt(process.env.CURL_RUNNER_TIMEOUT,10)};if(process.env.CURL_RUNNER_RETRIES)$.defaults={...$.defaults,retry:{...$.defaults?.retry,count:Number.parseInt(process.env.CURL_RUNNER_RETRIES,10)}};if(process.env.CURL_RUNNER_RETRY_DELAY)$.defaults={...$.defaults,retry:{...$.defaults?.retry,delay:Number.parseInt(process.env.CURL_RUNNER_RETRY_DELAY,10)}};if(process.env.CURL_RUNNER_VERBOSE)$.output={...$.output,verbose:process.env.CURL_RUNNER_VERBOSE.toLowerCase()==="true"};if(process.env.CURL_RUNNER_EXECUTION)$.execution=process.env.CURL_RUNNER_EXECUTION;if(process.env.CURL_RUNNER_CONTINUE_ON_ERROR)$.continueOnError=process.env.CURL_RUNNER_CONTINUE_ON_ERROR.toLowerCase()==="true";if(process.env.CURL_RUNNER_OUTPUT_FORMAT){let w=process.env.CURL_RUNNER_OUTPUT_FORMAT;if(["json","pretty","raw"].includes(w))$.output={...$.output,format:w}}if(process.env.CURL_RUNNER_PRETTY_LEVEL){let w=process.env.CURL_RUNNER_PRETTY_LEVEL;if(["minimal","standard","detailed"].includes(w))$.output={...$.output,prettyLevel:w}}if(process.env.CURL_RUNNER_OUTPUT_FILE)$.output={...$.output,saveToFile:process.env.CURL_RUNNER_OUTPUT_FILE};return $}async run($){try{let{files:w,options:K}=this.parseArguments($);if(!K.version&&!K.help)new L().checkForUpdates().catch(()=>{});if(K.help){this.showHelp();return}if(K.version){console.log(`curl-runner v${S()}`);return}let X=this.loadEnvironmentVariables(),Q=await this.loadConfigFile(),W=await this.findYamlFiles(w,K);if(W.length===0)this.logger.logError("No YAML files found"),process.exit(1);this.logger.logInfo(`Found ${W.length} YAML file(s)`);let Z=this.mergeGlobalConfigs(X,Q),z=[],D=[];for(let J of W){this.logger.logInfo(`Processing: ${J}`);let{requests:G,config:I}=await this.processYamlFile(J),j=I?.output||{},_=G.map((Y)=>({...Y,sourceOutputConfig:j,sourceFile:J}));if(I){let{...Y}=I;Z=this.mergeGlobalConfigs(Z,Y)}D.push({file:J,requests:_,config:I}),z.push(..._)}if(K.execution)Z.execution=K.execution;if(K.continueOnError!==void 0)Z.continueOnError=K.continueOnError;if(K.verbose!==void 0)Z.output={...Z.output,verbose:K.verbose};if(K.quiet!==void 0)Z.output={...Z.output,verbose:!1};if(K.output)Z.output={...Z.output,saveToFile:K.output};if(K.outputFormat)Z.output={...Z.output,format:K.outputFormat};if(K.prettyLevel)Z.output={...Z.output,prettyLevel:K.prettyLevel};if(K.showHeaders!==void 0)Z.output={...Z.output,showHeaders:K.showHeaders};if(K.showBody!==void 0)Z.output={...Z.output,showBody:K.showBody};if(K.showMetrics!==void 0)Z.output={...Z.output,showMetrics:K.showMetrics};if(K.timeout)Z.defaults={...Z.defaults,timeout:K.timeout};if(K.retries||K.noRetry){let J=K.noRetry?0:K.retries||0;Z.defaults={...Z.defaults,retry:{...Z.defaults?.retry,count:J}}}if(K.retryDelay)Z.defaults={...Z.defaults,retry:{...Z.defaults?.retry,delay:K.retryDelay}};if(z.length===0)this.logger.logError("No requests found in YAML files"),process.exit(1);let U=new E(Z),O;if(D.length>1){let J=[],G=0;for(let _=0;_<D.length;_++){let Y=D[_];this.logger.logFileHeader(Y.file,Y.requests.length);let R=await U.execute(Y.requests);if(J.push(...R.results),G+=R.duration,_<D.length-1)console.log()}let I=J.filter((_)=>_.success).length,j=J.filter((_)=>!_.success).length;O={total:J.length,successful:I,failed:j,duration:G,results:J},U.logger.logSummary(O,!0)}else O=await U.execute(z);process.exit(O.failed>0&&!Z.continueOnError?1:0)}catch(w){this.logger.logError(w instanceof Error?w.message:String(w)),process.exit(1)}}parseArguments($){let w={},K=[];for(let X=0;X<$.length;X++){let Q=$[X];if(Q.startsWith("--")){let W=Q.slice(2),Z=$[X+1];if(W==="help"||W==="version")w[W]=!0;else if(W==="no-retry")w.noRetry=!0;else if(W==="quiet")w.quiet=!0;else if(W==="show-headers")w.showHeaders=!0;else if(W==="show-body")w.showBody=!0;else if(W==="show-metrics")w.showMetrics=!0;else if(Z&&!Z.startsWith("--")){if(W==="continue-on-error")w.continueOnError=Z==="true";else if(W==="verbose")w.verbose=Z==="true";else if(W==="timeout")w.timeout=Number.parseInt(Z,10);else if(W==="retries")w.retries=Number.parseInt(Z,10);else if(W==="retry-delay")w.retryDelay=Number.parseInt(Z,10);else if(W==="output-format"){if(["json","pretty","raw"].includes(Z))w.outputFormat=Z}else if(W==="pretty-level"){if(["minimal","standard","detailed"].includes(Z))w.prettyLevel=Z}else w[W]=Z;X++}else w[W]=!0}else if(Q.startsWith("-")){let W=Q.slice(1);for(let Z of W)switch(Z){case"h":w.help=!0;break;case"v":w.verbose=!0;break;case"p":w.execution="parallel";break;case"c":w.continueOnError=!0;break;case"q":w.quiet=!0;break;case"o":{let z=$[X+1];if(z&&!z.startsWith("-"))w.output=z,X++;break}}}else K.push(Q)}return{files:K,options:w}}async findYamlFiles($,w){let K=new Set,X=[];if($.length===0)X=w.all?["**/*.yaml","**/*.yml"]:["*.yaml","*.yml"];else for(let Q of $)try{let Z=await(await import("fs/promises")).stat(Q);if(Z.isDirectory()){if(X.push(`${Q}/*.yaml`,`${Q}/*.yml`),w.all)X.push(`${Q}/**/*.yaml`,`${Q}/**/*.yml`)}else if(Z.isFile())X.push(Q)}catch{X.push(Q)}for(let Q of X){let W=new g(Q);for await(let Z of W.scan("."))if(Z.endsWith(".yaml")||Z.endsWith(".yml"))K.add(Z)}return Array.from(K).sort()}async processYamlFile($){let w=await H.parseFile($),K=[],X;if(w.global)X=w.global;let Q={...w.global?.variables,...w.collection?.variables},W={...w.global?.defaults,...w.collection?.defaults};if(w.request){let Z=this.prepareRequest(w.request,Q,W);K.push(Z)}if(w.requests)for(let Z of w.requests){let z=this.prepareRequest(Z,Q,W);K.push(z)}if(w.collection?.requests)for(let Z of w.collection.requests){let z=this.prepareRequest(Z,Q,W);K.push(z)}return{requests:K,config:X}}prepareRequest($,w,K){let X=H.interpolateVariables($,w);return H.mergeConfigs(K,X)}mergeGlobalConfigs($,w){return{...$,...w,variables:{...$.variables,...w.variables},output:{...$.output,...w.output},defaults:{...$.defaults,...w.defaults}}}showHelp(){console.log(`
7
8
  ${this.logger.color("\uD83D\uDE80 CURL RUNNER","bright")}
8
9
 
9
10
  ${this.logger.color("USAGE:","yellow")}
@@ -78,7 +79,7 @@ ${this.logger.color("YAML STRUCTURE:","yellow")}
78
79
  requests:
79
80
  - url: \${BASE_URL}/users
80
81
  method: GET
81
- `)}}var f=new q;f.run(process.argv.slice(2));
82
+ `)}}var p=new h;p.run(process.argv.slice(2));
82
83
 
83
- //# debugId=809AF9F0078090B864756E2164756E21
84
+ //# debugId=A2B9AAA9B03B2F7164756E2164756E21
84
85
  //# sourceMappingURL=cli.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curl-runner/cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "A powerful CLI tool for HTTP request management using YAML configuration",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "start": "bun run dist/cli.js",
12
- "dev": "bun --watch src/cli.ts",
12
+ "dev": "bun run src/cli.ts",
13
13
  "build": "bun run scripts/build-with-version.ts",
14
14
  "format": "biome format --write .",
15
15
  "lint": "biome lint .",
@@ -5,6 +5,64 @@ import type {
5
5
  RequestConfig,
6
6
  } from '../types/config';
7
7
 
8
+ interface TreeNode {
9
+ label: string;
10
+ value?: string;
11
+ children?: TreeNode[];
12
+ color?: string;
13
+ }
14
+
15
+ class TreeRenderer {
16
+ private colors: Record<string, string>;
17
+
18
+ constructor(colors: Record<string, string>) {
19
+ this.colors = colors;
20
+ }
21
+
22
+ private color(text: string, colorName: string): string {
23
+ if (!colorName || !this.colors[colorName]) {
24
+ return text;
25
+ }
26
+ return `${this.colors[colorName]}${text}${this.colors.reset}`;
27
+ }
28
+
29
+ render(nodes: TreeNode[], basePrefix: string = ' '): void {
30
+ nodes.forEach((node, index) => {
31
+ const isLast = index === nodes.length - 1;
32
+ const prefix = isLast ? `${basePrefix}└─` : `${basePrefix}├─`;
33
+
34
+ if (node.label && node.value) {
35
+ // Regular labeled node with value
36
+ const displayValue = node.color ? this.color(node.value, node.color) : node.value;
37
+
38
+ // Handle multiline values (like Response Body)
39
+ const lines = displayValue.split('\n');
40
+ if (lines.length === 1) {
41
+ console.log(`${prefix} ${node.label}: ${displayValue}`);
42
+ } else {
43
+ console.log(`${prefix} ${node.label}:`);
44
+ const contentPrefix = isLast ? `${basePrefix} ` : `${basePrefix}│ `;
45
+ lines.forEach((line) => {
46
+ console.log(`${contentPrefix}${line}`);
47
+ });
48
+ }
49
+ } else if (node.label && !node.value) {
50
+ // Section header (like "Headers:" or "Metrics:")
51
+ console.log(`${prefix} ${node.label}:`);
52
+ } else if (!node.label && node.value) {
53
+ // Content line without label (like response body lines)
54
+ const continuationPrefix = isLast ? `${basePrefix} ` : `${basePrefix}│ `;
55
+ console.log(`${continuationPrefix}${node.value}`);
56
+ }
57
+
58
+ if (node.children && node.children.length > 0) {
59
+ const childPrefix = isLast ? `${basePrefix} ` : `${basePrefix}│ `;
60
+ this.render(node.children, childPrefix);
61
+ }
62
+ });
63
+ }
64
+ }
65
+
8
66
  export class Logger {
9
67
  private config: GlobalConfig['output'];
10
68
 
@@ -40,7 +98,7 @@ export class Logger {
40
98
  showBody: true,
41
99
  showMetrics: false,
42
100
  format: 'pretty',
43
- prettyLevel: 'standard',
101
+ prettyLevel: 'minimal',
44
102
  ...config,
45
103
  };
46
104
  }
@@ -239,56 +297,25 @@ export class Logger {
239
297
  return `${(bytes / 1024 ** i).toFixed(2)} ${sizes[i]}`;
240
298
  }
241
299
 
242
- private printSeparator(char: string = '─', length: number = 60): void {
243
- console.log(this.color(char.repeat(length), 'dim'));
244
- }
245
-
246
300
  logExecutionStart(count: number, mode: string): void {
247
301
  if (!this.shouldShowOutput()) {
248
302
  return;
249
303
  }
250
304
 
251
305
  if (this.shouldShowSeparators()) {
252
- this.printSeparator('═');
253
- console.log(this.color('🚀 CURL RUNNER', 'bright'));
254
- console.log(this.color(`Executing ${count} request(s) in ${mode} mode`, 'cyan'));
255
- this.printSeparator('═');
306
+ console.log(); // Add spacing before the execution header
307
+ console.log(this.color(`Executing ${count} request(s) in ${mode} mode`, 'dim'));
308
+ console.log();
309
+ } else {
310
+ // For minimal format, still add spacing after processing info
256
311
  console.log();
257
312
  }
258
313
  }
259
314
 
260
- logRequestStart(config: RequestConfig, index: number): void {
261
- if (!this.shouldShowOutput()) {
262
- return;
263
- }
264
-
265
- const name = config.name || `Request #${index}`;
266
- const sourceFile = config.sourceFile
267
- ? ` ${this.color(`[${this.getShortFilename(config.sourceFile)}]`, 'cyan')}`
268
- : '';
269
- console.log(this.color(`â–ļ ${name}`, 'bright') + sourceFile);
270
- console.log(
271
- ` ${this.color(config.method || 'GET', 'yellow')} ${this.color(config.url, 'blue')}`,
272
- );
273
-
274
- if (
275
- this.shouldShowRequestDetails() &&
276
- config.headers &&
277
- Object.keys(config.headers).length > 0
278
- ) {
279
- console.log(this.color(' Headers:', 'dim'));
280
- for (const [key, value] of Object.entries(config.headers)) {
281
- console.log(` ${key}: ${value}`);
282
- }
283
- }
284
-
285
- if (this.shouldShowRequestDetails() && config.body) {
286
- console.log(this.color(' Body:', 'dim'));
287
- const bodyStr = this.formatJson(config.body);
288
- for (const line of bodyStr.split('\n')) {
289
- console.log(` ${line}`);
290
- }
291
- }
315
+ logRequestStart(_config: RequestConfig, _index: number): void {
316
+ // In the new format, we show everything in logRequestComplete
317
+ // This method is kept for compatibility but simplified
318
+ return;
292
319
  }
293
320
 
294
321
  logCommand(command: string): void {
@@ -336,62 +363,173 @@ export class Logger {
336
363
  return;
337
364
  }
338
365
 
366
+ const level = this.config.prettyLevel || 'minimal';
339
367
  const statusColor = result.success ? 'green' : 'red';
340
- const statusIcon = result.success ? '✓' : '✗';
368
+ const statusIcon = result.success ? '✓' : 'x';
369
+ const name = result.request.name || 'Request';
370
+
371
+ if (level === 'minimal') {
372
+ // Minimal format: clean tree structure but compact
373
+ const fileTag = result.request.sourceFile
374
+ ? this.getShortFilename(result.request.sourceFile)
375
+ : 'inline';
376
+ console.log(
377
+ `${this.color(statusIcon, statusColor)} ${this.color(name, 'bright')} [${fileTag}]`,
378
+ );
379
+
380
+ const treeNodes: TreeNode[] = [];
381
+ const renderer = new TreeRenderer(this.colors);
382
+
383
+ treeNodes.push({
384
+ label: result.request.method || 'GET',
385
+ value: result.request.url,
386
+ color: 'blue',
387
+ });
388
+
389
+ const statusText = result.status ? `${result.status}` : 'ERROR';
390
+ treeNodes.push({
391
+ label: `${statusIcon} Status`,
392
+ value: statusText,
393
+ color: statusColor,
394
+ });
395
+
396
+ if (result.metrics) {
397
+ const durationSize = `${this.formatDuration(result.metrics.duration)} | ${this.formatSize(result.metrics.size)}`;
398
+ treeNodes.push({
399
+ label: 'Duration',
400
+ value: durationSize,
401
+ color: 'cyan',
402
+ });
403
+ }
341
404
 
342
- console.log(
343
- ` ${this.color(statusIcon, statusColor)} ` +
344
- `Status: ${this.color(String(result.status || 'ERROR'), statusColor)}`,
345
- );
405
+ renderer.render(treeNodes);
346
406
 
347
- if (result.error) {
348
- this.logValidationErrors(result.error);
407
+ if (result.error) {
408
+ console.log();
409
+ this.logValidationErrors(result.error);
410
+ }
411
+
412
+ console.log();
413
+ return;
349
414
  }
350
415
 
351
- if (this.shouldShowMetrics() && result.metrics) {
352
- const metrics = result.metrics;
353
- const parts = [`Duration: ${this.color(this.formatDuration(metrics.duration), 'cyan')}`];
416
+ // Standard and detailed formats: use clean tree structure
417
+ console.log(`${this.color(statusIcon, statusColor)} ${this.color(name, 'bright')}`);
354
418
 
355
- if (metrics.size !== undefined) {
356
- parts.push(`Size: ${this.color(this.formatSize(metrics.size), 'cyan')}`);
357
- }
419
+ // Build tree structure
420
+ const treeNodes: TreeNode[] = [];
421
+ const renderer = new TreeRenderer(this.colors);
358
422
 
359
- if (this.shouldShowRequestDetails()) {
360
- if (metrics.dnsLookup) {
361
- parts.push(`DNS: ${this.formatDuration(metrics.dnsLookup)}`);
362
- }
363
- if (metrics.tcpConnection) {
364
- parts.push(`TCP: ${this.formatDuration(metrics.tcpConnection)}`);
365
- }
366
- if (metrics.tlsHandshake) {
367
- parts.push(`TLS: ${this.formatDuration(metrics.tlsHandshake)}`);
368
- }
369
- if (metrics.firstByte) {
370
- parts.push(`TTFB: ${this.formatDuration(metrics.firstByte)}`);
371
- }
372
- }
423
+ // Main info nodes
424
+ treeNodes.push({ label: 'URL', value: result.request.url, color: 'blue' });
425
+ treeNodes.push({ label: 'Method', value: result.request.method || 'GET', color: 'yellow' });
426
+ treeNodes.push({
427
+ label: 'Status',
428
+ value: String(result.status || 'ERROR'),
429
+ color: statusColor,
430
+ });
373
431
 
374
- console.log(` ${parts.join(' | ')}`);
432
+ if (result.metrics) {
433
+ treeNodes.push({
434
+ label: 'Duration',
435
+ value: this.formatDuration(result.metrics.duration),
436
+ color: 'cyan',
437
+ });
375
438
  }
376
439
 
440
+ // Add headers section if needed
377
441
  if (this.shouldShowHeaders() && result.headers && Object.keys(result.headers).length > 0) {
378
- console.log(this.color(' Response Headers:', 'dim'));
379
- for (const [key, value] of Object.entries(result.headers)) {
380
- console.log(` ${key}: ${value}`);
381
- }
442
+ const headerChildren: TreeNode[] = Object.entries(result.headers).map(([key, value]) => ({
443
+ label: this.color(key, 'dim'),
444
+ value: String(value),
445
+ }));
446
+
447
+ treeNodes.push({
448
+ label: 'Headers',
449
+ children: headerChildren,
450
+ });
382
451
  }
383
452
 
453
+ // Add body section if needed
384
454
  if (this.shouldShowBody() && result.body) {
385
- console.log(this.color(' Response Body:', 'dim'));
386
455
  const bodyStr = this.formatJson(result.body);
387
456
  const lines = bodyStr.split('\n');
388
457
  const maxLines = this.shouldShowRequestDetails() ? Infinity : 10;
389
- for (const line of lines.slice(0, maxLines)) {
390
- console.log(` ${line}`);
391
- }
458
+ const bodyLines = lines.slice(0, maxLines);
459
+
392
460
  if (lines.length > maxLines) {
393
- console.log(this.color(` ... (${lines.length - maxLines} more lines)`, 'dim'));
461
+ bodyLines.push(this.color(`... (${lines.length - maxLines} more lines)`, 'dim'));
394
462
  }
463
+
464
+ treeNodes.push({
465
+ label: 'Response Body',
466
+ value: bodyLines.join('\n'),
467
+ });
468
+ }
469
+
470
+ // Add detailed metrics section if needed
471
+ if (this.shouldShowMetrics() && result.metrics && level === 'detailed') {
472
+ const metrics = result.metrics;
473
+ const metricChildren: TreeNode[] = [];
474
+
475
+ metricChildren.push({
476
+ label: 'Request Duration',
477
+ value: this.formatDuration(metrics.duration),
478
+ color: 'cyan',
479
+ });
480
+
481
+ if (metrics.size !== undefined) {
482
+ metricChildren.push({
483
+ label: 'Response Size',
484
+ value: this.formatSize(metrics.size),
485
+ color: 'cyan',
486
+ });
487
+ }
488
+
489
+ if (metrics.dnsLookup) {
490
+ metricChildren.push({
491
+ label: 'DNS Lookup',
492
+ value: this.formatDuration(metrics.dnsLookup),
493
+ color: 'cyan',
494
+ });
495
+ }
496
+
497
+ if (metrics.tcpConnection) {
498
+ metricChildren.push({
499
+ label: 'TCP Connection',
500
+ value: this.formatDuration(metrics.tcpConnection),
501
+ color: 'cyan',
502
+ });
503
+ }
504
+
505
+ if (metrics.tlsHandshake) {
506
+ metricChildren.push({
507
+ label: 'TLS Handshake',
508
+ value: this.formatDuration(metrics.tlsHandshake),
509
+ color: 'cyan',
510
+ });
511
+ }
512
+
513
+ if (metrics.firstByte) {
514
+ metricChildren.push({
515
+ label: 'Time to First Byte',
516
+ value: this.formatDuration(metrics.firstByte),
517
+ color: 'cyan',
518
+ });
519
+ }
520
+
521
+ treeNodes.push({
522
+ label: 'Metrics',
523
+ children: metricChildren,
524
+ });
525
+ }
526
+
527
+ // Render the tree
528
+ renderer.render(treeNodes);
529
+
530
+ if (result.error) {
531
+ console.log();
532
+ this.logValidationErrors(result.error);
395
533
  }
396
534
 
397
535
  console.log();
@@ -435,53 +573,64 @@ export class Logger {
435
573
  return;
436
574
  }
437
575
 
438
- if (this.shouldShowSeparators()) {
439
- this.printSeparator('═');
440
- const title = isGlobal ? 'đŸŽ¯ OVERALL SUMMARY' : '📊 EXECUTION SUMMARY';
441
- console.log(this.color(title, 'bright'));
442
- this.printSeparator();
576
+ const level = this.config.prettyLevel || 'minimal';
577
+
578
+ // Add spacing for global summary
579
+ if (isGlobal) {
580
+ console.log(); // Extra spacing before global summary
443
581
  }
444
582
 
445
- const successRate = ((summary.successful / summary.total) * 100).toFixed(1);
446
- const statusColor =
447
- summary.failed === 0 ? 'green' : summary.successful === 0 ? 'red' : 'yellow';
583
+ if (level === 'minimal') {
584
+ // Simple one-line summary for minimal, similar to docs example
585
+ const statusColor = summary.failed === 0 ? 'green' : 'red';
586
+ const successText =
587
+ summary.failed === 0
588
+ ? `${summary.total} request${summary.total === 1 ? '' : 's'} completed successfully`
589
+ : `${summary.successful}/${summary.total} request${summary.total === 1 ? '' : 's'} completed, ${summary.failed} failed`;
448
590
 
449
- console.log(` Total Requests: ${this.color(String(summary.total), 'cyan')}`);
450
- console.log(` Successful: ${this.color(String(summary.successful), 'green')}`);
451
- console.log(` Failed: ${this.color(String(summary.failed), 'red')}`);
452
- console.log(` Success Rate: ${this.color(`${successRate}%`, statusColor)}`);
453
- console.log(` Total Duration: ${this.color(this.formatDuration(summary.duration), 'cyan')}`);
591
+ const summaryPrefix = isGlobal ? '◆ Global Summary' : 'Summary';
592
+ console.log(`${summaryPrefix}: ${this.color(successText, statusColor)}`);
593
+ return;
594
+ }
595
+
596
+ // Compact summary for standard/detailed - much simpler
597
+ const _successRate = ((summary.successful / summary.total) * 100).toFixed(1);
598
+ const statusColor = summary.failed === 0 ? 'green' : 'red';
599
+ const successText =
600
+ summary.failed === 0
601
+ ? `${summary.total} request${summary.total === 1 ? '' : 's'} completed successfully`
602
+ : `${summary.successful}/${summary.total} request${summary.total === 1 ? '' : 's'} completed, ${summary.failed} failed`;
603
+
604
+ const summaryPrefix = isGlobal ? '◆ Global Summary' : 'Summary';
605
+ console.log();
606
+ console.log(
607
+ `${summaryPrefix}: ${this.color(successText, statusColor)} (${this.color(this.formatDuration(summary.duration), 'cyan')})`,
608
+ );
454
609
 
455
610
  if (summary.failed > 0 && this.shouldShowRequestDetails()) {
456
- console.log();
457
- console.log(this.color(' Failed Requests:', 'red'));
458
611
  summary.results
459
612
  .filter((r) => !r.success)
460
613
  .forEach((r) => {
461
614
  const name = r.request.name || r.request.url;
462
- console.log(` â€ĸ ${name}: ${r.error}`);
615
+ console.log(` ${this.color('â€ĸ', 'red')} ${name}: ${r.error}`);
463
616
  });
464
617
  }
465
-
466
- if (this.shouldShowSeparators()) {
467
- this.printSeparator('═');
468
- }
469
618
  }
470
619
 
471
620
  logError(message: string): void {
472
- console.error(this.color(`❌ ${message}`, 'red'));
621
+ console.error(this.color(`✗ ${message}`, 'red'));
473
622
  }
474
623
 
475
624
  logWarning(message: string): void {
476
- console.warn(this.color(`âš ī¸ ${message}`, 'yellow'));
625
+ console.warn(this.color(`⚠ ${message}`, 'yellow'));
477
626
  }
478
627
 
479
628
  logInfo(message: string): void {
480
- console.log(this.color(`â„šī¸ ${message}`, 'blue'));
629
+ console.log(this.color(`ℹ ${message}`, 'blue'));
481
630
  }
482
631
 
483
632
  logSuccess(message: string): void {
484
- console.log(this.color(`✅ ${message}`, 'green'));
633
+ console.log(this.color(`✓ ${message}`, 'green'));
485
634
  }
486
635
 
487
636
  logFileHeader(fileName: string, requestCount: number): void {
@@ -491,11 +640,9 @@ export class Logger {
491
640
 
492
641
  const shortName = fileName.replace(/.*\//, '').replace('.yaml', '');
493
642
  console.log();
494
- this.printSeparator('─');
495
643
  console.log(
496
- this.color(`📄 ${shortName}.yaml`, 'bright') +
644
+ this.color(`â–ļ ${shortName}.yaml`, 'bright') +
497
645
  this.color(` (${requestCount} request${requestCount === 1 ? '' : 's'})`, 'dim'),
498
646
  );
499
- this.printSeparator('─');
500
647
  }
501
648
  }