1import chalk from 'chalk';
2import path from 'path';
3import resolveFrom from 'resolve-from';
4
5import { Log } from '../../../log';
6import { directoryExistsSync } from '../../../utils/dir';
7
8const debug = require('debug')('expo:start:server:metro:router') as typeof console.log;
9
10/**
11 * Get the relative path for requiring the `/app` folder relative to the `expo-router/entry` file.
12 * This mechanism does require the server to restart after the `expo-router` package is installed.
13 */
14export function getAppRouterRelativeEntryPath(
15  projectRoot: string,
16  routerDirectory: string = getRouterDirectory(projectRoot)
17): string | undefined {
18  // Auto pick App entry
19  const routerEntry =
20    resolveFrom.silent(projectRoot, 'expo-router/entry') ?? getFallbackEntryRoot(projectRoot);
21  if (!routerEntry) {
22    return undefined;
23  }
24  // It doesn't matter if the app folder exists.
25  const appFolder = path.join(projectRoot, routerDirectory);
26  const appRoot = path.relative(path.dirname(routerEntry), appFolder);
27  debug('routerEntry', routerEntry, appFolder, appRoot);
28  return appRoot;
29}
30
31/** If the `expo-router` package is not installed, then use the `expo` package to determine where the node modules are relative to the project. */
32function getFallbackEntryRoot(projectRoot: string): string {
33  const expoRoot = resolveFrom.silent(projectRoot, 'expo/package.json');
34  if (expoRoot) {
35    return path.join(path.dirname(path.dirname(expoRoot)), 'expo-router/entry');
36  }
37  return path.join(projectRoot, 'node_modules/expo-router/entry');
38}
39
40export function getRouterDirectory(projectRoot: string): string {
41  // more specific directories first
42  if (directoryExistsSync(path.join(projectRoot, 'src/app'))) {
43    Log.log(chalk.gray('Using src/app as the root directory for Expo Router.'));
44    return 'src/app';
45  }
46
47  Log.debug('Using app as the root directory for Expo Router.');
48  return 'app';
49}
50