18d307f52SEvan Bacon/* eslint-env jest */
28d307f52SEvan Baconimport JsonFile from '@expo/json-file';
38d307f52SEvan Baconimport execa from 'execa';
48d307f52SEvan Baconimport fs from 'fs/promises';
56c8d1802SEvan Baconimport { sync as globSync } from 'glob';
68d307f52SEvan Baconimport klawSync from 'klaw-sync';
78d307f52SEvan Baconimport path from 'path';
88d307f52SEvan Bacon
98d307f52SEvan Baconimport {
108d307f52SEvan Bacon  bin,
118d307f52SEvan Bacon  execute,
128d307f52SEvan Bacon  projectRoot,
138d307f52SEvan Bacon  getRoot,
148d307f52SEvan Bacon  setupTestProjectAsync,
158d307f52SEvan Bacon  getLoadedModulesAsync,
168d307f52SEvan Bacon} from './utils';
178d307f52SEvan Bacon
188d307f52SEvan Baconconst originalForceColor = process.env.FORCE_COLOR;
198d307f52SEvan Baconconst originalCI = process.env.CI;
208d307f52SEvan Bacon
216c8d1802SEvan Baconconst templateFolder = path.join(__dirname, '../../../../../templates/expo-template-bare-minimum/');
226c8d1802SEvan Bacon
236c8d1802SEvan Baconfunction getTemplatePath() {
246c8d1802SEvan Bacon  const results = globSync(`*.tgz`, {
256c8d1802SEvan Bacon    absolute: true,
266c8d1802SEvan Bacon    cwd: templateFolder,
276c8d1802SEvan Bacon  });
286c8d1802SEvan Bacon
296c8d1802SEvan Bacon  return results[0];
306c8d1802SEvan Bacon}
316c8d1802SEvan Bacon
326c8d1802SEvan Baconasync function ensureTemplatePathAsync() {
336c8d1802SEvan Bacon  let templatePath = getTemplatePath();
346c8d1802SEvan Bacon  if (templatePath) return templatePath;
356c8d1802SEvan Bacon  await execa('npm', ['pack'], { cwd: templateFolder });
366c8d1802SEvan Bacon
376c8d1802SEvan Bacon  templatePath = getTemplatePath();
386c8d1802SEvan Bacon  if (templatePath) return templatePath;
396c8d1802SEvan Bacon
406c8d1802SEvan Bacon  throw new Error('Could not find template tarball');
416c8d1802SEvan Bacon}
426c8d1802SEvan Bacon
438d307f52SEvan BaconbeforeAll(async () => {
448d307f52SEvan Bacon  await fs.mkdir(projectRoot, { recursive: true });
458d307f52SEvan Bacon  process.env.FORCE_COLOR = '0';
468d307f52SEvan Bacon  process.env.CI = '1';
478d307f52SEvan Bacon});
488d307f52SEvan Bacon
498d307f52SEvan BaconafterAll(() => {
508d307f52SEvan Bacon  process.env.FORCE_COLOR = originalForceColor;
518d307f52SEvan Bacon  process.env.CI = originalCI;
528d307f52SEvan Bacon});
538d307f52SEvan Bacon
548d307f52SEvan Baconit('loads expected modules by default', async () => {
558d307f52SEvan Bacon  const modules = await getLoadedModulesAsync(`require('../../build/src/prebuild').expoPrebuild`);
568d307f52SEvan Bacon  expect(modules).toStrictEqual([
574067174dSWill Schurman    '../node_modules/ansi-styles/index.js',
588d307f52SEvan Bacon    '../node_modules/arg/index.js',
598d307f52SEvan Bacon    '../node_modules/chalk/source/index.js',
608d307f52SEvan Bacon    '../node_modules/chalk/source/util.js',
618d307f52SEvan Bacon    '../node_modules/has-flag/index.js',
628d307f52SEvan Bacon    '../node_modules/supports-color/index.js',
638d307f52SEvan Bacon    '@expo/cli/build/src/log.js',
648d307f52SEvan Bacon    '@expo/cli/build/src/prebuild/index.js',
658d307f52SEvan Bacon    '@expo/cli/build/src/utils/args.js',
668d307f52SEvan Bacon  ]);
678d307f52SEvan Bacon});
688d307f52SEvan Bacon
698d307f52SEvan Baconit('runs `npx expo prebuild --help`', async () => {
708d307f52SEvan Bacon  const results = await execute('prebuild', '--help');
718d307f52SEvan Bacon  expect(results.stdout).toMatchInlineSnapshot(`
728d307f52SEvan Bacon    "
7383d464dcSEvan Bacon      Info
7483d464dcSEvan Bacon        Create native iOS and Android project files for building natively
758d307f52SEvan Bacon
768d307f52SEvan Bacon      Usage
778d307f52SEvan Bacon        $ npx expo prebuild <dir>
788d307f52SEvan Bacon
798d307f52SEvan Bacon      Options
8083d464dcSEvan Bacon        <dir>                                    Directory of the Expo project. Default: Current working directory
8183d464dcSEvan Bacon        --no-install                             Skip installing npm packages and CocoaPods
828d307f52SEvan Bacon        --clean                                  Delete the native folders and regenerate them before applying changes
836caf5755SEvan Bacon        --npm                                    Use npm to install dependencies. Default when package-lock.json exists
846caf5755SEvan Bacon        --yarn                                   Use Yarn to install dependencies. Default when yarn.lock exists
85*9b1b5ec6SEvan Bacon        --bun                                    Use bun to install dependencies. Default when bun.lockb exists
866caf5755SEvan Bacon        --pnpm                                   Use pnpm to install dependencies. Default when pnpm-lock.yaml exists
878d307f52SEvan Bacon        --template <template>                    Project template to clone from. File path pointing to a local tar file or a github repo
888d307f52SEvan Bacon        -p, --platform <all|android|ios>         Platforms to sync: ios, android, all. Default: all
898d307f52SEvan Bacon        --skip-dependency-update <dependencies>  Preserves versions of listed packages in package.json (comma separated list)
9083d464dcSEvan Bacon        -h, --help                               Usage info
918d307f52SEvan Bacon    "
928d307f52SEvan Bacon  `);
938d307f52SEvan Bacon});
948d307f52SEvan Bacon
958d307f52SEvan Baconit('runs `npx expo prebuild` asserts when expo is not installed', async () => {
968d307f52SEvan Bacon  const projectName = 'basic-prebuild-assert-no-expo';
978d307f52SEvan Bacon  const projectRoot = getRoot(projectName);
988d307f52SEvan Bacon  // Create the project root aot
998d307f52SEvan Bacon  await fs.mkdir(projectRoot, { recursive: true });
1008d307f52SEvan Bacon  // Create a fake package.json -- this is a terminal file that cannot be overwritten.
1018d307f52SEvan Bacon  await fs.writeFile(path.join(projectRoot, 'package.json'), '{ "version": "1.0.0" }');
1028d307f52SEvan Bacon  await fs.writeFile(path.join(projectRoot, 'app.json'), '{ "expo": { "name": "foobar" } }');
1038d307f52SEvan Bacon
1048d307f52SEvan Bacon  await expect(execute('prebuild', projectName, '--no-install')).rejects.toThrowError(
1058d307f52SEvan Bacon    /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./
1068d307f52SEvan Bacon  );
1078d307f52SEvan Bacon});
1088d307f52SEvan Bacon
1098d307f52SEvan Baconit(
1108d307f52SEvan Bacon  'runs `npx expo prebuild`',
1118d307f52SEvan Bacon  async () => {
1128d307f52SEvan Bacon    const projectRoot = await setupTestProjectAsync('basic-prebuild', 'with-blank');
1138d307f52SEvan Bacon    // `npx expo prebuild --no-install`
1146c8d1802SEvan Bacon
1156c8d1802SEvan Bacon    const templateFolder = await ensureTemplatePathAsync();
1166c8d1802SEvan Bacon    console.log('Using local template:', templateFolder);
1176c8d1802SEvan Bacon
1186c8d1802SEvan Bacon    await execa('node', [bin, 'prebuild', '--no-install', '--template', templateFolder], {
1196c8d1802SEvan Bacon      cwd: projectRoot,
1206c8d1802SEvan Bacon    });
1218d307f52SEvan Bacon
1228d307f52SEvan Bacon    // List output files with sizes for snapshotting.
1238d307f52SEvan Bacon    // This is to make sure that any changes to the output are intentional.
1248d307f52SEvan Bacon    // Posix path formatting is used to make paths the same across OSes.
1258d307f52SEvan Bacon    const files = klawSync(projectRoot)
1268d307f52SEvan Bacon      .map((entry) => {
1278d307f52SEvan Bacon        if (entry.path.includes('node_modules') || !entry.stats.isFile()) {
1288d307f52SEvan Bacon          return null;
1298d307f52SEvan Bacon        }
1308d307f52SEvan Bacon        return path.posix.relative(projectRoot, entry.path);
1318d307f52SEvan Bacon      })
1328d307f52SEvan Bacon      .filter(Boolean);
1338d307f52SEvan Bacon
1348d307f52SEvan Bacon    const pkg = await JsonFile.readAsync(path.resolve(projectRoot, 'package.json'));
1358d307f52SEvan Bacon
1368d307f52SEvan Bacon    // Added new packages
137e1bb5bdfSKudo Chien    expect(Object.keys(pkg.dependencies ?? {}).sort()).toStrictEqual([
1388d307f52SEvan Bacon      'expo',
1398d307f52SEvan Bacon      'expo-splash-screen',
1408d307f52SEvan Bacon      'expo-status-bar',
1418d307f52SEvan Bacon      'react',
1428d307f52SEvan Bacon      'react-native',
1438d307f52SEvan Bacon    ]);
1448d307f52SEvan Bacon
1458d307f52SEvan Bacon    // Updated scripts
1468d307f52SEvan Bacon    expect(pkg.scripts).toStrictEqual({
1478d307f52SEvan Bacon      android: 'expo run:android',
1488d307f52SEvan Bacon      ios: 'expo run:ios',
1498d307f52SEvan Bacon    });
1508d307f52SEvan Bacon
1518d307f52SEvan Bacon    // If this changes then everything else probably changed as well.
1528d307f52SEvan Bacon    expect(files).toMatchInlineSnapshot(`
153e1bb5bdfSKudo Chien      [
1548d307f52SEvan Bacon        "App.js",
155fa5d5955SEvan Bacon        "android/.gitignore",
1568d307f52SEvan Bacon        "android/app/build.gradle",
1578d307f52SEvan Bacon        "android/app/debug.keystore",
1588d307f52SEvan Bacon        "android/app/proguard-rules.pro",
1598d307f52SEvan Bacon        "android/app/src/debug/AndroidManifest.xml",
1608d307f52SEvan Bacon        "android/app/src/debug/java/com/example/minimal/ReactNativeFlipper.java",
1618d307f52SEvan Bacon        "android/app/src/main/AndroidManifest.xml",
1628d307f52SEvan Bacon        "android/app/src/main/java/com/example/minimal/MainActivity.java",
1638d307f52SEvan Bacon        "android/app/src/main/java/com/example/minimal/MainApplication.java",
164fa5d5955SEvan Bacon        "android/app/src/main/res/drawable/rn_edit_text_material.xml",
1658d307f52SEvan Bacon        "android/app/src/main/res/drawable/splashscreen.xml",
1668d307f52SEvan Bacon        "android/app/src/main/res/mipmap-hdpi/ic_launcher.png",
1678d307f52SEvan Bacon        "android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png",
1688d307f52SEvan Bacon        "android/app/src/main/res/mipmap-mdpi/ic_launcher.png",
1698d307f52SEvan Bacon        "android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png",
1708d307f52SEvan Bacon        "android/app/src/main/res/mipmap-xhdpi/ic_launcher.png",
1718d307f52SEvan Bacon        "android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png",
1728d307f52SEvan Bacon        "android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png",
1738d307f52SEvan Bacon        "android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png",
1748d307f52SEvan Bacon        "android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png",
1758d307f52SEvan Bacon        "android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png",
1768d307f52SEvan Bacon        "android/app/src/main/res/values/colors.xml",
1778d307f52SEvan Bacon        "android/app/src/main/res/values/strings.xml",
1788d307f52SEvan Bacon        "android/app/src/main/res/values/styles.xml",
1798d307f52SEvan Bacon        "android/app/src/main/res/values-night/colors.xml",
180e1bb5bdfSKudo Chien        "android/app/src/release/java/com/example/minimal/ReactNativeFlipper.java",
1818d307f52SEvan Bacon        "android/build.gradle",
1828d307f52SEvan Bacon        "android/gradle/wrapper/gradle-wrapper.jar",
1838d307f52SEvan Bacon        "android/gradle/wrapper/gradle-wrapper.properties",
1848d307f52SEvan Bacon        "android/gradle.properties",
1858d307f52SEvan Bacon        "android/gradlew",
1868d307f52SEvan Bacon        "android/gradlew.bat",
1878d307f52SEvan Bacon        "android/settings.gradle",
1888d307f52SEvan Bacon        "app.json",
189fa5d5955SEvan Bacon        "ios/.gitignore",
1906c8d1802SEvan Bacon        "ios/.xcode.env",
1918d307f52SEvan Bacon        "ios/Podfile",
1928d307f52SEvan Bacon        "ios/Podfile.properties.json",
1938d307f52SEvan Bacon        "ios/basicprebuild/AppDelegate.h",
194fa5d5955SEvan Bacon        "ios/basicprebuild/AppDelegate.mm",
1958d307f52SEvan Bacon        "ios/basicprebuild/Images.xcassets/AppIcon.appiconset/Contents.json",
1968d307f52SEvan Bacon        "ios/basicprebuild/Images.xcassets/Contents.json",
1978d307f52SEvan Bacon        "ios/basicprebuild/Images.xcassets/SplashScreenBackground.imageset/Contents.json",
1988d307f52SEvan Bacon        "ios/basicprebuild/Images.xcassets/SplashScreenBackground.imageset/image.png",
1998d307f52SEvan Bacon        "ios/basicprebuild/Info.plist",
2008d307f52SEvan Bacon        "ios/basicprebuild/SplashScreen.storyboard",
2018d307f52SEvan Bacon        "ios/basicprebuild/Supporting/Expo.plist",
202b6b91c50SEvan Bacon        "ios/basicprebuild/basicprebuild-Bridging-Header.h",
2038d307f52SEvan Bacon        "ios/basicprebuild/basicprebuild.entitlements",
2048d307f52SEvan Bacon        "ios/basicprebuild/main.m",
2058d307f52SEvan Bacon        "ios/basicprebuild/noop-file.swift",
2068d307f52SEvan Bacon        "ios/basicprebuild.xcodeproj/project.pbxproj",
2078d307f52SEvan Bacon        "ios/basicprebuild.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
2088d307f52SEvan Bacon        "ios/basicprebuild.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
2098d307f52SEvan Bacon        "ios/basicprebuild.xcodeproj/xcshareddata/xcschemes/basicprebuild.xcscheme",
2108d307f52SEvan Bacon        "package.json",
2118d307f52SEvan Bacon        "yarn.lock",
2128d307f52SEvan Bacon      ]
2138d307f52SEvan Bacon    `);
2148d307f52SEvan Bacon  },
2158d307f52SEvan Bacon  // Could take 45s depending on how fast npm installs
2168d307f52SEvan Bacon  60 * 1000
2178d307f52SEvan Bacon);
218