@@ -13,6 +13,7 @@ import { execSync } from 'child_process';
13
13
import * as fs from 'fs' ;
14
14
import * as path from 'path' ;
15
15
import * as semver from 'semver' ;
16
+ import { VERSION } from '../lib/cli' ;
16
17
import { PackageManager } from '../lib/config/schema' ;
17
18
import { Command } from '../models/command' ;
18
19
import { Arguments } from '../models/interface' ;
@@ -38,11 +39,6 @@ const pickManifest = require('npm-pick-manifest') as (
38
39
39
40
const oldConfigFileNames = [ '.angular-cli.json' , 'angular-cli.json' ] ;
40
41
41
- const NG_VERSION_9_POST_MSG = colors . cyan (
42
- '\nYour project has been updated to Angular version 9!\n' +
43
- 'For more info, please see: https://v9.angular.io/guide/updating-to-version-9' ,
44
- ) ;
45
-
46
42
/**
47
43
* Disable CLI version mismatch checks and forces usage of the invoked CLI
48
44
* instead of invoking the local installed version.
@@ -53,6 +49,8 @@ const disableVersionCheck =
53
49
disableVersionCheckEnv !== '0' &&
54
50
disableVersionCheckEnv . toLowerCase ( ) !== 'false' ;
55
51
52
+ const ANGULAR_PACKAGES_REGEXP = / ^ @ (?: a n g u l a r | n g u n i v e r s a l ) \/ / ;
53
+
56
54
export class UpdateCommand extends Command < UpdateCommandSchema > {
57
55
public readonly allowMissingWorkspace = true ;
58
56
private workflow ! : NodeWorkflow ;
@@ -84,7 +82,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
84
82
let logs : string [ ] = [ ] ;
85
83
const files = new Set < string > ( ) ;
86
84
87
- const reporterSubscription = this . workflow . reporter . subscribe ( event => {
85
+ const reporterSubscription = this . workflow . reporter . subscribe ( ( event ) => {
88
86
// Strip leading slash to prevent confusion.
89
87
const eventPath = event . path . startsWith ( '/' ) ? event . path . substr ( 1 ) : event . path ;
90
88
@@ -114,11 +112,11 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
114
112
}
115
113
} ) ;
116
114
117
- const lifecycleSubscription = this . workflow . lifeCycle . subscribe ( event => {
115
+ const lifecycleSubscription = this . workflow . lifeCycle . subscribe ( ( event ) => {
118
116
if ( event . kind == 'end' || event . kind == 'post-tasks-start' ) {
119
117
if ( ! error ) {
120
118
// Output the logging queue, no error happened.
121
- logs . forEach ( log => this . logger . info ( log ) ) ;
119
+ logs . forEach ( ( log ) => this . logger . info ( log ) ) ;
122
120
logs = [ ] ;
123
121
}
124
122
}
@@ -141,12 +139,14 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
141
139
return { success : ! error , files } ;
142
140
} catch ( e ) {
143
141
if ( e instanceof UnsuccessfulWorkflowExecution ) {
144
- this . logger . error ( `${ colors . symbols . cross } Migration failed. See above for further details.\n` ) ;
142
+ this . logger . error (
143
+ `${ colors . symbols . cross } Migration failed. See above for further details.\n` ,
144
+ ) ;
145
145
} else {
146
146
const logPath = writeErrorToLogFile ( e ) ;
147
147
this . logger . fatal (
148
148
`${ colors . symbols . cross } Migration failed: ${ e . message } \n` +
149
- ` See "${ logPath } " for further details.\n` ,
149
+ ` See "${ logPath } " for further details.\n` ,
150
150
) ;
151
151
}
152
152
@@ -164,7 +164,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
164
164
commit ?: boolean ,
165
165
) : Promise < boolean > {
166
166
const collection = this . workflow . engine . createCollection ( collectionPath ) ;
167
- const name = collection . listSchematicNames ( ) . find ( name => name === migrationName ) ;
167
+ const name = collection . listSchematicNames ( ) . find ( ( name ) => name === migrationName ) ;
168
168
if ( ! name ) {
169
169
this . logger . error ( `Cannot find migration '${ migrationName } ' in '${ packageName } '.` ) ;
170
170
@@ -213,20 +213,20 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
213
213
return true ;
214
214
}
215
215
216
- this . logger . info (
217
- colors . cyan ( `** Executing migrations of package '${ packageName } ' **\n` ) ,
218
- ) ;
216
+ this . logger . info ( colors . cyan ( `** Executing migrations of package '${ packageName } ' **\n` ) ) ;
219
217
220
218
return this . executePackageMigrations ( migrations , packageName , commit ) ;
221
219
}
222
220
223
221
private async executePackageMigrations (
224
- migrations : Iterable < { name : string ; description : string ; collection : { name : string } } > ,
222
+ migrations : Iterable < { name : string ; description : string ; collection : { name : string } } > ,
225
223
packageName : string ,
226
224
commit = false ,
227
225
) : Promise < boolean > {
228
226
for ( const migration of migrations ) {
229
- this . logger . info ( `${ colors . symbols . pointer } ${ migration . description . replace ( / \. / g, '.\n ' ) } ` ) ;
227
+ this . logger . info (
228
+ `${ colors . symbols . pointer } ${ migration . description . replace ( / \. / g, '.\n ' ) } ` ,
229
+ ) ;
230
230
231
231
const result = await this . executeSchematic ( migration . collection . name , migration . name ) ;
232
232
if ( ! result . success ) {
@@ -280,19 +280,27 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
280
280
throw e ;
281
281
}
282
282
283
- // Check if the current installed CLI version is older than the latest version.
284
- if ( ! disableVersionCheck && await this . checkCLILatestVersion ( options . verbose , options . next ) ) {
285
- this . logger . warn (
286
- `The installed local Angular CLI version is older than the latest ${ options . next ? 'pre-release' : 'stable' } version.\n` +
287
- 'Installing a temporary version to perform the update.' ,
283
+ // Check if the current installed CLI version is older than the latest compatible version.
284
+ if ( ! disableVersionCheck ) {
285
+ const cliVersionToInstall = await this . checkCLIVersion (
286
+ options [ '--' ] ,
287
+ options . verbose ,
288
+ options . next ,
288
289
) ;
289
290
290
- return runTempPackageBin (
291
- `@angular/cli@${ options . next ? 'next' : 'latest' } ` ,
292
- this . logger ,
293
- this . packageManager ,
294
- process . argv . slice ( 2 ) ,
295
- ) ;
291
+ if ( cliVersionToInstall ) {
292
+ this . logger . warn (
293
+ 'The installed Angular CLI version is outdated.\n' +
294
+ `Installing a temporary Angular CLI versioned ${ cliVersionToInstall } to perform the update.` ,
295
+ ) ;
296
+
297
+ return runTempPackageBin (
298
+ `@angular/cli@${ cliVersionToInstall } ` ,
299
+ this . logger ,
300
+ this . packageManager ,
301
+ process . argv . slice ( 2 ) ,
302
+ ) ;
303
+ }
296
304
}
297
305
298
306
const packages : PackageIdentifier [ ] = [ ] ;
@@ -307,7 +315,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
307
315
return 1 ;
308
316
}
309
317
310
- if ( packages . some ( v => v . name === packageIdentifier . name ) ) {
318
+ if ( packages . some ( ( v ) => v . name === packageIdentifier . name ) ) {
311
319
this . logger . error ( `Duplicate package '${ packageIdentifier . name } ' specified.` ) ;
312
320
313
321
return 1 ;
@@ -410,7 +418,9 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
410
418
411
419
if ( options . migrateOnly ) {
412
420
if ( ! options . from && typeof options . migrateOnly !== 'string' ) {
413
- this . logger . error ( '"from" option is required when using the "migrate-only" option without a migration name.' ) ;
421
+ this . logger . error (
422
+ '"from" option is required when using the "migrate-only" option without a migration name.' ,
423
+ ) ;
414
424
415
425
return 1 ;
416
426
} else if ( packages . length !== 1 ) {
@@ -436,7 +446,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
436
446
// Allow running migrations on transitively installed dependencies
437
447
// There can technically be nested multiple versions
438
448
// TODO: If multiple, this should find all versions and ask which one to use
439
- const child = packageTree . children . find ( c => c . name === packageName ) ;
449
+ const child = packageTree . children . find ( ( c ) => c . name === packageName ) ;
440
450
if ( child ) {
441
451
packageNode = child ;
442
452
}
@@ -471,8 +481,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
471
481
472
482
if ( migrations . startsWith ( '../' ) ) {
473
483
this . logger . error (
474
- 'Package contains an invalid migrations field. ' +
475
- 'Paths outside the package root are not permitted.' ,
484
+ 'Package contains an invalid migrations field. Paths outside the package root are not permitted.' ,
476
485
) ;
477
486
478
487
return 1 ;
@@ -498,14 +507,15 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
498
507
}
499
508
}
500
509
501
- let success = false ;
502
510
if ( typeof options . migrateOnly == 'string' ) {
503
- success = await this . executeMigration (
511
+ await this . executeMigration (
504
512
packageName ,
505
513
migrations ,
506
514
options . migrateOnly ,
507
515
options . createCommits ,
508
516
) ;
517
+
518
+ return 0 ;
509
519
} else {
510
520
const from = coerceVersionNumber ( options . from ) ;
511
521
if ( ! from ) {
@@ -518,28 +528,15 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
518
528
'>' + from + ' <=' + ( options . to || packageNode . package . version ) ,
519
529
) ;
520
530
521
- success = await this . executeMigrations (
531
+ await this . executeMigrations (
522
532
packageName ,
523
533
migrations ,
524
534
migrationRange ,
525
535
options . createCommits ,
526
536
) ;
527
- }
528
-
529
- if ( success ) {
530
- if (
531
- packageName === '@angular/core'
532
- && options . from
533
- && + options . from . split ( '.' ) [ 0 ] < 9
534
- && ( options . to || packageNode . package . version ) . split ( '.' ) [ 0 ] === '9'
535
- ) {
536
- this . logger . info ( NG_VERSION_9_POST_MSG ) ;
537
- }
538
537
539
538
return 0 ;
540
539
}
541
-
542
- return 1 ;
543
540
}
544
541
545
542
const requests : {
@@ -634,7 +631,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
634
631
continue ;
635
632
}
636
633
637
- if ( node . package && / ^ @ (?: a n g u l a r | n g u n i v e r s a l ) \/ / . test ( node . package . name ) ) {
634
+ if ( node . package && ANGULAR_PACKAGES_REGEXP . test ( node . package . name ) ) {
638
635
const { name, version } = node . package ;
639
636
const toBeInstalledMajorVersion = + manifest . version . split ( '.' ) [ 0 ] ;
640
637
const currentMajorVersion = + version . split ( '.' ) [ 0 ] ;
@@ -681,7 +678,8 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
681
678
682
679
if ( success && options . createCommits ) {
683
680
const committed = this . commit (
684
- `Angular CLI update for packages - ${ packagesToUpdate . join ( ', ' ) } ` ) ;
681
+ `Angular CLI update for packages - ${ packagesToUpdate . join ( ', ' ) } ` ,
682
+ ) ;
685
683
if ( ! committed ) {
686
684
return 1 ;
687
685
}
@@ -711,10 +709,6 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
711
709
return 0 ;
712
710
}
713
711
}
714
-
715
- if ( migrations . some ( m => m . package === '@angular/core' && m . to . split ( '.' ) [ 0 ] === '9' && + m . from . split ( '.' ) [ 0 ] < 9 ) ) {
716
- this . logger . info ( NG_VERSION_9_POST_MSG ) ;
717
- }
718
712
}
719
713
720
714
return success ? 0 : 1 ;
@@ -744,8 +738,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
744
738
try {
745
739
createCommit ( message ) ;
746
740
} catch ( err ) {
747
- this . logger . error (
748
- `Failed to commit update (${ message } ):\n${ err . stderr } ` ) ;
741
+ this . logger . error ( `Failed to commit update (${ message } ):\n${ err . stderr } ` ) ;
749
742
750
743
return false ;
751
744
}
@@ -754,8 +747,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
754
747
const hash = findCurrentGitSha ( ) ;
755
748
const shortMessage = message . split ( '\n' ) [ 0 ] ;
756
749
if ( hash ) {
757
- this . logger . info ( ` Committed migration step (${ getShortHash ( hash ) } ): ${
758
- shortMessage } .`) ;
750
+ this . logger . info ( ` Committed migration step (${ getShortHash ( hash ) } ): ${ shortMessage } .` ) ;
759
751
} else {
760
752
// Commit was successful, but reading the hash was not. Something weird happened,
761
753
// but nothing that would stop the update. Just log the weirdness and continue.
@@ -768,7 +760,10 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
768
760
769
761
private checkCleanGit ( ) : boolean {
770
762
try {
771
- const topLevel = execSync ( 'git rev-parse --show-toplevel' , { encoding : 'utf8' , stdio : 'pipe' } ) ;
763
+ const topLevel = execSync ( 'git rev-parse --show-toplevel' , {
764
+ encoding : 'utf8' ,
765
+ stdio : 'pipe' ,
766
+ } ) ;
772
767
const result = execSync ( 'git status --porcelain' , { encoding : 'utf8' , stdio : 'pipe' } ) ;
773
768
if ( result . trim ( ) . length === 0 ) {
774
769
return true ;
@@ -791,22 +786,55 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
791
786
}
792
787
793
788
/**
794
- * Checks if the current installed CLI version is older than the latest version.
795
- * @returns `true` when the installed version is older.
796
- */
797
- private async checkCLILatestVersion ( verbose = false , next = false ) : Promise < boolean > {
798
- const { version : installedCLIVersion } = require ( '../package.json' ) ;
799
-
800
- const LatestCLIManifest = await fetchPackageManifest (
801
- `@angular/cli@${ next ? 'next' : 'latest' } ` ,
789
+ * Checks if the current installed CLI version is older or newer than a compatible version.
790
+ * @returns the version to install or null when there is no update to install.
791
+ */
792
+ private async checkCLIVersion (
793
+ packagesToUpdate : string [ ] | undefined ,
794
+ verbose = false ,
795
+ next = false ,
796
+ ) : Promise < string | null > {
797
+ const { version } = await fetchPackageManifest (
798
+ `@angular/cli@${ this . getCLIUpdateRunnerVersion ( packagesToUpdate , next ) } ` ,
802
799
this . logger ,
803
800
{
804
801
verbose,
805
802
usingYarn : this . packageManager === PackageManager . Yarn ,
806
803
} ,
807
804
) ;
808
805
809
- return semver . lt ( installedCLIVersion , LatestCLIManifest . version ) ;
806
+ return VERSION . full === version ? null : version ;
807
+ }
808
+
809
+ private getCLIUpdateRunnerVersion (
810
+ packagesToUpdate : string [ ] | undefined ,
811
+ next : boolean ,
812
+ ) : string | number {
813
+ if ( next ) {
814
+ return 'next' ;
815
+ }
816
+
817
+ const updatingAngularPackage = packagesToUpdate ?. find ( ( r ) => ANGULAR_PACKAGES_REGEXP . test ( r ) ) ;
818
+ if ( updatingAngularPackage ) {
819
+ // If we are updating any Angular package we can update the CLI to the target version because
820
+ // migrations for @angular /core@13 can be executed using Angular/cli@13.
821
+ // This is same behaviour as `npx @angular/cli@13 update @angular/core@13`.
822
+
823
+ // `@angular/cli@13` -> ['', 'angular/cli', '13']
824
+ // `@angular/cli` -> ['', 'angular/cli']
825
+ const tempVersion = coerceVersionNumber ( updatingAngularPackage . split ( '@' ) [ 2 ] ) ;
826
+
827
+ return semver . parse ( tempVersion ) ?. major ?? 'latest' ;
828
+ }
829
+
830
+ // When not updating an Angular package we cannot determine which schematic runtime the migration should to be executed in.
831
+ // Typically, we can assume that the `@angular/cli` was updated previously.
832
+ // Example: Angular official packages are typically updated prior to NGRX etc...
833
+ // Therefore, we only update to the latest patch version of the installed major version of the Angular CLI.
834
+
835
+ // This is important because we might end up in a scenario where locally Angular v12 is installed, updating NGRX from 11 to 12.
836
+ // We end up using Angular ClI v13 to run the migrations if we run the migrations using the CLI installed major version + 1 logic.
837
+ return VERSION . major ;
810
838
}
811
839
}
812
840
@@ -839,7 +867,7 @@ function createCommit(message: string) {
839
867
*/
840
868
function findCurrentGitSha ( ) : string | null {
841
869
try {
842
- const hash = execSync ( 'git rev-parse HEAD' , { encoding : 'utf8' , stdio : 'pipe' } ) ;
870
+ const hash = execSync ( 'git rev-parse HEAD' , { encoding : 'utf8' , stdio : 'pipe' } ) ;
843
871
844
872
return hash . trim ( ) ;
845
873
} catch {
0 commit comments