UNPKG

@boilerbuilder/deps-analyzer

Version:

CLI tool to analyze dependency evolution and release frequency

262 lines (227 loc) 8.51 kB
#!/usr/bin/env node const path = require('path'); const { main } = require('../lib/index'); /** * Parse command line arguments * @param {Array} args - Command line arguments * @returns {Object} Parsed arguments and options */ function parseArguments(args) { const options = { months: 24, output: 'dependency-report', analyzeNpm: true, useNpmCli: true, ignoreFilters: [], internalLibs: [], scope: 'all', maxDepsConfig: null }; const patterns = []; for (let i = 2; i < args.length; i++) { const arg = args[i]; if (arg.startsWith('--months=')) { options.months = parseInt(arg.split('=')[1]) || 24; } else if (arg === '--months') { options.months = parseInt(args[++i]) || 24; } else if (arg.startsWith('--output=')) { options.output = arg.split('=')[1] || 'dependency-report'; } else if (arg === '--output' || arg === '-o') { options.output = args[++i] || 'dependency-report'; } else if (arg === '--no-npm') { options.analyzeNpm = false; } else if (arg === '--use-http') { options.useNpmCli = false; } else if (arg.startsWith('--ignore-filter=')) { const filters = arg.split('=')[1].split(',').map(f => f.trim()); options.ignoreFilters = filters; } else if (arg === '--ignore-filter') { const filters = args[++i].split(',').map(f => f.trim()); options.ignoreFilters = filters; } else if (arg.startsWith('--internal-libs=')) { const libs = arg.split('=')[1].split(',').map(l => l.trim()); options.internalLibs = libs; } else if (arg === '--internal-libs') { const libs = args[++i].split(',').map(l => l.trim()); options.internalLibs = libs; } else if (arg.startsWith('--scope=')) { const scopeValue = arg.split('=')[1]; if (['internal', 'external', 'all'].includes(scopeValue)) { options.scope = scopeValue; } else { console.error(`❌ Invalid scope value: ${scopeValue}. Must be one of: internal, external, all`); process.exit(1); } } else if (arg === '--scope') { const scopeValue = args[++i]; if (!scopeValue) { console.error('❌ Error: --scope requires a value (internal, external, all)'); process.exit(1); } if (['internal', 'external', 'all'].includes(scopeValue)) { options.scope = scopeValue; } else { console.error(`❌ Invalid scope value: ${scopeValue}. Must be one of: internal, external, all`); process.exit(1); } } else if (arg.startsWith('--max-deps-config=')) { options.maxDepsConfig = arg.split('=')[1]; } else if (arg === '--max-deps-config') { options.maxDepsConfig = args[++i]; if (!options.maxDepsConfig) { console.error('❌ Error: --max-deps-config requires a file path'); process.exit(1); } } else if (arg === '--help' || arg === '-h') { showHelp(); process.exit(0); } else if (arg === '--version' || arg === '-v') { showVersion(); process.exit(0); } else if (!arg.startsWith('--')) { // It's a pattern/path patterns.push(arg); } else { console.error(`❌ Unknown argument: ${arg}`); process.exit(1); } } if (patterns.length === 0) { console.error('❌ Error: At least one pattern is required'); showUsage(); process.exit(1); } return { patterns, options }; } /** * Show help information */ function showHelp() { console.log(` 📊 Dependencies Evolution Analyzer USAGE: deps-analyzer <pattern> [pattern2] [pattern3] ... [options] ARGUMENTS: pattern Path(s) to analyze (directory, package.json, or glob pattern) You can specify multiple patterns OPTIONS: --months <number> Analysis period in months (default: 24) --output <path> Output file base name (default: dependency-report) --scope <type> Filter dependencies by scope: internal, external, all (default: all) --max-deps-config <path> Custom path to max-deps.json file --no-npm Skip NPM registry analysis (only discover dependencies) --use-http Use HTTP requests instead of npm CLI (slower) --ignore-filter <list> Skip filtering specific version types (alpha,beta,rc,etc) --internal-libs <list> Libraries to include alpha/beta versions (supports wildcards: @akad/*,design-system*) -h, --help Show this help message -v, --version Show version EXAMPLES: deps-analyzer ./templates deps-analyzer ./package.json deps-analyzer "templates/**/package.json" deps-analyzer ./templates --months 18 --output custom-report deps-analyzer ./templates --no-npm deps-analyzer ./templates --use-http deps-analyzer ./templates --ignore-filter alpha,beta deps-analyzer ./react-imo-eao --internal-libs "@akad/*" deps-analyzer ./templates --internal-libs "@akad/*,design-system*" --ignore-filter rc deps-analyzer templates/next-app templates/react-spa-vite --output selected-projects MAX-DEPS CONFIGURATION: deps-analyzer ./templates --max-deps-config ./configs/production.json deps-analyzer ./templates --max-deps-config /shared/team-constraints.json deps-analyzer ./templates --max-deps-config ./benchmarks/zero-drift.json --output benchmark SCOPE FILTERING: deps-analyzer ./templates --scope=internal --internal-libs "@company/*" deps-analyzer ./templates --scope=external --internal-libs "@company/*" deps-analyzer ./templates --scope=all --internal-libs "@company/*" OUTPUT: - Creates <output>.json with structured data - Creates <output>.md with formatted tables `); } /** * Show usage information */ function showUsage() { console.log(` Usage: deps-analyzer <pattern> [options] Try 'deps-analyzer --help' for more information. `); } /** * Show version information */ function showVersion() { try { const packageJson = require('../package.json'); console.log(`deps-analyzer v${packageJson.version}`); } catch (error) { console.log('deps-analyzer v1.0.0'); } } /** * Main CLI function */ async function cli() { try { const { patterns, options } = parseArguments(process.argv); console.log('🚀 Dependencies Evolution Analyzer\n'); // Resolve patterns to absolute paths if they're relative paths const resolvedPatterns = patterns.map(pattern => path.isAbsolute(pattern) ? pattern : path.resolve(pattern) ); console.log(`📋 Configuration: - Pattern${patterns.length > 1 ? 's' : ''}: ${patterns.join(', ')} - Analysis period: ${options.months} months - Scope filter: ${options.scope} - NPM analysis: ${options.analyzeNpm ? 'enabled' : 'disabled'} - Ignore filters: ${options.ignoreFilters.length > 0 ? options.ignoreFilters.join(', ') : 'none'} - Internal libs: ${options.internalLibs.length > 0 ? options.internalLibs.join(', ') : 'none'} - Output: ${options.output} `); const startTime = Date.now(); // Process multiple patterns const result = await main(resolvedPatterns, options); const endTime = Date.now(); const duration = ((endTime - startTime) / 1000).toFixed(2); console.log(`\n🎉 Analysis completed in ${duration}s`); console.log(`📊 Summary: - Projects analyzed: ${result.metadata.totalProjects} - Total dependencies: ${result.projects.reduce((sum, p) => sum + p.dependencyCount, 0)} `); if (options.analyzeNpm) { const totalAnalyzed = result.projects.reduce((sum, p) => sum + Object.keys(p.dependencyAnalysis || {}).length, 0 ); const totalErrors = result.projects.reduce((sum, p) => sum + (p.analysisErrors?.length || 0), 0 ); console.log(`- NPM packages analyzed: ${totalAnalyzed}`); if (totalErrors > 0) { console.log(`- Analysis errors: ${totalErrors}`); } } process.exit(0); } catch (error) { console.error('\n❌ Fatal error:', error.message); if (process.env.DEBUG) { console.error('\nStack trace:'); console.error(error.stack); } else { console.error('\nRun with DEBUG=1 for detailed error information.'); } process.exit(1); } } // Handle unhandled promise rejections process.on('unhandledRejection', (error) => { console.error('❌ Unhandled promise rejection:', error.message); process.exit(1); }); // Handle SIGINT (Ctrl+C) process.on('SIGINT', () => { console.log('\n\n👋 Analysis interrupted by user'); process.exit(0); }); // Run CLI cli();