-
Notifications
You must be signed in to change notification settings - Fork 877
/
sync-wp-dependencies.js
352 lines (323 loc) · 12.5 KB
/
sync-wp-dependencies.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
/* eslint-disable no-console */
/**
* This script is used to sync the WordPress dependencies in our package.json files with the versions in their package.json.
*
* Usage:
* $ node config/scripts/sync-wp-dependencies.js [packageFolder1] [packageFolder2] ...
* If no packageFolders are specified, all packages will be updated.
* If no semver modifier is specified, exact match will be used.
* Options:
* --caret Use caret (^) as semver modifier
* --tilde Use tilde (~) as semver modifier
* --exact Use no semver modifier
*
* What does it do?
* Steps:
* 1. Get the lowest supported WordPress version from the PHP file.
* 2. Get the package.json from the WordPress repository, using the WP version found in step 1 as tag.
* 3. Extract the versions of the dependencies (that we care about) from the package.json from the WordPress repository.
* 4. Update our packages with the versions from the WordPress package.json.
* 5. Optionally, we could try and fix some obvious dependency errors we know about.
*/
const { existsSync, readFileSync, readdirSync } = require( "fs" );
const { get } = require( "https" );
const { execSync } = require( "child_process" );
/**
* These are the dependencies we want to sync with the WordPress package.json.
* When using @wordpress/dependency-extraction-webpack-plugin in our Webpack config, these dependencies are extracted.
*
* @see https://github.com/WordPress/gutenberg/blob/trunk/packages/dependency-extraction-webpack-plugin/README.md#behavior-with-scripts
*/
const wpPackagesAllowList = [
"@babel/runtime/regenerator",
"@wordpress/a11y",
"@wordpress/annotations",
"@wordpress/api-fetch",
"@wordpress/autop",
"@wordpress/blob",
"@wordpress/block-directory",
"@wordpress/block-editor",
"@wordpress/block-library",
"@wordpress/block-serialization-default-parser",
"@wordpress/blocks",
"@wordpress/commands",
"@wordpress/components",
"@wordpress/compose",
"@wordpress/core-commands",
"@wordpress/core-data",
"@wordpress/customize-widgets",
"@wordpress/data",
"@wordpress/data-controls",
"@wordpress/date",
"@wordpress/deprecated",
"@wordpress/dom",
"@wordpress/dom-ready",
"@wordpress/edit-post",
"@wordpress/edit-site",
"@wordpress/edit-widgets",
"@wordpress/editor",
"@wordpress/element",
"@wordpress/escape-html",
"@wordpress/format-library",
"@wordpress/hooks",
"@wordpress/html-entities",
"@wordpress/i18n",
"@wordpress/icons",
"@wordpress/interface",
"@wordpress/is-shallow-equal",
"@wordpress/keyboard-shortcuts",
"@wordpress/keycodes",
"@wordpress/list-reusable-blocks",
"@wordpress/media-utils",
"@wordpress/notices",
"@wordpress/nux",
"@wordpress/plugins",
"@wordpress/preferences",
"@wordpress/preferences-persistence",
"@wordpress/primitives",
"@wordpress/priority-queue",
"@wordpress/private-apis",
"@wordpress/redux-routine",
"@wordpress/reusable-blocks",
"@wordpress/rich-text",
"@wordpress/router",
"@wordpress/server-side-render",
"@wordpress/shortcode",
"@wordpress/style-engine",
"@wordpress/token-list",
"@wordpress/url",
"@wordpress/viewport",
"@wordpress/warning",
"@wordpress/widgets",
"@wordpress/wordcount",
"jquery",
"lodash",
"moment",
"react",
"react-dom",
];
// These packages are considered tooling, and not meant to load into a WordPress environment.
const packageFolderDisallowList = [
"babel-preset",
"browserslist-config",
"e2e-tests",
"eslint",
"jest-preset",
"postcss-preset",
"tailwindcss-preset",
];
const SEMVER_MODIFIERS = {
exact: "",
caret: "^",
tilde: "~",
};
/**
* Gets the lowest supported WordPress version.
* @returns {null|string} The lowest supported WordPress version or null.
*/
const getLowestSupportedWordPressVersion = () => {
const data = readFileSync( "./wp-seo.php", "utf8" );
// Get the latest version number from the PHP file.
const match = data.match( /Requires at least: (\d+\.\d+)/ );
if ( match && match.length > 0 ) {
return match[ 1 ];
}
console.error( "Lowest supported WordPress version not found" );
return null;
};
/**
* Fetches the data from the specified URL.
* @param {string} url The URL to fetch the data from.
* @returns {Promise<any|e>} A promise of the data or a reject with the error.
*/
const fetchData = ( url ) => new Promise( ( resolve, reject ) => {
// Get the package.json from the WordPress repository.
get( url, ( res ) => {
let data = "";
res.on( "data", ( chunk ) => {
data += chunk;
} );
res.on( "end", () => resolve( data ) );
} ).on( "error", ( e ) => reject( e ) );
} );
/**
* Tries to parse the JSON string.
* @param {string} json The JSON string to parse.
* @returns {any|null} The parse result or null.
*/
const tryJsonParse = ( json ) => {
try {
return JSON.parse( json );
} catch ( e ) {
return null;
}
};
/**
* Gets the parsed WordPress package.json.
* @param {string} wpVersion The WordPress version to get the dependencies for.
* @returns {Promise<Object<string, string>|null>} A promise of the dependencies or null.
*/
const getWordPressPackageJson = ( wpVersion ) => {
const url = `https://raw.githubusercontent.com/WordPress/wordpress-develop/${ wpVersion }/package.json`;
// Perform the request and process the data.
return new Promise( ( resolve ) => {
fetchData( url )
.then( ( data ) => resolve( tryJsonParse( data ) ) )
.catch( ( e ) => {
console.error( "Error fetching URL", url, e );
resolve( null );
} );
} );
};
/**
* Gets the dependencies from the package.json of the specified package.
* @param {string} packageName The package to get the dependencies for.
* @returns {Object<string,string>|null} The dependencies or null.
*/
const getPackageJsonForPackage = ( packageName ) => {
const data = readFileSync( `./packages/${ packageName }/package.json`, "utf8" );
return tryJsonParse( data );
};
/**
* Gets the dependencies from the specified package.json.
* Doing this instead of optional chaining, because ESLint doesn't seem to like it.
* @param {Object} packageJson The package.json to get the dependencies from.
* @returns {{}|Object<string,string>} The dependencies or an empty object.
*/
const getDependenciesFromPackageJson = ( packageJson ) => {
if ( ! packageJson || ! packageJson.dependencies ) {
return {};
}
return packageJson.dependencies;
};
/**
* Filters the dependencies, based on the allowList.
* @param {string[]} dependencies The dependencies to filter.
* @param {string[]} allowList The allowList to filter with.
* @returns {string[]} The filtered dependencies.
*/
const filterDependencies = ( dependencies, allowList ) => {
return dependencies.filter( ( dependency ) => allowList.includes( dependency ) );
};
/**
* Gets the dependencies with the wanted versions.
* @param {string[]} dependencies The dependencies we want to update.
* @param {Object|Object<string,string>} listedVersions The versions of the dependencies we want to update.
* @param {string} [semverModifier] The semver modifier to use. Defaults to exact. See SEMVER_MODIFIERS.
* @returns {string[]} The dependencies with the wanted versions.
*/
const getDependenciesWithWantedVersions = ( dependencies, listedVersions, semverModifier = "" ) => {
return dependencies.map( ( dependency ) => ( `${ dependency }@${ semverModifier }${ listedVersions[ dependency ] }` ) );
};
/**
* Syncs the dependencies for the specified package.
* @param {string} packageFolder The package to sync the dependencies for.
* @param {Object|Object<string,string>} wpDependencies The WordPress dependencies to sync with.
* @param {string} [semverModifier] The semver modifier to use. Defaults to exact. See SEMVER_MODIFIERS.
* @returns {Promise<boolean>} A promise that resolves when the dependencies are synced.
*/
const syncPackageDependenciesFor = async( packageFolder, wpDependencies, semverModifier ) => {
const packageJson = getPackageJsonForPackage( packageFolder );
let dependenciesToUpdate = Object.keys( getDependenciesFromPackageJson( packageJson ) );
// Filter out the dependencies we don't care about.
dependenciesToUpdate = filterDependencies( dependenciesToUpdate, wpPackagesAllowList );
// Filter out the dependencies that are unknown to WordPress.
dependenciesToUpdate = filterDependencies( dependenciesToUpdate, Object.keys( wpDependencies ) );
const dependenciesWithWantedVersions = getDependenciesWithWantedVersions( dependenciesToUpdate, wpDependencies, semverModifier );
if ( dependenciesWithWantedVersions.length === 0 ) {
console.log( "=============================================" );
console.info( "No WordPress dependencies found in:", packageJson.name );
return true;
}
console.log( "=============================================" );
console.log( `yarn workspace ${ packageJson.name } add ${ dependenciesWithWantedVersions.join( " " ) }` );
try {
execSync( `yarn workspace ${ packageJson.name } add ${ dependenciesWithWantedVersions.join( " " ) }`, { stdio: "inherit" } );
return true;
} catch ( e ) {
console.error( "Error updating dependencies for:", packageJson.name );
return false;
}
};
/**
* Filters the requested package folders.
* Defaults to all packages if no arguments are provided.
* Filters out folders that we don't want, or that don't have a package.json.
* @param {string[]} requestedPackages The requested packages.
* @returns {string[]} Valid package folders.
*/
const filterPackageFolders = ( requestedPackages = [] ) => {
const packages = requestedPackages.length === 0 ? readdirSync( "./packages" ) : requestedPackages;
return packages.filter( ( packageFolder ) => (
// Ignore folders that are not packages that run inside a WordPress environment.
! packageFolderDisallowList.includes( packageFolder ) &&
// Ignore folders that don't have a package.json.
existsSync( `./packages/${ packageFolder }/package.json` )
) );
};
/**
* Gets the semver modifier from the arguments.
* @param {string[]} args The arguments.
* @returns {string} The semver modifier.
*/
const getSemverModifierFromArguments = ( args ) => {
if ( args.includes( "--caret" ) ) {
return SEMVER_MODIFIERS.caret;
}
if ( args.includes( "--tilde" ) ) {
return SEMVER_MODIFIERS.tilde;
}
return SEMVER_MODIFIERS.exact;
};
/**
* Parses the arguments and returns commands.
* @param {string[]} args The arguments to parse.
* @returns {Object} The commands.
*/
const parseArguments = ( args ) => {
if ( args.includes( "--help" ) ) {
console.log( "Usage: node sync-wp-dependencies.js [packageFolder1] [packageFolder2] ..." );
console.log( "If no packageFolders are specified, all packages will be updated." );
console.log( "If no semver modifier is specified, exact match will be used." );
console.log( "Options:" );
console.log( " --caret Use caret (^) as semver modifier" );
console.log( " --tilde Use tilde (~) as semver modifier" );
console.log( " --exact Use no semver modifier" );
// eslint-disable-next-line no-process-exit
process.exit( 0 );
}
return {
packageFolders: filterPackageFolders( args.filter( ( arg ) => ! arg.startsWith( "--" ) ) ),
semverModifier: getSemverModifierFromArguments( args ),
};
};
/**
* Syncs the WordPress dependencies for the specified packages (in command).
* @param {string[]} packageFolders The package folders to sync the dependencies for.
* @param {string} semverModifier The semver modifier to use. Defaults to exact. See SEMVER_MODIFIERS.
* @returns {Promise<void>} A promise that resolves when the dependencies are synced.
*/
const syncPackageDependencies = async( { packageFolders, semverModifier } ) => {
if ( packageFolders.length === 0 ) {
console.error( "No valid package folders found" );
return;
}
const lowestSupportedWordPressVersion = getLowestSupportedWordPressVersion();
console.log( "Lowest supported WordPress version:", lowestSupportedWordPressVersion );
const wpDependencies = getDependenciesFromPackageJson( await getWordPressPackageJson( lowestSupportedWordPressVersion ) );
const result = {};
for ( const packageFolder of packageFolders ) {
result[ packageFolder ] = await syncPackageDependenciesFor( packageFolder, wpDependencies, semverModifier );
}
console.log( "=============================================" );
console.log( "Result:" );
const successful = Object.keys( result ).filter( ( packageFolder ) => result[ packageFolder ] );
if ( successful.length > 0 ) {
console.log( "Successfully synced:", Object.values( successful ).join( ", " ) );
}
const failure = Object.keys( result ).filter( ( packageFolder ) => ! result[ packageFolder ] );
if ( failure.length > 0 ) {
console.log( "Errors occurred in:", Object.values( failure ).join( ", " ) );
}
};
syncPackageDependencies( parseArguments( process.argv.slice( 2 ) ) );