|
1 | | -/* eslint-disable no-console */ |
2 | | -/** |
3 | | - * External dependencies |
4 | | - */ |
5 | | -const fs = require( 'fs' ); |
6 | | -const spawn = require( 'cross-spawn' ); |
7 | | -const { zip, uniq, identity, groupBy } = require( 'lodash' ); |
8 | | - |
9 | | -/** |
10 | | - * Constants |
11 | | - */ |
12 | | -const WORDPRESS_PACKAGES_PREFIX = '@wordpress/'; |
13 | | -const { getArgFromCLI } = require( `../../node_modules/@wordpress/scripts/utils` ); |
14 | | -const distTag = getArgFromCLI( '--dist-tag' ) || 'latest'; |
15 | | - |
16 | | -/** |
17 | | - * The main function of this task. |
18 | | - * |
19 | | - * It installs any missing WordPress packages, and updates the |
20 | | - * mismatched dependencies versions, e.g. it would detect that Gutenberg |
21 | | - * updated react from 16.0.4 to 17.0.2 and install the latter. |
22 | | - */ |
23 | | -function main() { |
24 | | - const initialPackageJSON = readJSONFile( `package.json` ); |
25 | | - |
26 | | - // Install any missing WordPress packages: |
27 | | - const missingWordPressPackages = getMissingWordPressPackages(); |
28 | | - if ( missingWordPressPackages.length ) { |
29 | | - console.log( "The following @wordpress dependencies are missing: " ); |
30 | | - console.log( missingWordPressPackages ); |
31 | | - console.log( "Installing via npm..." ); |
32 | | - installPackages( missingWordPressPackages.map( name => [name, distTag] ) ); |
33 | | - } |
34 | | - |
35 | | - // Update any outdated non-WordPress packages: |
36 | | - const versionMismatches = getMismatchedNonWordPressDependencies(); |
37 | | - if ( versionMismatches.length ) { |
38 | | - console.log( "The following dependencies are outdated: " ); |
39 | | - console.log( versionMismatches ); |
40 | | - console.log( "Updating via npm..." ); |
41 | | - const requiredPackages = versionMismatches.map( ( { name, required } ) => [name, required] ); |
42 | | - installPackages( requiredPackages ); |
43 | | - } |
44 | | - |
45 | | - const finalPackageJSON = readJSONFile( "package.json" ); |
46 | | - outputPackageDiffReport( |
47 | | - getPackageVersionDiff( initialPackageJSON, finalPackageJSON ), |
48 | | - ); |
49 | | - process.exit( 0 ); |
50 | | -} |
51 | | - |
52 | | -/** |
53 | | - * @param {string} fileName File to read. |
54 | | - * @return {Object} Parsed data. |
55 | | - */ |
56 | | -function readJSONFile( fileName ) { |
57 | | - const data = fs.readFileSync( fileName, 'utf8' ); |
58 | | - return JSON.parse( data ); |
59 | | -} |
60 | | - |
61 | | -/** |
62 | | - * Spawns npm install --save. |
63 | | - * |
64 | | - * @param {Array} packages List of tuples [packageName, version] to install. |
65 | | - * @return {string} CLI output. |
66 | | - */ |
67 | | -function installPackages( packages ) { |
68 | | - const packagesWithVersion = packages.map( |
69 | | - ( [packageName, version] ) => `${ packageName }@${ version }`, |
70 | | - ); |
71 | | - return spawn.sync( 'npm', ['install', ...packagesWithVersion, '--save'], { |
72 | | - stdio: 'inherit', |
73 | | - } ); |
74 | | -} |
75 | | - |
76 | | -/** |
77 | | - * Computes which @wordpress packages are required by the Gutenberg |
78 | | - * dependencies that are missing from WordPress package.json. |
79 | | - * |
80 | | - * @return {Array} List of tuples [packageName, version]. |
81 | | - */ |
82 | | -function getMissingWordPressPackages() { |
83 | | - const perPackageDeps = getPerPackageDeps(); |
84 | | - const currentPackages = perPackageDeps.map( ( [name] ) => name ); |
85 | | - |
86 | | - const requiredWpPackages = uniq( perPackageDeps |
87 | | - // Capture the @wordpress dependencies of our dependencies into a flat list. |
88 | | - .flatMap( ( [, dependencies] ) => getWordPressPackages( { dependencies } ) ) |
89 | | - .sort(), |
90 | | - ); |
91 | | - |
92 | | - return requiredWpPackages.filter( |
93 | | - packageName => !currentPackages.includes( packageName ) ); |
94 | | -} |
95 | | - |
96 | | -/** |
97 | | - * Computes which third party packages are required by the @wordpress |
98 | | - * packages, but not by the WordPress repo itself. This includes |
99 | | - * both packages that are missing from package.json and any version |
100 | | - * mismatches. |
101 | | - * |
102 | | - * @return {Array} List of objects {name, required, actual} describing version mismatches. |
103 | | - */ |
104 | | -function getMismatchedNonWordPressDependencies() { |
105 | | - // Get the installed dependencies from package-lock.json |
106 | | - const currentPackageJSON = readJSONFile( "package.json" ); |
107 | | - const currentPackages = getWordPressPackages( currentPackageJSON ); |
108 | | - |
109 | | - const packageLock = readJSONFile( "package-lock.json" ); |
110 | | - const versionConflicts = Object.entries( packageLock.packages[''].dependencies ) |
111 | | - .filter( ( [packageName] ) => currentPackages.includes( packageName ) ) |
112 | | - .flatMap( ( [, { dependencies }] ) => Object.entries( dependencies || {} ) ) |
113 | | - .filter( identity ) |
114 | | - .map( ( [name, { version }] ) => ( { |
115 | | - name, |
116 | | - required: version, |
117 | | - actual: packageLock.dependencies[ name ].version, |
118 | | - } ) ) |
119 | | - .filter( ( { required, actual } ) => required !== actual ) |
120 | | - ; |
121 | | - |
122 | | - // Ensure that all the conflicts can be resolved with the same version |
123 | | - const unresolvableConflicts = Object.entries( groupBy( versionConflicts, ( {name} ) => name ) ) |
124 | | - .map( ( [name, group] ) => [name, uniq( group.map( ( { required } ) => required ) )] ) |
125 | | - .filter( ( [, group] ) => group.length > 1 ); |
126 | | - if ( unresolvableConflicts.length > 0 ) { |
127 | | - console.error( "Can't resolve some conflicts automatically." ); |
128 | | - console.error( "Multiple required versions of the following packages were detected:" ); |
129 | | - console.error( unresolvableConflicts ); |
130 | | - process.exit( 1 ); |
131 | | - } |
132 | | - return versionConflicts; |
133 | | -} |
134 | | - |
135 | | -/** |
136 | | - * Returns a list of dependencies of each @wordpress dependency. |
137 | | - * |
138 | | - * @return {Object} An object of shape {packageName: [[packageName, version]]}. |
139 | | - */ |
140 | | -function getPerPackageDeps() { |
141 | | - // Get the dependencies currently listed in the wordpress-develop package.json |
142 | | - const currentPackageJSON = readJSONFile( "package.json" ); |
143 | | - const currentPackages = getWordPressPackages( currentPackageJSON ); |
144 | | - |
145 | | - // Get the dependencies that the above dependencies list in their package.json. |
146 | | - const deps = currentPackages |
147 | | - .map( ( packageName ) => `node_modules/${ packageName }/package.json` ) |
148 | | - .map( ( jsonPath ) => readJSONFile( jsonPath ).dependencies ); |
149 | | - return zip( currentPackages, deps ); |
150 | | -} |
151 | | - |
152 | | -/** |
153 | | - * Takes unserialized package.json data and returns a list of @wordpress dependencies. |
154 | | - * |
155 | | - * @param {Object} dependencies unserialized package.json data. |
156 | | - * @return {string[]} a list of @wordpress dependencies. |
157 | | - */ |
158 | | -function getWordPressPackages( { dependencies = {} } ) { |
159 | | - return Object.keys( dependencies ) |
160 | | - .filter( isWordPressPackage ); |
161 | | -} |
162 | | - |
163 | | -/** |
164 | | - * Returns true if packageName represents a @wordpress package. |
165 | | - * |
166 | | - * @param {string} packageName Package name to test. |
167 | | - * @return {boolean} Is it a @wodpress package? |
168 | | - */ |
169 | | -function isWordPressPackage( packageName ) { |
170 | | - return packageName.startsWith( WORDPRESS_PACKAGES_PREFIX ); |
171 | | -} |
172 | | - |
173 | | -/** |
174 | | - * Computes the dependencies difference between two unserialized |
175 | | - * package JSON objects. Needed only for the final reporting. |
176 | | - * |
177 | | - * @param {Object} initialPackageJSON Initial package JSON data. |
178 | | - * @param {Object} finalPackageJSON Final package JSON data. |
179 | | - * @return {Object} Delta. |
180 | | - */ |
181 | | -function getPackageVersionDiff( initialPackageJSON, finalPackageJSON ) { |
182 | | - const diff = ['dependencies', 'devDependencies'].reduce( |
183 | | - ( result, keyPackageJSON ) => { |
184 | | - return Object.keys( |
185 | | - finalPackageJSON[ keyPackageJSON ] || {}, |
186 | | - ).reduce( ( _result, dependency ) => { |
187 | | - const initial = |
188 | | - initialPackageJSON[ keyPackageJSON ][ dependency ]; |
189 | | - const final = finalPackageJSON[ keyPackageJSON ][ dependency ]; |
190 | | - if ( initial !== final ) { |
191 | | - _result.push( { dependency, initial, final } ); |
192 | | - } |
193 | | - return _result; |
194 | | - }, result ); |
195 | | - }, |
196 | | - [], |
197 | | - ); |
198 | | - return diff.sort( ( a, b ) => a.dependency.localeCompare( b.dependency ) ); |
199 | | -} |
200 | | - |
201 | | -/** |
202 | | - * Prints the delta between two package.json files. |
203 | | - * |
204 | | - * @param {Object} packageDiff Delta. |
205 | | - */ |
206 | | -function outputPackageDiffReport( packageDiff ) { |
207 | | - const readableDiff = |
208 | | - packageDiff |
209 | | - .map( ( { dependency, initial, final } ) => { |
210 | | - return `${ dependency }: ${ initial } -> ${ final }`; |
211 | | - } ) |
212 | | - .filter( identity ); |
213 | | - if ( !readableDiff.length ) { |
214 | | - console.log( 'No changes detected' ); |
215 | | - return; |
216 | | - } |
217 | | - console.log( |
218 | | - [ |
219 | | - 'The following package versions were changed:', |
220 | | - ...readableDiff, |
221 | | - ].join( '\n' ), |
222 | | - ); |
223 | | -} |
224 | | - |
225 | | -main(); |
226 | | - |
227 | | -/* eslint-enable no-console */ |
0 commit comments