1/* eslint-env jest */
2import JsonFile from '@expo/json-file';
3import execa from 'execa';
4import fs from 'fs/promises';
5import klawSync from 'klaw-sync';
6import path from 'path';
7
8import {
9  bin,
10  execute,
11  projectRoot,
12  getRoot,
13  setupTestProjectAsync,
14  getLoadedModulesAsync,
15} from './utils';
16
17const originalForceColor = process.env.FORCE_COLOR;
18const originalCI = process.env.CI;
19
20beforeAll(async () => {
21  await fs.mkdir(projectRoot, { recursive: true });
22  process.env.FORCE_COLOR = '0';
23  process.env.CI = '1';
24});
25
26afterAll(() => {
27  process.env.FORCE_COLOR = originalForceColor;
28  process.env.CI = originalCI;
29});
30
31it('loads expected modules by default', async () => {
32  const modules = await getLoadedModulesAsync(`require('../../build/src/prebuild').expoPrebuild`);
33  expect(modules).toStrictEqual([
34    '../node_modules/ansi-styles/index.js',
35    '../node_modules/arg/index.js',
36    '../node_modules/chalk/source/index.js',
37    '../node_modules/chalk/source/util.js',
38    '../node_modules/has-flag/index.js',
39    '../node_modules/supports-color/index.js',
40    '@expo/cli/build/src/log.js',
41    '@expo/cli/build/src/prebuild/index.js',
42    '@expo/cli/build/src/utils/args.js',
43  ]);
44});
45
46it('runs `npx expo prebuild --help`', async () => {
47  const results = await execute('prebuild', '--help');
48  expect(results.stdout).toMatchInlineSnapshot(`
49    "
50      Info
51        Create native iOS and Android project files for building natively
52
53      Usage
54        $ npx expo prebuild <dir>
55
56      Options
57        <dir>                                    Directory of the Expo project. Default: Current working directory
58        --no-install                             Skip installing npm packages and CocoaPods
59        --clean                                  Delete the native folders and regenerate them before applying changes
60        --npm                                    Use npm to install dependencies. Default when package-lock.json exists
61        --yarn                                   Use Yarn to install dependencies. Default when yarn.lock exists
62        --pnpm                                   Use pnpm to install dependencies. Default when pnpm-lock.yaml exists
63        --template <template>                    Project template to clone from. File path pointing to a local tar file or a github repo
64        -p, --platform <all|android|ios>         Platforms to sync: ios, android, all. Default: all
65        --skip-dependency-update <dependencies>  Preserves versions of listed packages in package.json (comma separated list)
66        -h, --help                               Usage info
67    "
68  `);
69});
70
71it('runs `npx expo prebuild` asserts when expo is not installed', async () => {
72  const projectName = 'basic-prebuild-assert-no-expo';
73  const projectRoot = getRoot(projectName);
74  // Create the project root aot
75  await fs.mkdir(projectRoot, { recursive: true });
76  // Create a fake package.json -- this is a terminal file that cannot be overwritten.
77  await fs.writeFile(path.join(projectRoot, 'package.json'), '{ "version": "1.0.0" }');
78  await fs.writeFile(path.join(projectRoot, 'app.json'), '{ "expo": { "name": "foobar" } }');
79
80  await expect(execute('prebuild', projectName, '--no-install')).rejects.toThrowError(
81    /Cannot determine which native SDK version your project uses because the module `expo` is not installed\. Please install it with `yarn add expo` and try again./
82  );
83});
84
85it(
86  'runs `npx expo prebuild`',
87  async () => {
88    const projectRoot = await setupTestProjectAsync('basic-prebuild', 'with-blank');
89    // `npx expo prebuild --no-install`
90    await execa('node', [bin, 'prebuild', '--no-install'], { cwd: projectRoot });
91
92    // List output files with sizes for snapshotting.
93    // This is to make sure that any changes to the output are intentional.
94    // Posix path formatting is used to make paths the same across OSes.
95    const files = klawSync(projectRoot)
96      .map((entry) => {
97        if (entry.path.includes('node_modules') || !entry.stats.isFile()) {
98          return null;
99        }
100        return path.posix.relative(projectRoot, entry.path);
101      })
102      .filter(Boolean);
103
104    const pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json'));
105
106    // Deleted
107    expect(pkg.main).not.toBeDefined();
108
109    // Added new packages
110    expect(Object.keys(pkg.dependencies).sort()).toStrictEqual([
111      'expo',
112      'expo-splash-screen',
113      'expo-status-bar',
114      'react',
115      'react-dom',
116      'react-native',
117      'react-native-web',
118    ]);
119
120    // Updated scripts
121    expect(pkg.scripts).toStrictEqual({
122      android: 'expo run:android',
123      ios: 'expo run:ios',
124      start: 'expo start --dev-client',
125    });
126
127    // If this changes then everything else probably changed as well.
128    expect(files).toMatchInlineSnapshot(`
129      Array [
130        "App.js",
131        "android/.gitignore",
132        "android/app/BUCK",
133        "android/app/build.gradle",
134        "android/app/build_defs.bzl",
135        "android/app/debug.keystore",
136        "android/app/proguard-rules.pro",
137        "android/app/src/debug/AndroidManifest.xml",
138        "android/app/src/debug/java/com/example/minimal/ReactNativeFlipper.java",
139        "android/app/src/main/AndroidManifest.xml",
140        "android/app/src/main/java/com/example/minimal/MainActivity.java",
141        "android/app/src/main/java/com/example/minimal/MainApplication.java",
142        "android/app/src/main/java/com/example/minimal/newarchitecture/MainApplicationReactNativeHost.java",
143        "android/app/src/main/java/com/example/minimal/newarchitecture/components/MainComponentsRegistry.java",
144        "android/app/src/main/java/com/example/minimal/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java",
145        "android/app/src/main/jni/Android.mk",
146        "android/app/src/main/jni/MainApplicationModuleProvider.cpp",
147        "android/app/src/main/jni/MainApplicationModuleProvider.h",
148        "android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp",
149        "android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h",
150        "android/app/src/main/jni/MainComponentsRegistry.cpp",
151        "android/app/src/main/jni/MainComponentsRegistry.h",
152        "android/app/src/main/jni/OnLoad.cpp",
153        "android/app/src/main/res/drawable/rn_edit_text_material.xml",
154        "android/app/src/main/res/drawable/splashscreen.xml",
155        "android/app/src/main/res/mipmap-hdpi/ic_launcher.png",
156        "android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png",
157        "android/app/src/main/res/mipmap-mdpi/ic_launcher.png",
158        "android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png",
159        "android/app/src/main/res/mipmap-xhdpi/ic_launcher.png",
160        "android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png",
161        "android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png",
162        "android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png",
163        "android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png",
164        "android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png",
165        "android/app/src/main/res/values/colors.xml",
166        "android/app/src/main/res/values/strings.xml",
167        "android/app/src/main/res/values/styles.xml",
168        "android/app/src/main/res/values-night/colors.xml",
169        "android/build.gradle",
170        "android/gradle/wrapper/gradle-wrapper.jar",
171        "android/gradle/wrapper/gradle-wrapper.properties",
172        "android/gradle.properties",
173        "android/gradlew",
174        "android/gradlew.bat",
175        "android/settings.gradle",
176        "app.json",
177        "index.js",
178        "ios/.gitignore",
179        "ios/Podfile",
180        "ios/Podfile.properties.json",
181        "ios/basicprebuild/AppDelegate.h",
182        "ios/basicprebuild/AppDelegate.mm",
183        "ios/basicprebuild/Images.xcassets/AppIcon.appiconset/Contents.json",
184        "ios/basicprebuild/Images.xcassets/Contents.json",
185        "ios/basicprebuild/Images.xcassets/SplashScreenBackground.imageset/Contents.json",
186        "ios/basicprebuild/Images.xcassets/SplashScreenBackground.imageset/image.png",
187        "ios/basicprebuild/Info.plist",
188        "ios/basicprebuild/SplashScreen.storyboard",
189        "ios/basicprebuild/Supporting/Expo.plist",
190        "ios/basicprebuild/basicprebuild.entitlements",
191        "ios/basicprebuild/main.m",
192        "ios/basicprebuild/noop-file.swift",
193        "ios/basicprebuild.xcodeproj/project.pbxproj",
194        "ios/basicprebuild.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
195        "ios/basicprebuild.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
196        "ios/basicprebuild.xcodeproj/xcshareddata/xcschemes/basicprebuild.xcscheme",
197        "metro.config.js",
198        "package.json",
199        "yarn.lock",
200      ]
201    `);
202  },
203  // Could take 45s depending on how fast npm installs
204  60 * 1000
205);
206