Skip to content

Commit d942487

Browse files
Luna Weikelset
authored andcommitted
Use CircleCI API to trigger releases (#32937)
Summary: Changelog: [Internal] * Refactor release automation so it doesn't use intermediate tags to trigger the release workflow. Now, we POST to CircleCI's ["trigger pipeline" API](https://circleci.com/docs/api/v2/#operation/triggerPipeline) * This does have the con of needing to give CircleCI project permission for whoever wants to run a release as you'll need a token to trigger * See related discussion: reactwg/react-native-releases#7 (reply in thread) Description of changes: * Changes to config.yml allow us to use our POST data to trigger a certain workflow. There is no direct API to trigger a workflow, CircleCI only has an API to trigger pipelines, so the suggestion is to leverage conditionals: https://support.circleci.com/hc/en-us/articles/360050351292-How-to-trigger-a-workflow-via-CircleCI-API-v2 * Update `bump-oss-version` to make the api call -- the instructions for running a release are still the same: https://github.com/facebook/react-native/wiki/Release-Process#creating-0minor0-rc0 * Would be good to make this a yarn script as tido64 mentioned * Remove unused utilities now that we don't use intermediate tags like `publish-...` Pull Request resolved: #32937 Test Plan: Have this on a Github branch and tried this out locally: ## Running release script Running the bump-oss script (I had to hack it locally to be allowed to run on a non-release branch): {F694804729} * Link to resulting workflow: https://app.circleci.com/pipelines/github/facebook/react-native/11836 -- you can [verify that the parameters are the same as I passed](https://app.circleci.com/pipelines/github/facebook/react-native/11836/workflows/59ac0c86-5fe3-4d7a-80e9-c61129d49e9f/jobs/232388?invite=true#step-106-2) ## Other attempts triggering this workflow Notice that when I tried to run it on an actual release-branch (0.67-stable) but because the `config.yml` changes aren't on that branch, the job faisl | {F694804321} | ## Verifying the right workflows trigger on a regular push * Notice that the workflows "analysis" and "test" are still triggered on push (ie, the `unless:` clause isn't messing things up) {F694804320} Reviewed By: sota000 Differential Revision: D33715336 Pulled By: lunaleaps fbshipit-source-id: 82672d6d50768015bdfc9f4ea4d22aa801b84f06
1 parent 983f0be commit d942487

File tree

5 files changed

+132
-155
lines changed

5 files changed

+132
-155
lines changed

.circleci/config.yml

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,12 @@ jobs:
711711
# JOBS: Releases
712712
# -------------------------
713713
prepare_package_for_release:
714+
parameters:
715+
version:
716+
type: string
717+
latest:
718+
type: boolean
719+
default: false
714720
executor: reactnativeios
715721
steps:
716722
- checkout
@@ -721,7 +727,7 @@ jobs:
721727
- run:
722728
name: "Set new react-native version and commit changes"
723729
command: |
724-
node ./scripts/prepare-package-for-release.js
730+
node ./scripts/prepare-package-for-release.js -v << parameters.version >> -l << parameters.latest >>
725731
726732
build_npm_package:
727733
parameters:
@@ -787,13 +793,35 @@ jobs:
787793
command: |
788794
echo "Nightly build run"
789795
796+
797+
# -------------------------
798+
# PIPELINE PARAMETERS
799+
# -------------------------
800+
parameters:
801+
run_package_release_workflow_only:
802+
default: false
803+
type: boolean
804+
805+
release_latest:
806+
default: false
807+
type: boolean
808+
809+
release_version:
810+
default: "9999"
811+
type: string
812+
790813
# -------------------------
791814
# WORK FLOWS
815+
#
816+
# When creating a new workflow, make sure to include condition `unless: << pipeline.parameters.run_package_release_workflow_only >>`
817+
# It's setup this way so we can trigger a release via a POST
818+
# See limitations: https://support.circleci.com/hc/en-us/articles/360050351292-How-to-trigger-a-workflow-via-CircleCI-API-v2
792819
# -------------------------
793820
workflows:
794821
version: 2
795822

796823
tests:
824+
unless: << pipeline.parameters.run_package_release_workflow_only >>
797825
jobs:
798826
- test_js:
799827
run_disabled_tests: false
@@ -854,20 +882,20 @@ workflows:
854882
ignore: gh-pages
855883
run_disabled_tests: false
856884

857-
releases:
885+
# This workflow should only be triggered by release script
886+
package_release:
887+
when: << pipeline.parameters.run_package_release_workflow_only >>
858888
jobs:
859-
# This job will trigger on relevant release branch pushes.
860-
# It prepares the package and triggers `build_npm_package` for release
889+
# This job will trigger publish_release workflow
861890
- prepare_package_for_release:
862891
name: prepare_package_for_release
863-
# Since CircleCI does not support branch AND tag filters, we manually check in job
864-
# and no-op if there is no `publish-v{version}` tag set.
865-
filters:
866-
branches:
867-
only:
868-
- /^(\d+)\.(\d+)-stable$/
892+
version: << pipeline.parameters.release_version >>
893+
latest : << pipeline.parameters.release_latest >>
869894

870-
# This job will trigger when a version tag is pushed (by prepare_package_for_release)
895+
publish_release:
896+
unless: << pipeline.parameters.run_package_release_workflow_only >>
897+
jobs:
898+
# This job will trigger when a version tag is pushed (by package_release)
871899
- build_npm_package:
872900
name: build_and_publish_npm_package
873901
publish_npm_args: --release
@@ -895,6 +923,7 @@ workflows:
895923
ignore: /v[0-9]+(\.[0-9]+)*(\-rc(\.[0-9]+)?)?/
896924

897925
analysis:
926+
unless: << pipeline.parameters.run_package_release_workflow_only >>
898927
jobs:
899928
# Run lints on every commit other than those to the gh-pages branch
900929
- analyze_code:
@@ -915,6 +944,7 @@ workflows:
915944
ignore: gh-pages
916945

917946
nightly:
947+
unless: << pipeline.parameters.run_package_release_workflow_only >>
918948
triggers:
919949
- schedule:
920950
cron: "0 20 * * *"

scripts/__tests__/version-utils-test.js

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
const {
1111
parseVersion,
1212
isTaggedLatest,
13-
getPublishVersion,
1413
isReleaseBranch,
15-
getPublishTag,
1614
} = require('../version-utils');
1715

1816
let execResult = null;
@@ -49,42 +47,6 @@ describe('version-utils', () => {
4947
});
5048
});
5149

52-
describe('getPublishTag', () => {
53-
it('Should return null no tags are returned', () => {
54-
execResult = '\n';
55-
expect(getPublishTag()).toBe(null);
56-
});
57-
it('Should return tag', () => {
58-
execResult = 'publish-v999.0.0-rc.0\n';
59-
expect(getPublishTag()).toBe('publish-v999.0.0-rc.0');
60-
});
61-
});
62-
63-
describe('getPublishVersion', () => {
64-
it('Should return null if invalid tag provided', () => {
65-
expect(getPublishVersion('')).toBe(null);
66-
expect(getPublishVersion('something')).toBe(null);
67-
});
68-
it('should throw error if invalid tag version provided', () => {
69-
function testInvalidVersion() {
70-
getPublishVersion('publish-<invalid-version>');
71-
}
72-
expect(testInvalidVersion).toThrowErrorMatchingInlineSnapshot(
73-
`"You must pass a correctly formatted version; couldn't parse <invalid-version>"`,
74-
);
75-
});
76-
it('Should return version for tag', () => {
77-
const {version, major, minor, patch, prerelease} = getPublishVersion(
78-
'publish-v0.67.0-rc.6',
79-
);
80-
expect(version).toBe('0.67.0-rc.6');
81-
expect(major).toBe('0');
82-
expect(minor).toBe('67');
83-
expect(patch).toBe('0');
84-
expect(prerelease).toBe('rc.6');
85-
});
86-
});
87-
8850
describe('parseVersion', () => {
8951
it('should throw error if invalid match', () => {
9052
function testInvalidVersion() {

scripts/bump-oss-version.js

Lines changed: 67 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
const {exec, exit} = require('shelljs');
1919
const yargs = require('yargs');
2020
const inquirer = require('inquirer');
21+
const request = require('request');
22+
2123
const {
2224
parseVersion,
2325
isReleaseBranch,
@@ -29,6 +31,17 @@ let argv = yargs
2931
alias: 'remote',
3032
default: 'origin',
3133
})
34+
.option('t', {
35+
alias: 'token',
36+
describe:
37+
'Your CircleCI personal API token. See https://circleci.com/docs/2.0/managing-api-tokens/#creating-a-personal-api-token to set one',
38+
required: true,
39+
})
40+
.option('v', {
41+
alias: 'to-version',
42+
describe: 'Version you aim to release, ex. 0.67.0-rc.1, 0.66.3',
43+
required: true,
44+
})
3245
.check(() => {
3346
const branch = getBranchName();
3447
exitIfNotOnReleaseBranch(branch);
@@ -57,47 +70,47 @@ function getLatestTag(versionPrefix) {
5770
return null;
5871
}
5972

73+
function triggerReleaseWorkflow(options) {
74+
return new Promise((resolve, reject) => {
75+
request(options, function (error, response, body) {
76+
if (error) {
77+
reject(error);
78+
} else {
79+
resolve(body);
80+
}
81+
});
82+
});
83+
}
84+
6085
async function main() {
6186
const branch = getBranchName();
87+
const token = argv.token;
88+
const releaseVersion = argv.toVersion;
6289

63-
const {pulled} = await inquirer.prompt({
90+
const {pushed} = await inquirer.prompt({
6491
type: 'confirm',
65-
name: 'pulled',
66-
message: `You are currently on branch: ${branch}. Have you run "git pull ${argv.remote} ${branch} --tags"?`,
92+
name: 'pushed',
93+
message: `This script will trigger a release with whatever changes are on the remote branch: ${branch}. \nMake sure you have pushed any updates remotely.`,
6794
});
6895

69-
if (!pulled) {
70-
console.log(`Please run 'git pull ${argv.remote} ${branch} --tags'`);
96+
if (!pushed) {
97+
console.log(`Please run 'git push ${argv.remote} ${branch}'`);
7198
exit(1);
7299
return;
73100
}
74101

75-
const lastVersionTag = getLatestTag(branch.replace('-stable', ''));
76-
const lastVersion = lastVersionTag
77-
? parseVersion(lastVersionTag).version
78-
: null;
79-
const lastVersionMessage = lastVersion
80-
? `Last version tagged is ${lastVersion}.\n`
81-
: '';
82-
83-
const {releaseVersion} = await inquirer.prompt({
84-
type: 'input',
85-
name: 'releaseVersion',
86-
message: `What version are you releasing? (Ex. 0.66.0-rc.4)\n${lastVersionMessage}`,
87-
});
88-
89-
let setLatest = false;
90-
102+
let latest = false;
91103
const {version, prerelease} = parseVersion(releaseVersion);
92104
if (!prerelease) {
93-
const {latest} = await inquirer.prompt({
105+
const {setLatest} = await inquirer.prompt({
94106
type: 'confirm',
95-
name: 'latest',
96-
message: 'Set this version as "latest" on npm?',
107+
name: 'setLatest',
108+
message: `Do you want to set ${version} as "latest" release on npm?`,
97109
});
98-
setLatest = latest;
110+
latest = setLatest;
99111
}
100-
const npmTag = setLatest ? 'latest' : !prerelease ? branch : 'next';
112+
113+
const npmTag = latest ? 'latest' : !prerelease ? branch : 'next';
101114
const {confirmRelease} = await inquirer.prompt({
102115
type: 'confirm',
103116
name: 'confirmRelease',
@@ -109,32 +122,36 @@ async function main() {
109122
return;
110123
}
111124

112-
if (
113-
exec(`git tag -a publish-v${version} -m "publish version ${version}"`).code
114-
) {
115-
console.error(`Failed to tag publish-v${version}`);
116-
exit(1);
117-
return;
118-
}
119-
120-
if (setLatest) {
121-
exec('git tag -d latest');
122-
exec(`git push ${argv.remote} :latest`);
123-
exec('git tag -a latest -m "latest"');
124-
}
125-
126-
if (exec(`git push ${argv.remote} ${branch} --follow-tags`).code) {
127-
console.error(`Failed to push tag publish-v${version}`);
128-
exit(1);
129-
return;
130-
}
125+
const parameters = {
126+
release_version: version,
127+
release_latest: latest,
128+
run_package_release_workflow_only: true,
129+
};
130+
131+
const options = {
132+
method: 'POST',
133+
url: 'https://circleci.com/api/v2/project/github/facebook/react-native/pipeline',
134+
headers: {
135+
'Circle-Token': token,
136+
'content-type': 'application/json',
137+
},
138+
body: {
139+
branch,
140+
parameters,
141+
},
142+
json: true,
143+
};
144+
145+
// See response: https://circleci.com/docs/api/v2/#operation/triggerPipeline
146+
const body = await triggerReleaseWorkflow(options);
147+
console.log(
148+
`Monitor your release workflow: https://app.circleci.com/pipelines/github/facebook/react-native/${body.number}`,
149+
);
131150

132151
// TODO
133-
// 1. Link to CircleCI job to watch
134-
// 2. Output the release changelog to paste into Github releases
135-
// 3. Link to release discussions to update
136-
// 4. Verify RN-diff publish is through
137-
// 5. General changelog update on PR?
152+
// - Output the release changelog to paste into Github releases
153+
// - Link to release discussions to update
154+
// - Verify RN-diff publish is through
138155
}
139156

140157
main().then(() => {

0 commit comments

Comments
 (0)