From 91578418f91d845d30aefc0be766810d18936530 Mon Sep 17 00:00:00 2001 From: Lorenzo Sciandra Date: Wed, 9 Aug 2023 00:46:14 -0700 Subject: [PATCH] chore(releases): improve bump oss script to allow less human errors (#38666) Summary: One of the limitations of the existing flow for the release crew is that they need to manually remember to publish all the other packages in the monorepo ahead of a new patch release - this PR modifies the logic for the bump-oss-version script (and makes it available via yarn) so that it will not run if: * there are git changes lying around * if some of the packages need a new release it required a bit of refactoring to extract some portions of the logic from the bump-all-package-versions script, but I think the end result is pretty decent. ## Changelog: [INTERNAL] [CHANGED] - improve bump oss script to allow less human errors Pull Request resolved: https://github.com/facebook/react-native/pull/38666 Test Plan: * checkout this branch * comment L54 of bump-oss-version.js (to remove the check on the branch name) * run `yarn bump-all-updated-packages`, verify that it works and that it detects that some packages have unreleased code * run `yarn bump-oss-version -t asd -v asd` (the "fake" parameters are needed to pass the yargs check), verify that it will throw an error because it finds a package that has unreleased code Reviewed By: mdvacca Differential Revision: D48156963 Pulled By: cortinico fbshipit-source-id: 2473ad5a84578c5236c905fd9aa9a88113fe8d22 # Conflicts: # scripts/publish-npm.js re-add the file nit # Conflicts: # package.json --- package.json | 3 +- .../bump-all-updated-packages/bump-utils.js | 49 ++++++++++++ .../bump-all-updated-packages/index.js | 34 ++------ scripts/publish-npm.js | 2 +- ...ion.js => trigger-react-native-release.js} | 77 ++++++++++++++++++- 5 files changed, 134 insertions(+), 31 deletions(-) create mode 100644 scripts/monorepo/bump-all-updated-packages/bump-utils.js rename scripts/{bump-oss-version.js => trigger-react-native-release.js} (65%) mode change 100755 => 100644 diff --git a/package.json b/package.json index 682b9dcbaded7c..808f619f77d447 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,8 @@ "test-typescript": "dtslint types", "test-typescript-offline": "dtslint --localTs node_modules/typescript/lib types", "bump-all-updated-packages": "node ./scripts/monorepo/bump-all-updated-packages", - "align-package-versions": "node ./scripts/monorepo/align-package-versions.js" + "align-package-versions": "node ./scripts/monorepo/align-package-versions.js", + "trigger-react-native-release": "node ./scripts/trigger-react-native-release.js" }, "peerDependencies": { "react": "18.2.0" diff --git a/scripts/monorepo/bump-all-updated-packages/bump-utils.js b/scripts/monorepo/bump-all-updated-packages/bump-utils.js new file mode 100644 index 00000000000000..07c57e5313ab27 --- /dev/null +++ b/scripts/monorepo/bump-all-updated-packages/bump-utils.js @@ -0,0 +1,49 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +const chalk = require('chalk'); +const {echo, exec} = require('shelljs'); + +const detectPackageUnreleasedChanges = ( + packageRelativePathFromRoot, + packageName, + ROOT_LOCATION, +) => { + const hashOfLastCommitInsidePackage = exec( + `git log -n 1 --format=format:%H -- ${packageRelativePathFromRoot}`, + {cwd: ROOT_LOCATION, silent: true}, + ).stdout.trim(); + + const hashOfLastCommitThatChangedVersion = exec( + `git log -G\\"version\\": --format=format:%H -n 1 -- ${packageRelativePathFromRoot}/package.json`, + {cwd: ROOT_LOCATION, silent: true}, + ).stdout.trim(); + + if (hashOfLastCommitInsidePackage === hashOfLastCommitThatChangedVersion) { + echo( + `\uD83D\uDD0E No changes for package ${chalk.green( + packageName, + )} since last version bump`, + ); + return false; + } else { + echo(`\uD83D\uDCA1 Found changes for ${chalk.yellow(packageName)}:`); + exec( + `git log --pretty=oneline ${hashOfLastCommitThatChangedVersion}..${hashOfLastCommitInsidePackage} ${packageRelativePathFromRoot}`, + { + cwd: ROOT_LOCATION, + }, + ); + echo(); + + return true; + } +}; + +module.exports = detectPackageUnreleasedChanges; diff --git a/scripts/monorepo/bump-all-updated-packages/index.js b/scripts/monorepo/bump-all-updated-packages/index.js index 09291c0d723423..760ca3d3140f8d 100644 --- a/scripts/monorepo/bump-all-updated-packages/index.js +++ b/scripts/monorepo/bump-all-updated-packages/index.js @@ -24,6 +24,7 @@ const { const forEachPackage = require('../for-each-package'); const checkForGitChanges = require('../check-for-git-changes'); const bumpPackageVersion = require('./bump-package-version'); +const detectPackageUnreleasedChanges = require('./bump-utils'); const ROOT_LOCATION = path.join(__dirname, '..', '..', '..'); @@ -61,35 +62,16 @@ const buildExecutor = return; } - const hashOfLastCommitInsidePackage = exec( - `git log -n 1 --format=format:%H -- ${packageRelativePathFromRoot}`, - {cwd: ROOT_LOCATION, silent: true}, - ).stdout.trim(); - - const hashOfLastCommitThatChangedVersion = exec( - `git log -G\\"version\\": --format=format:%H -n 1 -- ${packageRelativePathFromRoot}/package.json`, - {cwd: ROOT_LOCATION, silent: true}, - ).stdout.trim(); - - if (hashOfLastCommitInsidePackage === hashOfLastCommitThatChangedVersion) { - echo( - `\uD83D\uDD0E No changes for package ${chalk.green( - packageName, - )} since last version bump`, - ); - + if ( + !detectPackageUnreleasedChanges( + packageRelativePathFromRoot, + packageName, + ROOT_LOCATION, + ) + ) { return; } - echo(`\uD83D\uDCA1 Found changes for ${chalk.yellow(packageName)}:`); - exec( - `git log --pretty=oneline ${hashOfLastCommitThatChangedVersion}..${hashOfLastCommitInsidePackage} ${packageRelativePathFromRoot}`, - { - cwd: ROOT_LOCATION, - }, - ); - echo(); - await inquirer .prompt([ { diff --git a/scripts/publish-npm.js b/scripts/publish-npm.js index 3cf96f5f1783fc..60b96d4c68b4b6 100755 --- a/scripts/publish-npm.js +++ b/scripts/publish-npm.js @@ -99,7 +99,7 @@ const rawVersion = : // For nightly we continue to use 0.0.0 for clarity for npm nightlyBuild ? '0.0.0' - : // For pre-release and stable releases, we use the git tag of the version we're releasing (set in bump-oss-version) + : // For pre-release and stable releases, we use the git tag of the version we're releasing (set in trigger-react-native-release) buildTag; let version, diff --git a/scripts/bump-oss-version.js b/scripts/trigger-react-native-release.js old mode 100755 new mode 100644 similarity index 65% rename from scripts/bump-oss-version.js rename to scripts/trigger-react-native-release.js index bf8c3d8e91394a..25640c31d9aaaa --- a/scripts/bump-oss-version.js +++ b/scripts/trigger-react-native-release.js @@ -14,14 +14,21 @@ * This script walks a releaser through bumping the version for a release * It will commit the appropriate tags to trigger the CircleCI jobs. */ -const {exit} = require('shelljs'); +const {exit, echo} = require('shelljs'); +const chalk = require('chalk'); const yargs = require('yargs'); const inquirer = require('inquirer'); const request = require('request'); +const path = require('path'); const {getBranchName, exitIfNotOnGit} = require('./scm-utils'); const {parseVersion, isReleaseBranch} = require('./version-utils'); const {failIfTagExists} = require('./release-utils'); +const checkForGitChanges = require('./monorepo/check-for-git-changes'); +const forEachPackage = require('./monorepo/for-each-package'); +const detectPackageUnreleasedChanges = require('./monorepo/bump-all-updated-packages/bump-utils.js'); + +const ROOT_LOCATION = path.join(__dirname, '..'); let argv = yargs .option('r', { @@ -42,7 +49,7 @@ let argv = yargs .check(() => { const branch = exitIfNotOnGit( () => getBranchName(), - "Not in git. You can't invoke bump-oss-versions.js from outside a git repo.", + "Not in git. You can't invoke trigger-react-native-release from outside a git repo.", ); exitIfNotOnReleaseBranch(branch); return true; @@ -57,6 +64,52 @@ function exitIfNotOnReleaseBranch(branch) { } } +const buildExecutor = + (packageAbsolutePath, packageRelativePathFromRoot, packageManifest) => + async () => { + const {name: packageName} = packageManifest; + if (packageManifest.private) { + return; + } + + if ( + detectPackageUnreleasedChanges( + packageRelativePathFromRoot, + packageName, + ROOT_LOCATION, + ) + ) { + // if I enter here, I want to throw an error upward + throw new Error( + `Package ${packageName} has unreleased changes. Please release it first.`, + ); + } + }; + +const buildAllExecutors = () => { + const executors = []; + + forEachPackage((...params) => { + executors.push(buildExecutor(...params)); + }); + + return executors; +}; + +async function exitIfUnreleasedPackages() { + // use the other script to verify that there's no packages in the monorepo + // that have changes that haven't been released + + const executors = buildAllExecutors(); + for (const executor of executors) { + await executor().catch(error => { + echo(chalk.red(error)); + // need to throw upward + throw error; + }); + } +} + function triggerReleaseWorkflow(options) { return new Promise((resolve, reject) => { request(options, function (error, response, body) { @@ -72,8 +125,26 @@ function triggerReleaseWorkflow(options) { async function main() { const branch = exitIfNotOnGit( () => getBranchName(), - "Not in git. You can't invoke bump-oss-versions.js from outside a git repo.", + "Not in git. You can't invoke trigger-react-native-release from outside a git repo.", ); + + // check for uncommitted changes + if (checkForGitChanges()) { + echo( + chalk.red( + 'Found uncommitted changes. Please commit or stash them before running this script', + ), + ); + exit(1); + } + + // now check for unreleased packages + try { + await exitIfUnreleasedPackages(); + } catch (error) { + exit(1); + } + const token = argv.token; const releaseVersion = argv.toVersion; failIfTagExists(releaseVersion, 'release');