UNPKG

@neurolint/cli

Version:

NeuroLint CLI for React/Next.js modernization with advanced 6-layer orchestration and intelligent AST transformations

357 lines (314 loc) 12.4 kB
import chalk from "chalk"; import ora from "ora"; import { table } from "table"; import fs from "fs-extra"; import path from "path"; import { loadConfig } from "../utils/config"; import { formatDetailedJSON, formatHTMLReport } from "../utils/formatter"; interface AnalyticsOptions { timeRange?: string; output?: string; outputFile?: string; detailed?: boolean; teamId?: string; } export async function analyticsCommand(options: AnalyticsOptions) { const spinner = ora("Loading analytics data...").start(); try { // Load configuration const config = await loadConfig(); if (!config.apiKey) { spinner.fail("Authentication required"); console.log(chalk.white('Run "neurolint login" to authenticate first')); return; } // Fetch analytics from API const params = new URLSearchParams({ timeRange: options.timeRange || 'week', detailed: (options.detailed || false).toString() }); if (options.teamId) { params.append('teamId', options.teamId); } const response = await fetch(`${config.api?.url || "https://app.neurolint.dev/api"}/analytics?${params}`, { headers: { "X-API-Key": config.apiKey, "Content-Type": "application/json", }, }); if (!response.ok) { throw new Error(`API request failed: ${response.status} ${response.statusText}`); } const result = await response.json(); spinner.succeed("Analytics data loaded successfully"); // Format and display results based on output format await displayAnalytics(result.analytics, options); } catch (error) { spinner.fail(`Analytics failed: ${error instanceof Error ? error.message : "Unknown error"}`); if (error instanceof Error) { if (error.message.includes("ECONNREFUSED")) { console.log(chalk.white("\nMake sure the NeuroLint server is running")); } else if (error.message.includes("401") || error.message.includes("403")) { console.log(chalk.white('\nAuthentication failed. Run "neurolint login" to re-authenticate')); } else if (error.message.includes("429")) { console.log(chalk.white("\nRate limit exceeded. Please wait before trying again")); } } process.exit(1); } } async function displayAnalytics(analytics: any, options: AnalyticsOptions) { const format = options.output || "table"; switch (format) { case "json": const jsonOutput = JSON.stringify(analytics, null, 2); if (options.outputFile) { await fs.writeFile(options.outputFile, jsonOutput); console.log(chalk.green(`Analytics data saved to: ${options.outputFile}`)); } else { console.log(jsonOutput); } break; case "detailed-json": const detailedOutput = formatDetailedJSON(analytics); if (options.outputFile) { await fs.writeFile(options.outputFile, detailedOutput); console.log(chalk.green(`Detailed analytics saved to: ${options.outputFile}`)); } else { console.log(detailedOutput); } break; case "html": const htmlOutput = formatHTMLReport(analytics); const htmlFile = options.outputFile || `neurolint-analytics-${Date.now()}.html`; await fs.writeFile(htmlFile, htmlOutput); console.log(chalk.green(`HTML analytics report generated: ${htmlFile}`)); break; case "csv": await generateCSVReport(analytics, options.outputFile); break; case "table": default: displayTableAnalytics(analytics); break; } } function displayTableAnalytics(analytics: any) { console.log(chalk.white.bold("\nNeuroLint Analytics Report\n")); // Overview Section console.log(chalk.blue.bold("Overview")); const overviewData = [ ["Metric", "Value"], ["Total Analyses", analytics.overview.totalAnalyses.toLocaleString()], ["Success Rate", `${analytics.overview.successRate.toFixed(1)}%`], ["Avg Execution Time", `${analytics.overview.avgExecutionTime}ms`], ["Issues Found", analytics.issueAnalysis.totalIssuesFound.toLocaleString()] ]; console.log(table(overviewData, { border: { topBody: chalk.gray('─'), topJoin: chalk.gray('┬'), topLeft: chalk.gray('┌'), topRight: chalk.gray('┐'), bottomBody: chalk.gray('─'), bottomJoin: chalk.gray('┴'), bottomLeft: chalk.gray('└'), bottomRight: chalk.gray('┘'), bodyLeft: chalk.gray('│'), bodyRight: chalk.gray('│'), bodyJoin: chalk.gray('│'), joinBody: chalk.gray('─'), joinLeft: chalk.gray('├'), joinRight: chalk.gray('┤'), joinJoin: chalk.gray('┼') }, columnDefault: { paddingLeft: 1, paddingRight: 1 } })); // Issue Types Section if (Object.keys(analytics.issueAnalysis.issueTypes).length > 0) { console.log(chalk.blue.bold("\nTop Issue Types")); const issueData = [["Issue Type", "Count", "Percentage"]]; const totalIssues = analytics.issueAnalysis.totalIssuesFound; Object.entries(analytics.issueAnalysis.issueTypes) .sort(([,a], [,b]) => (b as number) - (a as number)) .slice(0, 10) .forEach(([type, count]) => { const percentage = ((count as number) / totalIssues * 100).toFixed(1); issueData.push([ type.replace(/-/g, ' '), (count as number).toLocaleString(), `${percentage}%` ]); }); console.log(table(issueData, { border: { topBody: chalk.gray('─'), topJoin: chalk.gray('┬'), topLeft: chalk.gray('┌'), topRight: chalk.gray('┐'), bottomBody: chalk.gray('─'), bottomJoin: chalk.gray('┴'), bottomLeft: chalk.gray('└'), bottomRight: chalk.gray('┘'), bodyLeft: chalk.gray('│'), bodyRight: chalk.gray('│'), bodyJoin: chalk.gray('│'), joinBody: chalk.gray('─'), joinLeft: chalk.gray('├'), joinRight: chalk.gray('┤'), joinJoin: chalk.gray('┼') } })); } // Layer Performance Section if (analytics.layerPerformance && analytics.layerPerformance.length > 0) { console.log(chalk.blue.bold("\nLayer Performance")); const layerData = [["Layer", "Name", "Success Rate", "Avg Time", "Issues"]]; analytics.layerPerformance .sort((a: any, b: any) => a.layerId - b.layerId) .forEach((layer: any) => { layerData.push([ layer.layerId.toString(), layer.layerName, `${layer.successRate.toFixed(1)}%`, `${layer.avgTime.toFixed(0)}ms`, layer.issues.toString() ]); }); console.log(table(layerData, { border: { topBody: chalk.gray('─'), topJoin: chalk.gray('┬'), topLeft: chalk.gray('┌'), topRight: chalk.gray('┐'), bottomBody: chalk.gray('─'), bottomJoin: chalk.gray('┴'), bottomLeft: chalk.gray('└'), bottomRight: chalk.gray('┘'), bodyLeft: chalk.gray('│'), bodyRight: chalk.gray('│'), bodyJoin: chalk.gray('│'), joinBody: chalk.gray('─'), joinLeft: chalk.gray('├'), joinRight: chalk.gray('┤'), joinJoin: chalk.gray('┼') } })); } // File Types Section if (Object.keys(analytics.fileAnalysis.fileTypes).length > 0) { console.log(chalk.blue.bold("\nFile Types Analysis")); const fileData = [["File Type", "Count", "Percentage"]]; const totalFiles = Object.values(analytics.fileAnalysis.fileTypes).reduce((sum: number, count) => sum + (count as number), 0); Object.entries(analytics.fileAnalysis.fileTypes) .sort(([,a], [,b]) => (b as number) - (a as number)) .slice(0, 8) .forEach(([type, count]) => { const percentage = ((count as number) / totalFiles * 100).toFixed(1); fileData.push([ `.${type}`, (count as number).toLocaleString(), `${percentage}%` ]); }); console.log(table(fileData, { border: { topBody: chalk.gray('─'), topJoin: chalk.gray('┬'), topLeft: chalk.gray('┌'), topRight: chalk.gray('┐'), bottomBody: chalk.gray('─'), bottomJoin: chalk.gray('┴'), bottomLeft: chalk.gray('└'), bottomRight: chalk.gray('┘'), bodyLeft: chalk.gray('│'), bodyRight: chalk.gray('│'), bodyJoin: chalk.gray('│'), joinBody: chalk.gray('─'), joinLeft: chalk.gray('├'), joinRight: chalk.gray('┤'), joinJoin: chalk.gray('┼') } })); } // Code Quality Metrics (if detailed) if (analytics.codeQualityMetrics) { console.log(chalk.blue.bold("\nCode Quality Metrics")); const qualityData = [ ["Metric", "Score"], ["Quality Score", `${analytics.codeQualityMetrics.qualityScore.toFixed(0)}/100`], ["Improvement Rate", `${analytics.codeQualityMetrics.improvementRate > 0 ? '+' : ''}${analytics.codeQualityMetrics.improvementRate.toFixed(1)}%`], ["Maintainability Index", `${analytics.codeQualityMetrics.maintainabilityIndex.toFixed(0)}/100`], ["Total Issues", analytics.codeQualityMetrics.totalIssues.toLocaleString()] ]; console.log(table(qualityData, { border: { topBody: chalk.gray('─'), topJoin: chalk.gray('┬'), topLeft: chalk.gray('┌'), topRight: chalk.gray('┐'), bottomBody: chalk.gray('─'), bottomJoin: chalk.gray('┴'), bottomLeft: chalk.gray('└'), bottomRight: chalk.gray('┘'), bodyLeft: chalk.gray('│'), bodyRight: chalk.gray('│'), bodyJoin: chalk.gray('│'), joinBody: chalk.gray('─'), joinLeft: chalk.gray('├'), joinRight: chalk.gray('┤'), joinJoin: chalk.gray('┼') } })); } // Recommendations if (analytics.recommendations && analytics.recommendations.length > 0) { console.log(chalk.blue.bold("\nRecommendations")); analytics.recommendations.forEach((recommendation: string, index: number) => { console.log(chalk.white(`${index + 1}. ${recommendation}`)); }); } console.log(chalk.gray("\n" + "─".repeat(60))); console.log(chalk.white("For detailed analytics, visit: https://neurolint.dev/dashboard")); console.log(chalk.gray("Generated by NeuroLint CLI v1.0.0")); } async function generateCSVReport(analytics: any, outputFile?: string) { const csvFile = outputFile || `neurolint-analytics-${Date.now()}.csv`; let csvContent = "Type,Metric,Value\n"; // Overview metrics csvContent += `Overview,Total Analyses,${analytics.overview.totalAnalyses}\n`; csvContent += `Overview,Success Rate,${analytics.overview.successRate.toFixed(2)}\n`; csvContent += `Overview,Average Execution Time (ms),${analytics.overview.avgExecutionTime}\n`; csvContent += `Overview,Total Issues Found,${analytics.issueAnalysis.totalIssuesFound}\n`; // Issue types Object.entries(analytics.issueAnalysis.issueTypes).forEach(([type, count]) => { csvContent += `Issue Type,${type.replace(/,/g, ';')},${count}\n`; }); // Layer performance analytics.layerPerformance?.forEach((layer: any) => { csvContent += `Layer ${layer.layerId},Success Rate,${layer.successRate.toFixed(2)}\n`; csvContent += `Layer ${layer.layerId},Average Time (ms),${layer.avgTime.toFixed(2)}\n`; csvContent += `Layer ${layer.layerId},Issues Found,${layer.issues}\n`; }); // File types Object.entries(analytics.fileAnalysis.fileTypes).forEach(([type, count]) => { csvContent += `File Type,.${type},${count}\n`; }); await fs.writeFile(csvFile, csvContent); console.log(chalk.green(`CSV analytics report generated: ${csvFile}`)); } // Add analytics command to CLI export function addAnalyticsCommand(program: any) { program .command("analytics") .description("View detailed analytics and usage insights") .option("-t, --time-range <range>", "Time range (day|week|month|quarter|year)", "week") .option("-o, --output <format>", "Output format (table|json|detailed-json|html|csv)", "table") .option("--output-file <file>", "Save output to file") .option("--detailed", "Include detailed metrics and trends") .option("--team-id <id>", "Include team analytics") .action(analyticsCommand); }