UNPKG

static-code-analyzer

Version:

Universal static code analysis tool for calculating API operation costs across multiple frameworks (NestJS, Express, Fastify)

331 lines • 14.3 kB
#!/usr/bin/env bun "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.program = void 0; const commander_1 = require("commander"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const analyzer_1 = require("./analyzer"); const reports_1 = require("./reports"); const config_1 = require("./config"); const program = new commander_1.Command(); exports.program = program; program .name('static-code-analyzer') .description('Static code analysis tool for calculating API operation costs') .version('1.0.0'); // Analyze single file program .command('file <filePath>') .description('Analyze a single file') .option('-f, --framework <framework>', 'Framework preset (nestjs, express, fastify)', 'nestjs') .option('-o, --output <file>', 'Output file path') .option('--format <format>', 'Output format (text, json, markdown, html)', 'text') .option('-c, --config <file>', 'Custom configuration file') .option('-v, --verbose', 'Verbose logging') .action(async (filePath, options) => { try { const analyzer = new analyzer_1.ApiCostAnalyzer(); // Set framework if (options.framework && config_1.FRAMEWORK_PRESETS[options.framework]) { analyzer.setFramework(options.framework); if (options.verbose) { console.log(`šŸ“‹ Using ${config_1.FRAMEWORK_PRESETS[options.framework].name} preset`); } } // Load custom config if provided if (options.config) { const customConfig = JSON.parse(fs.readFileSync(options.config, 'utf8')); analyzer.updateConfig(customConfig); if (options.verbose) { console.log(`āš™ļø Loaded custom config from ${options.config}`); } } console.log(`šŸ” Analyzing file ${filePath}...`); const result = analyzer.analyzeFile(filePath); const report = reports_1.ReportGenerator.generateFileReport(result, options.format); if (options.output) { reports_1.ReportGenerator.saveReport(report, options.output); console.log(`āœ… Report saved to ${options.output}`); } else { console.log(report); } } catch (error) { console.error('āŒ Error:', error instanceof Error ? error.message : error); process.exit(1); } }); // Analyze project directory program .command('project <projectPath>') .description('Analyze an entire project directory') .option('-f, --framework <framework>', 'Framework preset (nestjs, express, fastify)', 'nestjs') .option('-o, --output <file>', 'Output file path') .option('--format <format>', 'Output format (text, json, markdown, html)', 'text') .option('-c, --config <file>', 'Custom configuration file') .option('-p, --patterns <patterns>', 'File patterns to analyze (comma-separated)', '') .option('-v, --verbose', 'Verbose logging') .option('--exclude <patterns>', 'Patterns to exclude (comma-separated)', '') .action(async (projectPath, options) => { try { const analyzer = new analyzer_1.ApiCostAnalyzer(); // Set framework if (options.framework && config_1.FRAMEWORK_PRESETS[options.framework]) { analyzer.setFramework(options.framework); if (options.verbose) { console.log(`šŸ“‹ Using ${config_1.FRAMEWORK_PRESETS[options.framework].name} preset`); } } // Load custom config if provided if (options.config) { const customConfig = JSON.parse(fs.readFileSync(options.config, 'utf8')); analyzer.updateConfig(customConfig); if (options.verbose) { console.log(`āš™ļø Loaded custom config from ${options.config}`); } } // Parse file patterns let patterns = ['**/*.ts', '**/*.js']; if (options.patterns) { patterns = options.patterns.split(',').map((p) => p.trim()); } else if (options.framework === 'nestjs') { patterns = ['**/*.service.ts', '**/*.controller.ts', '**/*.gateway.ts', '**/*.resolver.ts']; } else if (options.framework === 'express') { patterns = ['**/routes/**/*.js', '**/routes/**/*.ts', '**/controllers/**/*.js', '**/controllers/**/*.ts']; } console.log(`šŸ” Analyzing project ${projectPath}...`); if (options.verbose) { console.log(`šŸ“‚ Patterns: ${patterns.join(', ')}`); } const result = analyzer.analyzeProject(projectPath, patterns); console.log(`āœ… Found ${result.files.length} files with ${result.summary.totalMethods} methods`); const report = reports_1.ReportGenerator.generateProjectReport(result.files, options.format); if (options.output) { reports_1.ReportGenerator.saveReport(report, options.output); console.log(`āœ… Report saved to ${options.output}`); } else { console.log(report); } } catch (error) { console.error('āŒ Analysis failed:', error instanceof Error ? error.message : error); process.exit(1); } }); // Quick NestJS commands program .command('nestjs <path>') .description('Quick analysis for NestJS projects') .option('-o, --output <file>', 'Output file path') .option('--format <format>', 'Output format (text, json, markdown, html)', 'text') .option('-v, --verbose', 'Verbose logging') .action(async (projectPath, options) => { try { console.log(`šŸ” Analyzing NestJS project ${projectPath}...`); const result = (0, analyzer_1.analyzeNestJSProject)(projectPath); const report = reports_1.ReportGenerator.generateProjectReport(result.files, options.format); console.log(`āœ… Found ${result.files.length} files with ${result.summary.totalMethods} methods`); if (options.output) { reports_1.ReportGenerator.saveReport(report, options.output); console.log(`āœ… Report saved to ${options.output}`); } else { console.log(report); } } catch (error) { console.error('āŒ Analysis failed:', error instanceof Error ? error.message : error); process.exit(1); } }); // Configuration commands program .command('config') .description('Configuration management') .action(() => { console.log('šŸ“‹ Available framework presets:'); Object.entries(config_1.FRAMEWORK_PRESETS).forEach(([key, preset]) => { console.log(` ${key}: ${preset.description}`); }); console.log('\nšŸ“‹ Example configurations:'); console.log(' --framework nestjs # Use NestJS preset'); console.log(' --config custom.json # Use custom config file'); console.log('\nšŸ“‹ Custom config file example:'); console.log(JSON.stringify({ operations: { 'customMethod': 2, 'expensiveOperation': 5 }, patterns: { 'custom_framework': { pattern: /\.customMethod\(/, description: 'Custom framework operations' } } }, null, 2)); }); // Generate sample config program .command('init') .description('Generate sample configuration files') .option('-f, --framework <framework>', 'Framework preset', 'nestjs') .option('-o, --output <dir>', 'Output directory', '.') .action((options) => { try { const outputDir = options.output; // Create config file const config = (0, config_1.getFrameworkConfig)(options.framework); const configPath = path.join(outputDir, 'static-code-analyzer.config.json'); fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); console.log(`āœ… Generated config file: ${configPath}`); // Create package.json scripts const packageJsonPath = path.join(outputDir, 'package.json'); if (fs.existsSync(packageJsonPath)) { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); if (!packageJson.scripts) packageJson.scripts = {}; packageJson.scripts['analyze:api'] = 'sca project ./src'; packageJson.scripts['analyze:file'] = 'sca file'; packageJson.scripts['analyze:report'] = 'sca project ./src --format html --output reports/api-cost-report.html'; fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); console.log(`āœ… Added scripts to package.json`); console.log(' šŸ“‹ npm run analyze:api # Analyze entire project'); console.log(' šŸ“‹ npm run analyze:file <file> # Analyze single file'); console.log(' šŸ“‹ npm run analyze:report # Generate HTML report'); } // Create .gitignore entries const gitignorePath = path.join(outputDir, '.gitignore'); const gitignoreEntry = '\n# API Cost Analysis Reports\nreports/\n*.cost-report.*\n'; if (fs.existsSync(gitignorePath)) { const gitignore = fs.readFileSync(gitignorePath, 'utf8'); if (!gitignore.includes('# API Cost Analysis Reports')) { fs.appendFileSync(gitignorePath, gitignoreEntry); console.log(`āœ… Added entries to .gitignore`); } } else { fs.writeFileSync(gitignorePath, gitignoreEntry.trim()); console.log(`āœ… Created .gitignore with report entries`); } console.log('\nšŸŽ‰ Setup complete! Quick start:'); console.log(` sca project ./src --framework ${options.framework}`); } catch (error) { console.error('āŒ Init failed:', error instanceof Error ? error.message : error); process.exit(1); } }); // Watch mode program .command('watch <path>') .description('Watch files for changes and re-analyze') .option('-f, --framework <framework>', 'Framework preset', 'nestjs') .option('--format <format>', 'Output format', 'text') .action((filePath, options) => { const analyzer = new analyzer_1.ApiCostAnalyzer(); analyzer.setFramework(options.framework); console.log(`šŸ‘€ Watching ${filePath} for changes...`); const analyzeAndReport = () => { try { console.log('\nšŸ”„ File changed, re-analyzing...'); const result = analyzer.analyzeFile(filePath); const report = reports_1.ReportGenerator.generateFileReport(result, options.format); console.log(report); } catch (error) { console.error('āŒ Analysis failed:', error instanceof Error ? error.message : error); } }; // Initial analysis analyzeAndReport(); // Watch for changes fs.watchFile(filePath, analyzeAndReport); // Keep process alive process.on('SIGINT', () => { console.log('\nšŸ‘‹ Stopping watcher...'); fs.unwatchFile(filePath); process.exit(0); }); }); // Demo command program .command('demo') .description('Run demo analysis on sample files') .action(() => { console.log('šŸŽ® API Cost Analyzer Demo'); console.log('='.repeat(50)); console.log('\nšŸ“‹ Available Frameworks:'); Object.entries(config_1.FRAMEWORK_PRESETS).forEach(([key, preset]) => { console.log(` ${key.padEnd(10)} - ${preset.description}`); }); console.log('\nšŸ“‹ Example Commands:'); console.log(' sca file ./src/users/users.service.ts --framework nestjs'); console.log(' sca project ./src --framework nestjs --format html --output report.html'); console.log(' sca nestjs ./src --output nestjs-analysis.md --format markdown'); console.log(' sca watch ./src/auth/auth.service.ts --framework nestjs'); console.log('\nšŸ“‹ Supported Patterns:'); const nestjsConfig = (0, config_1.getFrameworkConfig)('nestjs'); Object.entries(nestjsConfig.patterns).forEach(([name, config]) => { console.log(` ${name}: ${config.description}`); }); console.log('\nšŸš€ Get started with: sca init'); }); // Help command enhancement program.on('--help', () => { console.log(''); console.log('Examples:'); console.log(' $ sca file ./src/users.service.ts --framework nestjs'); console.log(' $ sca project ./src --format html --output report.html'); console.log(' $ sca nestjs ./src --output analysis.json --format json'); console.log(' $ sca watch ./src/auth.service.ts'); console.log(' $ sca init --framework nestjs'); console.log(''); console.log('Supported Frameworks:'); Object.entries(config_1.FRAMEWORK_PRESETS).forEach(([key, preset]) => { console.log(` ${key.padEnd(10)} - ${preset.description}`); }); console.log(''); }); // Parse command line arguments if (require.main === module) { program.parse(); } //# sourceMappingURL=cli.js.map