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

489 lines 17.6 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ProjectAnalyzer = void 0; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); class ProjectAnalyzer { projectPath; constructor(projectPath) { this.projectPath = projectPath; } /** * Analyze Angular project for upgrade readiness */ async analyze() { const currentVersion = await this.detectAngularVersion(); const projectType = await this.detectProjectType(); const buildSystem = await this.detectBuildSystem(); const dependencies = await this.analyzeDependencies(); const codeMetrics = await this.calculateCodeMetrics(); const riskAssessment = await this.assessRisks(dependencies, codeMetrics); return { currentVersion, projectType, buildSystem, dependencies, codeMetrics, riskAssessment }; } /** * Detect current Angular version */ async detectAngularVersion() { try { const packageJsonPath = path.join(this.projectPath, 'package.json'); const packageJson = await fs.readJson(packageJsonPath); const coreVersion = packageJson.dependencies?.['@angular/core'] || packageJson.devDependencies?.['@angular/core']; if (!coreVersion) { throw new Error('Angular core dependency not found'); } const versionString = coreVersion.replace(/[\^~]/, ''); const [major, minor = 0, patch = 0] = versionString.split('.').map(Number); return { major, minor, patch, full: `${major}.${minor}.${patch}` }; } catch (error) { throw new Error(`Failed to detect Angular version: ${error}`); } } /** * Detect project type */ async detectProjectType() { const angularJsonPath = path.join(this.projectPath, 'angular.json'); if (await fs.pathExists(angularJsonPath)) { const angularJson = await fs.readJson(angularJsonPath); const projects = angularJson.projects || {}; const projectEntries = Object.entries(projects); if (projectEntries.length > 1) { return 'workspace'; } else if (projectEntries.length === 1) { const [, projectConfig] = projectEntries[0]; return projectConfig.projectType === 'library' ? 'library' : 'application'; } } return 'application'; } /** * Detect build system */ async detectBuildSystem() { // Check for Nx workspace if (await fs.pathExists(path.join(this.projectPath, 'nx.json'))) { return 'nx'; } // Check for Angular CLI if (await fs.pathExists(path.join(this.projectPath, 'angular.json'))) { return 'angular-cli'; } // Check for custom webpack config const webpackConfigs = [ 'webpack.config.js', 'webpack.config.ts', 'webpack.common.js', 'webpack.dev.js', 'webpack.prod.js' ]; for (const config of webpackConfigs) { if (await fs.pathExists(path.join(this.projectPath, config))) { return 'webpack'; } } return 'other'; } /** * Analyze project dependencies */ async analyzeDependencies() { const packageJsonPath = path.join(this.projectPath, 'package.json'); const packageJson = await fs.readJson(packageJsonPath); const allDependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }; const compatible = []; const incompatible = []; const requiresUpdate = []; const conflicts = []; // Analyze each dependency for (const [name, version] of Object.entries(allDependencies)) { const library = await this.analyzeLibrary(name, version); if (library.compatibilityMatrix && Object.keys(library.compatibilityMatrix).length > 0) { if (library.migrationRequired) { requiresUpdate.push(library); } else { compatible.push(library); } } else { incompatible.push(library); } } // Detect conflicts const detectedConflicts = await this.detectDependencyConflicts(allDependencies); conflicts.push(...detectedConflicts); return { compatible, incompatible, requiresUpdate, conflicts }; } /** * Analyze individual library */ async analyzeLibrary(name, version) { // This would contain a comprehensive database of Angular library compatibility // For now, implementing basic logic for common libraries const compatibilityMatrix = this.getLibraryCompatibilityMatrix(name); const migrationRequired = this.checkMigrationRequired(name, version); const alternativeLibraries = this.getAlternativeLibraries(name); const deprecationStatus = this.getDeprecationStatus(name); return { name, currentVersion: version, compatibilityMatrix, migrationRequired, alternativeLibraries, deprecationStatus }; } /** * Get library compatibility matrix */ getLibraryCompatibilityMatrix(libraryName) { // Comprehensive compatibility matrix for popular Angular libraries const compatibilityDatabase = { '@angular/material': { '12': ['^12.0.0'], '13': ['^13.0.0'], '14': ['^14.0.0'], '15': ['^15.0.0'], '16': ['^16.0.0'], '17': ['^17.0.0'], '18': ['^18.0.0'], '19': ['^19.0.0'], '20': ['^20.0.0'] }, '@ngrx/store': { '12': ['^12.0.0'], '13': ['^13.0.0'], '14': ['^14.0.0'], '15': ['^15.0.0'], '16': ['^16.0.0'], '17': ['^17.0.0'], '18': ['^18.0.0'], '19': ['^19.0.0'], '20': ['^20.0.0'] }, 'primeng': { '12': ['^12.0.0', '^13.0.0'], '13': ['^13.0.0', '^14.0.0'], '14': ['^14.0.0', '^15.0.0'], '15': ['^15.0.0', '^16.0.0'], '16': ['^16.0.0', '^17.0.0'], '17': ['^17.0.0', '^18.0.0'], '18': ['^18.0.0', '^19.0.0'], '19': ['^19.0.0', '^20.0.0'], '20': ['^20.0.0'] }, 'ng-bootstrap': { '12': ['^12.0.0'], '13': ['^13.0.0'], '14': ['^14.0.0'], '15': ['^15.0.0'], '16': ['^16.0.0'], '17': ['^17.0.0'], '18': ['^18.0.0'], '19': ['^19.0.0'], '20': ['^20.0.0'] } }; return compatibilityDatabase[libraryName] || {}; } /** * Check if library migration is required */ checkMigrationRequired(libraryName, currentVersion) { // Libraries that commonly require migration const migrationRequiredLibraries = [ '@angular/material', '@ngrx/store', '@angular/flex-layout', // Deprecated 'primeng' ]; return migrationRequiredLibraries.includes(libraryName); } /** * Get alternative libraries */ getAlternativeLibraries(libraryName) { const alternatives = { '@angular/flex-layout': ['@angular/cdk/layout', 'tailwindcss', 'bootstrap'], 'tslint': ['eslint', '@typescript-eslint/eslint-plugin'], 'protractor': ['cypress', '@playwright/test', 'webdriver-io'], 'karma': ['jest', 'vitest'], 'codelyzer': ['@typescript-eslint/eslint-plugin'] }; return alternatives[libraryName] || []; } /** * Get deprecation status */ getDeprecationStatus(libraryName) { const deprecatedLibraries = { '@angular/flex-layout': 'deprecated', 'tslint': 'discontinued', 'protractor': 'discontinued', 'codelyzer': 'deprecated' }; return deprecatedLibraries[libraryName] || 'stable'; } /** * Detect dependency conflicts */ async detectDependencyConflicts(dependencies) { const conflicts = []; // Check for known conflicting libraries const conflictPairs = [ ['@angular/flex-layout', '@angular/cdk'], ['tslint', 'eslint'], ['karma', 'jest'] ]; for (const [lib1, lib2] of conflictPairs) { if (dependencies[lib1] && dependencies[lib2]) { conflicts.push({ library1: lib1, library2: lib2, conflictType: 'api', severity: 'warning', resolution: `Consider migrating from ${lib1} to ${lib2}` }); } } return conflicts; } /** * Calculate code metrics */ async calculateCodeMetrics() { let totalFiles = 0; let componentCount = 0; let serviceCount = 0; let moduleCount = 0; let linesOfCode = 0; const srcPath = path.join(this.projectPath, 'src'); if (await fs.pathExists(srcPath)) { await this.analyzeDirectory(srcPath, (filePath, content) => { totalFiles++; linesOfCode += content.split('\n').length; if (filePath.includes('.component.ts')) { componentCount++; } else if (filePath.includes('.service.ts')) { serviceCount++; } else if (filePath.includes('.module.ts')) { moduleCount++; } }); } return { totalFiles, componentCount, serviceCount, moduleCount, linesOfCode, testCoverage: await this.getTestCoverage(), technicalDebt: await this.calculateTechnicalDebt() }; } /** * Analyze directory recursively */ async analyzeDirectory(dirPath, fileProcessor) { const files = await fs.readdir(dirPath); for (const file of files) { const filePath = path.join(dirPath, file); const stat = await fs.stat(filePath); if (stat.isDirectory() && !this.shouldSkipDirectory(file)) { await this.analyzeDirectory(filePath, fileProcessor); } else if (stat.isFile() && this.shouldAnalyzeFile(file)) { const content = await fs.readFile(filePath, 'utf-8'); fileProcessor(filePath, content); } } } /** * Check if directory should be skipped */ shouldSkipDirectory(dirName) { const skipDirs = ['node_modules', 'dist', '.angular', 'coverage', '.nyc_output']; return skipDirs.includes(dirName); } /** * Check if file should be analyzed */ shouldAnalyzeFile(fileName) { return fileName.endsWith('.ts') || fileName.endsWith('.js') || fileName.endsWith('.html'); } /** * Get test coverage */ async getTestCoverage() { const coverageFile = path.join(this.projectPath, 'coverage', 'lcov-report', 'index.html'); if (await fs.pathExists(coverageFile)) { // Parse coverage file for percentage // This is a simplified implementation return 75; // Placeholder } return undefined; } /** * Calculate technical debt */ async calculateTechnicalDebt() { // This would analyze code for technical debt indicators // For now, return placeholder return 15; // Placeholder percentage } /** * Assess upgrade risks */ async assessRisks(dependencies, codeMetrics) { const riskFactors = []; // Dependency risks if (dependencies.incompatible.length > 0) { riskFactors.push({ type: 'dependency', severity: 'high', description: `${dependencies.incompatible.length} incompatible dependencies`, impact: 'May require manual migration or replacement', likelihood: 0.8 }); } if (dependencies.conflicts.length > 0) { riskFactors.push({ type: 'dependency', severity: 'medium', description: `${dependencies.conflicts.length} dependency conflicts`, impact: 'May cause build or runtime issues', likelihood: 0.6 }); } // Code complexity risks if (codeMetrics.linesOfCode > 50000) { riskFactors.push({ type: 'code', severity: 'medium', description: 'Large codebase', impact: 'Increased upgrade time and potential for issues', likelihood: 0.7 }); } if (codeMetrics.testCoverage && codeMetrics.testCoverage < 50) { riskFactors.push({ type: 'code', severity: 'high', description: 'Low test coverage', impact: 'Difficult to validate upgrade success', likelihood: 0.9 }); } // Calculate overall risk const overallRisk = this.calculateOverallRisk(riskFactors); const mitigationStrategies = this.generateMitigationStrategies(riskFactors); return { overallRisk, riskFactors, mitigationStrategies }; } /** * Calculate overall risk level */ calculateOverallRisk(riskFactors) { if (riskFactors.length === 0) return 'low'; const hasHighRisk = riskFactors.some(rf => rf.severity === 'high' || rf.severity === 'critical'); const mediumRiskCount = riskFactors.filter(rf => rf.severity === 'medium').length; if (riskFactors.some(rf => rf.severity === 'critical')) return 'critical'; if (hasHighRisk || mediumRiskCount > 2) return 'high'; if (mediumRiskCount > 0) return 'medium'; return 'low'; } /** * Generate mitigation strategies */ generateMitigationStrategies(riskFactors) { const strategies = []; for (const factor of riskFactors) { switch (factor.type) { case 'dependency': if (factor.description.includes('incompatible')) { strategies.push('Review and update incompatible dependencies before upgrade'); } if (factor.description.includes('conflicts')) { strategies.push('Resolve dependency conflicts to prevent build issues'); } break; case 'code': if (factor.description.includes('Large codebase')) { strategies.push('Consider upgrading in smaller increments with extensive testing'); } if (factor.description.includes('test coverage')) { strategies.push('Increase test coverage before attempting upgrade'); } break; } } return Array.from(new Set(strategies)); } } exports.ProjectAnalyzer = ProjectAnalyzer; //# sourceMappingURL=ProjectAnalyzer.js.map