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
This commit is contained in:
Luna Wei
2022-01-18 12:15:08 -08:00
parent c3aa86bea2
commit 6fc004e95f
4 changed files with 86 additions and 117 deletions
+2 -1
View File
@@ -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:
+34 -44
View File
@@ -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-<invalid-version>');
}
expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
`"You must pass a correctly formatted version; couldn't parse <invalid-version>"`,
);
});
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');
});
});
+33 -21
View File
@@ -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`);
+17 -51
View File
@@ -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,
};