UNPKG

next-api-analyzer

Version:

Minimal, efficient Next.js API analyzer with Postman-ready testing guides for security, performance, and maintainability

27 lines (18 loc) 20.8 kB
#!/usr/bin/env node "use strict";var _=Object.create;var O=Object.defineProperty;var G=Object.getOwnPropertyDescriptor;var q=Object.getOwnPropertyNames;var j=Object.getPrototypeOf,W=Object.prototype.hasOwnProperty;var K=(n,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of q(e))!W.call(n,r)&&r!==t&&O(n,r,{get:()=>e[r],enumerable:!(s=G(e,r))||s.enumerable});return n};var d=(n,e,t)=>(t=n!=null?_(j(n)):{},K(e||!n||!n.__esModule?O(t,"default",{value:n,enumerable:!0}):t,n));var k=require("commander");var N=d(require("fs")),U=d(require("path")),f=d(require("typescript"));var B=[{name:"NextAuth.js",pattern:/getServerSession|getToken|next-auth/i,type:"NextAuth.js",confidence:.9},{name:"JWT",pattern:/jwt\.verify|jsonwebtoken|jose/i,type:"JWT",confidence:.8},{name:"Bearer Token",pattern:/bearer\s+token|authorization.*bearer/i,type:"Bearer Token",confidence:.7},{name:"API Key",pattern:/api[_-]?key|x-api-key/i,type:"API Key",confidence:.6},{name:"Session",pattern:/req\.session|express-session/i,type:"Session",confidence:.7},{name:"Firebase Auth",pattern:/firebase.*auth|getAuth/i,type:"Firebase Auth",confidence:.8},{name:"Supabase Auth",pattern:/supabase.*auth|createClient.*auth/i,type:"Supabase Auth",confidence:.8}],V=[{name:"CORS",pattern:/cors\(|Access-Control-Allow/i,category:"security"},{name:"Rate Limiting",pattern:/rateLimit|express-rate-limit|slowDown/i,category:"security"},{name:"Validation",pattern:/joi\.|yup\.|zod\.|express-validator/i,category:"validation"}],E={apiDir:"src/app/api",outputDir:"./api-analysis",includePatterns:["**/*.ts","**/*.js"],excludePatterns:["**/node_modules/**","**/*.test.*","**/*.spec.*","**/*.d.ts","**/dist/**","**/build/**"],authPatterns:B,middlewarePatterns:V,enablePerformanceAnalysis:!0,enableSecurityAnalysis:!0,thresholds:{security:80,performance:70,maintainability:75,complexity:10}};var y=d(require("fs/promises")),R=d(require("path")),L=require("glob");var w=class n{constructor(){this.config={level:1,colors:!0}}static getInstance(){return n.instance||(n.instance=new n),n.instance}configure(e){this.config={...this.config,...e}}shouldLog(e){return e>=this.config.level}formatMessage(e,t,s){let r=this.config.prefix?`[${this.config.prefix}] `:"";return this.config.colors?`${r}${s} ${t}`:`${r}${e}: ${t}`}debug(e,...t){this.shouldLog(0)&&console.log(this.formatMessage("DEBUG",e,"\u{1F41B}"),...t)}info(e,...t){this.shouldLog(1)&&console.log(this.formatMessage("INFO",e,"\u2139\uFE0F"),...t)}success(e,...t){this.shouldLog(1)&&console.log(this.formatMessage("SUCCESS",e,"\u2705"),...t)}warn(e,...t){this.shouldLog(2)&&console.warn(this.formatMessage("WARN",e,"\u26A0\uFE0F"),...t)}error(e,...t){this.shouldLog(3)&&console.error(this.formatMessage("ERROR",e,"\u274C"),...t)}progress(e){this.shouldLog(1)&&process.stdout.write(`\u23F3 ${e}...\r`)}clearProgress(){process.stdout.write("\r\x1B[K")}separator(){this.shouldLog(1)&&console.log("\u2500".repeat(80))}},l=w.getInstance();var p=class{static async findApiFiles(e){try{let t=e.includePatterns.map(i=>R.default.join(e.apiDir,i).replace(/\\/g,"/")),s=[];for(let i of t){let o=await(0,L.glob)(i,{ignore:e.excludePatterns,absolute:!0,nodir:!0});s.push(...o)}return[...new Set(s)].filter(i=>this.isApiFile(i))}catch(t){throw l.error("Error finding API files:",t),new Error(`Failed to find API files: ${t}`)}}static isApiFile(e){let t=R.default.basename(e),s=/\.(js|ts)$/.test(t),r=t==="route.js"||t==="route.ts",i=e.includes("/api/")||e.includes("\\api\\");return s&&(r||i)}static async getFileStats(e){try{let[t,s]=await Promise.all([y.default.stat(e),y.default.readFile(e,"utf-8")]),r=s.split(` `).filter(i=>{let o=i.trim();return o&&!o.startsWith("//")&&!o.startsWith("/*")}).length;return{size:t.size,lastModified:t.mtime,linesOfCode:r}}catch(t){throw l.error(`Error getting file stats for ${e}:`,t),t}}static async ensureDirectoryExists(e){try{await y.default.mkdir(e,{recursive:!0})}catch(t){if(t.code!=="EEXIST")throw t}}static async writeJsonFile(e,t){await this.ensureDirectoryExists(R.default.dirname(e)),await y.default.writeFile(e,JSON.stringify(t,null,2),"utf-8")}static async writeFile(e,t){await this.ensureDirectoryExists(R.default.dirname(e)),await y.default.writeFile(e,t,"utf-8")}static async fileExists(e){try{return await y.default.access(e),!0}catch{return!1}}};var P=(n,e,t,s,r,i,o,c,a,u,I=[],M,H)=>({id:`${n}_${i.replace(/[^a-zA-Z0-9]/g,"_")}`,type:e,severity:t,title:s,description:r,route:i,solution:o,impact:c,effort:a,category:u,tags:I,codeExample:M,fixExample:H}),D=n=>n>=50?"CRITICAL":n>=30?"HIGH":n>=15?"MEDIUM":"LOW",T=n=>n>=90?"\u{1F7E2}":n>=70?"\u{1F7E1}":n>=50?"\u{1F7E0}":"\u{1F534}";var b=n=>({LOW:1,MEDIUM:2,HIGH:3,CRITICAL:4})[n],F={SQL_INJECTION:[/query\s*\+\s*['"`]/,/\$\{[^}]*query[^}]*\}/,/SELECT\s+.*\+.*FROM/i],XSS:[/innerHTML\s*=\s*.*req\./,/dangerouslySetInnerHTML/,/eval\s*\(.*req\./],HARDCODED_SECRETS:[/password\s*[:=]\s*['"`][^'"`]{8,}/,/api[_-]?key\s*[:=]\s*['"`][A-Za-z0-9]{16,}/,/secret\s*[:=]\s*['"`][A-Za-z0-9]{16,}/]},$={BLOCKING_OPERATIONS:[/fs\.readFileSync/,/fs\.writeFileSync/,/child_process\.execSync/],INEFFICIENT_QUERIES:[/SELECT \*/i,/\.find$$$$/,/N\+1/i],MEMORY_LEAKS:[/setInterval\s*\(/,/new Array\s*\(\s*\d{6,}/,/global\./]};var m=d(require("typescript")),h=class{static extractMethods(e,t,s){let r=new Set;if(s){let i=["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"];m.default.forEachChild(t,o=>{m.default.isFunctionDeclaration(o)&&o.name&&i.includes(o.name.text)&&r.add(o.name.text)})}else{let i=/req\.method\s*===?\s*['"`](\w+)['"`]/g,o;for(;(o=i.exec(e))!==null;)r.add(o[1].toUpperCase())}return r.size===0?["GET"]:Array.from(r)}static extractParams(e,t){let s=new Set,r;for(;(r=t.exec(e))!==null;)s.add(r[1]);return Array.from(s)}static calculateComplexity(e){let t=1,s=r=>{switch(r.kind){case m.default.SyntaxKind.IfStatement:case m.default.SyntaxKind.WhileStatement:case m.default.SyntaxKind.ForStatement:case m.default.SyntaxKind.SwitchStatement:case m.default.SyntaxKind.ConditionalExpression:t++;break}m.default.forEachChild(r,s)};return s(e),t}};h.createRecommendation=P;var A=class extends h{static analyzeRoute(e,t){let s=[],r=[],i=0;try{!e.hasAuth&&this.isSensitiveRoute(e)&&(i+=30,s.push(this.createRecommendation("MISSING_AUTHENTICATION","SECURITY","HIGH","Missing Authentication",`Route ${e.path} allows ${e.methods.join(", ")} operations without authentication`,e.path,"Add authentication middleware","Unauthorized access to sensitive operations","MEDIUM","authentication",["security","auth"])));for(let a of this.VULNERABILITIES)for(let u of a.patterns)if(u.test(t)){i+=a.score,r.push(a.name),s.push(this.createRecommendation(a.name,"SECURITY",a.severity,`Potential ${a.name.replace("_"," ")}`,`Detected pattern that may indicate ${a.name.toLowerCase().replace("_"," ")} vulnerability`,e.path,this.getVulnerabilitySolution(a.name),this.getVulnerabilityImpact(a.name),"MEDIUM",a.category,["security",a.category]));break}Object.entries(F).forEach(([a,u])=>{if(u.some(I=>I.test(t))){let I=a==="SQL_INJECTION"?"CRITICAL":"HIGH";i+=I==="CRITICAL"?40:25,s.push(this.createRecommendation(a,"SECURITY",I,`Potential ${a.replace("_"," ")}`,`Detected pattern that may indicate ${a.toLowerCase().replace("_"," ")} vulnerability`,e.path,this.getVulnerabilitySolution(a),this.getVulnerabilityImpact(a),"MEDIUM","security",["security",a.toLowerCase()]))}});let o=D(i),c=Math.max(0,100-i);return l.debug(`Security analysis for ${e.path}: score=${c}, risk=${o}`),{riskLevel:o,recommendations:s,securityScore:c,vulnerabilities:r}}catch(o){return l.error(`Error in security analysis for ${e.path}:`,o),{riskLevel:"MEDIUM",recommendations:[],securityScore:50,vulnerabilities:[]}}}static isSensitiveRoute(e){let t=["POST","PUT","DELETE","PATCH"],s=[/\/admin\//i,/\/user[s]?\//i,/\/auth\//i,/\/config\//i,/\/settings\//i,/\/payment[s]\//i,/\/checkout\//i,/\/order[s]?\//i];return e.methods.some(r=>t.includes(r))||s.some(r=>r.test(e.path))}static getVulnerabilitySolution(e){return{SQL_INJECTION:"Use parameterized queries or ORM with proper escaping",XSS:"Sanitize user input and use Content Security Policy",HARDCODED_SECRETS:"Move secrets to environment variables"}[e]||"Review and fix the security issue"}static getVulnerabilityImpact(e){return{SQL_INJECTION:"Database compromise and data theft",XSS:"Client-side code execution and session hijacking",HARDCODED_SECRETS:"Credential exposure and unauthorized access"}[e]||"Security vulnerability"}};A.VULNERABILITIES=[{name:"SQL_INJECTION",patterns:[/query\s*\+\s*['"`]/,/\$\{[^}]*query[^}]*\}/,/execute\s*\(\s*['"`].*\$\{/,/SELECT\s+.*\+.*FROM/i,/INSERT\s+.*\+.*VALUES/i,/UPDATE\s+.*SET\s+.*\+/i,/DELETE\s+.*WHERE\s+.*\+/i],severity:"CRITICAL",score:40,category:"injection",cwe:"CWE-89"},{name:"XSS",patterns:[/innerHTML\s*=\s*.*req\./,/dangerouslySetInnerHTML/,/document\.write\s*\(.*req\./,/eval\s*\(.*req\./,/Function\s*\(.*req\./],severity:"HIGH",score:30,category:"injection",cwe:"CWE-79"},{name:"HARDCODED_SECRETS",patterns:[/password\s*[:=]\s*['"`][^'"`]{8,}/,/api[_-]?key\s*[:=]\s*['"`][A-Za-z0-9]{16,}/,/secret\s*[:=]\s*['"`][A-Za-z0-9]{16,}/,/token\s*[:=]\s*['"`][A-Za-z0-9]{20,}/,/private[_-]?key\s*[:=]\s*['"`][^'"`]{20,}/],severity:"CRITICAL",score:35,category:"secrets",cwe:"CWE-798"},{name:"WEAK_CRYPTO",patterns:[/md5\s*\(/,/sha1\s*\(/,/Math\.random\s*$$\s*$$/,/crypto\.pseudoRandomBytes/,/des\s*\(/i,/rc4\s*\(/i],severity:"MEDIUM",score:15,category:"cryptography",cwe:"CWE-327"},{name:"CORS_MISCONFIGURATION",patterns:[/Access-Control-Allow-Origin.*\*/,/cors\s*\(\s*\{\s*origin\s*:\s*true/,/Access-Control-Allow-Credentials.*true.*Access-Control-Allow-Origin.*\*/],severity:"MEDIUM",score:20,category:"configuration",cwe:"CWE-942"},{name:"PATH_TRAVERSAL",patterns:[/\.\.[/\\]/,/path\.join\s*\([^)]*req\./,/fs\.readFile\s*\([^)]*req\./,/require\s*\([^)]*req\./],severity:"HIGH",score:25,category:"path-traversal",cwe:"CWE-22"},{name:"COMMAND_INJECTION",patterns:[/exec\s*\([^)]*req\./,/spawn\s*\([^)]*req\./,/system\s*\([^)]*req\./,/shell\s*\([^)]*req\./],severity:"CRITICAL",score:45,category:"injection",cwe:"CWE-78"}];var C=class extends h{static analyzeRoute(e,t,s){let r=[],i=100,o=this.calculateComplexity(s);if(Object.entries($).forEach(([c,a])=>{if(a.some(u=>u.test(t))){let u=this.getIssueScore(c);i-=u,r.push(this.createRecommendation(c,"PERFORMANCE",this.getIssueSeverity(c),this.getIssueTitle(c),this.getIssueDescription(c),e.path,this.getIssueSolution(c),this.getIssueImpact(c),"MEDIUM","performance",["performance",c.toLowerCase()]))}}),o>10){let c=Math.min(20,(o-10)*2);i-=c,r.push(this.createRecommendation("HIGH_COMPLEXITY","PERFORMANCE",o>20?"HIGH":"MEDIUM","High Cyclomatic Complexity",`Route has high cyclomatic complexity (${o})`,e.path,"Refactor into smaller functions and reduce nesting","Harder to maintain and potentially slower execution","HIGH","complexity",["complexity","maintainability"]))}return{performanceScore:Math.max(0,i),recommendations:r,complexity:o}}static getIssueScore(e){return{BLOCKING_OPERATIONS:20,INEFFICIENT_QUERIES:15,MEMORY_LEAKS:25}[e]||10}static getIssueSeverity(e){return{BLOCKING_OPERATIONS:"HIGH",INEFFICIENT_QUERIES:"MEDIUM",MEMORY_LEAKS:"HIGH"}[e]||"MEDIUM"}static getIssueTitle(e){return{BLOCKING_OPERATIONS:"Blocking Operations",INEFFICIENT_QUERIES:"Inefficient Database Queries",MEMORY_LEAKS:"Potential Memory Leaks"}[e]||"Performance Issue"}static getIssueDescription(e){return{BLOCKING_OPERATIONS:"Synchronous operations that can block the event loop",INEFFICIENT_QUERIES:"Database queries that may be inefficient",MEMORY_LEAKS:"Code patterns that may cause memory leaks"}[e]||"Performance issue detected"}static getIssueSolution(e){return{BLOCKING_OPERATIONS:"Use asynchronous alternatives",INEFFICIENT_QUERIES:"Optimize queries and use proper indexing",MEMORY_LEAKS:"Ensure proper cleanup of resources"}[e]||"Optimize the code"}static getIssueImpact(e){return{BLOCKING_OPERATIONS:"Reduced server performance and responsiveness",INEFFICIENT_QUERIES:"Slower response times and increased database load",MEMORY_LEAKS:"Memory consumption growth over time"}[e]||"Performance degradation"}};var v=class{constructor(e={}){this.routes=[];this.startTime=0;this.config={...E,...e}}async analyzeRoutes(){this.startTime=Date.now(),this.routes=[],l.info("Starting API routes analysis...");let e=await p.findApiFiles(this.config);l.info(`Found ${e.length} API files`);for(let t of e)await this.analyzeFile(t);return l.success(`Analyzed ${this.routes.length} API routes`),this.generateAnalysisResult()}async analyzeFile(e){try{let t=N.default.readFileSync(e,"utf-8"),s=await p.getFileStats(e),r=await this.parseRouteInfo(e,t,s);this.routes.push(r)}catch(t){l.error(`Error analyzing file ${e}:`,t)}}async parseRouteInfo(e,t,s){let r=this.getRoutePath(e),i=this.isAppRouterFile(e),o=f.default.createSourceFile(e,t,f.default.ScriptTarget.Latest,!0),c={path:r,methods:this.extractMethods(t,o,i),hasAuth:this.detectAuth(t),authTypes:this.extractAuthTypes(t),queryParams:this.extractQueryParams(t,i),pathParams:this.extractPathParams(r),bodyParams:this.extractBodyParams(t,i),headers:this.extractHeaders(t),responseStatuses:this.extractResponseStatuses(t,i),middlewares:this.extractMiddlewares(t),description:this.extractDescription(t),hasRateLimit:this.detectRateLimit(t),hasCors:this.detectCors(t),hasInputValidation:this.detectInputValidation(t),dependencies:this.extractDependencies(o),fileSize:s.size,linesOfCode:s.linesOfCode,lastModified:s.lastModified},a=A.analyzeRoute(c,t),u=C.analyzeRoute(c,t,o);return{...c,riskLevel:a.riskLevel,complexity:u.complexity,performanceScore:u.performanceScore}}generateAnalysisResult(){let e=Date.now()-this.startTime,t=this.generateRecommendations(),s={totalRoutes:this.routes.length,secureRoutes:this.routes.filter(r=>r.hasAuth).length,publicRoutes:this.routes.filter(r=>!r.hasAuth).length,methodsBreakdown:this.calculateMethodsBreakdown(),statusCodeDistribution:this.calculateStatusCodeDistribution(),riskDistribution:this.calculateRiskDistribution(),securityScore:this.calculateSecurityScore(),performanceScore:this.calculatePerformanceScore(),maintainabilityScore:this.calculateMaintainabilityScore(),testCoverageScore:75};return{routes:this.routes,summary:s,metadata:{analyzedAt:new Date,version:"4.0.1",duration:e,totalFiles:this.routes.length,totalLinesOfCode:this.routes.reduce((r,i)=>r+(i.linesOfCode||0),0)},recommendations:t}}generateReport(e){let t=`# \u{1F50D} API Routes Analysis Report `;return t+=`**Generated:** ${e.metadata.analyzedAt.toLocaleString()} `,t+=`**Duration:** ${e.metadata.duration}ms `,t+=`**Files:** ${e.metadata.totalFiles} `,t+=`## \u{1F4CA} Summary `,t+=`| Metric | Value | Status | `,t+=`|--------|-------|--------| `,t+=`| Total Routes | ${e.summary.totalRoutes} | \u2139\uFE0F | `,t+=`| Security Score | ${e.summary.securityScore.toFixed(1)}% | ${T(e.summary.securityScore)} | `,t+=`| Performance Score | ${e.summary.performanceScore.toFixed(1)}% | ${T(e.summary.performanceScore)} | `,e.recommendations.length>0&&(t+=`## \u{1F4A1} Recommendations `,e.recommendations.sort((s,r)=>b(r.severity)-b(s.severity)).slice(0,10).forEach((s,r)=>{t+=`### ${r+1}. ${s.title} `,t+=`**Severity:** ${s.severity} | **Type:** ${s.type} `,t+=`${s.description} `,t+=`**Solution:** ${s.solution} `})),t}isAppRouterFile(e){return e.includes("/route.")||e.endsWith("route.js")||e.endsWith("route.ts")}getRoutePath(e){return"/"+U.default.relative(this.config.apiDir,e).replace(/\\/g,"/").replace(/\.(js|ts|tsx)$/,"").replace(/\/index$/,"").replace(/\/route$/,"").replace(/\[([^\]]+)\]/g,":$1")}extractMethods(e,t,s){let r=new Set;if(s){let i=["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"];f.default.forEachChild(t,o=>{f.default.isFunctionDeclaration(o)&&o.name&&i.includes(o.name.text)&&r.add(o.name.text)})}else{let i=/req\.method\s*===?\s*['"`](\w+)['"`]/g,o;for(;(o=i.exec(e))!==null;)r.add(o[1].toUpperCase())}return r.size===0?["GET"]:Array.from(r)}detectAuth(e){return this.config.authPatterns.some(t=>t.pattern.test(e))}extractAuthTypes(e){return this.config.authPatterns.filter(t=>t.pattern.test(e)).map(t=>t.type)}extractQueryParams(e,t){let s=new Set,r=t?/searchParams\.get$$['"`]([^'"`]+)['"`]$$/g:/req\.query\.(\w+)/g,i;for(;(i=r.exec(e))!==null;)s.add(i[1]);return Array.from(s).map(o=>({name:o,type:"string",required:!0}))}extractPathParams(e){let t=new Set,s=/\[([^\]]+)\]/g,r;for(;(r=s.exec(e))!==null;)t.add(r[1]);return Array.from(t).map(i=>({name:i,type:"string",required:!0}))}extractBodyParams(e,t){let s=new Set,r=t?/const\s*\{\s*([^}]+)\s*\}\s*=\s*await\s+request\.json$$$$/g:/req\.body\.(\w+)/g,i;for(;(i=r.exec(e))!==null;)t?i[1].split(",").map(c=>c.trim()).forEach(c=>s.add(c)):s.add(i[1]);return Array.from(s).map(o=>({name:o,type:"string",required:!0}))}extractHeaders(e){let t=new Set;return[/headers\.get$$['"`]([^'"`]+)['"`]$$/g,/req\.headers\[['"`]([^'"`]+)['"`]\]/g].forEach(r=>{let i;for(;(i=r.exec(e))!==null;)t.add(i[1])}),Array.from(t)}extractResponseStatuses(e,t){let s=new Set;if(t){let r=/Response\.json\([^,]*,\s*\{\s*status:\s*(\d+)/g,i;for(;(i=r.exec(e))!==null;)s.add(Number.parseInt(i[1]))}else{let r=/res\.status$$(\d+)$$/g,i;for(;(i=r.exec(e))!==null;)s.add(Number.parseInt(i[1]))}return s.size===0?[200]:Array.from(s).sort()}extractMiddlewares(e){return this.config.middlewarePatterns.filter(t=>t.pattern.test(e)).map(t=>t.name)}extractDescription(e){let t=/\/\*\*\s*\n\s*\*\s*(.+?)\s*\n/,s=e.match(t);return s?s[1].trim():void 0}detectRateLimit(e){return/rate[_-]?limit|throttle|slowDown/i.test(e)}detectCors(e){return/cors|Access-Control-Allow/i.test(e)}detectInputValidation(e){return/joi\.|yup\.|zod\.|validate\(|schema\./i.test(e)}extractDependencies(e){let t=new Set;return f.default.forEachChild(e,s=>{if(f.default.isImportDeclaration(s)){let r=s.moduleSpecifier.getText().replace(/['"]/g,"");!r.startsWith(".")&&!r.startsWith("/")&&t.add(r)}}),Array.from(t)}generateRecommendations(){return[]}calculateMethodsBreakdown(){let e={};return this.routes.forEach(t=>{t.methods.forEach(s=>{e[s]=(e[s]||0)+1})}),e}calculateStatusCodeDistribution(){let e={};return this.routes.forEach(t=>{t.responseStatuses.forEach(s=>{let r=s.toString();e[r]=(e[r]||0)+1})}),e}calculateRiskDistribution(){let e={LOW:0,MEDIUM:0,HIGH:0,CRITICAL:0};return this.routes.forEach(t=>{e[t.riskLevel]++}),e}calculateSecurityScore(){return this.routes.length===0?100:this.routes.filter(t=>t.hasAuth).length/this.routes.length*100}calculatePerformanceScore(){return this.routes.length===0?100:this.routes.reduce((t,s)=>t+(s.performanceScore||100),0)/this.routes.length}calculateMaintainabilityScore(){if(this.routes.length===0)return 100;let e=this.routes.reduce((t,s)=>t+(s.complexity||1),0)/this.routes.length;return Math.max(0,100-(e>10?(e-10)*5:0))}};var z=d(require("path")),S=d(require("chalk")),x=new k.Command;x.name("next-api-analyzer").description("Next.js API routes analyzer").version("4.0.1");x.command("analyze").description("Analyze API routes").option("-d, --dir <directory>","API directory","src/app/api").option("-o, --output <file>","Output file","./api-analysis-report.md").option("--json","Output as JSON").option("--security","Security-focused analysis").option("--performance","Performance-focused analysis").action(async n=>{let e=Date.now();try{l.info("\u{1F680} Starting API analysis...");let t={...E,apiDir:n.dir,outputDir:z.default.dirname(n.output)};n.security&&(t.enableSecurityAnalysis=!0,t.enablePerformanceAnalysis=!1),n.performance&&(t.enablePerformanceAnalysis=!0,t.enableSecurityAnalysis=!1);let s=new v(t),r=await s.analyzeRoutes();if(l.info("\u{1F4CA} Analysis Summary:"),console.log(S.default.cyan(" Routes:"),r.summary.totalRoutes),console.log(S.default.green(" Security Score:"),`${r.summary.securityScore.toFixed(1)}%`),console.log(S.default.blue(" Performance Score:"),`${r.summary.performanceScore.toFixed(1)}%`),console.log(S.default.magenta(" Maintainability Score:"),`${r.summary.maintainabilityScore.toFixed(1)}%`),console.log(S.default.yellow(" Recommendations:"),r.recommendations.length),n.json)await p.writeJsonFile(n.output.replace(/\.md$/,".json"),r);else{let o=s.generateReport(r);await p.writeFile(n.output,o)}let i=Date.now()-e;l.success(`\u2705 Analysis complete in ${i}ms! Report saved to: ${n.output}`)}catch(t){l.error("Analysis failed:",t),process.exit(1)}});x.command("init").description("Initialize configuration file").option("-f, --force","Overwrite existing configuration").action(async n=>{try{let e="api-analyzer.config.json";if(await p.fileExists(e)&&!n.force){l.warn("Configuration file already exists. Use --force to overwrite.");return}await p.writeJsonFile(e,E),l.success(`\u2705 Configuration file created: ${e}`)}catch(e){l.error("Failed to create configuration:",e),process.exit(1)}});x.parse();