diff --git a/package.json b/package.json index 516792e85ab773..7dcaf0af9d546c 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "test-ios": "./scripts/objc-test.sh test", "test-typescript": "dtslint packages/react-native/types", "test-typescript-offline": "dtslint --localTs node_modules/typescript/lib packages/react-native/types", - "bump-all-updated-packages": "node ./scripts/monorepo/bump-all-updated-packages" + "bump-all-updated-packages": "node ./scripts/monorepo/bump-all-updated-packages", + "trigger-react-native-release": "node ./scripts/trigger-react-native-release.js" }, "workspaces": [ "packages/*" 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 d32e3ea1b5a744..eef3f493de562a 100644 --- a/scripts/monorepo/bump-all-updated-packages/index.js +++ b/scripts/monorepo/bump-all-updated-packages/index.js @@ -25,6 +25,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, '..', '..', '..'); @@ -62,35 +63,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 b6cb1a12525a8c..75685422bfd72c 100755 --- a/scripts/publish-npm.js +++ b/scripts/publish-npm.js @@ -113,7 +113,7 @@ function getNpmInfo(buildType) { ); // See if releaser indicated that this version should be tagged "latest" - // Set in `bump-oss-version` + // Set in `trigger-react-native-release` const isLatest = exitIfNotOnGit( () => isTaggedLatest(currentCommit), 'Not in git. We do not want to publish anything', diff --git a/scripts/bump-oss-version.js b/scripts/trigger-react-native-release.js similarity index 65% rename from scripts/bump-oss-version.js rename to scripts/trigger-react-native-release.js index 188d562d435bc3..2986e95fca625b 100755 --- 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');