1*46f023faSEvan Baconimport { ExpoConfig } from '@expo/config'; 28010f0caSEvan Baconimport chalk from 'chalk'; 3*46f023faSEvan Baconimport { sync as globSync } from 'glob'; 4e87e8ea8SEvan Baconimport path from 'path'; 5e87e8ea8SEvan Baconimport resolveFrom from 'resolve-from'; 6e87e8ea8SEvan Bacon 78010f0caSEvan Baconimport { Log } from '../../../log'; 88010f0caSEvan Baconimport { directoryExistsSync } from '../../../utils/dir'; 98010f0caSEvan Bacon 10e87e8ea8SEvan Baconconst debug = require('debug')('expo:start:server:metro:router') as typeof console.log; 11e87e8ea8SEvan Bacon 12e87e8ea8SEvan Bacon/** 13e87e8ea8SEvan Bacon * Get the relative path for requiring the `/app` folder relative to the `expo-router/entry` file. 14e87e8ea8SEvan Bacon * This mechanism does require the server to restart after the `expo-router` package is installed. 15e87e8ea8SEvan Bacon */ 168010f0caSEvan Baconexport function getAppRouterRelativeEntryPath( 178010f0caSEvan Bacon projectRoot: string, 188010f0caSEvan Bacon routerDirectory: string = getRouterDirectory(projectRoot) 198010f0caSEvan Bacon): string | undefined { 20e87e8ea8SEvan Bacon // Auto pick App entry 21e87e8ea8SEvan Bacon const routerEntry = 22e87e8ea8SEvan Bacon resolveFrom.silent(projectRoot, 'expo-router/entry') ?? getFallbackEntryRoot(projectRoot); 23e87e8ea8SEvan Bacon if (!routerEntry) { 24e87e8ea8SEvan Bacon return undefined; 25e87e8ea8SEvan Bacon } 26e87e8ea8SEvan Bacon // It doesn't matter if the app folder exists. 278010f0caSEvan Bacon const appFolder = path.join(projectRoot, routerDirectory); 28e87e8ea8SEvan Bacon const appRoot = path.relative(path.dirname(routerEntry), appFolder); 29e87e8ea8SEvan Bacon debug('routerEntry', routerEntry, appFolder, appRoot); 30e87e8ea8SEvan Bacon return appRoot; 31e87e8ea8SEvan Bacon} 32e87e8ea8SEvan Bacon 33e87e8ea8SEvan Bacon/** If the `expo-router` package is not installed, then use the `expo` package to determine where the node modules are relative to the project. */ 34e87e8ea8SEvan Baconfunction getFallbackEntryRoot(projectRoot: string): string { 35e87e8ea8SEvan Bacon const expoRoot = resolveFrom.silent(projectRoot, 'expo/package.json'); 36e87e8ea8SEvan Bacon if (expoRoot) { 37e87e8ea8SEvan Bacon return path.join(path.dirname(path.dirname(expoRoot)), 'expo-router/entry'); 38e87e8ea8SEvan Bacon } 39e87e8ea8SEvan Bacon return path.join(projectRoot, 'node_modules/expo-router/entry'); 40e87e8ea8SEvan Bacon} 411117330aSMark Lawlor 42*46f023faSEvan Baconexport function getRouterDirectoryModuleIdWithManifest( 43*46f023faSEvan Bacon projectRoot: string, 44*46f023faSEvan Bacon exp: ExpoConfig 45*46f023faSEvan Bacon): string { 46*46f023faSEvan Bacon return exp.extra?.router?.unstable_src ?? getRouterDirectory(projectRoot); 47*46f023faSEvan Bacon} 48*46f023faSEvan Bacon 49*46f023faSEvan Baconexport function getRouterDirectoryWithManifest(projectRoot: string, exp: ExpoConfig): string { 50*46f023faSEvan Bacon return path.join(projectRoot, getRouterDirectoryModuleIdWithManifest(projectRoot, exp)); 51*46f023faSEvan Bacon} 52*46f023faSEvan Bacon 538010f0caSEvan Baconexport function getRouterDirectory(projectRoot: string): string { 548010f0caSEvan Bacon // more specific directories first 558010f0caSEvan Bacon if (directoryExistsSync(path.join(projectRoot, 'src/app'))) { 568010f0caSEvan Bacon Log.log(chalk.gray('Using src/app as the root directory for Expo Router.')); 578010f0caSEvan Bacon return 'src/app'; 588010f0caSEvan Bacon } 598010f0caSEvan Bacon 608010f0caSEvan Bacon Log.debug('Using app as the root directory for Expo Router.'); 618010f0caSEvan Bacon return 'app'; 621117330aSMark Lawlor} 63*46f023faSEvan Bacon 64*46f023faSEvan Baconexport function isApiRouteConvention(name: string): boolean { 65*46f023faSEvan Bacon return /\+api\.[tj]sx?$/.test(name); 66*46f023faSEvan Bacon} 67*46f023faSEvan Bacon 68*46f023faSEvan Baconexport function getApiRoutesForDirectory(cwd: string) { 69*46f023faSEvan Bacon return globSync('**/*+api.@(ts|tsx|js|jsx)', { 70*46f023faSEvan Bacon cwd, 71*46f023faSEvan Bacon absolute: true, 72*46f023faSEvan Bacon }); 73*46f023faSEvan Bacon} 74*46f023faSEvan Bacon 75*46f023faSEvan Bacon// Used to emulate a context module, but way faster. TODO: May need to adjust the extensions to stay in sync with Metro. 76*46f023faSEvan Baconexport function getRoutePaths(cwd: string) { 77*46f023faSEvan Bacon return globSync('**/*.@(ts|tsx|js|jsx)', { 78*46f023faSEvan Bacon cwd, 79*46f023faSEvan Bacon }).map((p) => './' + normalizePaths(p)); 80*46f023faSEvan Bacon} 81*46f023faSEvan Bacon 82*46f023faSEvan Baconfunction normalizePaths(p: string) { 83*46f023faSEvan Bacon return p.replace(/\\/g, '/'); 84*46f023faSEvan Bacon} 85