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

422 lines 21.9 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.Angular13Handler = void 0; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const BaseVersionHandler_1 = require("./BaseVersionHandler"); const SSRDetector_1 = require("../utils/SSRDetector"); const DependencyCompatibilityMatrix_1 = require("../utils/DependencyCompatibilityMatrix"); /** * Angular 13 Handler - Complete View Engine removal and APF updates * * Key Features in Angular 13: * - Complete View Engine removal - Ivy only * - Angular Package Format (APF) improvements * - Dynamic import support for lazy routes * - Node.js ES modules support * - Angular CLI modernization * - Webpack 5 full support * - IE11 deprecation warnings */ class Angular13Handler extends BaseVersionHandler_1.BaseVersionHandler { version = '13'; getRequiredNodeVersion() { return '>=12.20.0'; } getRequiredTypeScriptVersion() { return '>=4.4.2 <4.6.0'; } /** * Get Angular 13 dependencies with correct versions */ getDependencyUpdates() { return [ // Core Angular packages { name: '@angular/animations', version: '^13.0.0', type: 'dependencies' }, { name: '@angular/common', version: '^13.0.0', type: 'dependencies' }, { name: '@angular/compiler', version: '^13.0.0', type: 'dependencies' }, { name: '@angular/core', version: '^13.0.0', type: 'dependencies' }, { name: '@angular/forms', version: '^13.0.0', type: 'dependencies' }, { name: '@angular/platform-browser', version: '^13.0.0', type: 'dependencies' }, { name: '@angular/platform-browser-dynamic', version: '^13.0.0', type: 'dependencies' }, { name: '@angular/router', version: '^13.0.0', type: 'dependencies' }, // Angular CLI and dev dependencies { name: '@angular/cli', version: '^13.0.0', type: 'devDependencies' }, { name: '@angular/compiler-cli', version: '^13.0.0', type: 'devDependencies' }, { name: '@angular-devkit/build-angular', version: '^13.0.0', type: 'devDependencies' }, // TypeScript and supporting packages { name: 'typescript', version: '~4.4.2', type: 'devDependencies' }, // Angular Material and CDK { name: '@angular/material', version: '^13.0.0', type: 'dependencies' }, { name: '@angular/cdk', version: '^13.0.0', type: 'dependencies' }, { name: 'zone.js', version: '~0.11.4', type: 'dependencies' }, { name: 'rxjs', version: '~7.4.0', type: 'dependencies' }, // Third-party Angular ecosystem packages ...DependencyCompatibilityMatrix_1.DependencyCompatibilityMatrix.getCompatibleDependencies('13').map(dep => ({ name: dep.name, version: dep.version, type: dep.type })) ]; } async applyVersionSpecificChanges(projectPath, options) { this.progressReporter?.updateMessage('Applying Angular 13 transformations...'); // Check if this is an SSR application const isSSRApp = await SSRDetector_1.SSRDetector.isSSRApplication(projectPath); this.progressReporter?.info(`Application type: ${isSSRApp ? 'SSR (Server-Side Rendering)' : 'CSR (Client-Side Rendering)'}`); // 1. Remove View Engine references and ensure Ivy compatibility await this.ensureIvyCompatibility(projectPath); // 2. Update Angular Package Format for libraries await this.updateAngularPackageFormat(projectPath); // 3. Migrate to dynamic imports for lazy routes await this.migrateToDynamicImports(projectPath); // 4. Update webpack configuration for v5 compatibility await this.updateWebpackConfiguration(projectPath); // 5. Handle Node.js ES modules support await this.configureESModulesSupport(projectPath); // 6. Update Angular CLI configuration await this.updateAngularCliConfiguration(projectPath); // 7. Remove IE11 polyfills and update browser support await this.removeIE11Support(projectPath); // 8. Update third-party library compatibility await this.validateThirdPartyCompatibility(projectPath); this.progressReporter?.success('✓ Angular 13 transformations completed'); } getBreakingChanges() { return [ // View Engine removal - Critical this.createBreakingChange('ng13-view-engine-removal', 'build', 'critical', 'View Engine completely removed', 'All applications must use Ivy renderer. View Engine is no longer supported.', 'Ensure all dependencies are Ivy-compatible and remove View Engine configurations'), // Angular Package Format updates this.createBreakingChange('ng13-angular-package-format', 'build', 'high', 'Angular Package Format v13 changes', 'Libraries must use the new package format with updated metadata', 'Update library build configurations and package.json exports'), // TypeScript version requirement this.createBreakingChange('ng13-typescript-version', 'dependency', 'medium', 'TypeScript 4.4+ required', 'Angular 13 requires TypeScript 4.4.2 or higher', 'Update TypeScript to version 4.4.2 or higher'), // IE11 deprecation this.createBreakingChange('ng13-ie11-deprecation', 'config', 'medium', 'IE11 support deprecated', 'IE11 support is deprecated and will be removed in Angular 15', 'Plan migration away from IE11 support and update browser compatibility'), // Dynamic imports requirement this.createBreakingChange('ng13-dynamic-imports', 'api', 'low', 'Dynamic imports for lazy routes', 'Lazy route loading now uses dynamic imports by default', 'Update route configurations to use dynamic import syntax'), // Webpack 5 compatibility this.createBreakingChange('ng13-webpack5', 'build', 'low', 'Webpack 5 full support', 'Build system now fully supports Webpack 5 features', 'Update custom webpack configurations for Webpack 5 compatibility') ]; } // Private implementation methods /** * Ensure Ivy compatibility and remove View Engine references */ async ensureIvyCompatibility(projectPath) { const tsconfigPath = path.join(projectPath, 'tsconfig.json'); if (await fs.pathExists(tsconfigPath)) { try { const tsconfig = await fs.readJson(tsconfigPath); // Remove View Engine configurations if (tsconfig.angularCompilerOptions) { delete tsconfig.angularCompilerOptions.enableIvy; delete tsconfig.angularCompilerOptions.disableTypeScriptVersionCheck; // Ensure Ivy-specific options are set tsconfig.angularCompilerOptions.enableI18nLegacyMessageIdFormat = false; tsconfig.angularCompilerOptions.strictInjectionParameters = true; tsconfig.angularCompilerOptions.strictInputAccessModifiers = true; tsconfig.angularCompilerOptions.strictTemplates = true; } await fs.writeJson(tsconfigPath, tsconfig, { spaces: 2 }); this.progressReporter?.info('✓ Ensured Ivy compatibility in TypeScript configuration'); } catch (error) { this.progressReporter?.warn(`Could not update TypeScript config: ${error instanceof Error ? error.message : String(error)}`); } } } /** * Update Angular Package Format for libraries */ async updateAngularPackageFormat(projectPath) { const angularJsonPath = path.join(projectPath, 'angular.json'); if (await fs.pathExists(angularJsonPath)) { try { const angularJson = await fs.readJson(angularJsonPath); // Update library projects for new APF for (const projectName in angularJson.projects) { const project = angularJson.projects[projectName]; if (project.projectType === 'library') { // Update build configuration for APF v13 if (project.architect?.build?.builder === '@angular-devkit/build-angular:ng-packagr') { project.architect.build.options = { ...project.architect.build.options, project: project.architect.build.options.project || 'ng-package.json' }; } } } await fs.writeJson(angularJsonPath, angularJson, { spaces: 2 }); this.progressReporter?.info('✓ Updated Angular Package Format configuration'); } catch (error) { this.progressReporter?.warn(`Could not update Angular Package Format: ${error instanceof Error ? error.message : String(error)}`); } } } /** * Migrate to dynamic imports for lazy routes */ async migrateToDynamicImports(projectPath) { const routingFiles = await this.findRoutingFiles(projectPath); for (const file of routingFiles) { try { let content = await fs.readFile(file, 'utf-8'); // Replace loadChildren string syntax with dynamic imports const loadChildrenRegex = /loadChildren:\s*['"`]([^'"`]+)#([^'"`]+)['"`]/g; content = content.replace(loadChildrenRegex, (match, modulePath, className) => { return `loadChildren: () => import('${modulePath}').then(m => m.${className})`; }); // Update relative path imports content = content.replace(/loadChildren:\s*['"`]\.\/([^'"`]+)#([^'"`]+)['"`]/g, `loadChildren: () => import('./$1').then(m => m.$2)`); await fs.writeFile(file, content); } catch (error) { this.progressReporter?.warn(`Could not update routing file ${file}: ${error instanceof Error ? error.message : String(error)}`); } } if (routingFiles.length > 0) { this.progressReporter?.info(`✓ Updated ${routingFiles.length} routing files to use dynamic imports`); } } /** * Update webpack configuration for v5 compatibility */ async updateWebpackConfiguration(projectPath) { const webpackConfigPath = path.join(projectPath, 'webpack.config.js'); const customWebpackPath = path.join(projectPath, 'webpack.config.ts'); const configPath = await fs.pathExists(webpackConfigPath) ? webpackConfigPath : await fs.pathExists(customWebpackPath) ? customWebpackPath : null; if (configPath) { try { let content = await fs.readFile(configPath, 'utf-8'); // Add Webpack 5 compatibility configurations if (!content.includes('resolve.fallback')) { const fallbackConfig = ` resolve: { fallback: { "path": require.resolve("path-browserify"), "crypto": require.resolve("crypto-browserify"), "stream": require.resolve("stream-browserify"), "util": require.resolve("util") } },`; content = content.replace(/module\.exports\s*=\s*{/, `module.exports = {${fallbackConfig}`); } await fs.writeFile(configPath, content); this.progressReporter?.info('✓ Updated webpack configuration for v5 compatibility'); } catch (error) { this.progressReporter?.warn(`Could not update webpack config: ${error instanceof Error ? error.message : String(error)}`); } } } /** * Configure ES modules support */ async configureESModulesSupport(projectPath) { const packageJsonPath = path.join(projectPath, 'package.json'); if (await fs.pathExists(packageJsonPath)) { try { const packageJson = await fs.readJson(packageJsonPath); // Add ES modules support configuration if (!packageJson.type) { // Don't force ES modules, but ensure compatibility packageJson.exports = packageJson.exports || {}; // Add conditional exports for better ES modules support if (!packageJson.exports['.']) { packageJson.exports['.'] = { "import": "./dist/index.js", "require": "./dist/index.js" }; } } await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); this.progressReporter?.info('✓ Configured ES modules support'); } catch (error) { this.progressReporter?.warn(`Could not configure ES modules: ${error instanceof Error ? error.message : String(error)}`); } } } /** * Update Angular CLI configuration for v13 */ async updateAngularCliConfiguration(projectPath) { const angularJsonPath = path.join(projectPath, 'angular.json'); if (await fs.pathExists(angularJsonPath)) { try { const angularJson = await fs.readJson(angularJsonPath); // Update CLI workspace version angularJson.version = 1; // Update project configurations for (const projectName in angularJson.projects) { const project = angularJson.projects[projectName]; // Update build configurations if (project.architect?.build) { // Enable build optimizer by default project.architect.build.options.buildOptimizer = true; // Update production configuration if (project.architect.build.configurations?.production) { project.architect.build.configurations.production.buildOptimizer = true; } } // Update test configuration if (project.architect?.test) { project.architect.test.options.codeCoverage = false; } } await fs.writeJson(angularJsonPath, angularJson, { spaces: 2 }); this.progressReporter?.info('✓ Updated Angular CLI configuration'); } catch (error) { this.progressReporter?.warn(`Could not update Angular CLI config: ${error instanceof Error ? error.message : String(error)}`); } } } /** * Remove IE11 support and update browser compatibility */ async removeIE11Support(projectPath) { const browserslistPath = path.join(projectPath, '.browserslistrc'); if (await fs.pathExists(browserslistPath)) { try { let content = await fs.readFile(browserslistPath, 'utf-8'); // Remove IE11 from supported browsers content = content.replace(/IE\s+\d+/gi, ''); content = content.replace(/not IE \d+/gi, ''); // Add modern browser support if (!content.includes('last 2 Chrome versions')) { content += '\nlast 2 Chrome versions\nlast 2 Firefox versions\nlast 2 Safari versions\nlast 2 Edge versions'; } await fs.writeFile(browserslistPath, content); this.progressReporter?.info('✓ Updated browser support configuration (removed IE11)'); } catch (error) { this.progressReporter?.warn(`Could not update browserslist: ${error instanceof Error ? error.message : String(error)}`); } } // Remove IE11 polyfills from polyfills.ts const polyfillsPath = path.join(projectPath, 'src/polyfills.ts'); if (await fs.pathExists(polyfillsPath)) { try { let content = await fs.readFile(polyfillsPath, 'utf-8'); // Comment out IE11 polyfills content = content.replace(/import 'classlist\.js';/g, '// import \'classlist.js\'; // IE11 support removed in Angular 13'); content = content.replace(/import 'web-animations-js';/g, '// import \'web-animations-js\'; // IE11 support removed in Angular 13'); await fs.writeFile(polyfillsPath, content); this.progressReporter?.info('✓ Removed IE11 polyfills'); } catch (error) { this.progressReporter?.warn(`Could not update polyfills: ${error instanceof Error ? error.message : String(error)}`); } } } /** * Validate third-party library compatibility */ async validateThirdPartyCompatibility(projectPath) { const packageJsonPath = path.join(projectPath, 'package.json'); if (await fs.pathExists(packageJsonPath)) { try { const packageJson = await fs.readJson(packageJsonPath); const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }; const incompatibleLibraries = []; const warnings = []; // Check for known incompatible libraries for (const [libName, version] of Object.entries(dependencies)) { if (typeof version === 'string') { // Check Angular Material compatibility if (libName === '@angular/material' && !version.includes('13')) { warnings.push(`${libName}@${version} should be updated to v13 for full compatibility`); } // Check for View Engine dependent libraries if (this.isViewEngineDependentLibrary(libName)) { incompatibleLibraries.push(`${libName}@${version} may not be compatible with Ivy-only Angular 13`); } } } if (warnings.length > 0) { this.progressReporter?.warn(`Library compatibility warnings: ${warnings.join(', ')}`); } if (incompatibleLibraries.length > 0) { this.progressReporter?.warn(`Potentially incompatible libraries detected: ${incompatibleLibraries.join(', ')}`); } this.progressReporter?.info('✓ Third-party library compatibility validation completed'); } catch (error) { this.progressReporter?.warn(`Could not validate third-party compatibility: ${error instanceof Error ? error.message : String(error)}`); } } } /** * Find all routing files in the project */ async findRoutingFiles(dir) { const files = []; try { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') { files.push(...await this.findRoutingFiles(fullPath)); } else if (entry.name.endsWith('-routing.module.ts') || entry.name.endsWith('.routing.ts') || (entry.name.endsWith('.module.ts') && entry.name.includes('routing'))) { files.push(fullPath); } } } catch (error) { // Directory might not exist or be inaccessible } return files; } /** * Check if a library is known to be View Engine dependent */ isViewEngineDependentLibrary(libName) { const viewEngineDependentLibraries = [ '@angular/upgrade', 'ngx-bootstrap', 'ng-bootstrap' ]; return viewEngineDependentLibraries.some(lib => libName.includes(lib)); } } exports.Angular13Handler = Angular13Handler; //# sourceMappingURL=Angular13Handler.js.map