UNPKG

dependency-guardian

Version:

A powerful dependency management and analysis tool for Node.js projects

241 lines (207 loc) 9.74 kB
const ora = require('ora'); const chalk = require('chalk'); const path = require('path'); const dependencyScanner = require('../core/analyzers/dependency-scanner'); const policyChecker = require('../core/policy-checker'); const logger = require('../utils/logger'); const versionUtils = require('../utils/version-utils'); const licenseUtils = require('../utils/license-utils'); const DependencyScanner = require('../core/analyzers/dependency-scanner'); const fs = require('fs').promises; const { DependencyGuardianError, NetworkError, ValidationError } = require('../utils/error-utils'); const securityChecker = require('../core/checkers/security-checker'); const { convertToCSV, convertToHTML } = require('../utils/formatters'); async function validateProjectPath(projectPath) { try { const stats = await fs.stat(projectPath); if (!stats.isDirectory()) { throw new Error('Project path must be a directory'); } const packageJsonPath = path.join(projectPath, 'package.json'); await fs.access(packageJsonPath); return true; } catch (error) { if (error.code === 'ENOENT') { throw new Error('package.json not found in project directory'); } throw error; } } function analyzeCommand(program) { program .command('analyze') .description('Analyze project dependencies') .option('-f, --format <type>', 'Output format (table, json, csv, html)', 'table') .option('-o, --output <file>', 'Output file path') .action(async (options) => { const spinner = ora('Analyzing dependencies...').start(); try { // Validate project path const projectPath = options.path || process.cwd(); try { await validateProjectPath(projectPath); } catch (error) { throw new ValidationError(`Invalid project path: ${error.message}`, { path: projectPath }); } const scanner = new DependencyScanner({ registry: options.registry, maxRetries: options.maxRetries || 3, timeout: options.timeout || 30000, cacheTimeout: options.cacheTimeout || 3600000 }); // Read and validate package.json let packageJson; try { packageJson = await scanner.readPackageJson(projectPath); } catch (error) { throw new ValidationError('Failed to parse package.json', { path: projectPath, error: error.message }); } // Get dependencies to analyze const dependencies = { ...packageJson.dependencies, ...(options.dev ? packageJson.devDependencies : {}) }; if (!dependencies || Object.keys(dependencies).length === 0) { const message = 'No dependencies found'; if (options.json) { console.log(JSON.stringify({ summary: { total: 0 }, dependencies: [], message })); } else { logger.info(message); } return; } // Scan dependencies with timeout const timeoutMs = options.timeout || 30000; const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Analysis timed out')), timeoutMs) ); const results = await Promise.race([ scanner.scanDependencies(dependencies), timeoutPromise ]); // Format and output results if (options.json) { console.log(JSON.stringify({ summary: { total: results.length, outdated: results.filter(r => r.updateType !== 'none').length, errors: results.filter(r => r.error).length }, dependencies: results }, null, 2)); } else { logger.info('Dependency Analysis Report'); logger.info('--------------------------'); results.forEach(dep => { if (dep.error) { logger.error(`✗ ${dep.name}@${dep.version} - ${dep.error}`); } else { const status = dep.updateType === 'none' ? '✓' : '!'; logger.info(`${status} ${dep.name}@${dep.version} -> ${dep.latestVersion}`); } }); logger.info('\nSummary:'); logger.info(`Total Dependencies: ${results.length}`); logger.info(`Outdated: ${results.filter(r => r.updateType !== 'none').length}`); logger.info(`Errors: ${results.filter(r => r.error).length}`); } // Exit with error code if required if (options.strict && results.some(r => r.error || r.updateType !== 'none')) { process.exit(1); } } catch (error) { spinner.fail(chalk.red('Analysis failed')); logger.error('Analysis error:', error); process.exit(1); } }); } function displayAnalysis(analysis) { // Title console.log('\n' + chalk.bold.blue('╭─────────────────────────────────────╮')); console.log(chalk.bold.blue('│ Dependency Guardian Report │')); console.log(chalk.bold.blue('╰─────────────────────────────────────╯\n')); // Quick Stats const stats = [ { icon: '📦', label: 'Dependencies', value: analysis.summary.total, color: 'white' }, { icon: '⚠️', label: 'Issues', value: analysis.summary.issues, color: 'yellow' }, { icon: '🚨', label: 'Critical', value: analysis.summary.critical, color: 'red' } ]; const maxLabelLength = Math.max(...stats.map(s => s.label.length)); stats.forEach(stat => { const padding = ' '.repeat(maxLabelLength - stat.label.length); console.log(`${stat.icon} ${chalk.dim(stat.label + ':')}${padding} ${chalk[stat.color].bold(stat.value)}`); }); // Updates Overview const updates = analysis.summary.updates; const totalUpdates = updates.major + updates.minor + updates.patch; if (totalUpdates > 0) { console.log('\n' + chalk.bold.yellow('╭─ Available Updates ──────────────────╮')); const updateTypes = [ { type: 'major', icon: '⬆️', label: 'Major Updates', color: 'red', count: updates.major }, { type: 'minor', icon: '↗️', label: 'Minor Updates', color: 'yellow', count: updates.minor }, { type: 'patch', icon: '✨', label: 'Patch Updates', color: 'green', count: updates.patch } ].filter(u => u.count > 0); updateTypes.forEach(update => { console.log(`${update.icon} ${chalk.dim(update.label)}: ${chalk[update.color].bold(update.count)}`); }); console.log(chalk.bold.yellow('╰────────────────────────────────────╯')); } // Detailed Dependencies Section const depsWithIssues = analysis.dependencies.filter(dep => dep.issues.length > 0); if (depsWithIssues.length > 0) { console.log('\n' + chalk.bold.magenta('╭─ Dependency Details ─────────────────╮')); depsWithIssues.forEach((dep, index) => { // Package name with version console.log(`\n${chalk.bold(dep.name)} ${chalk.dim(`v${dep.version}`)}`); // Update information const updateIssue = dep.issues.find(i => i.type === 'update'); if (updateIssue) { const arrow = updateIssue.level === 'high' ? '⬆️' : '↗️'; console.log(`${arrow} ${chalk.dim('Update:')} ${dep.latestVersion} ${chalk.dim('available')}`); } // License information const licenseIssue = dep.issues.find(i => i.type === 'license'); if (licenseIssue) { console.log(`📜 ${chalk.yellow('License:')} ${licenseIssue.message}`); } // Other issues const otherIssues = dep.issues.filter(i => !['update', 'license'].includes(i.type)); otherIssues.forEach(issue => { console.log(`❗ ${chalk.red(issue.message)}`); }); // Separator between dependencies if (index < depsWithIssues.length - 1) { console.log(chalk.dim('├' + '─'.repeat(38))); } }); console.log(chalk.bold.magenta('\n╰────────────────────────────────────╯')); } // Action Items if (analysis.summary.issues > 0) { console.log('\n' + chalk.bold.cyan('╭─ Recommended Actions ────────────────╮')); if (updates.major > 0) { console.log(`🔍 ${chalk.dim('Review')} ${chalk.red.bold(updates.major)} major updates ${chalk.dim('(breaking changes)')}`); } if (updates.minor > 0) { console.log(`📦 ${chalk.dim('Update')} ${chalk.yellow.bold(updates.minor)} packages ${chalk.dim('with new features')}`); } if (updates.patch > 0) { console.log(`🛡️ ${chalk.dim('Apply')} ${chalk.green.bold(updates.patch)} security patches`); } if (analysis.summary.licenses?.unknown > 0) { console.log(`📜 ${chalk.dim('Verify')} ${chalk.yellow.bold(analysis.summary.licenses.unknown)} unknown licenses`); } console.log(chalk.bold.cyan('╰────────────────────────────────────╯\n')); } } module.exports = analyzeCommand;