@@ -35,6 +35,73 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
35
35
return { } ;
36
36
}
37
37
38
+ async executeMigrations (
39
+ packageName : string ,
40
+ collectionPath : string ,
41
+ range : semver . Range ,
42
+ commit = false ,
43
+ ) {
44
+ const collection = this . getEngine ( ) . createCollection ( collectionPath ) ;
45
+
46
+ const migrations = [ ] ;
47
+ for ( const name of collection . listSchematicNames ( ) ) {
48
+ const schematic = this . getEngine ( ) . createSchematic ( name , collection ) ;
49
+ const description = schematic . description as typeof schematic . description & {
50
+ version ?: string ;
51
+ } ;
52
+ if ( ! description . version ) {
53
+ continue ;
54
+ }
55
+
56
+ if ( semver . satisfies ( description . version , range , { includePrerelease : true } ) ) {
57
+ migrations . push ( description as typeof schematic . description & { version : string } ) ;
58
+ }
59
+ }
60
+
61
+ if ( migrations . length === 0 ) {
62
+ return true ;
63
+ }
64
+
65
+ const startingGitSha = this . findCurrentGitSha ( ) ;
66
+
67
+ migrations . sort ( ( a , b ) => semver . compare ( a . version , b . version ) || a . name . localeCompare ( b . name ) ) ;
68
+
69
+ for ( const migration of migrations ) {
70
+ this . logger . info (
71
+ `** Executing migrations for version ${ migration . version } of package '${ packageName } ' **` ,
72
+ ) ;
73
+
74
+ // TODO: run the schematic directly; most of the logic in the following is unneeded
75
+ const result = await this . runSchematic ( {
76
+ collectionName : migration . collection . name ,
77
+ schematicName : migration . name ,
78
+ force : false ,
79
+ showNothingDone : false ,
80
+ } ) ;
81
+
82
+ // 1 = failure
83
+ if ( result === 1 ) {
84
+ if ( startingGitSha !== null ) {
85
+ const currentGitSha = this . findCurrentGitSha ( ) ;
86
+ if ( currentGitSha !== startingGitSha ) {
87
+ this . logger . warn ( `git HEAD was at ${ startingGitSha } before migrations.` ) ;
88
+ }
89
+ }
90
+
91
+ return false ;
92
+ }
93
+
94
+ // Commit migration
95
+ if ( commit ) {
96
+ let message = `migrate workspace for ${ packageName } @${ migration . version } ` ;
97
+ if ( migration . description ) {
98
+ message += '\n' + migration . description ;
99
+ }
100
+ this . createCommit ( [ ] , message ) ;
101
+ }
102
+ }
103
+ }
104
+
38
105
// tslint:disable-next-line:no-big-function
39
106
async run ( options : UpdateCommandSchema & Arguments ) {
40
107
const packages : PackageIdentifier [ ] = [ ] ;
@@ -112,9 +179,9 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
112
179
this . workspace . configFile &&
113
180
oldConfigFileNames . includes ( this . workspace . configFile )
114
181
) {
115
- options . migrateOnly = true ;
116
- options . from = '1.0.0' ;
117
- }
182
+ options . migrateOnly = true ;
183
+ options . from = '1.0.0' ;
184
+ }
118
185
119
186
this . logger . info ( 'Collecting installed dependencies...' ) ;
120
187
@@ -153,6 +220,13 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
153
220
return 1 ;
154
221
}
155
222
223
+ const from = coerceVersionNumber ( options . from ) ;
224
+ if ( ! from ) {
225
+ this . logger . error ( `"from" value [${ options . from } ] is not a valid version.` ) ;
226
+
227
+ return 1 ;
228
+ }
229
+
156
230
if ( options . next ) {
157
231
this . logger . warn ( '"next" option has no effect when using "migrate-only" option.' ) ;
158
232
}
@@ -230,20 +304,18 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
230
304
}
231
305
}
232
306
233
- return this . runSchematic ( {
234
- collectionName : '@schematics/update' ,
235
- schematicName : 'migrate' ,
236
- dryRun : ! ! options . dryRun ,
237
- force : false ,
238
- showNothingDone : false ,
239
- additionalOptions : {
240
- package : packageName ,
241
- collection : migrations ,
242
- from : options . from ,
243
- verbose : options . verbose || false ,
244
- to : options . to || packageNode . package . version ,
245
- } ,
246
- } ) ;
307
+ const migrationRange = new semver . Range (
308
+ '>' + from + ' <=' + ( options . to || packageNode . package . version ) ,
309
+ ) ;
310
+
311
+ const result = await this . executeMigrations (
312
+ packageName ,
313
+ migrations ,
314
+ migrationRange ,
315
+ ! options . skipCommits ,
316
+ ) ;
317
+
318
+ return result ? 1 : 0 ;
247
319
}
248
320
249
321
const requests : {
@@ -287,7 +359,9 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
287
359
try {
288
360
// Metadata requests are internally cached; multiple requests for same name
289
361
// does not result in additional network traffic
290
- metadata = await fetchPackageMetadata ( packageName , this . logger , { verbose : options . verbose } ) ;
362
+ metadata = await fetchPackageMetadata ( packageName , this . logger , {
363
+ verbose : options . verbose ,
364
+ } ) ;
291
365
} catch ( e ) {
292
366
this . logger . error ( `Error fetching metadata for '${ packageName } ': ` + e . message ) ;
293
367
@@ -366,9 +440,46 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
366
440
return false ;
367
441
}
368
442
}
369
-
370
- } catch { }
443
+ } catch { }
371
444
372
445
return true ;
373
446
}
447
+
448
+ createCommit ( files : string [ ] , message : string ) {
449
+ try {
450
+ execSync ( 'git add -A ' + files . join ( ' ' ) , { encoding : 'utf8' , stdio : 'pipe' } ) ;
451
+
452
+ execSync ( `git commit -m "${ message } "` , { encoding : 'utf8' , stdio : 'pipe' } ) ;
453
+ } catch ( error ) { }
454
+ }
455
+
456
+ findCurrentGitSha ( ) : string | null {
457
+ try {
458
+ const result = execSync ( 'git rev-parse HEAD' , { encoding : 'utf8' , stdio : 'pipe' } ) ;
459
+
460
+ return result . trim ( ) ;
461
+ } catch {
462
+ return null ;
463
+ }
464
+ }
465
+ }
466
+
467
+ function coerceVersionNumber ( version : string ) : string | null {
468
+ if ( ! version . match ( / ^ \d { 1 , 30 } \. \d { 1 , 30 } \. \d { 1 , 30 } / ) ) {
469
+ const match = version . match ( / ^ \d { 1 , 30 } ( \. \d { 1 , 30 } ) * / ) ;
470
+
471
+ if ( ! match ) {
472
+ return null ;
473
+ }
474
+
475
+ if ( ! match [ 1 ] ) {
476
+ version = version . substr ( 0 , match [ 0 ] . length ) + '.0.0' + version . substr ( match [ 0 ] . length ) ;
477
+ } else if ( ! match [ 2 ] ) {
478
+ version = version . substr ( 0 , match [ 0 ] . length ) + '.0' + version . substr ( match [ 0 ] . length ) ;
479
+ } else {
480
+ return null ;
481
+ }
482
+ }
483
+
484
+ return semver . valid ( version ) ;
374
485
}
0 commit comments