UNPKG

ng-upgrade-orchestrator

Version:

Enterprise-grade Angular Multi-Version Upgrade Orchestrator with automatic npm installation, comprehensive dependency management, and seamless integration of all 9 official Angular migrations. Safely migrate Angular applications across multiple major vers

344 lines • 16.1 kB
#!/usr/bin/env node "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; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.program = void 0; const commander_1 = require("commander"); const chalk_1 = __importDefault(require("chalk")); const NgCompatibilityUpdater_1 = require("../utils/NgCompatibilityUpdater"); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const program = new commander_1.Command(); exports.program = program; program .name('ng-check-updates') .description('Check and update dependencies for Angular compatibility (similar to npm-check-updates)') .version('1.0.0') .argument('[angular-version]', 'Target Angular version (e.g., 18, 19, 20)', '18') .option('-u, --upgrade', 'Apply updates to package.json (like ncu -u)') .option('-d, --dev', 'Include devDependencies') .option('-a, --angular-only', 'Only check Angular ecosystem packages') .option('-s, --strategy <type>', 'Update strategy: conservative|aggressive', 'conservative') .option('-t, --target <version>', 'Specific Angular version to target') .option('--dry-run', 'Show what would be updated without making changes') .option('--format <type>', 'Output format: table|json|summary', 'table') .option('-f, --filter <pattern>', 'Filter packages by pattern (regex)') .option('--exclude <pattern>', 'Exclude packages by pattern (regex)') .action(async (angularVersion, options) => { try { const targetVersion = options.target || angularVersion; const projectPath = process.cwd(); console.log(chalk_1.default.cyan(`\nšŸ” Checking Angular ${targetVersion} compatibility...\n`)); // Validate Angular version if (!isValidAngularVersion(targetVersion)) { console.error(chalk_1.default.red(`āŒ Invalid Angular version: ${targetVersion}`)); console.error(chalk_1.default.gray('Supported versions: 12, 13, 14, 15, 16, 17, 18, 19, 20')); process.exit(1); } // Check if package.json exists const packageJsonPath = path.join(projectPath, 'package.json'); if (!await fs.pathExists(packageJsonPath)) { console.error(chalk_1.default.red('āŒ No package.json found in current directory')); process.exit(1); } // Create updater instance const updater = new NgCompatibilityUpdater_1.NgCompatibilityUpdater(targetVersion); // Configure update options const updateOptions = { dryRun: options.dryRun || !options.upgrade, includeDevDependencies: options.dev, onlyAngularEcosystem: options.angularOnly, updateStrategy: options.strategy, filter: options.filter ? new RegExp(options.filter) : undefined, exclude: options.exclude ? new RegExp(options.exclude) : undefined }; console.log(chalk_1.default.gray('Options:')); console.log(chalk_1.default.gray(` Angular version: ${targetVersion}`)); console.log(chalk_1.default.gray(` Strategy: ${options.strategy}`)); console.log(chalk_1.default.gray(` Include dev deps: ${options.dev ? 'yes' : 'no'}`)); console.log(chalk_1.default.gray(` Angular only: ${options.angularOnly ? 'yes' : 'no'}`)); console.log(chalk_1.default.gray(` Apply updates: ${options.upgrade ? 'yes' : 'no'}`)); console.log(''); // Run compatibility check const result = await updater.checkAndUpdate(projectPath, updateOptions); // Display results displayResults(result, targetVersion, options); if (result.updates.length > 0 && !options.upgrade && !options.dryRun) { console.log(chalk_1.default.cyan('\nšŸ’” Run with -u to apply updates to package.json')); console.log(chalk_1.default.gray(' Example: ng-check-updates 18 -u')); } if (options.upgrade && result.updates.length > 0) { console.log(chalk_1.default.green('\nāœ… Dependencies updated! Run npm install to install new versions.')); } } catch (error) { console.error(chalk_1.default.red(`āŒ Error: ${error instanceof Error ? error.message : String(error)}`)); process.exit(1); } }); // Add interactive mode program .command('interactive') .alias('i') .description('Interactive mode to select which updates to apply') .argument('[angular-version]', 'Target Angular version', '18') .option('-d, --dev', 'Include devDependencies') .option('-a, --angular-only', 'Only check Angular ecosystem packages') .action(async (angularVersion, options) => { try { const projectPath = process.cwd(); console.log(chalk_1.default.cyan(`\nšŸ” Interactive Angular ${angularVersion} compatibility check...\n`)); const updater = new NgCompatibilityUpdater_1.NgCompatibilityUpdater(angularVersion); const result = await updater.checkAndUpdate(projectPath, { dryRun: true, includeDevDependencies: options.dev, onlyAngularEcosystem: options.angularOnly, updateStrategy: 'conservative' }); if (result.updates.length === 0) { console.log(chalk_1.default.green('āœ… All dependencies are already compatible!')); return; } console.log(chalk_1.default.yellow('šŸ“¦ Found updates:')); // Group and display updates const { createPromptModule } = await Promise.resolve().then(() => __importStar(require('inquirer'))); const prompt = createPromptModule(); const choices = result.updates.map(update => ({ name: `${update.name}: ${update.currentVersion} → ${update.compatibleVersion} ${update.notes ? `(${update.notes})` : ''}`, value: update.name, checked: update.required })); const { selectedUpdates } = await prompt({ type: 'checkbox', name: 'selectedUpdates', message: 'Select packages to update:', choices, pageSize: 15 }); if (selectedUpdates.length === 0) { console.log(chalk_1.default.gray('No updates selected.')); return; } // Apply selected updates const filteredResult = { ...result, updates: result.updates.filter(u => selectedUpdates.includes(u.name)) }; await applySelectedUpdates(projectPath, filteredResult); console.log(chalk_1.default.green(`\nāœ… Updated ${selectedUpdates.length} packages!`)); } catch (error) { console.error(chalk_1.default.red(`āŒ Error: ${error instanceof Error ? error.message : String(error)}`)); process.exit(1); } }); // Add doctor command for health check program .command('doctor') .description('Comprehensive Angular dependency health check') .argument('[angular-version]', 'Target Angular version', '18') .action(async (angularVersion) => { try { const projectPath = process.cwd(); console.log(chalk_1.default.cyan(`\nšŸ„ Angular ${angularVersion} Dependency Health Check\n`)); const updater = new NgCompatibilityUpdater_1.NgCompatibilityUpdater(angularVersion); const result = await updater.checkAndUpdate(projectPath, { dryRun: true, includeDevDependencies: true, onlyAngularEcosystem: false, updateStrategy: 'aggressive' }); // Health score calculation const totalDeps = await getTotalDependencyCount(projectPath); const healthScore = calculateHealthScore(result, totalDeps); console.log(chalk_1.default.bold('šŸŽÆ HEALTH SCORE: ') + getHealthScoreDisplay(healthScore)); console.log(''); // Detailed breakdown displayHealthBreakdown(result, totalDeps); if (result.deprecated.length > 0) { console.log(chalk_1.default.red('\nšŸ—‘ļø DEPRECATED PACKAGES:')); result.deprecated.forEach(pkg => { console.log(chalk_1.default.red(` āŒ ${pkg}`)); }); } // Recommendations console.log(chalk_1.default.cyan('\nšŸ’” RECOMMENDATIONS:')); if (result.criticalUpdates > 0) { console.log(chalk_1.default.red(` 🚨 ${result.criticalUpdates} critical updates need immediate attention`)); } if (result.deprecated.length > 0) { console.log(chalk_1.default.yellow(` šŸ“¦ ${result.deprecated.length} deprecated packages should be replaced`)); } if (healthScore >= 80) { console.log(chalk_1.default.green(' āœ… Dependencies are in good shape!')); } else if (healthScore >= 60) { console.log(chalk_1.default.yellow(' āš ļø Some maintenance required')); } else { console.log(chalk_1.default.red(' šŸ”§ Significant updates needed')); } } catch (error) { console.error(chalk_1.default.red(`āŒ Error: ${error instanceof Error ? error.message : String(error)}`)); process.exit(1); } }); function displayResults(result, targetVersion, options) { if (options.format === 'json') { console.log(JSON.stringify(result, null, 2)); return; } if (result.updates.length === 0) { console.log(chalk_1.default.green(`āœ… All dependencies are compatible with Angular ${targetVersion}`)); return; } if (options.format === 'summary') { console.log(chalk_1.default.yellow(`šŸ“Š ${result.totalUpdates} updates available (${result.criticalUpdates} critical)`)); return; } // Table format (default) console.log(chalk_1.default.bold('šŸ“¦ DEPENDENCY UPDATES:')); console.log(''); const maxNameLength = Math.max(...result.updates.map(u => u.name.length), 20); const maxCurrentLength = Math.max(...result.updates.map(u => u.currentVersion.length), 10); // Header console.log(chalk_1.default.gray('Package'.padEnd(maxNameLength) + 'Current'.padEnd(maxCurrentLength + 3) + 'Compatible'.padEnd(15) + 'Type'.padEnd(8) + 'Notes')); console.log(chalk_1.default.gray('─'.repeat(maxNameLength + maxCurrentLength + 40))); // Updates result.updates.forEach(update => { const typeColor = getUpdateTypeColor(update.updateType); const requiredIcon = update.required ? 'šŸ”“' : ''; console.log(chalk_1.default.white(update.name.padEnd(maxNameLength)) + chalk_1.default.gray(update.currentVersion.padEnd(maxCurrentLength + 3)) + chalk_1.default.green(update.compatibleVersion.padEnd(15)) + typeColor(update.updateType.padEnd(8)) + chalk_1.default.gray(update.notes || '') + requiredIcon); }); if (result.warnings.length > 0) { console.log(chalk_1.default.yellow('\nāš ļø WARNINGS:')); result.warnings.forEach(warning => { console.log(chalk_1.default.yellow(` ${warning}`)); }); } } function getUpdateTypeColor(type) { const colors = { major: chalk_1.default.red, minor: chalk_1.default.yellow, patch: chalk_1.default.green, compatible: chalk_1.default.blue, deprecated: chalk_1.default.gray }; return colors[type] || chalk_1.default.white; } function isValidAngularVersion(version) { const validVersions = ['12', '13', '14', '15', '16', '17', '18', '19', '20']; return validVersions.includes(version); } async function applySelectedUpdates(projectPath, result) { const packageJsonPath = path.join(projectPath, 'package.json'); const packageJson = await fs.readJson(packageJsonPath); for (const update of result.updates) { if (update.updateType === 'deprecated') { // Remove deprecated packages if (packageJson.dependencies?.[update.name]) { delete packageJson.dependencies[update.name]; } if (packageJson.devDependencies?.[update.name]) { delete packageJson.devDependencies[update.name]; } } else { // Update to compatible version if (packageJson.dependencies?.[update.name]) { packageJson.dependencies[update.name] = update.compatibleVersion; } if (packageJson.devDependencies?.[update.name]) { packageJson.devDependencies[update.name] = update.compatibleVersion; } } } await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); } async function getTotalDependencyCount(projectPath) { const packageJsonPath = path.join(projectPath, 'package.json'); const packageJson = await fs.readJson(packageJsonPath); const depCount = Object.keys(packageJson.dependencies || {}).length; const devDepCount = Object.keys(packageJson.devDependencies || {}).length; return depCount + devDepCount; } function calculateHealthScore(result, totalDeps) { if (totalDeps === 0) return 100; const upToDateDeps = totalDeps - result.totalUpdates; const deprecatedPenalty = result.deprecated.length * 15; const criticalPenalty = result.criticalUpdates * 10; const minorPenalty = (result.totalUpdates - result.criticalUpdates) * 2; const baseScore = (upToDateDeps / totalDeps) * 100; const finalScore = Math.max(0, baseScore - deprecatedPenalty - criticalPenalty - minorPenalty); return Math.round(finalScore); } function getHealthScoreDisplay(score) { if (score >= 90) return chalk_1.default.green(`${score}/100 🟢 EXCELLENT`); if (score >= 80) return chalk_1.default.green(`${score}/100 🟢 GOOD`); if (score >= 70) return chalk_1.default.yellow(`${score}/100 🟔 FAIR`); if (score >= 60) return chalk_1.default.yellow(`${score}/100 🟔 NEEDS ATTENTION`); return chalk_1.default.red(`${score}/100 šŸ”“ POOR`); } function displayHealthBreakdown(result, totalDeps) { console.log(chalk_1.default.bold('šŸ“‹ BREAKDOWN:')); console.log(` Total dependencies: ${totalDeps}`); console.log(` Up to date: ${chalk_1.default.green(totalDeps - result.totalUpdates)}`); console.log(` Need updates: ${chalk_1.default.yellow(result.totalUpdates)}`); console.log(` Critical updates: ${chalk_1.default.red(result.criticalUpdates)}`); console.log(` Deprecated: ${chalk_1.default.gray(result.deprecated.length)}`); } if (require.main === module) { program.parse(); } //# sourceMappingURL=ng-check-updates.js.map