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 is a re-interpretation of the old test-manual.e2e.sh script.
14 * the idea is to provide a better DX for the manual testing.
15 * It's using Javascript over Bash for consistency with the rest of the recent scripts
16 * and to make it more accessible for other devs to play around with.
17 */
18
19const {exec, exit, pushd, popd, pwd, cd, cp} = require('shelljs');
20const yargs = require('yargs');
21const fs = require('fs');
22const path = require('path');
23const os = require('os');
24
25const {
26  launchAndroidEmulator,
27  isPackagerRunning,
28  launchPackagerInSeparateWindow,
29} = require('./testing-utils');
30
31const {
32  generateAndroidArtifacts,
33  saveFilesToRestore,
34  generateiOSArtifacts,
35} = require('./release-utils');
36
37const {
38  downloadHermesSourceTarball,
39  expandHermesSourceTarball,
40} = require('./hermes/hermes-utils');
41
42const argv = yargs
43  .option('t', {
44    alias: 'target',
45    default: 'RNTester',
46    choices: ['RNTester', 'RNTestProject'],
47  })
48  .option('p', {
49    alias: 'platform',
50    default: 'iOS',
51    choices: ['iOS', 'Android'],
52  })
53  .option('h', {
54    alias: 'hermes',
55    type: 'boolean',
56    default: true,
57  }).argv;
58
59/*
60 * see the test-local-e2e.js script for clean up process
61 */
62
63// command order: we ask the user to select if they want to test RN tester
64// or RNTestProject
65
66// if they select RN tester, we ask if iOS or Android, and then we run the tests
67// if they select RNTestProject, we run the RNTestProject test
68
69// let's check if Metro is already running, if it is let's kill it and start fresh
70if (isPackagerRunning() === 'running') {
71  exec("lsof -i :8081 | grep LISTEN | /usr/bin/awk '{print $2}' | xargs kill");
72}
73
74const onReleaseBranch = exec('git rev-parse --abbrev-ref HEAD', {
75  silent: true,
76})
77  .stdout.trim()
78  .endsWith('-stable');
79
80if (argv.target === 'RNTester') {
81  // FIXME: make sure that the commands retains colors
82  // (--ansi) doesn't always work
83  // see also https://github.com/shelljs/shelljs/issues/86
84
85  if (argv.platform === 'iOS') {
86    console.info(
87      `We're going to test the ${
88        argv.hermes ? 'Hermes' : 'JSC'
89      } version of RNTester iOS with the new Architecture enabled`,
90    );
91
92    // remember that for this to be successful
93    // you should have run bundle install once
94    // in your local setup - also: if I'm on release branch, I pick the
95    // hermes ref from the hermes ref file (see hermes-engine.podspec)
96    exec(
97      `cd packages/rn-tester && USE_HERMES=${
98        argv.hermes ? 1 : 0
99      } REACT_NATIVE_CI=${onReleaseBranch} RCT_NEW_ARCH_ENABLED=1 bundle exec pod install --ansi`,
100    );
101
102    // if everything succeeded so far, we can launch Metro and the app
103    // start the Metro server in a separate window
104    launchPackagerInSeparateWindow();
105
106    // launch the app on iOS simulator
107    pushd('packages/rn-tester');
108    exec('npx react-native run-ios --scheme RNTester');
109    popd();
110  } else {
111    // we do the android path here
112
113    launchAndroidEmulator();
114
115    console.info(
116      `We're going to test the ${
117        argv.hermes ? 'Hermes' : 'JSC'
118      } version of RNTester Android with the new Architecture enabled`,
119    );
120    exec(
121      `./gradlew :packages:rn-tester:android:app:${
122        argv.hermes ? 'installHermesDebug' : 'installJscDebug'
123      } --quiet`,
124    );
125
126    // launch the app on Android simulator
127    // TODO: we should find a way to make it work like for iOS, via npx react-native run-android
128    // currently, that fails with an error.
129
130    // if everything succeeded so far, we can launch Metro and the app
131    // start the Metro server in a separate window
132    launchPackagerInSeparateWindow();
133
134    // launch the app
135    exec(
136      'adb shell am start -n com.facebook.react.uiapp/com.facebook.react.uiapp.RNTesterActivity',
137    );
138
139    // just to make sure that the Android up won't have troubles finding the Metro server
140    exec('adb reverse tcp:8081 tcp:8081');
141  }
142} else {
143  console.info("We're going to test a fresh new RN project");
144
145  // create the local npm package to feed the CLI
146
147  // base setup required (specular to publish-npm.js)
148  const tmpPublishingFolder = fs.mkdtempSync(
149    path.join(os.tmpdir(), 'rn-publish-'),
150  );
151  console.info(`The temp publishing folder is ${tmpPublishingFolder}`);
152
153  saveFilesToRestore(tmpPublishingFolder);
154
155  // we need to add the unique timestamp to avoid npm/yarn to use some local caches
156  const baseVersion = require('../package.json').version;
157
158  // in local testing, 1000.0.0 mean we are on main, every other case means we are
159  // working on a release version
160  const buildType = baseVersion !== '1000.0.0' ? 'release' : 'dry-run';
161
162  const dateIdentifier = new Date()
163    .toISOString()
164    .slice(0, -8)
165    .replace(/[-:]/g, '')
166    .replace(/[T]/g, '-');
167
168  const releaseVersion = `${baseVersion}-${dateIdentifier}`;
169
170  // this is needed to generate the Android artifacts correctly
171  const exitCode = exec(
172    `node scripts/set-rn-version.js --to-version ${releaseVersion} --build-type ${buildType}`,
173  ).code;
174
175  if (exitCode !== 0) {
176    console.error(
177      `Failed to set the RN version. Version ${releaseVersion} is not valid for ${buildType}`,
178    );
179    process.exit(exitCode);
180  }
181
182  // Generate native files for Android
183  generateAndroidArtifacts(releaseVersion, tmpPublishingFolder);
184
185  // Setting up generating native iOS (will be done later)
186  const repoRoot = pwd();
187  const jsiFolder = `${repoRoot}/ReactCommon/jsi`;
188  const hermesCoreSourceFolder = `${repoRoot}/sdks/hermes`;
189
190  if (!fs.existsSync(hermesCoreSourceFolder)) {
191    console.info('The Hermes source folder is missing. Downloading...');
192    downloadHermesSourceTarball();
193    expandHermesSourceTarball();
194  }
195
196  // need to move the scripts inside the local hermes cloned folder
197  // cp sdks/hermes-engine/utils/*.sh <your_hermes_checkout>/utils/.
198  cp(
199    `${repoRoot}/sdks/hermes-engine/utils/*.sh`,
200    `${repoRoot}/sdks/hermes/utils/.`,
201  );
202
203  // for this scenario, we only need to create the debug build
204  // (env variable PRODUCTION defines that podspec side)
205  const buildTypeiOSArtifacts = 'Debug';
206
207  // the android ones get set into /private/tmp/maven-local
208  const localMavenPath = '/private/tmp/maven-local';
209
210  // Generate native files for iOS
211  const tarballOutputPath = generateiOSArtifacts(
212    jsiFolder,
213    hermesCoreSourceFolder,
214    buildTypeiOSArtifacts,
215    localMavenPath,
216  );
217
218  // create locally the node module
219  exec('npm pack');
220
221  const localNodeTGZPath = `${repoRoot}/react-native-${releaseVersion}.tgz`;
222  exec(`node scripts/set-rn-template-version.js "file:${localNodeTGZPath}"`);
223
224  pushd('/tmp/');
225  // need to avoid the pod install step - we'll do it later
226  exec(
227    `node ${repoRoot}/cli.js init RNTestProject --template ${repoRoot} --skip-install`,
228  );
229
230  cd('RNTestProject');
231  exec('yarn install');
232
233  // need to do this here so that Android will be properly setup either way
234  exec(
235    'echo "REACT_NATIVE_MAVEN_LOCAL_REPO=/private/tmp/maven-local" >> android/gradle.properties',
236  );
237
238  // doing the pod install here so that it's easier to play around RNTestProject
239  cd('ios');
240  exec('bundle install');
241  exec(
242    `HERMES_ENGINE_TARBALL_PATH=${tarballOutputPath} USE_HERMES=${
243      argv.hermes ? 1 : 0
244    } bundle exec pod install --ansi`,
245  );
246
247  cd('..');
248
249  if (argv.platform === 'iOS') {
250    exec('yarn ios');
251  } else {
252    // android
253    exec('yarn android');
254  }
255  popd();
256
257  // just cleaning up the temp folder, the rest is done by the test clean script
258  exec(`rm -rf ${tmpPublishingFolder}`);
259}
260
261exit(0);
262