1#!/usr/bin/env node
2/**
3 * Copyright (c) Meta Platforms, Inc. and affiliates.
4 *
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the root directory of this source tree.
7 *
8 * @format
9 */
10
11'use strict';
12
13/**
14 * This script walks a releaser through bumping the version for a release
15 * It will commit the appropriate tags to trigger the CircleCI jobs.
16 */
17const {exit} = require('shelljs');
18const yargs = require('yargs');
19const inquirer = require('inquirer');
20const request = require('request');
21
22const {
23  parseVersion,
24  isReleaseBranch,
25  getBranchName,
26} = require('./version-utils');
27
28let argv = yargs
29  .option('r', {
30    alias: 'remote',
31    default: 'origin',
32  })
33  .option('t', {
34    alias: 'token',
35    describe:
36      'Your CircleCI personal API token. See https://circleci.com/docs/2.0/managing-api-tokens/#creating-a-personal-api-token to set one',
37    required: true,
38  })
39  .option('v', {
40    alias: 'to-version',
41    describe: 'Version you aim to release, ex. 0.67.0-rc.1, 0.66.3',
42    required: true,
43  })
44  .check(() => {
45    const branch = getBranchName();
46    exitIfNotOnReleaseBranch(branch);
47    return true;
48  }).argv;
49
50function exitIfNotOnReleaseBranch(branch) {
51  if (!isReleaseBranch(branch)) {
52    console.log(
53      'This script must be run in a react-native git repository checkout and on a release branch',
54    );
55    exit(1);
56  }
57}
58
59function triggerReleaseWorkflow(options) {
60  return new Promise((resolve, reject) => {
61    request(options, function (error, response, body) {
62      if (error) {
63        reject(error);
64      } else {
65        resolve(body);
66      }
67    });
68  });
69}
70
71async function main() {
72  const branch = getBranchName();
73  const token = argv.token;
74  const releaseVersion = argv.toVersion;
75
76  const {pushed} = await inquirer.prompt({
77    type: 'confirm',
78    name: 'pushed',
79    message: `This script will trigger a release with whatever changes are on the remote branch: ${branch}. \nMake sure you have pushed any updates remotely.`,
80  });
81
82  if (!pushed) {
83    console.log(`Please run 'git push ${argv.remote} ${branch}'`);
84    exit(1);
85    return;
86  }
87
88  let latest = false;
89  const {version, prerelease} = parseVersion(releaseVersion);
90  if (!prerelease) {
91    const {setLatest} = await inquirer.prompt({
92      type: 'confirm',
93      name: 'setLatest',
94      message: `Do you want to set ${version} as "latest" release on npm?`,
95    });
96    latest = setLatest;
97  }
98
99  const npmTag = latest ? 'latest' : !prerelease ? branch : 'next';
100  const {confirmRelease} = await inquirer.prompt({
101    type: 'confirm',
102    name: 'confirmRelease',
103    message: `Releasing version "${version}" with npm tag "${npmTag}". Is this correct?`,
104  });
105
106  if (!confirmRelease) {
107    console.log('Aborting.');
108    return;
109  }
110
111  const parameters = {
112    release_version: version,
113    release_latest: latest,
114    run_package_release_workflow_only: true,
115  };
116
117  const options = {
118    method: 'POST',
119    url: 'https://circleci.com/api/v2/project/github/facebook/react-native/pipeline',
120    headers: {
121      'Circle-Token': token,
122      'content-type': 'application/json',
123    },
124    body: {
125      branch,
126      parameters,
127    },
128    json: true,
129  };
130
131  // See response: https://circleci.com/docs/api/v2/#operation/triggerPipeline
132  const body = await triggerReleaseWorkflow(options);
133  console.log(
134    `Monitor your release workflow: https://app.circleci.com/pipelines/github/facebook/react-native/${body.number}`,
135  );
136
137  // TODO
138  // - Output the release changelog to paste into Github releases
139  // - Link to release discussions to update
140  // - Verify RN-diff publish is through
141}
142
143main().then(() => {
144  exit(0);
145});
146