@boilerbuilder/deps-analyzer
Version:
CLI tool to analyze dependency evolution and release frequency
262 lines (227 loc) • 8.51 kB
JavaScript
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();