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

470 lines • 19.4 kB
"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.NgCompatibilityUpdater = void 0; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const child_process_1 = require("child_process"); const semver_1 = __importDefault(require("semver")); /** * Angular-aware dependency updater similar to npm-check-updates * Automatically detects and updates dependencies to Angular-compatible versions */ class NgCompatibilityUpdater { angularVersion; compatibilityMatrix; constructor(angularVersion) { this.angularVersion = angularVersion; this.compatibilityMatrix = this.buildCompatibilityMatrix(); } /** * Check and update all dependencies for Angular compatibility */ async checkAndUpdate(projectPath, options = {}) { const packageJsonPath = path.join(projectPath, 'package.json'); if (!await fs.pathExists(packageJsonPath)) { throw new Error('package.json not found'); } const packageJson = await fs.readJson(packageJsonPath); const result = { updates: [], warnings: [], deprecated: [], totalUpdates: 0, criticalUpdates: 0 }; // Check dependencies if (packageJson.dependencies) { const depUpdates = await this.checkDependencies(packageJson.dependencies, 'dependencies', options); result.updates.push(...depUpdates.updates); result.warnings.push(...depUpdates.warnings); result.deprecated.push(...depUpdates.deprecated); } // Check devDependencies if requested if (options.includeDevDependencies && packageJson.devDependencies) { const devDepUpdates = await this.checkDependencies(packageJson.devDependencies, 'devDependencies', options); result.updates.push(...devDepUpdates.updates); result.warnings.push(...devDepUpdates.warnings); result.deprecated.push(...devDepUpdates.deprecated); } result.totalUpdates = result.updates.length; result.criticalUpdates = result.updates.filter(u => u.required || u.updateType === 'major').length; // Apply updates if not dry run if (!options.dryRun && result.updates.length > 0) { await this.applyUpdates(packageJsonPath, packageJson, result.updates); } return result; } /** * Check specific dependencies for updates */ async checkDependencies(dependencies, type, options) { const updates = []; const warnings = []; const deprecated = []; for (const [name, currentVersion] of Object.entries(dependencies)) { try { const updateInfo = await this.checkPackageUpdate(name, currentVersion, options); if (updateInfo) { updates.push({ ...updateInfo, name }); if (updateInfo.updateType === 'deprecated') { deprecated.push(name); } } } catch (error) { warnings.push(`Failed to check ${name}: ${error instanceof Error ? error.message : String(error)}`); } } return { updates, warnings, deprecated }; } /** * Check individual package for Angular compatibility */ async checkPackageUpdate(packageName, currentVersion, options) { // Skip if only checking Angular ecosystem and this isn't an Angular package if (options.onlyAngularEcosystem && !this.isAngularEcosystemPackage(packageName)) { return null; } // Check if package is in our compatibility matrix const compatibilityInfo = this.compatibilityMatrix.get(packageName); if (compatibilityInfo) { return this.getCompatibilityUpdate(packageName, currentVersion, compatibilityInfo, options); } // For packages not in matrix, try to get latest compatible version return this.getLatestCompatibleVersion(packageName, currentVersion, options); } /** * Get update info from compatibility matrix */ getCompatibilityUpdate(packageName, currentVersion, compatibilityInfo, options) { const targetVersionInfo = compatibilityInfo[this.angularVersion]; if (!targetVersionInfo) { return null; } // Handle deprecated packages if (targetVersionInfo.deprecated) { return { currentVersion, compatibleVersion: 'DEPRECATED', updateType: 'deprecated', notes: targetVersionInfo.deprecationMessage || `${packageName} is deprecated for Angular ${this.angularVersion}`, required: false }; } const compatibleVersion = targetVersionInfo.version; const cleanCurrent = this.cleanVersion(currentVersion); const cleanCompatible = this.cleanVersion(compatibleVersion); if (semver_1.default.eq(cleanCurrent, cleanCompatible)) { return null; // Already up to date } const updateType = this.determineUpdateType(cleanCurrent, cleanCompatible); return { currentVersion, compatibleVersion, updateType, notes: targetVersionInfo.notes, required: targetVersionInfo.required || updateType === 'major' }; } /** * Get latest compatible version from npm registry */ async getLatestCompatibleVersion(packageName, currentVersion, options) { try { // Get package info from npm const packageInfo = await this.getPackageInfo(packageName); if (!packageInfo) { return null; } // Find best compatible version based on Angular peer dependencies const compatibleVersion = await this.findAngularCompatibleVersion(packageInfo); if (!compatibleVersion) { return null; } const cleanCurrent = this.cleanVersion(currentVersion); const cleanCompatible = this.cleanVersion(compatibleVersion); if (semver_1.default.gte(cleanCurrent, cleanCompatible)) { return null; // Current version is already compatible or newer } const updateType = this.determineUpdateType(cleanCurrent, cleanCompatible); // Only suggest updates for conservative strategy if they're minor/patch if (options.updateStrategy === 'conservative' && updateType === 'major') { return null; } return { currentVersion, compatibleVersion, updateType, notes: `Auto-detected Angular ${this.angularVersion} compatible version`, required: false }; } catch (error) { // Silently fail for packages that can't be checked return null; } } /** * Get package information from npm registry */ async getPackageInfo(packageName) { try { const result = (0, child_process_1.execSync)(`npm view ${packageName} --json`, { encoding: 'utf8', timeout: 5000 }); return JSON.parse(result); } catch (error) { return null; } } /** * Find Angular-compatible version by checking peer dependencies */ async findAngularCompatibleVersion(packageInfo) { const versions = Object.keys(packageInfo.versions || {}).reverse(); // Latest first const majorAngularVersion = parseInt(this.angularVersion); for (const version of versions) { const versionInfo = packageInfo.versions[version]; const peerDeps = versionInfo.peerDependencies || {}; // Check if this version supports our Angular version const angularPeerDep = peerDeps['@angular/core'] || peerDeps['@angular/common']; if (angularPeerDep) { // Parse the peer dependency range const supportedAngularVersions = this.parseVersionRange(angularPeerDep); if (supportedAngularVersions.includes(majorAngularVersion)) { return version; } } } // Fallback: return latest version return packageInfo['dist-tags']?.latest || null; } /** * Parse npm version range to extract supported versions */ parseVersionRange(range) { const versions = []; // Handle ranges like "^16.0.0 || ^17.0.0" or ">=16.0.0 <19.0.0" const cleanRange = range.replace(/[\^~]/g, '').replace(/\|\|/g, ' '); const versionMatches = cleanRange.match(/\d+/g); if (versionMatches) { const uniqueVersions = [...new Set(versionMatches.map(v => parseInt(v)))]; versions.push(...uniqueVersions.filter(v => v >= 12 && v <= 25)); // Reasonable Angular version range } return versions; } /** * Apply updates to package.json */ async applyUpdates(packageJsonPath, packageJson, updates) { for (const update of 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 }); } /** * Build comprehensive Angular compatibility matrix */ buildCompatibilityMatrix() { const matrix = new Map(); // Angular ecosystem packages const angularPackages = [ '@angular/core', '@angular/common', '@angular/forms', '@angular/http', '@angular/platform-browser', '@angular/platform-browser-dynamic', '@angular/router', '@angular/animations', '@angular/material', '@angular/cdk', '@angular/cli', '@angular/compiler-cli' ]; angularPackages.forEach(pkg => { matrix.set(pkg, this.getAngularVersionMap(pkg)); }); // Third-party Angular ecosystem matrix.set('@ngrx/store', { '12': { version: '^12.0.0' }, '13': { version: '^13.0.0' }, '14': { version: '^14.0.0' }, '15': { version: '^15.0.0' }, '16': { version: '^16.0.0' }, '17': { version: '^17.0.0' }, '18': { version: '^18.0.0' }, '19': { version: '^19.0.0' }, '20': { version: '^20.0.0' } }); matrix.set('@ngrx/effects', { '12': { version: '^12.0.0' }, '13': { version: '^13.0.0' }, '14': { version: '^14.0.0' }, '15': { version: '^15.0.0' }, '16': { version: '^16.0.0' }, '17': { version: '^17.0.0' }, '18': { version: '^18.0.0' }, '19': { version: '^19.0.0' }, '20': { version: '^20.0.0' } }); matrix.set('primeng', { '12': { version: '^12.0.0' }, '13': { version: '^13.0.0' }, '14': { version: '^14.0.0' }, '15': { version: '^15.0.0' }, '16': { version: '^16.0.0' }, '17': { version: '^17.0.0' }, '18': { version: '^18.0.0' }, '19': { version: '^19.0.0' }, '20': { version: '^20.0.0' } }); matrix.set('@ng-bootstrap/ng-bootstrap', { '12': { version: '^10.0.0' }, '13': { version: '^11.0.0' }, '14': { version: '^12.0.0' }, '15': { version: '^14.0.0' }, '16': { version: '^15.0.0' }, '17': { version: '^16.0.0' }, '18': { version: '^17.0.0' }, '19': { version: '^18.0.0' }, '20': { version: '^19.0.0' } }); // Deprecated packages matrix.set('@angular/flex-layout', { '12': { version: '^12.0.0' }, '13': { version: '^13.0.0' }, '14': { version: '^14.0.0', notes: 'Consider migrating to CSS Grid/Flexbox' }, '15': { deprecated: true, deprecationMessage: 'Angular Flex Layout is deprecated. Migrate to CSS Grid and Flexbox.' }, '16': { deprecated: true, deprecationMessage: 'Angular Flex Layout is deprecated. Use modern CSS layout solutions.' }, '17': { deprecated: true, deprecationMessage: 'Angular Flex Layout is no longer maintained. Use CSS Grid and Flexbox.' }, '18': { deprecated: true, deprecationMessage: 'Angular Flex Layout is no longer maintained. Use CSS Grid and Flexbox.' }, '19': { deprecated: true, deprecationMessage: 'Angular Flex Layout is no longer maintained. Use CSS Grid and Flexbox.' }, '20': { deprecated: true, deprecationMessage: 'Angular Flex Layout is no longer maintained. Use CSS Grid and Flexbox.' } }); // TypeScript compatibility matrix.set('typescript', { '12': { version: '~4.3.0' }, '13': { version: '~4.4.0' }, '14': { version: '~4.7.0' }, '15': { version: '~4.8.0' }, '16': { version: '~4.9.0' }, '17': { version: '~5.2.0' }, '18': { version: '~5.4.0' }, '19': { version: '~5.5.0' }, '20': { version: '~5.6.0' } }); return matrix; } /** * Generate Angular package version map */ getAngularVersionMap(packageName) { const baseMap = {}; for (let version = 12; version <= 20; version++) { baseMap[version.toString()] = { version: `^${version}.0.0`, required: true }; } return baseMap; } /** * Check if package is part of Angular ecosystem */ isAngularEcosystemPackage(packageName) { const angularPatterns = [ /^@angular\//, /^@ngrx\//, /^@ng-bootstrap\//, /^@ionic\//, /^primeng$/, /^primeicons$/, /angular/i ]; return angularPatterns.some(pattern => pattern.test(packageName)); } /** * Clean version string for semver comparison */ cleanVersion(version) { return version.replace(/^[\^~]/, ''); } /** * Determine update type */ determineUpdateType(current, target) { if (semver_1.default.major(target) > semver_1.default.major(current)) { return 'major'; } else if (semver_1.default.minor(target) > semver_1.default.minor(current)) { return 'minor'; } else if (semver_1.default.patch(target) > semver_1.default.patch(current)) { return 'patch'; } return 'compatible'; } /** * Generate update report similar to npm-check-updates */ generateReport(result) { let report = `\nšŸ” Angular ${this.angularVersion} Compatibility Check\n`; report += '='.repeat(50) + '\n\n'; if (result.updates.length === 0) { report += 'āœ… All dependencies are already compatible with Angular ' + this.angularVersion + '\n'; return report; } report += `šŸ“Š Found ${result.totalUpdates} updates (${result.criticalUpdates} critical)\n\n`; // Group updates by type const groups = { critical: result.updates.filter(u => u.required), major: result.updates.filter(u => u.updateType === 'major' && !u.required), minor: result.updates.filter(u => u.updateType === 'minor'), patch: result.updates.filter(u => u.updateType === 'patch'), deprecated: result.updates.filter(u => u.updateType === 'deprecated') }; Object.entries(groups).forEach(([type, updates]) => { if (updates.length > 0) { report += `\n${this.getTypeIcon(type)} ${type.toUpperCase()} UPDATES:\n`; updates.forEach(update => { report += ` ${update.name}: ${update.currentVersion} → ${update.compatibleVersion}`; if (update.notes) { report += ` (${update.notes})`; } report += '\n'; }); } }); if (result.warnings.length > 0) { report += '\nāš ļø WARNINGS:\n'; result.warnings.forEach(warning => { report += ` ${warning}\n`; }); } report += '\nšŸ’” Run with --apply to update package.json\n'; return report; } getTypeIcon(type) { const icons = { critical: '🚨', major: 'šŸ”“', minor: '🟔', patch: '🟢', deprecated: 'šŸ—‘ļø' }; return icons[type] || 'šŸ“¦'; } } exports.NgCompatibilityUpdater = NgCompatibilityUpdater; //# sourceMappingURL=NgCompatibilityUpdater.js.map