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

604 lines 25.2 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.IntelligentMergeEngine = void 0; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); // Simple deep merge utility - fallback for ts-deepmerge function deepmerge(target, source, customMergers) { if (typeof source !== 'object' || source === null) { return source; } if (typeof target !== 'object' || target === null) { return source; } const result = { ...target }; for (const key in source) { if (source.hasOwnProperty(key)) { if (customMergers && customMergers[key]) { result[key] = customMergers[key](target[key], source[key]); } else if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) { result[key] = deepmerge(target[key] || {}, source[key]); } else { result[key] = source[key]; } } } return result; } const ts_morph_1 = require("ts-morph"); /** * Intelligent merge engine that combines ts-deepmerge for configurations * and custom logic for TypeScript code merging */ class IntelligentMergeEngine { project; progressReporter; constructor(projectPath, progressReporter) { const tsConfigPath = path.join(projectPath, 'tsconfig.json'); const tsConfigExists = fs.existsSync(tsConfigPath); this.project = new ts_morph_1.Project({ ...(tsConfigExists ? { tsConfigFilePath: tsConfigPath } : { compilerOptions: { target: ts_morph_1.ScriptTarget.ES2020, module: ts_morph_1.ModuleKind.ES2020, moduleResolution: ts_morph_1.ModuleResolutionKind.NodeNext, strict: false, esModuleInterop: true, skipLibCheck: true } }) }); this.progressReporter = progressReporter; } /** * Merge package.json configurations intelligently */ async mergePackageJson(packageJsonPath, migrationUpdates, options = this.getDefaultOptions()) { try { const userPackageJson = await fs.readJson(packageJsonPath); const backup = options.createBackups ? await this.createBackup(packageJsonPath) : undefined; // Use ts-deepmerge for deep merging with custom merge functions const mergedPackageJson = deepmerge(userPackageJson, migrationUpdates, { // Custom merge for dependencies - preserve user versions unless migration requires specific version dependencies: (userDeps, migrationDeps) => { return this.mergeDependencies(userDeps, migrationDeps, options); }, devDependencies: (userDeps, migrationDeps) => { return this.mergeDependencies(userDeps, migrationDeps, options); }, scripts: (userScripts, migrationScripts) => { return this.mergeScripts(userScripts, migrationScripts, options); } }); await fs.writeJson(packageJsonPath, mergedPackageJson, { spaces: 2 }); this.progressReporter?.success('✓ package.json merged successfully'); return { success: true, conflicts: [], warnings: [], merged: true, backupPath: backup }; } catch (error) { return { success: false, conflicts: [], warnings: [`Failed to merge package.json: ${error instanceof Error ? error.message : String(error)}`], merged: false }; } } /** * Merge angular.json configurations */ async mergeAngularJson(angularJsonPath, migrationUpdates, options = this.getDefaultOptions()) { try { const userAngularJson = await fs.readJson(angularJsonPath); const backup = options.createBackups ? await this.createBackup(angularJsonPath) : undefined; // Intelligent merge for angular.json with special handling for architect configs const mergedAngularJson = deepmerge(userAngularJson, migrationUpdates, { // Custom merge for architect configurations projects: (userProjects, migrationProjects) => { return this.mergeProjects(userProjects, migrationProjects, options); } }); await fs.writeJson(angularJsonPath, mergedAngularJson, { spaces: 2 }); this.progressReporter?.success('✓ angular.json merged successfully'); return { success: true, conflicts: [], warnings: [], merged: true, backupPath: backup }; } catch (error) { return { success: false, conflicts: [], warnings: [`Failed to merge angular.json: ${error instanceof Error ? error.message : String(error)}`], merged: false }; } } /** * Merge TypeScript configuration files (tsconfig.json) */ async mergeTsConfig(tsConfigPath, migrationUpdates, options = this.getDefaultOptions()) { try { const userTsConfig = await fs.readJson(tsConfigPath); const backup = options.createBackups ? await this.createBackup(tsConfigPath) : undefined; // Deep merge with special handling for compiler options const mergedTsConfig = deepmerge(userTsConfig, migrationUpdates, { compilerOptions: (userOptions, migrationOptions) => { return this.mergeCompilerOptions(userOptions, migrationOptions, options); } }); await fs.writeJson(tsConfigPath, mergedTsConfig, { spaces: 2 }); this.progressReporter?.success('✓ tsconfig.json merged successfully'); return { success: true, conflicts: [], warnings: [], merged: true, backupPath: backup }; } catch (error) { return { success: false, conflicts: [], warnings: [`Failed to merge tsconfig.json: ${error instanceof Error ? error.message : String(error)}`], merged: false }; } } /** * Merge TypeScript source files with AST-based merging */ async mergeTypeScriptFile(filePath, migrationChanges, options = this.getDefaultOptions()) { try { const backup = options.createBackups ? await this.createBackup(filePath) : undefined; const sourceFile = this.project.addSourceFileAtPath(filePath); const conflicts = []; // Merge imports if (migrationChanges.imports) { this.mergeImports(sourceFile, migrationChanges.imports, conflicts, options); } // Merge class decorators if (migrationChanges.decorators) { this.mergeDecorators(sourceFile, migrationChanges.decorators, conflicts, options); } // Merge class members (properties, methods) if (migrationChanges.classMembers) { this.mergeClassMembers(sourceFile, migrationChanges.classMembers, conflicts, options); } // Merge providers and configurations if (migrationChanges.providers) { this.mergeProviders(sourceFile, migrationChanges.providers, conflicts, options); } await sourceFile.save(); this.progressReporter?.success(`✓ ${path.basename(filePath)} merged successfully`); return { success: true, conflicts, warnings: [], merged: true, backupPath: backup }; } catch (error) { return { success: false, conflicts: [], warnings: [`Failed to merge ${filePath}: ${error instanceof Error ? error.message : String(error)}`], merged: false }; } } /** * Merge template files (.html) */ async mergeTemplateFile(templatePath, migrationChanges, options = this.getDefaultOptions()) { try { const userTemplate = await fs.readFile(templatePath, 'utf-8'); const backup = options.createBackups ? await this.createBackup(templatePath) : undefined; let mergedTemplate = userTemplate; const conflicts = []; // Merge control flow changes (*ngIf to @if) if (migrationChanges.controlFlow) { const result = this.mergeControlFlow(mergedTemplate, migrationChanges.controlFlow, options); mergedTemplate = result.template; conflicts.push(...result.conflicts); } // Merge directive changes if (migrationChanges.directives) { const result = this.mergeDirectives(mergedTemplate, migrationChanges.directives, options); mergedTemplate = result.template; conflicts.push(...result.conflicts); } await fs.writeFile(templatePath, mergedTemplate); this.progressReporter?.success(`✓ ${path.basename(templatePath)} template merged successfully`); return { success: true, conflicts, warnings: [], merged: true, backupPath: backup }; } catch (error) { return { success: false, conflicts: [], warnings: [`Failed to merge template ${templatePath}: ${error instanceof Error ? error.message : String(error)}`], merged: false }; } } /** * Custom dependency merging logic */ mergeDependencies(userDeps, migrationDeps, options) { if (!userDeps && !migrationDeps) return {}; if (!userDeps) return migrationDeps; if (!migrationDeps) return userDeps; const merged = { ...userDeps }; Object.entries(migrationDeps).forEach(([pkg, version]) => { if (options.preferUserConfiguration && merged[pkg]) { // Keep user version unless it's a critical Angular dependency if (this.isCriticalAngularDependency(pkg)) { merged[pkg] = version; } } else { merged[pkg] = version; } }); return merged; } /** * Merge npm scripts intelligently */ mergeScripts(userScripts, migrationScripts, options) { if (!userScripts && !migrationScripts) return {}; if (!userScripts) return migrationScripts; if (!migrationScripts) return userScripts; const merged = { ...userScripts }; Object.entries(migrationScripts).forEach(([scriptName, scriptValue]) => { if (options.preferUserConfiguration && merged[scriptName]) { // Keep user script unless it's a build/test script that needs updating if (this.isCriticalScript(scriptName)) { merged[scriptName] = scriptValue; } } else { merged[scriptName] = scriptValue; } }); return merged; } /** * Merge Angular projects configuration */ mergeProjects(userProjects, migrationProjects, options) { if (!userProjects) return migrationProjects; if (!migrationProjects) return userProjects; const merged = { ...userProjects }; Object.entries(migrationProjects).forEach(([projectName, projectConfig]) => { if (merged[projectName]) { // Deep merge project configurations merged[projectName] = deepmerge(merged[projectName], projectConfig); } else { merged[projectName] = projectConfig; } }); return merged; } /** * Merge compiler options with special handling */ mergeCompilerOptions(userOptions, migrationOptions, options) { if (!userOptions) return migrationOptions; if (!migrationOptions) return userOptions; const merged = { ...userOptions }; // Special handling for certain compiler options Object.entries(migrationOptions).forEach(([option, value]) => { if (this.isStrictOption(option) && options.preferUserConfiguration) { // Don't override user's strict settings unless they explicitly want migration if (options.mergeStrategy === 'aggressive') { merged[option] = value; } } else { merged[option] = value; } }); return merged; } /** * Merge imports in TypeScript files */ mergeImports(sourceFile, newImports, conflicts, options) { newImports.forEach(newImport => { const existingImport = sourceFile.getImportDeclarations() .find(imp => imp.getModuleSpecifierValue() === newImport.moduleSpecifier); if (existingImport) { // Merge named imports const existingNamedImports = existingImport.getNamedImports().map(ni => ni.getName()); const newNamedImports = newImport.namedImports || []; const allImports = [...new Set([...existingNamedImports, ...newNamedImports])]; if (allImports.length > existingNamedImports.length) { // Update existing import with merged imports existingImport.removeNamedImports(); existingImport.addNamedImports(allImports); } } else { // Add new import sourceFile.addImportDeclaration({ moduleSpecifier: newImport.moduleSpecifier, namedImports: newImport.namedImports }); } }); } /** * Merge decorators on classes */ mergeDecorators(sourceFile, decoratorChanges, conflicts, options) { decoratorChanges.forEach(change => { const classDecl = sourceFile.getClass(change.className); if (!classDecl) return; const existingDecorator = classDecl.getDecorator(change.decoratorName); if (existingDecorator) { // Merge decorator arguments if (change.mergeArguments && options.preferUserConfiguration) { const existingArgs = existingDecorator.getArguments(); if (existingArgs.length > 0) { // Parse and merge object arguments try { const existingConfig = this.parseDecoratorArgument(existingArgs[0].getText()); const newConfig = change.newConfiguration; const mergedConfig = deepmerge(existingConfig, newConfig); existingDecorator.remove(); classDecl.addDecorator({ name: change.decoratorName, arguments: [JSON.stringify(mergedConfig, null, 2)] }); } catch (error) { // If parsing fails, create conflict conflicts.push({ file: sourceFile.getFilePath(), type: 'code', section: `${change.className}.${change.decoratorName}`, userValue: existingDecorator.getText(), migrationValue: change.newConfiguration, resolution: options.conflictResolution === 'user' ? 'user' : 'migration' }); } } } } else { // Add new decorator classDecl.addDecorator({ name: change.decoratorName, arguments: [JSON.stringify(change.newConfiguration, null, 2)] }); } }); } /** * Merge class members (properties and methods) */ mergeClassMembers(sourceFile, memberChanges, conflicts, options) { memberChanges.forEach(change => { const classDecl = sourceFile.getClass(change.className); if (!classDecl) return; if (change.type === 'property') { const existingProperty = classDecl.getProperty(change.name); if (!existingProperty && !options.preferUserConfiguration) { classDecl.addProperty(change.propertyStructure); } } else if (change.type === 'method') { const existingMethod = classDecl.getMethod(change.name); if (existingMethod && options.preserveUserCode) { // Create conflict - user has custom implementation conflicts.push({ file: sourceFile.getFilePath(), type: 'code', section: `${change.className}.${change.name}()`, userValue: existingMethod.getText(), migrationValue: change.methodStructure, resolution: 'user' // Preserve user code by default }); } else if (!existingMethod) { classDecl.addMethod(change.methodStructure); } } }); } /** * Merge providers and dependency injection configurations */ mergeProviders(sourceFile, providerChanges, conflicts, options) { // Implementation for merging providers in main.ts, app.config.ts, etc. providerChanges.forEach(change => { // Find bootstrapApplication or similar provider arrays const callExpressions = sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression); callExpressions.forEach(callExpr => { if (callExpr.getExpression().getText() === 'bootstrapApplication') { // Merge providers in bootstrapApplication const args = callExpr.getArguments(); if (args.length > 1) { // Second argument contains providers this.mergeProviderArray(args[1], change.providers, options); } } }); }); } /** * Merge control flow syntax in templates */ mergeControlFlow(template, controlFlowChanges, options) { let mergedTemplate = template; const conflicts = []; // Only migrate simple cases if user prefers to keep their code if (options.preserveUserCode) { // Check for complex *ngIf expressions that should be preserved const complexNgIfPattern = /\*ngIf="[^"]*(\|\||&&|\?)[^"]*"/g; const complexMatches = template.match(complexNgIfPattern); if (complexMatches) { // Create conflicts for complex expressions complexMatches.forEach(match => { conflicts.push({ file: 'template', type: 'template', section: 'control-flow', userValue: match, migrationValue: this.convertToNewControlFlow(match), resolution: 'user' // Preserve complex user logic }); }); } // Only migrate simple cases const simpleNgIfPattern = /\*ngIf="([a-zA-Z_$][a-zA-Z0-9_$]*)"/g; mergedTemplate = mergedTemplate.replace(simpleNgIfPattern, '@if ($1) {'); } return { template: mergedTemplate, conflicts }; } /** * Merge directive changes in templates */ mergeDirectives(template, directiveChanges, options) { let mergedTemplate = template; const conflicts = []; // Apply directive changes while preserving user customizations directiveChanges.forEach((change) => { if (options.preserveUserCode && this.hasCustomDirectiveUsage(template, change.directive)) { conflicts.push({ file: 'template', type: 'template', section: change.directive, userValue: this.extractDirectiveUsage(template, change.directive), migrationValue: change.newUsage, resolution: 'user' }); } else { mergedTemplate = mergedTemplate.replace(change.pattern, change.replacement); } }); return { template: mergedTemplate, conflicts }; } // Helper methods async createBackup(filePath) { const backupPath = `${filePath}.backup-${Date.now()}`; await fs.copy(filePath, backupPath); return backupPath; } isCriticalAngularDependency(packageName) { return packageName.startsWith('@angular/') || packageName === 'typescript' || packageName === 'zone.js'; } isCriticalScript(scriptName) { return ['build', 'test', 'lint', 'start'].includes(scriptName); } isStrictOption(option) { return option.includes('strict') || option === 'noImplicitAny' || option === 'noImplicitReturns'; } parseDecoratorArgument(argText) { try { return JSON.parse(argText); } catch { return {}; } } mergeProviderArray(providerArg, newProviders, options) { // Implementation for merging provider arrays // This would involve AST manipulation to add new providers while preserving existing ones } convertToNewControlFlow(oldSyntax) { // Convert *ngIf="complex expression" to @if (complex expression) { } const match = oldSyntax.match(/\*ngIf="([^"]+)"/); return match ? `@if (${match[1]}) { }` : oldSyntax; } hasCustomDirectiveUsage(template, directive) { // Check if directive has custom attributes or complex usage const pattern = new RegExp(`${directive}[^>]*\\[[^\\]]+\\]`, 'g'); return pattern.test(template); } extractDirectiveUsage(template, directive) { const pattern = new RegExp(`<[^>]*${directive}[^>]*>`, 'g'); const matches = template.match(pattern); return matches ? matches[0] : ''; } getDefaultOptions() { return { preserveUserCode: true, preserveComments: true, preferUserConfiguration: true, createBackups: true, mergeStrategy: 'conservative', conflictResolution: 'user' }; } } exports.IntelligentMergeEngine = IntelligentMergeEngine; //# sourceMappingURL=IntelligentMergeEngine.js.map