diff --git a/bin/plugin/cli.js b/bin/plugin/cli.js index 5dbaac5d08e869..7e65d1cf147dbc 100755 --- a/bin/plugin/cli.js +++ b/bin/plugin/cli.js @@ -21,6 +21,7 @@ const catchException = ( command ) => { */ const { publishNpmLatestDistTag, + publishNpmBugfixLatestDistTag, publishNpmNextDistTag, } = require( './commands/packages' ); const { getReleaseChangelog } = require( './commands/changelog' ); @@ -34,6 +35,14 @@ program ) .action( catchException( publishNpmLatestDistTag ) ); +program + .command( 'publish-npm-packages-bugfix-latest' ) + .alias( 'npm-bugfix' ) + .description( + 'Publishes bugfixes for packages to npm (latest dist-tag, production version)' + ) + .action( catchException( publishNpmBugfixLatestDistTag ) ); + program .command( 'publish-npm-packages-next' ) .alias( 'npm-next' ) diff --git a/bin/plugin/commands/packages.js b/bin/plugin/commands/packages.js index 532366b1329c88..2d89ca72824879 100644 --- a/bin/plugin/commands/packages.js +++ b/bin/plugin/commands/packages.js @@ -22,6 +22,12 @@ const { } = require( './common' ); const git = require( '../lib/git' ); +/** + * Release type names. + * + * @typedef {('latest'|'bugfix'|'patch'|'next')} ReleaseType + */ + /** * Semantic Versioning labels. * @@ -32,27 +38,23 @@ const git = require( '../lib/git' ); * Checks out the WordPress release branch and syncs it with the changes from * the last plugin release. * - * @param {string} gitWorkingDirectoryPath Git working directory path. - * @param {boolean} isPrerelease Whether the package version to publish is a prerelease. - * @param {string} abortMessage Abort Message. + * @param {string} gitWorkingDirectoryPath Git working directory path. + * @param {ReleaseType} releaseType Release type selected from CLI. + * @param {string} abortMessage Abort Message. * * @return {Promise} WordPress release branch. */ async function runWordPressReleaseBranchSyncStep( gitWorkingDirectoryPath, - isPrerelease, + releaseType, abortMessage ) { - const wordpressReleaseBranch = isPrerelease ? 'wp/next' : 'wp/trunk'; + const wordpressReleaseBranch = + releaseType === 'next' ? 'wp/next' : 'wp/trunk'; await runStep( 'Getting into the WordPress release branch', abortMessage, async () => { - const packageJsonPath = gitWorkingDirectoryPath + '/package.json'; - const pluginReleaseBranch = findReleaseBranchName( - packageJsonPath - ); - // Creating the release branch await git.checkoutRemoteBranch( gitWorkingDirectoryPath, @@ -64,27 +66,35 @@ async function runWordPressReleaseBranchSyncStep( ' has been successfully checked out.' ); - await askForConfirmation( - `The branch is ready for sync with the latest plugin release changes applied to "${ pluginReleaseBranch }". Proceed?`, - true, - abortMessage - ); + if ( [ 'latest', 'next' ].includes( releaseType ) ) { + const packageJsonPath = + gitWorkingDirectoryPath + '/package.json'; + const pluginReleaseBranch = findReleaseBranchName( + packageJsonPath + ); - await git.replaceContentFromRemoteBranch( - gitWorkingDirectoryPath, - pluginReleaseBranch - ); + await askForConfirmation( + `The branch is ready for sync with the latest plugin release changes applied to "${ pluginReleaseBranch }". Proceed?`, + true, + abortMessage + ); - await git.commit( - gitWorkingDirectoryPath, - `Merge changes published in the Gutenberg plugin "${ pluginReleaseBranch }" branch` - ); + await git.replaceContentFromRemoteBranch( + gitWorkingDirectoryPath, + pluginReleaseBranch + ); - log( - '>> The local WordPress release branch ' + - formats.success( wordpressReleaseBranch ) + - ' has been successfully synced.' - ); + await git.commit( + gitWorkingDirectoryPath, + `Merge changes published in the Gutenberg plugin "${ pluginReleaseBranch }" branch` + ); + + log( + '>> The local WordPress release branch ' + + formats.success( wordpressReleaseBranch ) + + ' has been successfully synced.' + ); + } } ); @@ -97,15 +107,15 @@ async function runWordPressReleaseBranchSyncStep( * Update CHANGELOG files with the new version number for those packages that * contain new entries. * - * @param {string} gitWorkingDirectoryPath Git working directory path. - * @param {SemVer} minimumVersionBump Minimum version bump for the packages. - * @param {boolean} isPrerelease Whether the package version to publish is a prerelease. - * @param {string} abortMessage Abort Message. + * @param {string} gitWorkingDirectoryPath Git working directory path. + * @param {SemVer} minimumVersionBump Minimum version bump for the packages. + * @param {ReleaseType} releaseType Release type selected from CLI. + * @param {string} abortMessage Abort Message. */ async function updatePackages( gitWorkingDirectoryPath, minimumVersionBump, - isPrerelease, + releaseType, abortMessage ) { const changelogFiles = await glob( @@ -148,7 +158,7 @@ async function updatePackages( // the stable minor or major version bump requested. if ( ! versionBump && - ! isPrerelease && + releaseType !== 'next' && minimumVersionBump !== 'patch' && productionPackageNames.includes( packageName ) ) { @@ -207,7 +217,9 @@ async function updatePackages( content.replace( '## Unreleased', `## Unreleased\n\n## ${ - isPrerelease ? nextVersion + '-next.0' : nextVersion + releaseType === 'next' + ? nextVersion + '-next.0' + : nextVersion } (${ publishDate })` ) ); @@ -225,7 +237,9 @@ async function updatePackages( log( ` - ${ packageName }: ${ version } -> ${ - isPrerelease ? nextVersion + '-next.0' : nextVersion + releaseType === 'next' + ? nextVersion + '-next.0' + : nextVersion }` ); } @@ -274,21 +288,21 @@ async function runPushGitChangesStep( /** * Publishes all changed packages to npm. * - * @param {string} gitWorkingDirectoryPath Git working directory path. - * @param {SemVer} minimumVersionBump Minimum version bump for the packages. - * @param {boolean} isPrerelease Whether the package version to publish is a prerelease. + * @param {string} gitWorkingDirectoryPath Git working directory path. + * @param {SemVer} minimumVersionBump Minimum version bump for the packages. + * @param {ReleaseType} releaseType Release type selected from CLI. */ async function publishPackagesToNpm( gitWorkingDirectoryPath, minimumVersionBump, - isPrerelease + releaseType ) { log( '>> Installing npm packages.' ); await command( 'npm ci', { cwd: gitWorkingDirectoryPath, } ); - if ( isPrerelease ) { + if ( releaseType === 'next' ) { log( '>> Bumping version of public packages changed since the last release.' ); @@ -306,6 +320,12 @@ async function publishPackagesToNpm( cwd: gitWorkingDirectoryPath, stdio: 'inherit', } ); + } else if ( releaseType === 'bugfix' ) { + log( '>> Publishing modified packages to npm.' ); + await command( `npm run publish:latest`, { + cwd: gitWorkingDirectoryPath, + stdio: 'inherit', + } ); } else { log( '>> Bumping version of public packages changed since the last release.' @@ -329,12 +349,18 @@ async function publishPackagesToNpm( /** * Prepare everything to publish WordPress packages to npm. * - * @param {boolean} [isPrerelease] Whether the package version to publish is a prerelease. + * @param {ReleaseType} releaseType Release type selected from CLI. + * @param {SemVer} [minimumVersionBump] Minimum version bump for the packages. Default: `true`. + * @param {string} [confirmationMessage] Confirmation message to show at first. * * @return {Promise} Github release object. */ -async function prepareForPackageRelease( isPrerelease ) { - await askForConfirmation( 'Ready to go?' ); +async function prepareForPackageRelease( + releaseType, + minimumVersionBump = 'patch', + confirmationMessage = 'Ready to go?' +) { + await askForConfirmation( confirmationMessage ); // Cloning the Git repository. const abortMessage = 'Aborting!'; @@ -347,24 +373,14 @@ async function prepareForPackageRelease( isPrerelease ) { // Checking out the WordPress release branch and doing sync with the last plugin release. const { releaseBranch } = await runWordPressReleaseBranchSyncStep( gitWorkingDirectoryPath, - isPrerelease, + releaseType, abortMessage ); - const { minimumVersionBump } = await prompt( [ - { - type: 'list', - name: 'minimumVersionBump', - message: 'Select the minimum version bump for packages:', - default: 'patch', - choices: [ 'patch', 'minor', 'major' ], - }, - ] ); - await updatePackages( gitWorkingDirectoryPath, minimumVersionBump, - isPrerelease, + releaseType, abortMessage ); @@ -377,7 +393,7 @@ async function prepareForPackageRelease( isPrerelease ) { await publishPackagesToNpm( gitWorkingDirectoryPath, minimumVersionBump, - isPrerelease + releaseType ); await runCleanLocalFoldersStep( temporaryFolders, 'Cleaning failed.' ); @@ -395,7 +411,44 @@ async function publishNpmLatestDistTag() { "To perform a release you'll have to be a member of the WordPress Team on npm.\n" ); - await prepareForPackageRelease(); + const minimumVersionBump = await prompt( [ + { + type: 'list', + name: 'minimumVersionBump', + message: 'Select the minimum version bump for packages:', + default: 'patch', + choices: [ 'patch', 'minor', 'major' ], + }, + ] ); + + await prepareForPackageRelease( 'latest', minimumVersionBump ); + + log( + '\n>> šŸŽ‰ WordPress packages are now published!\n\n', + 'Please remember to run `git cherry-pick` in the `trunk` branch for the newly created commits during the release with labels:\n', + ' - Update changelog files (if exists)\n', + ' - chore(release): publish\n\n', + 'Finally, let also people know on WordPress Slack and celebrate together.' + ); +} + +/** + * Publishes a new latest version of WordPress packages. + */ +async function publishNpmBugfixLatestDistTag() { + log( + formats.title( + '\nšŸ’ƒ Time to publish WordPress packages to npm šŸ•ŗ\n\n' + ), + 'Welcome! This tool is going to help you with publishing a new bugfix version of WordPress packages with the latest dist tag.\n', + "To perform a release you'll have to be a member of the WordPress Team on npm.\n" + ); + + await prepareForPackageRelease( + 'bugfix', + 'patch', + 'Before we proceed, can you confirm that all required changes have beed already cherry-picked to the release branch?' + ); log( '\n>> šŸŽ‰ WordPress packages are now published!\n\n', @@ -418,7 +471,7 @@ async function publishNpmNextDistTag() { "To perform a release you'll have to be a member of the WordPress Team on npm.\n" ); - await prepareForPackageRelease( true ); + await prepareForPackageRelease( 'next' ); log( '\n>> šŸŽ‰ WordPress packages are now published!\n', @@ -426,4 +479,8 @@ async function publishNpmNextDistTag() { ); } -module.exports = { publishNpmLatestDistTag, publishNpmNextDistTag }; +module.exports = { + publishNpmLatestDistTag, + publishNpmBugfixLatestDistTag, + publishNpmNextDistTag, +}; diff --git a/docs/contributors/code/release.md b/docs/contributors/code/release.md index d07dbbf0b920b8..e9dff32206dad3 100644 --- a/docs/contributors/code/release.md +++ b/docs/contributors/code/release.md @@ -51,6 +51,7 @@ Once released, all that's left to do is writing a release post on [make.wordpres ### Writing the Release Notes and Post Documenting the release is comprised of the following steps: + 1. Curating the changelog 2. Selecting the release highlights 3. Drafting the release post @@ -58,6 +59,7 @@ Documenting the release is comprised of the following steps: 5. Publishing the post #### 1. Curating the changelog + The release notes draft is auto-generated by a script that looks for pull requests for the current milestone, and groups them by pull request label. This is intended to be a starting point for release notes; you will still want to manually review and curate the changelog entries. Because the release candidate changelog is reused in the stable release and it can be a time-consuming process, it is recommended to start this process as soon as the milestone is closed and the release candidate is published. @@ -71,6 +73,7 @@ Guidelines for proof-reading include: - Remove mobile app pull request entries. #### 2. Selecting the release highlights + Once you have cleaned up the changelog, the next step is to choose a few changes to highlight in the release post. These highlights usually focus on new features and enhancements, including performance and accessibility ones, but can also include important API changes or critical bug fixes. Given the big scope of Gutenberg and the high number of pull requests merged in each milestone, it is not uncommon to overlook impactful changes worth highlighting; it is recommended to make this step a collaborative effort and share your picks with the Gutenberg Core team. @@ -88,8 +91,9 @@ Your post should also include a performance audit at the end, comparing the curr If the GitHub workflow fails, the performance audit can be executed locally using `bin/plugin/cli.js perf` and passing as parameters the tags to run the performance suite against, such as `bin/plugin/cli.js perf release/x.y release/x.z wp/a.b`. The performance values usually displayed in the release post are: -- Post Editor Loading Time (test named `load`) -- KeyPress Event (test named `typing`) + +- Post Editor Loading Time (test named `load`) +- KeyPress Event (test named `typing`) #### 5. Publishing the post @@ -121,7 +125,7 @@ Release types and their schedule: - [Minor WordPress Releases](#minor-wordpress-releases) (`patch` dist tag) – only when bug fixes or security releases need to be backported into WordPress Core. - [Development Releases](#development-releases) (`next` dist tag) – at least every two weeks when the RC version for the Gutenberg plugin is released. -There is also an option to perform [Standalone Package Releases](#standalone-package-releases) at will. It should be reserved only for critical bug fixes or security releases that must be published to _npm_ outside of a regular WordPress release cycle. +There is also an option to perform [Standalone Bugfix Package Releases](#standalone-bugfix-package-releases) at will. It should be reserved only for critical bug fixes or security releases that must be published to _npm_ outside of a regular WordPress release cycle. ### Synchronizing WordPress Trunk @@ -172,7 +176,7 @@ Now, the branch is ready to be used to publish the npm packages. Now, the npm packages should be ready and a patch can be created and committed into the corresponding WordPress SVN branch. -### Standalone Package Releases +### Standalone Bugfix Package Releases The following workflow is needed when packages require bug fixes or security releases to be published to _npm_ outside of a regular WordPress release cycle. @@ -217,6 +221,8 @@ Check the versions listed in the current `CHANGELOG.md` file, looking through th Note: You may discover the current version of each package is not up to date, if so updating the previous released versions would be appreciated. +The good news is that the rest of the process is automated with `./bin/plugin/cli.js npm-bugfix` command. The rest of the section covers all the necessary steps for publishing the packages if you prefer to do it manually. + Begin updating the _changelogs_ based on the [Maintaining Changelogs](https://github.com/WordPress/gutenberg/blob/HEAD/packages/README.md#maintaining-changelogs) documentation and commit the changes: 1. `git checkout wp/trunk`