From 6fc004e95f30e13945bbbe223e44de19ddec4c36 Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Fri, 17 Dec 2021 18:35:48 -0800 Subject: [PATCH] Use tag to set publish version (#32757) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/32757 Changelog: [Internal] - Update release automation to still be manually triggered as from discussion: https://github.com/reactwg/react-native-releases/discussions/7 A releaser needs to do the following on a release branch like `0.99-stable`: * For an initial release branch cut: * Tag the head of the branch `git tag publish-v0.99.0-rc.0` * `git push origin 0.99-stable --follow-tags` * For cherry-picks on the pre-release: * Make the picks on `0.99-stable` * Tag the head of the branch `git tag publish-v0.99.0-rc.1` * `git push origin 0.99-stable --follow-tags` * For promoting pre-release to stable with intention of making this the `latest` npm version: * Tag the head of the branch `git tag publish-v0.99.0` * Tag the head of the branch `git tag latest` * `git push origin 0.99-stable --follow-tags` Follow-up diff to make this codified via a script Reviewed By: sota000 Differential Revision: D33101594 fbshipit-source-id: 74b065229a3705fccbe1a25ed7ece4a28d9aa76d --- .circleci/config.yml | 3 +- scripts/__tests__/version-utils-test.js | 78 +++++++++++-------------- scripts/prepare-package-for-release.js | 54 ++++++++++------- scripts/version-utils.js | 68 ++++++--------------- 4 files changed, 86 insertions(+), 117 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 897cd545a12..f9d4dbb5598 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -856,7 +856,8 @@ workflows: releases: jobs: - # This job will trigger on pushes to release branch and commit a version tag to trigger `build_npm_package` for release + # This job will trigger on relevant `publish...` tags. + # It prepares the package and triggers `build_npm_package` for release - prepare_package_for_release: name: prepare_package_for_release filters: diff --git a/scripts/__tests__/version-utils-test.js b/scripts/__tests__/version-utils-test.js index d9a463f91a2..8c8b17ae519 100644 --- a/scripts/__tests__/version-utils-test.js +++ b/scripts/__tests__/version-utils-test.js @@ -9,10 +9,10 @@ const { parseVersion, - getNextVersionFromTags, isTaggedLatest, - isTaggedVersion, + getPublishVersion, isReleaseBranch, + getPublishTag, } = require('../version-utils'); let execResult = null; @@ -25,33 +25,6 @@ jest.mock('shelljs', () => ({ })); describe('version-utils', () => { - describe('isTaggedVersion', () => { - it('should return true on pre-release versions', () => { - execResult = 'v0.66.0-rc.3\nlatest\n\n'; - expect(isTaggedVersion('6c19dc3266b84f47a076b647a1c93b3c3b69d2c5')).toBe( - true, - ); - }); - it('should return true on release versions', () => { - execResult = 'latest\nv0.66.2\n\n'; - expect(isTaggedVersion('6c19dc3266b84f47a076b647a1c93b3c3b69d2c5')).toBe( - true, - ); - }); - it('should return false when no tags', () => { - execResult = '\n'; - expect(isTaggedVersion('6c19dc3266b84f47a076b647a1c93b3c3b69d2c5')).toBe( - false, - ); - }); - it('should return false on tags that are not versions', () => { - execResult = 'latest\n0.someother-made-up-tag\n\n'; - expect(isTaggedVersion('6c19dc3266b84f47a076b647a1c93b3c3b69d2c5')).toBe( - false, - ); - }); - }); - describe('isReleaseBranch', () => { it('should identify as release branch', () => { expect(isReleaseBranch('v0.66-stable')).toBe(true); @@ -76,22 +49,39 @@ describe('version-utils', () => { }); }); - describe('getNextVersionFromTags', () => { - it('should increment last stable tag', () => { - execResult = - 'v0.66.3\nv0.66.2\nv0.66.1\nv0.66.0-rc.4\nv0.66.0-rc.3\nv0.66.0-rc.2\nv0.66.0-rc.1\nv0.66.0-rc.0'; - expect(getNextVersionFromTags('0.66-stable')).toBe('0.66.4'); - }); - - it('should find last prerelease tag and increment', () => { - execResult = - 'v0.66.0-rc.4\nv0.66.0-rc.3\nv0.66.0-rc.2\nv0.66.0-rc.1\nv0.66.0-rc.0'; - expect(getNextVersionFromTags('0.66-stable')).toBe('0.66.0-rc.5'); - }); - - it('should return rc.0 version if no previous tags', () => { + describe('getPublishTag', () => { + it('Should return null no tags are returned', () => { execResult = '\n'; - expect(getNextVersionFromTags('0.66-stable')).toBe('0.66.0-rc.0'); + expect(getPublishTag()).toBe(null); + }); + it('Should return tag', () => { + execResult = 'publish-v999.0.0-rc.0\n'; + expect(getPublishTag()).toBe('publish-v999.0.0-rc.0'); + }); + }); + + describe('getPublishVersion', () => { + it('Should return null if invalid tag provided', () => { + expect(getPublishVersion('')).toBe(null); + expect(getPublishVersion('something')).toBe(null); + }); + it('should throw error if invalid tag version provided', () => { + function testInvalidVersion() { + getPublishVersion('publish-'); + } + expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot( + `"You must pass a correctly formatted version; couldn't parse "`, + ); + }); + it('Should return version for tag', () => { + const {version, major, minor, patch, prerelease} = getPublishVersion( + 'publish-v0.67.0-rc.6', + ); + expect(version).toBe('0.67.0-rc.6'); + expect(major).toBe('0'); + expect(minor).toBe('67'); + expect(patch).toBe('0'); + expect(prerelease).toBe('rc.6'); }); }); diff --git a/scripts/prepare-package-for-release.js b/scripts/prepare-package-for-release.js index 529fa5e870f..d5a47e0e60c 100755 --- a/scripts/prepare-package-for-release.js +++ b/scripts/prepare-package-for-release.js @@ -23,8 +23,8 @@ const yargs = require('yargs'); const { isReleaseBranch, isTaggedLatest, - isTaggedVersion, - getNextVersionFromTags, + getPublishVersion, + getPublishTag, } = require('./version-utils'); const branch = process.env.CIRCLE_BRANCH; @@ -34,12 +34,12 @@ const argv = yargs.option('r', { alias: 'remote', default: 'origin', }).argv; +const remote = argv.remote; -// We do this check to prevent a loop of commit in this script to trigger the job again. -// I haven't figured out a way for CircleCI to filter out commits from CircleCI jobs -if (isTaggedVersion(currentCommit)) { +const tag = getPublishTag(); +if (tag == null) { console.log( - 'Skip running prepare-package-for-release as this job was triggered from previous run of this script.', + 'No publish tag set. Not publishing this release.\nCircleCI cannot filter workflows on both branch and tag so we do this check in prepare-package-for-release', ); exit(0); } @@ -49,8 +49,21 @@ if (!isReleaseBranch(branch)) { exit(1); } -// Progress the version by 1 using existing git tags -const version = getNextVersionFromTags(branch); +// Get the version we're publishing from the publish tag +// Tag of the form `publish-v{versionStr}` +const versionInfo = getPublishVersion(tag); +if (versionInfo == null) { + console.error( + `Invalid tag provided: ${tag}, needs to be of form 'publish-v{major}.{minor}.{patch}'`, + ); + exit(1); +} + +// Clean up tag now that we're publishing the release. +exec(`git tag -d ${tag}`); +exec(`git push ${remote} :${tag}`); + +const {version} = versionInfo; if (exec(`node scripts/set-rn-version.js --to-version ${version}`).code) { echo(`Failed to set React Native version to ${version}`); @@ -65,24 +78,12 @@ if (exec('source scripts/update_podfile_lock.sh && update_pods').code) { exit(1); } -// Check if this release has been tagged as latest -const isLatest = isTaggedLatest(currentCommit); - // Make commit [0.21.0-rc] Bump version numbers if (exec(`git commit -a -m "[${version}] Bump version numbers"`).code) { echo('failed to commit'); exit(1); } -// Since we just committed, if `isLatest`, move the tag to commit we just made -// This tag will also update npm release as `latest` -if (isLatest) { - exec('git tag -d latest'); - exec(`git push ${remote} :latest`); - exec('git tag latest'); - exec(`git push ${remote} latest`); -} - // Add tag v0.21.0-rc.1 if (exec(`git tag v${version}`).code) { echo( @@ -93,8 +94,19 @@ if (exec(`git tag v${version}`).code) { exit(1); } +// See if `latest` was set on the commit that triggered this script +// If yes, move the tag to commit we just made +// This tag will also update npm release as `latest` +const isLatest = isTaggedLatest(currentCommit); +if (isLatest) { + exec('git tag -d latest'); + exec(`git push ${remote} :latest`); + + // This will be pushed with the `--follow-tags` + exec('git tag latest'); +} + // Push newly created tag -let remote = argv.remote; exec(`git push ${remote} v${version}`); exec(`git push ${remote} ${branch} --follow-tags`); diff --git a/scripts/version-utils.js b/scripts/version-utils.js index 807bd9a5fd2..58177067c53 100644 --- a/scripts/version-utils.js +++ b/scripts/version-utils.js @@ -28,59 +28,17 @@ function parseVersion(versionStr) { }; } -function getLatestVersionTag(branchVersion) { - // Returns list of tags like ["v0.67.2", "v0.67.1", "v0.67.0-rc.3", "v0.67.0-rc.2", ...] in reverse lexical order - const tags = exec(`git tag --list "v${branchVersion}*" --sort=-refname`, { - silent: true, - }) - .stdout.trim() - .split('\n') - .filter(tag => tag.length > 0); - - // If there are no tags, return null - if (tags.length === 0) { - return null; - } - - // Return most recent tag (with the "v" prefix) - return tags[0]; -} - -function getNextVersionFromTags(branch) { - // Assumption that branch names will follow pattern `{major}.{minor}-stable` - // Ex. "0.67-stable" -> "0.67" - const branchVersion = branch.replace('-stable', ''); - - // Get the latest version tag of the release branch - const versionTag = getLatestVersionTag(branchVersion); - - // If there are no tags , we assume this is the first pre-release - if (versionTag == null) { - return `${branchVersion}.0-rc.0`; - } - - const {major, minor, patch, prerelease} = parseVersion(versionTag); - if (prerelease != null) { - // prelease is of the form "rc.X" - const prereleasePatch = parseInt(prerelease.slice(3), 10); - return `${major}.${minor}.${patch}-rc.${prereleasePatch + 1}`; - } - - // If not prerelease, increment the patch version - return `${major}.${minor}.${parseInt(patch, 10) + 1}`; -} - function isReleaseBranch(branch) { return branch.endsWith('-stable'); } -function isTaggedVersion(commitSha) { - const tags = exec(`git tag --points-at ${commitSha}`, { - silent: true, - }) - .stdout.trim() - .split('\n'); - return tags.some(tag => !!tag.match(VERSION_REGEX)); +function getPublishVersion(tag) { + if (!tag.startsWith('publish-')) { + return null; + } + + const versionStr = tag.replace('publish-', ''); + return parseVersion(versionStr); } function isTaggedLatest(commitSha) { @@ -91,10 +49,18 @@ function isTaggedLatest(commitSha) { ); } +function getPublishTag() { + // Assumes we only ever have one tag with the prefix `publish-v` + const tag = exec("git tag --points-at HEAD | grep 'publish-v'", { + silent: true, + }).stdout.trim(); + return tag ? tag : null; +} + module.exports = { isTaggedLatest, - isTaggedVersion, + getPublishTag, + getPublishVersion, parseVersion, - getNextVersionFromTags, isReleaseBranch, };