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 updates relevant React Native files with supplied version:
14 *   * Prepares a package.json suitable for package consumption
15 *   * Updates package.json for template project
16 *   * Updates the version in gradle files and makes sure they are consistent between each other
17 *   * Creates a gemfile
18 */
19const fs = require('fs');
20const os = require('os');
21const path = require('path');
22const {cat, echo, exec, exit, sed} = require('shelljs');
23const yargs = require('yargs');
24const {parseVersion, validateBuildType} = require('./version-utils');
25const {saveFiles} = require('./scm-utils');
26
27let argv = yargs
28  .option('v', {
29    alias: 'to-version',
30    type: 'string',
31    required: true,
32  })
33  .option('b', {
34    alias: 'build-type',
35    type: 'string',
36    required: true,
37  }).argv;
38
39const buildType = argv.buildType;
40const version = argv.toVersion;
41
42try {
43  validateBuildType(buildType);
44} catch (e) {
45  throw e;
46}
47
48let major,
49  minor,
50  patch,
51  prerelease = -1;
52try {
53  ({major, minor, patch, prerelease} = parseVersion(version, buildType));
54} catch (e) {
55  throw e;
56}
57
58const tmpVersioningFolder = fs.mkdtempSync(
59  path.join(os.tmpdir(), 'rn-set-version'),
60);
61echo(`The temp versioning folder is ${tmpVersioningFolder}`);
62
63saveFiles(['package.json', 'template/package.json'], tmpVersioningFolder);
64
65fs.writeFileSync(
66  'ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java',
67  cat('scripts/versiontemplates/ReactNativeVersion.java.template')
68    .replace('${major}', major)
69    .replace('${minor}', minor)
70    .replace('${patch}', patch)
71    .replace(
72      '${prerelease}',
73      prerelease !== undefined ? `"${prerelease}"` : 'null',
74    ),
75  'utf-8',
76);
77
78fs.writeFileSync(
79  'React/Base/RCTVersion.m',
80  cat('scripts/versiontemplates/RCTVersion.m.template')
81    .replace('${major}', `@(${major})`)
82    .replace('${minor}', `@(${minor})`)
83    .replace('${patch}', `@(${patch})`)
84    .replace(
85      '${prerelease}',
86      prerelease !== undefined ? `@"${prerelease}"` : '[NSNull null]',
87    ),
88  'utf-8',
89);
90
91fs.writeFileSync(
92  'ReactCommon/cxxreact/ReactNativeVersion.h',
93  cat('scripts/versiontemplates/ReactNativeVersion.h.template')
94    .replace('${major}', major)
95    .replace('${minor}', minor)
96    .replace('${patch}', patch)
97    .replace(
98      '${prerelease}',
99      prerelease !== undefined ? `"${prerelease}"` : '""',
100    ),
101  'utf-8',
102);
103
104fs.writeFileSync(
105  'Libraries/Core/ReactNativeVersion.js',
106  cat('scripts/versiontemplates/ReactNativeVersion.js.template')
107    .replace('${major}', major)
108    .replace('${minor}', minor)
109    .replace('${patch}', patch)
110    .replace(
111      '${prerelease}',
112      prerelease !== undefined ? `'${prerelease}'` : 'null',
113    ),
114  'utf-8',
115);
116
117let packageJson = JSON.parse(cat('package.json'));
118packageJson.version = version;
119delete packageJson.workspaces;
120delete packageJson.private;
121
122// Copy repo-config/package.json dependencies as devDependencies
123const repoConfigJson = JSON.parse(cat('repo-config/package.json'));
124packageJson.devDependencies = {
125  ...packageJson.devDependencies,
126  ...repoConfigJson.dependencies,
127};
128// Make react-native-codegen a direct dependency of react-native
129delete packageJson.devDependencies['react-native-codegen'];
130packageJson.dependencies = {
131  ...packageJson.dependencies,
132  'react-native-codegen': repoConfigJson.dependencies['react-native-codegen'],
133};
134fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2), 'utf-8');
135
136// Change ReactAndroid/gradle.properties
137saveFiles(['ReactAndroid/gradle.properties'], tmpVersioningFolder);
138if (
139  sed(
140    '-i',
141    /^VERSION_NAME=.*/,
142    `VERSION_NAME=${version}`,
143    'ReactAndroid/gradle.properties',
144  ).code
145) {
146  echo("Couldn't update version for Gradle");
147  exit(1);
148}
149
150// Change react-native version in the template's package.json
151exec(`node scripts/set-rn-template-version.js ${version}`);
152
153// Make sure to update ruby version
154if (exec('scripts/update-ruby.sh').code) {
155  echo('Failed to update Ruby version');
156  exit(1);
157}
158
159// Verify that files changed, we just do a git diff and check how many times version is added across files
160const filesToValidate = [
161  'package.json',
162  'ReactAndroid/gradle.properties',
163  'template/package.json',
164];
165
166const numberOfChangedLinesWithNewVersion = exec(
167  `diff -r ${tmpVersioningFolder} . | grep '^[>]' | grep -c ${version} `,
168  {silent: true},
169).stdout.trim();
170
171if (+numberOfChangedLinesWithNewVersion !== filesToValidate.length) {
172  // TODO: the logic that checks whether all the changes have been applied
173  // is missing several files. For example, it is not checking Ruby version nor that
174  // the Objecive-C files, the codegen and other files are properly updated.
175  // We are going to work on this in another PR.
176  echo('WARNING:');
177  echo(
178    `Failed to update all the files: [${filesToValidate.join(
179      ', ',
180    )}] must have versions in them`,
181  );
182  echo(`These files already had version ${version} set.`);
183}
184
185exit(0);
186