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
561 lines (556 loc) • 29.2 kB
JavaScript
;
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.Angular20Handler = void 0;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const BaseVersionHandler_1 = require("./BaseVersionHandler");
const FileContentPreserver_1 = require("../utils/FileContentPreserver");
const SSRDetector_1 = require("../utils/SSRDetector");
/**
* Angular 20 Handler - Latest Angular version with cutting-edge features
*
* Key Features in Angular 20:
* - Stable incremental hydration for SSR applications
* - Advanced signal stabilization and optimization
* - Enhanced zoneless change detection
* - Improved build performance and tree-shaking
* - Advanced SSR streaming and edge-side rendering
* - Material 3 design system maturation
* - Enhanced developer experience and debugging tools
*/
class Angular20Handler extends BaseVersionHandler_1.BaseVersionHandler {
version = '20';
getRequiredNodeVersion() {
return '>=20.11.1'; // Angular 20 requires Node 20.11.1+
}
getRequiredTypeScriptVersion() {
return '>=5.8.0 <5.9.0';
}
/**
* Get Angular 20 dependencies with latest versions
*/
getDependencyUpdates() {
return [
// Core Angular packages
{ name: '@angular/animations', version: '^20.0.0', type: 'dependencies' },
{ name: '@angular/common', version: '^20.0.0', type: 'dependencies' },
{ name: '@angular/compiler', version: '^20.0.0', type: 'dependencies' },
{ name: '@angular/core', version: '^20.0.0', type: 'dependencies' },
{ name: '@angular/forms', version: '^20.0.0', type: 'dependencies' },
{ name: '@angular/platform-browser', version: '^20.0.0', type: 'dependencies' },
{ name: '@angular/platform-browser-dynamic', version: '^20.0.0', type: 'dependencies' },
{ name: '@angular/router', version: '^20.0.0', type: 'dependencies' },
// Angular CLI and dev dependencies
{ name: '@angular/cli', version: '^20.0.0', type: 'devDependencies' },
{ name: '@angular/compiler-cli', version: '^20.0.0', type: 'devDependencies' },
{ name: '@angular-devkit/build-angular', version: '^20.0.0', type: 'devDependencies' },
// SSR packages (if present)
{ name: '@angular/ssr', version: '^20.0.0', type: 'dependencies' },
{ name: '@angular/platform-server', version: '^20.0.0', type: 'dependencies' },
// TypeScript and supporting packages - Angular 20 requires TypeScript >=5.8.0 <5.9.0
{ name: 'typescript', version: '>=5.8.0 <5.9.0', type: 'devDependencies' },
{ name: 'zone.js', version: '~0.15.0', type: 'dependencies' },
{ name: 'rxjs', version: '~7.8.0', type: 'dependencies' },
// Angular Material (if present)
{ name: '@angular/material', version: '^20.0.0', type: 'dependencies' },
{ name: '@angular/cdk', version: '^20.0.0', type: 'dependencies' },
// Testing packages
{ name: '@angular/testing', version: '^20.0.0', type: 'devDependencies' },
{ name: 'jasmine-core', version: '~5.4.0', type: 'devDependencies' },
{ name: 'karma', version: '~6.4.0', type: 'devDependencies' },
{ name: 'karma-chrome-launcher', version: '~3.2.0', type: 'devDependencies' },
{ name: 'karma-coverage', version: '~2.2.0', type: 'devDependencies' },
{ name: 'karma-jasmine', version: '~5.1.0', type: 'devDependencies' },
{ name: 'karma-jasmine-html-reporter', version: '~2.1.0', type: 'devDependencies' }
];
}
/**
* Apply Angular 20 specific changes and optimizations
*/
async applyVersionSpecificChanges(projectPath, options) {
this.progressReporter?.updateMessage('Applying Angular 20 cutting-edge features...');
// 1. Update build configurations for Angular 20 optimizations
await this.updateBuildConfigurations(projectPath);
// 2. Configure incremental hydration for SSR applications
await this.configureIncrementalHydration(projectPath);
// 3. Setup advanced signal optimization
await this.setupSignalOptimizations(projectPath);
// 4. Configure zoneless change detection (opt-in)
if (options.enableZonelessChangeDetection) {
await this.configureZonelessDetection(projectPath);
}
// 5. Update main.ts for Angular 20 bootstrap optimizations
await this.updateMainTsForAngular20(projectPath);
// 6. Configure advanced SSR features
await this.configureAdvancedSSR(projectPath);
// 7. Update TypeScript configuration for Angular 20
await this.updateTypeScriptConfigForAngular20(projectPath);
// 8. Configure Material 3 design system (if Material is present)
await this.configureMaterial3DesignSystem(projectPath);
// 9. Setup enhanced developer tools and debugging
await this.setupEnhancedDevTools(projectPath);
// 10. Remove webpack-dev-server and ensure esbuild dev server
await this.migrateToEsbuildDevServer(projectPath);
// 11. Configure advanced build optimizations
await this.configureAdvancedBuildOptimizations(projectPath);
this.progressReporter?.success('✓ Angular 20 cutting-edge features configured successfully');
}
/**
* Get comprehensive breaking changes for Angular 20
*/
getBreakingChanges() {
return [
// Node.js version requirement
this.createBreakingChange('ng20-node-version', 'dependency', 'high', 'Node.js 20.11.1+ required', 'Angular 20 requires Node.js 20.11.1 or higher', 'Update Node.js to version 20.11.1 or higher'),
// TypeScript version update
this.createBreakingChange('ng20-typescript-version', 'dependency', 'medium', 'TypeScript 5.6+ required', 'Angular 20 requires TypeScript 5.6.0 or higher', 'Update TypeScript to version 5.6.0'),
// Incremental hydration default changes
this.createBreakingChange('ng20-incremental-hydration', 'api', 'low', 'Incremental hydration stabilized', 'Incremental hydration is now stable and recommended for SSR applications', 'Opt-in feature - existing SSR applications continue to work unchanged'),
// Signal optimization changes
this.createBreakingChange('ng20-signal-optimizations', 'api', 'low', 'Enhanced signal performance', 'Signal-based applications get automatic performance optimizations', 'Existing applications benefit automatically - no action required'),
// Zoneless change detection improvements
this.createBreakingChange('ng20-zoneless-improvements', 'api', 'low', 'Enhanced zoneless change detection', 'Zoneless change detection is more stable and performant', 'Opt-in feature - Zone.js applications continue to work unchanged'),
// Build system optimizations
this.createBreakingChange('ng20-build-optimizations', 'build', 'low', 'Advanced build optimizations', 'Enhanced tree-shaking and bundle optimization', 'Applications may see smaller bundle sizes automatically'),
// Developer experience improvements
this.createBreakingChange('ng20-dev-experience', 'config', 'low', 'Enhanced developer tools', 'Improved debugging and development experience', 'New tools available - existing workflows continue to work')
];
}
// Private implementation methods
/**
* Update build configurations for Angular 20 optimizations
*/
async updateBuildConfigurations(projectPath) {
const angularJsonPath = path.join(projectPath, 'angular.json');
if (await fs.pathExists(angularJsonPath)) {
try {
const angularJson = await fs.readJson(angularJsonPath);
// Update build configurations for each project
for (const projectName in angularJson.projects) {
const project = angularJson.projects[projectName];
if (project.architect?.build?.options) {
// Enable advanced optimizations
project.architect.build.options.optimization = {
scripts: true,
styles: true,
fonts: true
};
// Enable advanced bundle optimization
project.architect.build.options.bundleOptimization = true;
// Enable source map optimization
project.architect.build.options.sourceMap = {
scripts: true,
styles: true,
vendor: false,
hidden: true
};
// Configure advanced build options
project.architect.build.options.buildOptimizer = true;
project.architect.build.options.vendorChunk = true;
project.architect.build.options.commonChunk = true;
}
}
await fs.writeJson(angularJsonPath, angularJson, { spaces: 2 });
this.progressReporter?.info('✓ Updated build configurations for Angular 20 optimizations');
}
catch (error) {
this.progressReporter?.warn(`Could not update angular.json: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
/**
* Configure incremental hydration for SSR applications only
*/
async configureIncrementalHydration(projectPath) {
// Check if this is an SSR application first
const isSSRApp = await SSRDetector_1.SSRDetector.isSSRApplication(projectPath);
if (!isSSRApp) {
this.progressReporter?.info('✓ Skipping incremental hydration configuration (CSR application detected)');
return;
}
const appConfigPath = path.join(projectPath, 'src/app/app.config.ts');
if (await fs.pathExists(appConfigPath)) {
try {
let content = await fs.readFile(appConfigPath, 'utf-8');
// Add incremental hydration import if not present
if (!content.includes('provideClientHydration')) {
content = content.replace(/import { ApplicationConfig } from '@angular\/core';/, `import { ApplicationConfig } from '@angular/core';
import { provideClientHydration, withIncrementalHydration } from '@angular/platform-browser';`);
// Add incremental hydration provider
content = content.replace(/providers: \[([\s\S]*?)\]/, `providers: [
provideClientHydration(withIncrementalHydration()),
$1
]`);
await fs.writeFile(appConfigPath, content);
this.progressReporter?.info('✓ Configured incremental hydration for SSR application');
}
}
catch (error) {
this.progressReporter?.warn(`Could not configure incremental hydration: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
/**
* Setup advanced signal optimizations
*/
async setupSignalOptimizations(projectPath) {
const tsConfigPath = path.join(projectPath, 'tsconfig.json');
if (await fs.pathExists(tsConfigPath)) {
try {
const tsConfig = await fs.readJson(tsConfigPath);
// Enable signal optimization compiler options
if (!tsConfig.compilerOptions) {
tsConfig.compilerOptions = {};
}
tsConfig.compilerOptions.experimentalDecorators = true;
tsConfig.compilerOptions.useDefineForClassFields = false;
// Enable Angular 20 signal optimizations
if (!tsConfig.angularCompilerOptions) {
tsConfig.angularCompilerOptions = {};
}
tsConfig.angularCompilerOptions.enableSignalOptimizations = true;
tsConfig.angularCompilerOptions.optimizeFor = 'signals';
await fs.writeJson(tsConfigPath, tsConfig, { spaces: 2 });
this.progressReporter?.info('✓ Configured advanced signal optimizations');
}
catch (error) {
this.progressReporter?.warn(`Could not setup signal optimizations: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
/**
* Configure zoneless change detection (opt-in)
*/
async configureZonelessDetection(projectPath) {
const appConfigPath = path.join(projectPath, 'src/app/app.config.ts');
if (await fs.pathExists(appConfigPath)) {
try {
let content = await fs.readFile(appConfigPath, 'utf-8');
// Add zoneless change detection import
if (!content.includes('provideExperimentalZonelessChangeDetection')) {
content = content.replace(/import { ApplicationConfig } from '@angular\/core';/, `import { ApplicationConfig, provideExperimentalZonelessChangeDetection } from '@angular/core';`);
// Add zoneless change detection provider
content = content.replace(/providers: \[([\s\S]*?)\]/, `providers: [
provideExperimentalZonelessChangeDetection(),
$1
]`);
await fs.writeFile(appConfigPath, content);
this.progressReporter?.info('✓ Configured zoneless change detection (experimental)');
}
}
catch (error) {
this.progressReporter?.warn(`Could not configure zoneless detection: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
/**
* Update main.ts for Angular 20 optimizations
*/
async updateMainTsForAngular20(projectPath) {
const mainTsPath = path.join(projectPath, 'src/main.ts');
if (await fs.pathExists(mainTsPath)) {
await FileContentPreserver_1.FileContentPreserver.updateMainTsFile(mainTsPath, 20);
this.progressReporter?.info('✓ Updated main.ts for Angular 20 optimizations');
}
}
/**
* Configure advanced SSR features
*/
async configureAdvancedSSR(projectPath) {
// Check if this is an SSR application first
const isSSRApp = await SSRDetector_1.SSRDetector.isSSRApplication(projectPath);
if (!isSSRApp) {
this.progressReporter?.info('✓ Skipping advanced SSR configuration (CSR application detected)');
return;
}
const serverTsPath = path.join(projectPath, 'src/main.server.ts');
if (await fs.pathExists(serverTsPath)) {
try {
let content = await fs.readFile(serverTsPath, 'utf-8');
// Add advanced SSR optimizations
if (!content.includes('withSSROptimizations')) {
content = content.replace(/import { bootstrapApplication } from '@angular\/platform-browser';/, `import { bootstrapApplication } from '@angular/platform-browser';
import { withSSROptimizations, withStreamingRendering } from '@angular/ssr';`);
// Configure SSR optimizations
content = content.replace(/bootstrapApplication\(([^,]+),\s*([^)]+)\)/, `bootstrapApplication($1, {
...$2,
providers: [
...$2.providers,
withSSROptimizations(),
withStreamingRendering()
]
})`);
await fs.writeFile(serverTsPath, content);
this.progressReporter?.info('✓ Configured advanced SSR features for SSR application');
}
}
catch (error) {
this.progressReporter?.warn(`Could not configure advanced SSR: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
/**
* Update TypeScript configuration for Angular 20
*/
async updateTypeScriptConfigForAngular20(projectPath) {
const tsConfigPath = path.join(projectPath, 'tsconfig.json');
if (await fs.pathExists(tsConfigPath)) {
try {
const tsConfig = await fs.readJson(tsConfigPath);
// Update TypeScript compiler options for Angular 20
if (!tsConfig.compilerOptions) {
tsConfig.compilerOptions = {};
}
// Enable latest TypeScript features
tsConfig.compilerOptions.target = 'ES2022';
tsConfig.compilerOptions.lib = ['ES2022', 'DOM'];
tsConfig.compilerOptions.module = 'ES2022';
tsConfig.compilerOptions.moduleResolution = 'bundler';
// Keep strict mode false for safer migration (as per user requirements)
tsConfig.compilerOptions.strict = false;
tsConfig.compilerOptions.strictNullChecks = false;
tsConfig.compilerOptions.strictFunctionTypes = false;
await fs.writeJson(tsConfigPath, tsConfig, { spaces: 2 });
this.progressReporter?.info('✓ Updated TypeScript configuration for Angular 20');
}
catch (error) {
this.progressReporter?.warn(`Could not update TypeScript config: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
/**
* Configure Material 3 design system (if Material is present)
*/
async configureMaterial3DesignSystem(projectPath) {
const packageJsonPath = path.join(projectPath, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
try {
const packageJson = await fs.readJson(packageJsonPath);
// Check if Angular Material is present
if (packageJson.dependencies?.['@angular/material']) {
const stylesPath = path.join(projectPath, 'src/styles.css');
if (await fs.pathExists(stylesPath)) {
let styles = await fs.readFile(stylesPath, 'utf-8');
// Add Material 3 theme import if not present
if (!styles.includes('@angular/material/theming')) {
styles = `/* Angular Material 3 Design System */
@import '@angular/material/theming';
/* Include Material 3 theme */
@include mat.core();
/* Define Material 3 theme */
$primary: mat.define-palette(mat.$azure-palette);
$accent: mat.define-palette(mat.$blue-palette);
$warn: mat.define-palette(mat.$red-palette);
$theme: mat.define-theme((
color: (
theme-type: light,
primary: $primary,
tertiary: $accent,
),
typography: (
brand-family: 'Roboto, sans-serif',
plain-family: 'Roboto, sans-serif',
),
density: (
scale: 0,
)
));
/* Apply Material 3 theme */
@include mat.all-component-themes($theme);
${styles}`;
await fs.writeFile(stylesPath, styles);
this.progressReporter?.info('✓ Configured Material 3 design system');
}
}
}
}
catch (error) {
this.progressReporter?.warn(`Could not configure Material 3: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
/**
* Setup enhanced developer tools and debugging
*/
async setupEnhancedDevTools(projectPath) {
const angularJsonPath = path.join(projectPath, 'angular.json');
if (await fs.pathExists(angularJsonPath)) {
try {
const angularJson = await fs.readJson(angularJsonPath);
// Update serve configurations for enhanced dev experience
for (const projectName in angularJson.projects) {
const project = angularJson.projects[projectName];
if (project.architect?.serve?.options) {
// Enable advanced dev server features
project.architect.serve.options.hmr = true;
project.architect.serve.options.liveReload = true;
project.architect.serve.options.watch = true;
// Enable enhanced debugging
project.architect.serve.options.verbose = true;
project.architect.serve.options.progress = true;
// Configure source maps for development
project.architect.serve.options.sourceMap = {
scripts: true,
styles: true,
vendor: true
};
}
}
await fs.writeJson(angularJsonPath, angularJson, { spaces: 2 });
this.progressReporter?.info('✓ Configured enhanced developer tools');
}
catch (error) {
this.progressReporter?.warn(`Could not setup enhanced dev tools: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
/**
* Migrate from webpack-dev-server to esbuild dev server (Angular 18+)
*/
async migrateToEsbuildDevServer(projectPath) {
const packageJsonPath = path.join(projectPath, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
try {
const packageJson = await fs.readJson(packageJsonPath);
// Remove webpack-dev-server if present
if (packageJson.devDependencies?.['webpack-dev-server']) {
delete packageJson.devDependencies['webpack-dev-server'];
this.progressReporter?.info('✓ Removed webpack-dev-server (Angular 18+ uses esbuild dev server)');
}
// Remove any custom webpack configurations that might interfere
if (packageJson.devDependencies?.['@angular-builders/custom-webpack']) {
this.progressReporter?.warn('⚠️ Custom webpack configuration detected - may need manual review for esbuild compatibility');
}
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
// Update angular.json to ensure esbuild dev server configuration
await this.configureEsbuildDevServer(projectPath);
}
catch (error) {
this.progressReporter?.warn(`Could not migrate dev server: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
/**
* Configure esbuild dev server in angular.json
*/
async configureEsbuildDevServer(projectPath) {
const angularJsonPath = path.join(projectPath, 'angular.json');
if (await fs.pathExists(angularJsonPath)) {
try {
const angularJson = await fs.readJson(angularJsonPath);
// Update serve configurations to use esbuild
for (const projectName in angularJson.projects) {
const project = angularJson.projects[projectName];
if (project.architect?.serve) {
// Ensure serve uses the correct builder for esbuild
project.architect.serve.builder = '@angular-devkit/build-angular:dev-server';
// Remove any webpack-specific configurations
if (project.architect.serve.options) {
delete project.architect.serve.options.customWebpackConfig;
delete project.architect.serve.options.webpackDevServerOptions;
// Configure esbuild-optimized dev server options
project.architect.serve.options = {
...project.architect.serve.options,
buildTarget: `${projectName}:build`
};
}
}
// Update build configuration to use esbuild (use browser-esbuild for easier migration from existing projects)
if (project.architect?.build) {
// Check if it's still using the old webpack browser builder
if (project.architect.build.builder === '@angular-devkit/build-angular:browser') {
project.architect.build.builder = '@angular-devkit/build-angular:browser-esbuild';
this.progressReporter?.info('✓ Migrated from webpack browser builder to esbuild browser-esbuild builder');
}
}
}
await fs.writeJson(angularJsonPath, angularJson, { spaces: 2 });
this.progressReporter?.info('✓ Configured angular.json for esbuild dev server');
}
catch (error) {
this.progressReporter?.warn(`Could not configure esbuild dev server: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
/**
* Configure advanced build optimizations
*/
async configureAdvancedBuildOptimizations(projectPath) {
const angularJsonPath = path.join(projectPath, 'angular.json');
if (await fs.pathExists(angularJsonPath)) {
try {
const angularJson = await fs.readJson(angularJsonPath);
// Configure advanced build optimizations
for (const projectName in angularJson.projects) {
const project = angularJson.projects[projectName];
if (project.architect?.build?.configurations?.production) {
const prodConfig = project.architect.build.configurations.production;
// Enable advanced tree-shaking
prodConfig.optimization = {
scripts: true,
styles: {
minify: true,
inlineCritical: true
},
fonts: true
};
// Enable advanced bundling
prodConfig.outputHashing = 'all';
prodConfig.namedChunks = false;
prodConfig.aot = true;
prodConfig.buildOptimizer = true;
// Configure advanced file replacements
if (!prodConfig.fileReplacements) {
prodConfig.fileReplacements = [];
}
// Enable service worker (if configured)
if (await fs.pathExists(path.join(projectPath, 'ngsw-config.json'))) {
prodConfig.serviceWorker = true;
prodConfig.ngswConfigPath = 'ngsw-config.json';
}
}
}
await fs.writeJson(angularJsonPath, angularJson, { spaces: 2 });
this.progressReporter?.info('✓ Configured advanced build optimizations');
}
catch (error) {
this.progressReporter?.warn(`Could not configure build optimizations: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
}
exports.Angular20Handler = Angular20Handler;
//# sourceMappingURL=Angular20Handler.js.map