diff --git a/.github/actions/prepare_release/action.yml b/.github/actions/create-release/action.yml similarity index 52% rename from .github/actions/prepare_release/action.yml rename to .github/actions/create-release/action.yml index 86c27c327b8..cce43d9348b 100644 --- a/.github/actions/prepare_release/action.yml +++ b/.github/actions/create-release/action.yml @@ -1,24 +1,18 @@ -name: prepare_release -description: Prepare React Native release +name: create_release +description: Creates a new React Native release runs: using: composite steps: - name: Yarn install shell: bash run: yarn install --non-interactive - - name: Versioning workspace packages - shell: bash - run: | - node scripts/releases/set-version "${{ inputs.version }}" --skip-react-native-version - - name: Versioning react-native package - shell: bash - run: | - node scripts/releases/set-rn-version.js -v "<< parameters.version >>" --build-type "release" - name: Creating release commit shell: bash run: | - git commit -a -m "Release ${{ inputs.version }}" -m "#publish-packages-to-npm&${{ inputs.tag }}" - git tag -a "v${{ inputs.version }}" -m "v${{ inputs.version }}" + node scripts/releases/create-release-commit.js \ + --reactNativeVersion "${{ inputs.version }}" \ + --tagAsLatestRelease "${{ inputs.is_latest_on_npm }}" \ + --dryRun "${{ inputs.dry_run }}" GIT_PAGER=cat git show HEAD - name: Update "latest" tag if needed shell: bash diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/create-release.yml similarity index 61% rename from .github/workflows/prepare-release.yml rename to .github/workflows/create-release.yml index e0af0de2241..85e14987843 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/create-release.yml @@ -1,16 +1,17 @@ -name: Prepare Release +name: Create release on: workflow_dispatch: inputs: version: - description: 'The version of React Native we want to release' + description: 'The version of React Native we want to release. For example 0.75.0-rc.0' required: true type: string - tag: - description: 'The tag name that will be associated with the published npm packages. A tag of "latest" will also be written as a Git tag.' + is_latest_on_npm: + description: 'Whether we want to tag this release as latest on NPM' required: true - type: string + type: boolean + default: false dry_run: description: 'Whether the job should be executed in dry-run mode or not' type: boolean @@ -33,6 +34,15 @@ jobs: fi - name: Print output run: echo "ON_STABLE_BRANCH ${{steps.check_stable_branch.outputs.ON_STABLE_BRANCH}}" + - name: Check if tag already exists + id: check_if_tag_exists + run: | + TAG="v${{ inputs.version }}" + TAG_EXISTS=$(git tag -l "$TAG") + if [[ -n "$TAG_EXISTS" ]]; then + echo "Version tag already exists!" + echo "TAG_EXISTS=true" >> $GITHUB_OUTPUT + fi - name: Execute Prepare Release - if: ${{ steps.check_stable_branch.outputs.ON_STABLE_BRANCH }} - uses: ./.github/actions/prepare_release + if: ${{ steps.check_stable_branch.outputs.ON_STABLE_BRANCH && !steps.check_if_tag_exists.outputs.TAG_EXISTS }} + uses: ./.github/actions/create-release diff --git a/scripts/releases-local/README.md b/scripts/releases-local/README.md deleted file mode 100644 index 38b818e601c..00000000000 --- a/scripts/releases-local/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# scripts/releases-local - -Local-only release scripts. - -## Commands - -For information on command arguments, run `node --help`. - -### `trigger-react-native-release.js` - -Trigger the external release publishing workflow on CircleCI. diff --git a/scripts/releases-local/trigger-react-native-release.js b/scripts/releases-local/trigger-react-native-release.js deleted file mode 100644 index 3d426f1a33c..00000000000 --- a/scripts/releases-local/trigger-react-native-release.js +++ /dev/null @@ -1,223 +0,0 @@ -/** - * 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. - * - * @flow - * @format - * @oncall react_native - */ - -'use strict'; - -const checkForGitChanges = require('../monorepo/check-for-git-changes'); -const {failIfTagExists} = require('../releases/utils/release-utils'); -const { - isReleaseBranch, - parseVersion, -} = require('../releases/utils/version-utils'); -const {exitIfNotOnGit, getBranchName} = require('../scm-utils'); -const {getPackages} = require('../utils/monorepo'); -const chalk = require('chalk'); -const inquirer = require('inquirer'); -const request = require('request'); -const {echo, exit} = require('shelljs'); -const yargs = require('yargs'); - -/** - * This script walks a releaser through bumping the version for a release - * It will commit the appropriate tags to trigger the CircleCI jobs. - */ - -let argv = yargs - .option('r', { - alias: 'remote', - default: 'origin', - }) - .option('t', { - alias: 'token', - describe: - 'Your GitHub personal API token. See https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens.', - required: true, - }) - .option('v', { - alias: 'to-version', - describe: 'Version you aim to release, ex. 0.67.0-rc.1, 0.66.3', - required: true, - }) - .option('dry-run', { - type: 'boolean', - default: false, - }) - .check(() => { - const branch = exitIfNotOnGit( - () => getBranchName(), - "Not in git. You can't invoke trigger-react-native-release from outside a git repo.", - ); - exitIfNotOnReleaseBranch(branch); - return true; - }).argv; - -function exitIfNotOnReleaseBranch(branch /*: string */) { - if (!isReleaseBranch(branch)) { - console.log( - 'This script must be run in a react-native git repository checkout and on a release branch', - ); - exit(1); - } -} - -/** - * Get the next version that all workspace packages will be set to. - * - * This approach is specific to the 0.74 release. For 0.75, the `--to-version` - * value will be used instead, setting all packages to a single version. - */ -async function getNextMonorepoPackagesVersion() /*: Promise */ { - // Based on last publish before this strategy - const _0_74_MIN_PATCH = 75; - - const packages = await getPackages({ - includeReactNative: false, - }); - - let patchVersion = _0_74_MIN_PATCH; - - for (const pkg of Object.values(packages)) { - const {version} = pkg.packageJson; - - if (!version.startsWith('0.74.') || version.endsWith('-main')) { - return null; - } - - const {patch} = parseVersion(version, 'release'); - patchVersion = Math.max(patchVersion, parseInt(patch, 10) + 1); - } - - return '0.74.' + patchVersion; -} - -function triggerReleaseWorkflow(options /*: $FlowFixMe */) { - return new Promise((resolve, reject) => { - request(options, function (error, response, body) { - if (error) { - reject(error); - } else { - resolve(body); - } - }); - }); -} - -async function main() { - const branch = exitIfNotOnGit( - () => getBranchName(), - "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); - } - - // $FlowFixMe[prop-missing] - const token = argv.token; - // $FlowFixMe[prop-missing] - const releaseVersion = argv.toVersion; - failIfTagExists(releaseVersion, 'release'); - - const {pushed} = await inquirer.prompt({ - type: 'confirm', - name: 'pushed', - message: `This script will trigger a release with whatever changes are on the remote branch: ${branch}. \nMake sure you have pushed any updates remotely.`, - }); - - if (!pushed) { - // $FlowFixMe[prop-missing] - console.log(`Please run 'git push ${argv.remote} ${branch}'`); - exit(1); - return; - } - - let latest = false; - const {version, prerelease} = parseVersion(releaseVersion, 'release'); - if (!prerelease) { - const {setLatest} = await inquirer.prompt({ - type: 'confirm', - name: 'setLatest', - message: `Do you want to set ${version} as "latest" release on npm?`, - }); - latest = setLatest; - } - - const npmTag = latest ? 'latest' : !prerelease ? branch : 'next'; - const {confirmRelease} = await inquirer.prompt({ - type: 'confirm', - name: 'confirmRelease', - message: `Releasing version "${version}" with npm tag "${npmTag}". Is this correct?`, - }); - - if (!confirmRelease) { - console.log('Aborting.'); - return; - } - - let nextMonorepoPackagesVersion = await getNextMonorepoPackagesVersion(); - - if (nextMonorepoPackagesVersion == null) { - // TODO(T182538198): Once this warning is hit, we can remove the - // `release_monorepo_packages_version` logic from here and the CI jobs, - // see other TODOs. - console.warn( - 'Warning: No longer on the 0.74-stable branch, meaning we will ' + - 'write all package versions identically. Please double-check the ' + - 'generated diff to see if this is correct.', - ); - nextMonorepoPackagesVersion = version; - } - - const parameters = { - version: version, - tag: npmTag, - // $FlowFixMe[prop-missing] - dry_run: argv.dryRun, - }; - - const options = { - method: 'POST', - url: 'https://api.github.com/repos/facebook/react-native/actions/workflows/prepare-release/dispatches', - headers: { - Authorization: `Bearer ${token}`, - Accept: 'application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - }, - body: { - ref: branch, - inputs: parameters, - }, - json: true, - }; - - // See response: https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event - await triggerReleaseWorkflow(options); - console.log( - // $FlowFixMe[incompatible-use] - 'Monitor your release workflow: https://github.com/facebook/react-native/actions/workflows/prepare-release.yml', - ); - - // TODO - // - Output the release changelog to paste into Github releases - // - Link to release discussions to update - // - Verify RN-diff publish is through -} - -// $FlowFixMe[unused-promise] -main().then(() => { - exit(0); -}); diff --git a/scripts/releases/README.md b/scripts/releases/README.md index 2c9f4ce06f8..e929d6fea71 100644 --- a/scripts/releases/README.md +++ b/scripts/releases/README.md @@ -21,3 +21,7 @@ Updates relevant files in the `react-native` package and template to materialize ### `update-template-package` Updates workspace dependencies in the template app`package.json` + +### `create-release-commit` + +Creates a release commit to trigger a new release diff --git a/scripts/releases/create-release-commit.js b/scripts/releases/create-release-commit.js new file mode 100644 index 00000000000..de240268be6 --- /dev/null +++ b/scripts/releases/create-release-commit.js @@ -0,0 +1,72 @@ +/** + * 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. + * + * @flow + * @format + * @oncall react_native + */ + +const setVersion = require('../releases/set-version'); +const {getBranchName} = require('../scm-utils'); +const {parseVersion} = require('./utils/version-utils'); +const {execSync} = require('child_process'); +const yargs = require('yargs'); + +async function main() { + const argv = await yargs + .option('reactNativeVersion', { + describe: 'The new React Native version.', + type: 'string', + required: true, + }) + .option('tagAsLatestRelease', { + describe: + 'Whether the version should be published as the latest version on npm.', + type: 'boolean', + default: false, + }) + .option('dryRun', { + description: 'Whether we should push the commit to github or not', + type: 'boolean', + default: true, + }).argv; + + const buildType = 'release'; + const version = argv.reactNativeVersion; + const latest = argv.tagAsLatestRelease; + const dryRun = argv.dryRun; + const branch = getBranchName(); + console.info(`Running on branch: ${branch}`); + + console.info('Validating version', version); + const {prerelease} = parseVersion(version, buildType); + + const npmTag = latest ? 'latest' : prerelease ? 'next' : branch; + console.info('NPM tag:', npmTag); + + console.info('Setting version for monorepo packages and react-native'); + await setVersion(version, false); // version, skip-react-native + + if (dryRun) { + console.info('Running in dry-run mode, skipping git commit'); + console.info( + `git commit -a -m "Release ${version}" -m "#publish-packages-to-npm&${npmTag}"`, + ); + console.info(`git tag -a v${version} -m "v${version}"`); + return; + } + + console.info('Committing to git'); + execSync( + `git commit -a -m "Release ${version}" -m "#publish-packages-to-npm&${npmTag}"`, + ); + execSync(`git tag -a v${version} -m "v${version}"`); +} + +if (require.main === module) { + // eslint-disable-next-line no-void + void main(); +}