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 --bun Use bun to install dependencies. Default when bun.lockb exists 86 --pnpm Use pnpm to install dependencies. Default when pnpm-lock.yaml exists 87 --template <template> Project template to clone from. File path pointing to a local tar file or a github repo 88 -p, --platform <all|android|ios> Platforms to sync: ios, android, all. Default: all 89 --skip-dependency-update <dependencies> Preserves versions of listed packages in package.json (comma separated list) 90 -h, --help Usage info 91 " 92 `); 93}); 94 95it('runs `npx expo prebuild` asserts when expo is not installed', async () => { 96 const projectName = 'basic-prebuild-assert-no-expo'; 97 const projectRoot = getRoot(projectName); 98 // Create the project root aot 99 await fs.mkdir(projectRoot, { recursive: true }); 100 // Create a fake package.json -- this is a terminal file that cannot be overwritten. 101 await fs.writeFile(path.join(projectRoot, 'package.json'), '{ "version": "1.0.0" }'); 102 await fs.writeFile(path.join(projectRoot, 'app.json'), '{ "expo": { "name": "foobar" } }'); 103 104 await expect(execute('prebuild', projectName, '--no-install')).rejects.toThrowError( 105 /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./ 106 ); 107}); 108 109it( 110 'runs `npx expo prebuild`', 111 async () => { 112 const projectRoot = await setupTestProjectAsync('basic-prebuild', 'with-blank'); 113 // `npx expo prebuild --no-install` 114 115 const templateFolder = await ensureTemplatePathAsync(); 116 console.log('Using local template:', templateFolder); 117 118 await execa('node', [bin, 'prebuild', '--no-install', '--template', templateFolder], { 119 cwd: projectRoot, 120 }); 121 122 // List output files with sizes for snapshotting. 123 // This is to make sure that any changes to the output are intentional. 124 // Posix path formatting is used to make paths the same across OSes. 125 const files = klawSync(projectRoot) 126 .map((entry) => { 127 if (entry.path.includes('node_modules') || !entry.stats.isFile()) { 128 return null; 129 } 130 return path.posix.relative(projectRoot, entry.path); 131 }) 132 .filter(Boolean); 133 134 const pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json')); 135 136 // Added new packages 137 expect(Object.keys(pkg.dependencies ?? {}).sort()).toStrictEqual([ 138 'expo', 139 'expo-splash-screen', 140 'expo-status-bar', 141 'react', 142 'react-native', 143 ]); 144 145 // Updated scripts 146 expect(pkg.scripts).toStrictEqual({ 147 android: 'expo run:android', 148 ios: 'expo run:ios', 149 }); 150 151 // If this changes then everything else probably changed as well. 152 expect(files).toMatchInlineSnapshot(` 153 [ 154 "App.js", 155 "android/.gitignore", 156 "android/app/build.gradle", 157 "android/app/debug.keystore", 158 "android/app/proguard-rules.pro", 159 "android/app/src/debug/AndroidManifest.xml", 160 "android/app/src/debug/java/com/example/minimal/ReactNativeFlipper.java", 161 "android/app/src/main/AndroidManifest.xml", 162 "android/app/src/main/java/com/example/minimal/MainActivity.java", 163 "android/app/src/main/java/com/example/minimal/MainApplication.java", 164 "android/app/src/main/res/drawable/rn_edit_text_material.xml", 165 "android/app/src/main/res/drawable/splashscreen.xml", 166 "android/app/src/main/res/mipmap-hdpi/ic_launcher.png", 167 "android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png", 168 "android/app/src/main/res/mipmap-mdpi/ic_launcher.png", 169 "android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png", 170 "android/app/src/main/res/mipmap-xhdpi/ic_launcher.png", 171 "android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png", 172 "android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png", 173 "android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png", 174 "android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png", 175 "android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png", 176 "android/app/src/main/res/values/colors.xml", 177 "android/app/src/main/res/values/strings.xml", 178 "android/app/src/main/res/values/styles.xml", 179 "android/app/src/main/res/values-night/colors.xml", 180 "android/app/src/release/java/com/example/minimal/ReactNativeFlipper.java", 181 "android/build.gradle", 182 "android/gradle/wrapper/gradle-wrapper.jar", 183 "android/gradle/wrapper/gradle-wrapper.properties", 184 "android/gradle.properties", 185 "android/gradlew", 186 "android/gradlew.bat", 187 "android/settings.gradle", 188 "app.json", 189 "ios/.gitignore", 190 "ios/.xcode.env", 191 "ios/Podfile", 192 "ios/Podfile.properties.json", 193 "ios/basicprebuild/AppDelegate.h", 194 "ios/basicprebuild/AppDelegate.mm", 195 "ios/basicprebuild/Images.xcassets/AppIcon.appiconset/Contents.json", 196 "ios/basicprebuild/Images.xcassets/Contents.json", 197 "ios/basicprebuild/Images.xcassets/SplashScreenBackground.imageset/Contents.json", 198 "ios/basicprebuild/Images.xcassets/SplashScreenBackground.imageset/image.png", 199 "ios/basicprebuild/Info.plist", 200 "ios/basicprebuild/SplashScreen.storyboard", 201 "ios/basicprebuild/Supporting/Expo.plist", 202 "ios/basicprebuild/basicprebuild-Bridging-Header.h", 203 "ios/basicprebuild/basicprebuild.entitlements", 204 "ios/basicprebuild/main.m", 205 "ios/basicprebuild/noop-file.swift", 206 "ios/basicprebuild.xcodeproj/project.pbxproj", 207 "ios/basicprebuild.xcodeproj/project.xcworkspace/contents.xcworkspacedata", 208 "ios/basicprebuild.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist", 209 "ios/basicprebuild.xcodeproj/xcshareddata/xcschemes/basicprebuild.xcscheme", 210 "package.json", 211 "yarn.lock", 212 ] 213 `); 214 }, 215 // Could take 45s depending on how fast npm installs 216 60 * 1000 217); 218