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          Description
51            Create native iOS and Android project files before building natively.
52
53          Usage
54            $ npx expo prebuild <dir>
55
56          <dir> is the directory of the Expo project.
57          Defaults to the current working directory.
58
59          Options
60          --no-install                             Skip installing npm packages and CocoaPods.
61          --clean                                  Delete the native folders and regenerate them before applying changes
62          --npm                                    Use npm to install dependencies. (default when Yarn is not installed)
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                               Output usage information
67
68        "
69  `);
70});
71
72it('runs `npx expo prebuild` asserts when expo is not installed', async () => {
73  const projectName = 'basic-prebuild-assert-no-expo';
74  const projectRoot = getRoot(projectName);
75  // Create the project root aot
76  await fs.mkdir(projectRoot, { recursive: true });
77  // Create a fake package.json -- this is a terminal file that cannot be overwritten.
78  await fs.writeFile(path.join(projectRoot, 'package.json'), '{ "version": "1.0.0" }');
79  await fs.writeFile(path.join(projectRoot, 'app.json'), '{ "expo": { "name": "foobar" } }');
80
81  await expect(execute('prebuild', projectName, '--no-install')).rejects.toThrowError(
82    /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./
83  );
84});
85
86it(
87  'runs `npx expo prebuild`',
88  async () => {
89    const projectRoot = await setupTestProjectAsync('basic-prebuild', 'with-blank');
90    // `npx expo prebuild --no-install`
91    await execa('node', [bin, 'prebuild', '--no-install'], { cwd: projectRoot });
92
93    // List output files with sizes for snapshotting.
94    // This is to make sure that any changes to the output are intentional.
95    // Posix path formatting is used to make paths the same across OSes.
96    const files = klawSync(projectRoot)
97      .map((entry) => {
98        if (entry.path.includes('node_modules') || !entry.stats.isFile()) {
99          return null;
100        }
101        return path.posix.relative(projectRoot, entry.path);
102      })
103      .filter(Boolean);
104
105    const pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json'));
106
107    // Deleted
108    expect(pkg.main).not.toBeDefined();
109
110    // Added new packages
111    expect(Object.keys(pkg.dependencies).sort()).toStrictEqual([
112      'expo',
113      'expo-splash-screen',
114      'expo-status-bar',
115      'react',
116      'react-dom',
117      'react-native',
118      'react-native-web',
119    ]);
120
121    // Updated scripts
122    expect(pkg.scripts).toStrictEqual({
123      android: 'expo run:android',
124      ios: 'expo run:ios',
125      start: 'expo start --dev-client',
126    });
127
128    // If this changes then everything else probably changed as well.
129    expect(files).toMatchInlineSnapshot(`
130      Array [
131        "App.js",
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/res/drawable/splashscreen.xml",
143        "android/app/src/main/res/mipmap-hdpi/ic_launcher.png",
144        "android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png",
145        "android/app/src/main/res/mipmap-mdpi/ic_launcher.png",
146        "android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png",
147        "android/app/src/main/res/mipmap-xhdpi/ic_launcher.png",
148        "android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png",
149        "android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png",
150        "android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png",
151        "android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png",
152        "android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png",
153        "android/app/src/main/res/values/colors.xml",
154        "android/app/src/main/res/values/strings.xml",
155        "android/app/src/main/res/values/styles.xml",
156        "android/app/src/main/res/values-night/colors.xml",
157        "android/build.gradle",
158        "android/gradle/wrapper/gradle-wrapper.jar",
159        "android/gradle/wrapper/gradle-wrapper.properties",
160        "android/gradle.properties",
161        "android/gradlew",
162        "android/gradlew.bat",
163        "android/settings.gradle",
164        "app.json",
165        "index.js",
166        "ios/Podfile",
167        "ios/Podfile.properties.json",
168        "ios/basicprebuild/AppDelegate.h",
169        "ios/basicprebuild/AppDelegate.m",
170        "ios/basicprebuild/Images.xcassets/AppIcon.appiconset/Contents.json",
171        "ios/basicprebuild/Images.xcassets/Contents.json",
172        "ios/basicprebuild/Images.xcassets/SplashScreenBackground.imageset/Contents.json",
173        "ios/basicprebuild/Images.xcassets/SplashScreenBackground.imageset/image.png",
174        "ios/basicprebuild/Info.plist",
175        "ios/basicprebuild/SplashScreen.storyboard",
176        "ios/basicprebuild/Supporting/Expo.plist",
177        "ios/basicprebuild/basicprebuild-Bridging-Header.h",
178        "ios/basicprebuild/basicprebuild.entitlements",
179        "ios/basicprebuild/main.m",
180        "ios/basicprebuild/noop-file.swift",
181        "ios/basicprebuild.xcodeproj/project.pbxproj",
182        "ios/basicprebuild.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
183        "ios/basicprebuild.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
184        "ios/basicprebuild.xcodeproj/project.xcworkspace/xcuserdata/brentvatne.xcuserdatad/UserInterfaceState.xcuserstate",
185        "ios/basicprebuild.xcodeproj/xcshareddata/xcschemes/basicprebuild.xcscheme",
186        "metro.config.js",
187        "package.json",
188        "yarn.lock",
189      ]
190    `);
191  },
192  // Could take 45s depending on how fast npm installs
193  60 * 1000
194);
195