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