1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @format
8 */
9
10'use strict';
11
12/**
13 * This script prepares a release version of react-native and may publish to NPM.
14 * It is supposed to run in CI environment, not on a developer's machine.
15 *
16 * For a dry run (commitly), this script will:
17 *  * Version the commitly of the form `1000.0.0-<commitSha>`
18 *  * Create Android artifacts
19 *  * It will not publish to npm
20 *
21 * For a nightly run, this script will:
22 *  * Version the nightly release of the form `0.0.0-<dateIdentifier>-<commitSha>`
23 *  * Create Android artifacts
24 *  * Publish to npm using `nightly` tag
25 *
26 * For a release run, this script will:
27 *  * Version the release by the tag version that triggered CI
28 *  * Create Android artifacts
29 *  * Publish to npm
30 *     * using `latest` tag if commit is currently tagged `latest`
31 *     * or otherwise `{major}.{minor}-stable`
32 */
33
34const {exec, echo, exit} = require('shelljs');
35const {parseVersion} = require('./version-utils');
36const {
37  exitIfNotOnGit,
38  getCurrentCommit,
39  isTaggedLatest,
40} = require('./scm-utils');
41const {
42  generateAndroidArtifacts,
43  publishAndroidArtifactsToMaven,
44  saveFilesToRestore,
45} = require('./release-utils');
46const fs = require('fs');
47const os = require('os');
48const path = require('path');
49const yargs = require('yargs');
50
51const buildTag = process.env.CIRCLE_TAG;
52const otp = process.env.NPM_CONFIG_OTP;
53const tmpPublishingFolder = fs.mkdtempSync(
54  path.join(os.tmpdir(), 'rn-publish-'),
55);
56
57const argv = yargs
58  .option('n', {
59    alias: 'nightly',
60    type: 'boolean',
61    default: false,
62  })
63  .option('d', {
64    alias: 'dry-run',
65    type: 'boolean',
66    default: false,
67  })
68  .option('r', {
69    alias: 'release',
70    type: 'boolean',
71    default: false,
72  })
73  .strict().argv;
74const nightlyBuild = argv.nightly;
75const dryRunBuild = argv.dryRun;
76const releaseBuild = argv.release;
77const isCommitly = nightlyBuild || dryRunBuild;
78
79const buildType = releaseBuild
80  ? 'release'
81  : nightlyBuild
82  ? 'nightly'
83  : 'dry-run';
84
85if (!argv.help) {
86  echo(`The temp publishing folder is ${tmpPublishingFolder}`);
87}
88
89saveFilesToRestore(tmpPublishingFolder);
90
91// 34c034298dc9cad5a4553964a5a324450fda0385
92const currentCommit = getCurrentCommit();
93const shortCommit = currentCommit.slice(0, 9);
94
95const rawVersion =
96  // 0.0.0 triggers issues with cocoapods for codegen when building template project.
97  dryRunBuild
98    ? '1000.0.0'
99    : // For nightly we continue to use 0.0.0 for clarity for npm
100    nightlyBuild
101    ? '0.0.0'
102    : // For pre-release and stable releases, we use the git tag of the version we're releasing (set in bump-oss-version)
103      buildTag;
104
105let version,
106  major,
107  minor,
108  prerelease = null;
109try {
110  ({version, major, minor, prerelease} = parseVersion(rawVersion, buildType));
111} catch (e) {
112  echo(e.message);
113  exit(1);
114}
115
116let releaseVersion;
117if (dryRunBuild) {
118  releaseVersion = `${version}-${shortCommit}`;
119} else if (nightlyBuild) {
120  // 2021-09-28T05:38:40.669Z -> 20210928-0538
121  const dateIdentifier = new Date()
122    .toISOString()
123    .slice(0, -8)
124    .replace(/[-:]/g, '')
125    .replace(/[T]/g, '-');
126  releaseVersion = `${version}-${dateIdentifier}-${shortCommit}`;
127} else {
128  releaseVersion = version;
129}
130
131// Bump version number in various files (package.json, gradle.properties etc)
132// For stable, pre-release releases, we rely on CircleCI job `prepare_package_for_release` to handle this
133if (isCommitly) {
134  if (
135    exec(
136      `node scripts/set-rn-version.js --to-version ${releaseVersion} --build-type ${buildType}`,
137    ).code
138  ) {
139    echo(`Failed to set version number to ${releaseVersion}`);
140    exit(1);
141  }
142}
143
144generateAndroidArtifacts(releaseVersion, tmpPublishingFolder);
145
146// Write version number to the build folder
147const releaseVersionFile = path.join('build', '.version');
148fs.writeFileSync(releaseVersionFile, releaseVersion);
149
150if (dryRunBuild) {
151  echo('Skipping `npm publish` because --dry-run is set.');
152  exit(0);
153}
154
155// Running to see if this commit has been git tagged as `latest`
156const isLatest = exitIfNotOnGit(
157  () => isTaggedLatest(currentCommit),
158  'Not in git. We do not want to publish anything',
159);
160
161// We first publish on Maven Central all the necessary artifacts.
162// NPM publishing is done just after.
163publishAndroidArtifactsToMaven(releaseVersion, nightlyBuild);
164
165const releaseBranch = `${major}.${minor}-stable`;
166
167// Set the right tag for nightly and prerelease builds
168// If a release is not git-tagged as `latest` we use `releaseBranch` to prevent
169// npm from overriding the current `latest` version tag, which it will do if no tag is set.
170const tagFlag = nightlyBuild
171  ? '--tag nightly'
172  : prerelease != null
173  ? '--tag next'
174  : isLatest
175  ? '--tag latest'
176  : `--tag ${releaseBranch}`;
177
178// use otp from envvars if available
179const otpFlag = otp ? `--otp ${otp}` : '';
180
181if (exec(`npm publish ${tagFlag} ${otpFlag}`).code) {
182  echo('Failed to publish package to npm');
183  exit(1);
184} else {
185  echo(`Published to npm ${releaseVersion}`);
186  exit(0);
187}
188