mirrored from git://develop.git.wordpress.org/
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Automate updating JS packages #2621
Closed
Closed
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
3b8b371
Add the backport-js-packages task that updates all the JavaScript pac…
adamziel f3e04c1
Reword backporting to syncing gutenberg packages
adamziel f8a809d
Remove ${ rootDir } – currentCwd should account for that
adamziel a799496
Add JSDoc
adamziel 99fcf90
Move refreshDependencies call to the end of the file
adamziel 16fdd0a
Spawn sync-gutenberg-packages.js directly from Gruntfile
adamziel fb82b43
Revert the actual package update
adamziel 38eceed
Rollback the actual package update
adamziel b4fb64a
Revert unintended indent
adamziel 76f566d
Rename refreshDependencies to main
adamziel a3e03c8
Add wp- prefix before tasks
adamziel 9036fe7
Document the browserlist:update task
adamziel 0801f0c
Make browserlist:update opt-in
adamziel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
/* eslint-disable no-console */ | ||
/** | ||
* External dependencies | ||
*/ | ||
const fs = require( 'fs' ); | ||
const spawn = require( 'cross-spawn' ); | ||
const { zip, uniq, identity, groupBy } = require( 'lodash' ); | ||
|
||
/** | ||
* Constants | ||
*/ | ||
const WORDPRESS_PACKAGES_PREFIX = '@wordpress/'; | ||
const { getArgFromCLI } = require( `../../node_modules/@wordpress/scripts/utils` ); | ||
const distTag = getArgFromCLI( '--dist-tag' ) || 'latest'; | ||
|
||
/** | ||
* The main function of this task. | ||
* | ||
* It installs any missing WordPress packages, and updates the | ||
* mismatched dependencies versions, e.g. it would detect that Gutenberg | ||
* updated react from 16.0.4 to 17.0.2 and install the latter. | ||
*/ | ||
function main() { | ||
const initialPackageJSON = readJSONFile( `package.json` ); | ||
|
||
// Install any missing WordPress packages: | ||
const missingWordPressPackages = getMissingWordPressPackages(); | ||
if ( missingWordPressPackages.length ) { | ||
console.log( "The following @wordpress dependencies are missing: " ); | ||
console.log( missingWordPressPackages ); | ||
console.log( "Installing via npm..." ); | ||
installPackages( missingWordPressPackages.map( name => [name, distTag] ) ); | ||
} | ||
|
||
// Update any outdated non-WordPress packages: | ||
const versionMismatches = getMismatchedNonWordPressDependencies(); | ||
if ( versionMismatches.length ) { | ||
console.log( "The following dependencies are outdated: " ); | ||
console.log( versionMismatches ); | ||
console.log( "Updating via npm..." ); | ||
const requiredPackages = versionMismatches.map( ( { name, required } ) => [name, required] ); | ||
installPackages( requiredPackages ); | ||
} | ||
|
||
const finalPackageJSON = readJSONFile( "package.json" ); | ||
outputPackageDiffReport( | ||
getPackageVersionDiff( initialPackageJSON, finalPackageJSON ), | ||
); | ||
process.exit( 0 ); | ||
} | ||
|
||
/** | ||
* @param {string} fileName File to read. | ||
* @return {Object} Parsed data. | ||
*/ | ||
function readJSONFile( fileName ) { | ||
const data = fs.readFileSync( fileName, 'utf8' ); | ||
return JSON.parse( data ); | ||
} | ||
|
||
/** | ||
* Spawns npm install --save. | ||
* | ||
* @param {Array} packages List of tuples [packageName, version] to install. | ||
* @return {string} CLI output. | ||
*/ | ||
function installPackages( packages ) { | ||
const packagesWithVersion = packages.map( | ||
( [packageName, version] ) => `${ packageName }@${ version }`, | ||
); | ||
return spawn.sync( 'npm', ['install', ...packagesWithVersion, '--save'], { | ||
stdio: 'inherit', | ||
} ); | ||
} | ||
|
||
/** | ||
* Computes which @wordpress packages are required by the Gutenberg | ||
* dependencies that are missing from WordPress package.json. | ||
* | ||
* @return {Array} List of tuples [packageName, version]. | ||
*/ | ||
function getMissingWordPressPackages() { | ||
const perPackageDeps = getPerPackageDeps(); | ||
const currentPackages = perPackageDeps.map( ( [name] ) => name ); | ||
|
||
const requiredWpPackages = uniq( perPackageDeps | ||
// Capture the @wordpress dependencies of our dependencies into a flat list. | ||
.flatMap( ( [, dependencies] ) => getWordPressPackages( { dependencies } ) ) | ||
.sort(), | ||
); | ||
|
||
return requiredWpPackages.filter( | ||
packageName => !currentPackages.includes( packageName ) ); | ||
} | ||
|
||
/** | ||
* Computes which third party packages are required by the @wordpress | ||
* packages, but not by the WordPress repo itself. This includes | ||
* both packages that are missing from package.json and any version | ||
* mismatches. | ||
* | ||
* @return {Array} List of objects {name, required, actual} describing version mismatches. | ||
*/ | ||
function getMismatchedNonWordPressDependencies() { | ||
// Get the installed dependencies from package-lock.json | ||
const currentPackageJSON = readJSONFile( "package.json" ); | ||
const currentPackages = getWordPressPackages( currentPackageJSON ); | ||
|
||
const packageLock = readJSONFile( "package-lock.json" ); | ||
const versionConflicts = Object.entries( packageLock.dependencies ) | ||
.filter( ( [packageName] ) => currentPackages.includes( packageName ) ) | ||
.flatMap( ( [, { dependencies }] ) => Object.entries( dependencies || {} ) ) | ||
.filter( identity ) | ||
.map( ( [name, { version }] ) => ( { | ||
name, | ||
required: version, | ||
actual: packageLock.dependencies[ name ].version, | ||
} ) ) | ||
.filter( ( { required, actual } ) => required !== actual ) | ||
; | ||
|
||
// Ensure that all the conflicts can be resolved with the same version | ||
const unresolvableConflicts = Object.entries( groupBy( versionConflicts, ( [name] ) => name ) ) | ||
.map( ( [name, group] ) => [name, group.map( ( [, { required }] ) => required )] ) | ||
.filter( ( [, group] ) => group.length > 1 ); | ||
if ( unresolvableConflicts.length > 0 ) { | ||
console.error( "Can't resolve some conflicts automatically." ); | ||
console.error( "Multiple required versions of the following packages were detected:" ); | ||
console.error( unresolvableConflicts ); | ||
process.exit( 1 ); | ||
} | ||
return versionConflicts; | ||
} | ||
|
||
/** | ||
* Returns a list of dependencies of each @wordpress dependency. | ||
* | ||
* @return {Object} An object of shape {packageName: [[packageName, version]]}. | ||
*/ | ||
function getPerPackageDeps() { | ||
// Get the dependencies currently listed in the wordpress-develop package.json | ||
const currentPackageJSON = readJSONFile( "package.json" ); | ||
const currentPackages = getWordPressPackages( currentPackageJSON ); | ||
|
||
// Get the dependencies that the above dependencies list in their package.json. | ||
const deps = currentPackages | ||
.map( ( packageName ) => `node_modules/${ packageName }/package.json` ) | ||
.map( ( jsonPath ) => readJSONFile( jsonPath ).dependencies ); | ||
return zip( currentPackages, deps ); | ||
} | ||
|
||
/** | ||
* Takes unserialized package.json data and returns a list of @wordpress dependencies. | ||
* | ||
* @param {Object} dependencies unserialized package.json data. | ||
* @return {string[]} a list of @wordpress dependencies. | ||
*/ | ||
function getWordPressPackages( { dependencies = {} } ) { | ||
return Object.keys( dependencies ) | ||
.filter( isWordPressPackage ); | ||
} | ||
|
||
/** | ||
* Returns true if packageName represents a @wordpress package. | ||
* | ||
* @param {string} packageName Package name to test. | ||
* @return {boolean} Is it a @wodpress package? | ||
*/ | ||
function isWordPressPackage( packageName ) { | ||
return packageName.startsWith( WORDPRESS_PACKAGES_PREFIX ); | ||
} | ||
|
||
/** | ||
* Computes the dependencies difference between two unserialized | ||
* package JSON objects. Needed only for the final reporting. | ||
* | ||
* @param {Object} initialPackageJSON Initial package JSON data. | ||
* @param {Object} finalPackageJSON Final package JSON data. | ||
* @return {Object} Delta. | ||
*/ | ||
function getPackageVersionDiff( initialPackageJSON, finalPackageJSON ) { | ||
const diff = ['dependencies', 'devDependencies'].reduce( | ||
( result, keyPackageJSON ) => { | ||
return Object.keys( | ||
finalPackageJSON[ keyPackageJSON ] || {}, | ||
).reduce( ( _result, dependency ) => { | ||
const initial = | ||
initialPackageJSON[ keyPackageJSON ][ dependency ]; | ||
const final = finalPackageJSON[ keyPackageJSON ][ dependency ]; | ||
if ( initial !== final ) { | ||
_result.push( { dependency, initial, final } ); | ||
} | ||
return _result; | ||
}, result ); | ||
}, | ||
[], | ||
); | ||
return diff.sort( ( a, b ) => a.dependency.localeCompare( b.dependency ) ); | ||
} | ||
|
||
/** | ||
* Prints the delta between two package.json files. | ||
* | ||
* @param {Object} packageDiff Delta. | ||
*/ | ||
function outputPackageDiffReport( packageDiff ) { | ||
const readableDiff = | ||
packageDiff | ||
.map( ( { dependency, initial, final } ) => { | ||
return `${ dependency }: ${ initial } -> ${ final }`; | ||
} ) | ||
.filter( identity ); | ||
if ( !readableDiff.length ) { | ||
console.log( 'No changes detected' ); | ||
return; | ||
} | ||
console.log( | ||
[ | ||
'The following package versions were changed:', | ||
...readableDiff, | ||
].join( '\n' ), | ||
); | ||
} | ||
|
||
main(); | ||
|
||
/* eslint-enable no-console */ |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My linting tool caught this. Seems fine to merge.