@@ -24,6 +24,13 @@ import type { InstalledPackage, PackageManager, PackageManifest } from '../../pa
2424import { colors } from '../../utilities/color' ;
2525import { disableVersionCheck } from '../../utilities/environment-options' ;
2626import { assertIsError } from '../../utilities/error' ;
27+ import {
28+ UpdatePlan ,
29+ applyUpdatePlan ,
30+ findPackageJson ,
31+ printUpdateUsageMessage ,
32+ resolveUserUpdatePlan ,
33+ } from './update-resolver' ;
2734import {
2835 checkCLIVersion ,
2936 coerceVersionNumber ,
@@ -32,12 +39,7 @@ import {
3239} from './utilities/cli-version' ;
3340import { ANGULAR_PACKAGES_REGEXP } from './utilities/constants' ;
3441import { checkCleanGit } from './utilities/git' ;
35- import {
36- commitChanges ,
37- executeMigration ,
38- executeMigrations ,
39- executeSchematic ,
40- } from './utilities/migration' ;
42+ import { commitChanges , executeMigration , executeMigrations } from './utilities/migration' ;
4143
4244interface UpdateCommandArgs {
4345 packages ?: string [ ] ;
@@ -54,8 +56,6 @@ interface UpdateCommandArgs {
5456
5557class CommandError extends Error { }
5658
57- const UPDATE_SCHEMATIC_COLLECTION = path . join ( __dirname , 'schematic/collection.json' ) ;
58-
5959export default class UpdateCommandModule extends CommandModule < UpdateCommandArgs > {
6060 override scope = CommandScope . In ;
6161 protected override shouldReportAnalytics = false ;
@@ -244,23 +244,28 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
244244 } ) ;
245245
246246 if ( packages . length === 0 ) {
247- // Show status
248- const { success } = await executeSchematic (
249- workflow ,
250- logger ,
251- UPDATE_SCHEMATIC_COLLECTION ,
252- 'update' ,
253- {
254- force : options . force ,
255- next : options . next ,
256- verbose : options . verbose ,
257- packageManager : packageManager . name ,
258- packages : [ ] ,
259- workspaceRoot : this . context . root ,
260- } ,
261- ) ;
247+ try {
248+ const plan = await resolveUserUpdatePlan (
249+ {
250+ force : options . force ,
251+ next : options . next ,
252+ verbose : options . verbose ,
253+ packageManager : packageManager . name ,
254+ packages : [ ] ,
255+ workspaceRoot : this . context . root ,
256+ } ,
257+ logger ,
258+ ) ;
259+
260+ printUpdateUsageMessage ( plan . packageInfoMap , logger , options . next ) ;
262261
263- return success ? 0 : 1 ;
262+ return 0 ;
263+ } catch ( error ) {
264+ assertIsError ( error ) ;
265+ logger . error ( error . message ) ;
266+
267+ return 1 ;
268+ }
264269 }
265270
266271 return options . migrateOnly
@@ -509,128 +514,113 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
509514 return 0 ;
510515 }
511516
512- const { success } = await executeSchematic (
513- workflow ,
514- logger ,
515- UPDATE_SCHEMATIC_COLLECTION ,
516- 'update' ,
517- {
518- verbose : options . verbose ,
519- force : options . force ,
520- next : options . next ,
521- packageManager : this . context . packageManager . name ,
522- packages : packagesToUpdate ,
523- workspaceRoot : this . context . root ,
524- } ,
525- ) ;
526-
527- if ( success ) {
528- const { root : commandRoot } = this . context ;
529- const ignorePeerDependencies = await shouldForcePackageManager (
530- packageManager ,
517+ let plan : UpdatePlan ;
518+ try {
519+ plan = await resolveUserUpdatePlan (
520+ {
521+ packages : packagesToUpdate ,
522+ force : options . force ,
523+ next : options . next ,
524+ packageManager : packageManager . name ,
525+ verbose : options . verbose ,
526+ workspaceRoot : this . context . root ,
527+ } ,
531528 logger ,
532- options . verbose ,
533529 ) ;
534- const tasks = new Listr ( [
535- {
536- title : 'Cleaning node modules directory' ,
537- async task ( _ , task ) {
538- try {
539- await fs . rm ( path . join ( commandRoot , 'node_modules' ) , {
540- force : true ,
541- recursive : true ,
542- maxRetries : 3 ,
543- } ) ;
544- } catch ( e ) {
545- assertIsError ( e ) ;
546- if ( e . code === 'ENOENT' ) {
547- task . skip ( 'Cleaning not required. Node modules directory not found.' ) ;
548- }
530+ } catch ( error ) {
531+ assertIsError ( error ) ;
532+ logger . error ( error . message ) ;
533+
534+ return 1 ;
535+ }
536+
537+ try {
538+ await applyUpdatePlan ( this . context . root , plan , logger ) ;
539+ } catch ( error ) {
540+ assertIsError ( error ) ;
541+ logger . error ( `Error updating package.json: ${ error . message } ` ) ;
542+
543+ return 1 ;
544+ }
545+
546+ const { root : commandRoot } = this . context ;
547+ const ignorePeerDependencies = await shouldForcePackageManager (
548+ packageManager ,
549+ logger ,
550+ options . verbose ,
551+ ) ;
552+ const tasks = new Listr ( [
553+ {
554+ title : 'Cleaning node modules directory' ,
555+ async task ( _ , task ) {
556+ try {
557+ await fs . rm ( path . join ( commandRoot , 'node_modules' ) , {
558+ force : true ,
559+ recursive : true ,
560+ maxRetries : 3 ,
561+ } ) ;
562+ } catch ( e ) {
563+ assertIsError ( e ) ;
564+ if ( e . code === 'ENOENT' ) {
565+ task . skip ( 'Cleaning not required. Node modules directory not found.' ) ;
549566 }
550- } ,
567+ }
551568 } ,
552- {
553- title : 'Installing packages' ,
554- async task ( ) {
555- try {
556- await packageManager . install ( {
557- ignorePeerDependencies ,
558- } ) ;
559- } catch ( e ) {
560- throw new CommandError ( 'Unable to install packages' ) ;
561- }
562- } ,
569+ } ,
570+ {
571+ title : 'Installing packages' ,
572+ async task ( ) {
573+ try {
574+ await packageManager . install ( {
575+ ignorePeerDependencies ,
576+ } ) ;
577+ } catch ( e ) {
578+ throw new CommandError ( 'Unable to install packages' ) ;
579+ }
563580 } ,
564- ] ) ;
565- try {
566- await tasks . run ( ) ;
567- } catch ( e ) {
568- if ( e instanceof CommandError ) {
569- return 1 ;
570- }
571-
572- throw e ;
581+ } ,
582+ ] ) ;
583+ try {
584+ await tasks . run ( ) ;
585+ // Clear Node's module resolution path cache to prevent stale lookups
586+ // when resolving migration package paths.
587+ const Module = require ( 'node:module' ) ;
588+ if ( Module && Module . _pathCache ) {
589+ Module . _pathCache = Object . create ( null ) ;
573590 }
591+ } catch ( e ) {
592+ if ( e instanceof CommandError ) {
593+ return 1 ;
594+ }
595+
596+ throw e ;
574597 }
575598
576- if ( success && options . createCommits ) {
599+ if ( options . createCommits ) {
577600 if (
578601 ! commitChanges ( logger , `Angular CLI update for packages - ${ packagesToUpdate . join ( ', ' ) } ` )
579602 ) {
580603 return 1 ;
581604 }
582605 }
583606
584- // This is a temporary workaround to allow data to be passed back from the update schematic
585- // eslint-disable-next-line @typescript-eslint/no-explicit-any
586- const migrations = ( global as any ) . externalMigrations as {
587- package : string ;
588- collection : string ;
589- from : string ;
590- to : string ;
591- } [ ] ;
592-
593- if ( success && migrations ) {
594- const rootRequire = createRequire ( this . context . root + '/' ) ;
607+ const migrations = plan . migrationsToRun ;
608+
609+ if ( migrations ) {
595610 for ( const migration of migrations ) {
596611 // Resolve the package from the workspace root, as otherwise it will be resolved from the temp
597612 // installed CLI version.
598- let packagePath ;
599- logVerbose (
600- `Resolving migration package '${ migration . package } ' from '${ this . context . root } '...` ,
601- ) ;
602- try {
603- try {
604- packagePath = path . dirname (
605- // This may fail if the `package.json` is not exported as an entry point
606- rootRequire . resolve ( path . join ( migration . package , 'package.json' ) ) ,
607- ) ;
608- } catch ( e ) {
609- assertIsError ( e ) ;
610- if ( e . code === 'MODULE_NOT_FOUND' ) {
611- // Fallback to trying to resolve the package's main entry point
612- packagePath = rootRequire . resolve ( migration . package ) ;
613- } else {
614- throw e ;
615- }
616- }
617- } catch ( e ) {
618- assertIsError ( e ) ;
619- if ( e . code === 'MODULE_NOT_FOUND' ) {
620- logVerbose ( e . toString ( ) ) ;
621- logger . error (
622- `Migrations for package (${ migration . package } ) were not found.` +
623- ' The package could not be found in the workspace.' ,
624- ) ;
625- } else {
626- logger . error (
627- `Unable to resolve migrations for package (${ migration . package } ). [${ e . message } ]` ,
628- ) ;
629- }
613+ const packageJsonPath = findPackageJson ( this . context . root , migration . package ) ;
614+ if ( ! packageJsonPath ) {
615+ logger . error (
616+ `Migrations for package (${ migration . package } ) were not found.` +
617+ ' The package could not be found in the workspace.' ,
618+ ) ;
630619
631620 return 1 ;
632621 }
633622
623+ const packagePath = path . dirname ( packageJsonPath ) ;
634624 let migrations ;
635625
636626 // Check if it is a package-local location
@@ -673,7 +663,7 @@ export default class UpdateCommandModule extends CommandModule<UpdateCommandArgs
673663 }
674664 }
675665
676- return success ? 0 : 1 ;
666+ return 0 ;
677667 }
678668}
679669
@@ -686,14 +676,3 @@ async function readPackageManifest(manifestPath: string): Promise<PackageManifes
686676 return undefined ;
687677 }
688678}
689-
690- function findPackageJson ( workspaceDir : string , packageName : string ) : string | undefined {
691- try {
692- const projectRequire = createRequire ( path . join ( workspaceDir , 'package.json' ) ) ;
693- const packageJsonPath = projectRequire . resolve ( `${ packageName } /package.json` ) ;
694-
695- return packageJsonPath ;
696- } catch {
697- return undefined ;
698- }
699- }
0 commit comments