1
1
import { execa as _execa } from 'execa' ;
2
2
import { EOL } from 'os' ;
3
- import { readPackageJson } from './package-json/package_json.js' ;
3
+ import { PackageJson , readPackageJson } from './package-json/package_json.js' ;
4
4
5
5
export type DependencyRule =
6
6
| {
@@ -22,6 +22,15 @@ type DependencyViolation = {
22
22
dependencyName : string ;
23
23
} ;
24
24
25
+ type DependencyDeclaration = {
26
+ dependentPackageName : string ;
27
+ version : string ;
28
+ } ;
29
+
30
+ type DependencyVersionPredicate = (
31
+ declarations : DependencyDeclaration [ ]
32
+ ) => true | string ;
33
+
25
34
/**
26
35
* Validates dependencies.
27
36
*
@@ -33,6 +42,8 @@ type DependencyViolation = {
33
42
* in order to assert consistency.
34
43
*/
35
44
export class DependenciesValidator {
45
+ private repoPackageJsons : PackageJson [ ] | undefined = undefined ;
46
+ private repoPackageNames : string [ ] | undefined = undefined ;
36
47
/**
37
48
* Creates dependency validator
38
49
* @param packagePaths paths of packages to validate
@@ -61,16 +72,10 @@ export class DependenciesValidator {
61
72
*/
62
73
private async validateDependencyVersionsConsistency ( ) : Promise < void > {
63
74
console . log ( 'Checking dependency versions consistency' ) ;
64
- const packageJsons = await Promise . all (
65
- this . packagePaths . map ( ( packagePath ) => readPackageJson ( packagePath ) )
66
- ) ;
75
+ const packageJsons = await this . getRepoPackageJsons ( ) ;
67
76
68
77
type DependencyVersionsUsage = {
69
- allVersions : Set < string > ;
70
- allDeclarations : Array < {
71
- packageName : string ;
72
- version : string ;
73
- } > ;
78
+ allDeclarations : DependencyDeclaration [ ] ;
74
79
} ;
75
80
76
81
const dependencyVersionsUsages : Record < string , DependencyVersionsUsage > =
@@ -80,23 +85,21 @@ export class DependenciesValidator {
80
85
packageJson . dependencies ,
81
86
packageJson . devDependencies ,
82
87
packageJson . peerDependencies ,
83
- ] . forEach ( ( dependencies ) => {
84
- if ( dependencies ) {
85
- for ( const dependencyName of Object . keys ( dependencies ) ) {
86
- const dependencyVersion = dependencies [ dependencyName ] ;
88
+ ] . forEach ( ( dependency ) => {
89
+ if ( dependency ) {
90
+ for ( const dependencyName of Object . keys ( dependency ) ) {
91
+ const dependencyVersion = dependency [ dependencyName ] ;
87
92
let dependencyVersionsUsage =
88
93
dependencyVersionsUsages [ dependencyName ] ;
89
94
if ( ! dependencyVersionsUsage ) {
90
95
dependencyVersionsUsage = {
91
- allVersions : new Set ( ) ,
92
96
allDeclarations : [ ] ,
93
97
} ;
94
98
dependencyVersionsUsages [ dependencyName ] =
95
99
dependencyVersionsUsage ;
96
100
}
97
- dependencyVersionsUsage . allVersions . add ( dependencyVersion ) ;
98
101
dependencyVersionsUsage . allDeclarations . push ( {
99
- packageName : packageJson . name ,
102
+ dependentPackageName : packageJson . name ,
100
103
version : dependencyVersion ,
101
104
} ) ;
102
105
}
@@ -105,20 +108,18 @@ export class DependenciesValidator {
105
108
}
106
109
107
110
const errors : Array < string > = [ ] ;
108
- for ( const dependencyName of Object . keys ( dependencyVersionsUsages ) ) {
109
- const dependencyVersionUsage = dependencyVersionsUsages [ dependencyName ] ;
110
-
111
- /**
112
- * TODO: This is a temporary fix for execa version inconsistency. Remove this once execa version is fixed.
113
- * Issue: https://github.com/aws-amplify/amplify-backend/issues/962
114
- */
115
- if (
116
- dependencyVersionUsage . allVersions . size > 1 /* Issue-962 Start */ &&
117
- dependencyName !== 'execa' /* Issue-962 End */
118
- ) {
111
+ for ( const [ dependencyName , dependencyVersionUsage ] of Object . entries (
112
+ dependencyVersionsUsages
113
+ ) ) {
114
+ const validationResult = (
115
+ await this . getPackageVersionDeclarationPredicate ( dependencyName )
116
+ ) ( dependencyVersionUsage . allDeclarations ) ;
117
+ if ( typeof validationResult === 'string' ) {
119
118
errors . push (
120
- `Dependency ${ dependencyName } is declared using inconsistent versions ${ JSON . stringify (
121
- dependencyVersionUsage . allDeclarations
119
+ `${ validationResult } ${ EOL } ${ JSON . stringify (
120
+ dependencyVersionUsage . allDeclarations ,
121
+ null ,
122
+ 2
122
123
) } `
123
124
) ;
124
125
}
@@ -127,7 +128,7 @@ export class DependenciesValidator {
127
128
const allLinkedVersions : Set < string > = new Set ( ) ;
128
129
for ( const dependencyName of linkedDependencySpec ) {
129
130
const dependencyVersionUsage = dependencyVersionsUsages [ dependencyName ] ;
130
- dependencyVersionUsage . allVersions . forEach ( ( version ) =>
131
+ dependencyVersionUsage . allDeclarations . forEach ( ( { version } ) =>
131
132
allLinkedVersions . add ( version )
132
133
) ;
133
134
}
@@ -238,4 +239,70 @@ export class DependenciesValidator {
238
239
239
240
return dependencies ;
240
241
}
242
+
243
+ private getPackageVersionDeclarationPredicate = async (
244
+ packageName : string
245
+ ) : Promise < DependencyVersionPredicate > => {
246
+ if ( packageName === 'execa' ) {
247
+ // @aws -amplify/plugin-types can depend on execa@^5.1.1 as a workaround for https://github.com/aws-amplify/amplify-backend/issues/962
248
+ // all other packages must depend on execa@^8.0.1
249
+ // this can be removed once execa is patched
250
+ return ( declarations ) => {
251
+ const validationResult = declarations . every (
252
+ ( { dependentPackageName, version } ) =>
253
+ ( dependentPackageName === '@aws-amplify/plugin-types' &&
254
+ version === '^5.1.1' ) ||
255
+ version === '^8.0.1'
256
+ ) ;
257
+ return (
258
+ validationResult ||
259
+ `${ packageName } dependency declarations must depend on version ^8.0.1 except in @aws-amplify/plugin-types where it must depend on ^5.1.1.`
260
+ ) ;
261
+ } ;
262
+ } else if ( ( await this . getRepoPackageNames ( ) ) . includes ( packageName ) ) {
263
+ // repo packages only need consistent major versions
264
+ return ( declarations ) => {
265
+ if ( declarations . length === 0 ) {
266
+ return true ;
267
+ }
268
+ const baselineMajorVersion = declarations
269
+ . at ( 0 ) !
270
+ . version . split ( '.' )
271
+ . at ( 0 ) ! ;
272
+ const validationResult = declarations . every ( ( { version } ) =>
273
+ version . startsWith ( baselineMajorVersion )
274
+ ) ;
275
+ return (
276
+ validationResult ||
277
+ `${ packageName } dependency declarations must all be on the same major version`
278
+ ) ;
279
+ } ;
280
+ }
281
+ // default behavior for all other packages is that they must be on the same version
282
+ return ( declarations ) => {
283
+ const versionSet = new Set ( declarations . map ( ( { version } ) => version ) ) ;
284
+ return (
285
+ versionSet . size === 1 ||
286
+ `${ packageName } dependency declarations must all the on the same semver range`
287
+ ) ;
288
+ } ;
289
+ } ;
290
+
291
+ private getRepoPackageJsons = async ( ) => {
292
+ if ( ! this . repoPackageJsons ) {
293
+ this . repoPackageJsons = await Promise . all (
294
+ this . packagePaths . map ( ( packagePath ) => readPackageJson ( packagePath ) )
295
+ ) ;
296
+ }
297
+ return this . repoPackageJsons ;
298
+ } ;
299
+
300
+ private getRepoPackageNames = async ( ) => {
301
+ if ( ! this . repoPackageNames ) {
302
+ this . repoPackageNames = ( await this . getRepoPackageJsons ( ) ) . map (
303
+ ( packageJson ) => packageJson . name
304
+ ) ;
305
+ }
306
+ return this . repoPackageNames ;
307
+ } ;
241
308
}
0 commit comments