1/* eslint-env jest */ 2import JsonFile from '@expo/json-file'; 3import execa, { ExecaError } from 'execa'; 4import fs from 'fs/promises'; 5import klawSync from 'klaw-sync'; 6import path from 'path'; 7 8import { 9 execute, 10 projectRoot, 11 getLoadedModulesAsync, 12 bin, 13 setupTestProjectAsync, 14 installAsync, 15} from './utils'; 16 17const originalForceColor = process.env.FORCE_COLOR; 18const originalCI = process.env.CI; 19beforeAll(async () => { 20 await fs.mkdir(projectRoot, { recursive: true }); 21 process.env.FORCE_COLOR = '0'; 22 process.env.CI = '1'; 23}); 24afterAll(() => { 25 process.env.FORCE_COLOR = originalForceColor; 26 process.env.CI = originalCI; 27}); 28 29it('loads expected modules by default', async () => { 30 const modules = await getLoadedModulesAsync(`require('../../build/src/install').expoInstall`); 31 expect(modules).toStrictEqual([ 32 '../node_modules/ansi-styles/index.js', 33 '../node_modules/arg/index.js', 34 '../node_modules/chalk/source/index.js', 35 '../node_modules/chalk/source/util.js', 36 '../node_modules/has-flag/index.js', 37 '../node_modules/supports-color/index.js', 38 '@expo/cli/build/src/install/index.js', 39 '@expo/cli/build/src/log.js', 40 '@expo/cli/build/src/utils/args.js', 41 ]); 42}); 43 44it('runs `npx install install --help`', async () => { 45 const results = await execute('install', '--help'); 46 expect(results.stdout).toMatchInlineSnapshot(` 47 " 48 Info 49 Install a module or other package to a project 50 51 Usage 52 $ npx expo install 53 54 Options 55 --check Check which installed packages need to be updated 56 --fix Automatically update any invalid package versions 57 --npm Use npm to install dependencies. Default when package-lock.json exists 58 --yarn Use Yarn to install dependencies. Default when yarn.lock exists 59 --bun Use bun to install dependencies. Default when bun.lockb exists 60 --pnpm Use pnpm to install dependencies. Default when pnpm-lock.yaml exists 61 -h, --help Usage info 62 63 Additional options can be passed to the underlying install command by using -- 64 $ npx expo install react -- --verbose 65 > yarn add react --verbose 66 " 67 `); 68}); 69 70it( 71 'runs `npx expo install expo-sms`', 72 async () => { 73 const projectRoot = await setupTestProjectAsync('basic-install', 'with-blank'); 74 // `npx expo install expo-sms` 75 await execa('node', [bin, 'install', 'expo-sms'], { cwd: projectRoot }); 76 77 // List output files with sizes for snapshotting. 78 // This is to make sure that any changes to the output are intentional. 79 // Posix path formatting is used to make paths the same across OSes. 80 const files = klawSync(projectRoot) 81 .map((entry) => { 82 if (entry.path.includes('node_modules') || !entry.stats.isFile()) { 83 return null; 84 } 85 return path.posix.relative(projectRoot, entry.path); 86 }) 87 .filter(Boolean); 88 89 const pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json')); 90 91 // Added expected package 92 const pkgDependencies = pkg.dependencies as Record<string, string>; 93 expect(pkgDependencies['expo-sms']).toBe('~11.0.0'); 94 expect(pkg.devDependencies).toEqual({ 95 '@babel/core': '^7.12.9', 96 }); 97 98 // Added new packages 99 expect(Object.keys(pkg.dependencies ?? {}).sort()).toStrictEqual([ 100 'expo', 101 'expo-sms', 102 'react', 103 'react-native', 104 ]); 105 106 expect(files).toStrictEqual(['App.js', 'app.json', 'package.json', 'yarn.lock']); 107 }, 108 // Could take 45s depending on how fast npm installs 109 60 * 1000 110); 111 112it( 113 'runs `npx expo install --check` fails', 114 async () => { 115 const projectRoot = await setupTestProjectAsync('install-check-fail', 'with-blank'); 116 await installAsync(projectRoot, ['add', '[email protected]', '[email protected]']); 117 118 let pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json')); 119 // Added expected package 120 let pkgDependencies = pkg.dependencies as Record<string, string>; 121 expect(pkgDependencies['expo-sms']).toBe('1.0.0'); 122 123 try { 124 await execa('node', [bin, 'install', '--check'], { cwd: projectRoot }); 125 throw new Error('SHOULD NOT HAPPEN'); 126 } catch (e) { 127 const error = e as ExecaError; 128 expect(error.stderr).toMatch(/expo-auth-session@1\.0\.0 - expected version: ~3\.\d\.\d/); 129 expect(error.stderr).toMatch(/expo-sms@1\.0\.0 - expected version: ~11\.\d\.\d/); 130 expect(error.stderr).toMatch(/npx expo install --fix/); 131 } 132 133 await expect( 134 execa('node', [bin, 'install', 'expo-sms', '--check'], { cwd: projectRoot }) 135 ).rejects.toThrowError(/expo-sms@1\.0\.0 - expected version: ~11\.\d\.\d/); 136 137 // Check doesn't fix packages 138 pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json')); 139 // Added expected package 140 pkgDependencies = pkg.dependencies as Record<string, string>; 141 expect(pkgDependencies['expo-sms']).toBe('1.0.0'); 142 }, 143 // Could take 45s depending on how fast npm installs 144 60 * 1000 145); 146 147it( 148 'runs `npx expo install --fix` fails', 149 async () => { 150 const projectRoot = await setupTestProjectAsync('install-fix-fail', 'with-blank'); 151 await installAsync(projectRoot, ['add', '[email protected]', '[email protected]']); 152 153 await execa('node', [bin, 'install', '--fix', 'expo-sms'], { cwd: projectRoot }); 154 155 // Ensure the versions are invalid 156 await expect( 157 execa('node', [bin, 'install', '--check'], { cwd: projectRoot }) 158 ).rejects.toThrow(); 159 160 // Check doesn't fix packages 161 let pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json')); 162 // Added expected package 163 let pkgDependencies = pkg.dependencies as Record<string, string>; 164 expect(pkgDependencies['expo-sms']).toBe('~11.0.0'); 165 166 // Didn't fix expo-auth-session since we didn't pass it in 167 expect(pkgDependencies['expo-auth-session']).toBe('1.0.0'); 168 169 // Fix all versions 170 await execa('node', [bin, 'install', '--fix'], { cwd: projectRoot }); 171 172 // Check that the versions are fixed 173 pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json')); 174 175 // Didn't fix expo-auth-session since we didn't pass it in 176 pkgDependencies = pkg.dependencies as Record<string, string>; 177 expect(pkgDependencies['expo-auth-session']).toBe('~3.8.0'); 178 }, 179 // Could take 45s depending on how fast npm installs 180 60 * 1000 181); 182