1import findUp from 'find-up'; 2import fs from 'fs-extra'; 3import path from 'path'; 4 5import { SearchOptions } from '../types'; 6 7/** 8 * Path to the `package.json` of the closest project in the current working dir. 9 */ 10export const projectPackageJsonPath = findUp.sync('package.json', { cwd: process.cwd() }) as string; 11 12// This won't happen in usual scenarios, but we need to unwrap the optional path :) 13if (!projectPackageJsonPath) { 14 throw new Error(`Couldn't find "package.json" up from path "${process.cwd()}"`); 15} 16 17/** 18 * Merges autolinking options from different sources (the later the higher priority) 19 * - options defined in package.json's `expo.autolinking` field 20 * - platform-specific options from the above (e.g. `expo.autolinking.ios`) 21 * - options provided to the CLI command 22 */ 23export async function mergeLinkingOptionsAsync<OptionsType extends SearchOptions>( 24 providedOptions: OptionsType 25): Promise<OptionsType> { 26 const packageJson = require(projectPackageJsonPath); 27 const baseOptions = packageJson.expo?.autolinking; 28 const platformOptions = providedOptions.platform && baseOptions?.[providedOptions.platform]; 29 const finalOptions = Object.assign( 30 {}, 31 baseOptions, 32 platformOptions, 33 providedOptions 34 ) as OptionsType; 35 36 // Makes provided paths absolute or falls back to default paths if none was provided. 37 finalOptions.searchPaths = await resolveSearchPathsAsync(finalOptions.searchPaths, process.cwd()); 38 39 finalOptions.nativeModulesDir = await resolveNativeModulesDirAsync( 40 finalOptions.nativeModulesDir, 41 process.cwd() 42 ); 43 44 return finalOptions; 45} 46 47/** 48 * Resolves autolinking search paths. If none is provided, it accumulates all node_modules when 49 * going up through the path components. This makes workspaces work out-of-the-box without any configs. 50 */ 51export async function resolveSearchPathsAsync( 52 searchPaths: string[] | null, 53 cwd: string 54): Promise<string[]> { 55 return searchPaths && searchPaths.length > 0 56 ? searchPaths.map((searchPath) => path.resolve(cwd, searchPath)) 57 : await findDefaultPathsAsync(cwd); 58} 59 60/** 61 * Looks up for workspace's `node_modules` paths. 62 */ 63async function findDefaultPathsAsync(cwd: string): Promise<string[]> { 64 const paths = []; 65 let dir = cwd; 66 let pkgJsonPath: string | undefined; 67 68 while ((pkgJsonPath = await findUp('package.json', { cwd: dir }))) { 69 dir = path.dirname(path.dirname(pkgJsonPath)); 70 paths.push(path.join(pkgJsonPath, '..', 'node_modules')); 71 72 // This stops the infinite loop when the package.json is placed at the root dir. 73 if (path.dirname(dir) === dir) { 74 break; 75 } 76 } 77 return paths; 78} 79 80/** 81 * Finds the real path to custom native modules directory. 82 * - When {@link cwd} is inside the project directory, the path is searched relatively 83 * to the project root (directory with the `package.json` file). 84 * - When {@link cwd} is outside project directory (no `package.json` found), it is relative to 85 * the current working directory (the {@link cwd} param). 86 * 87 * @param nativeModulesDir path to custom native modules directory. Defaults to `"./modules"` if null. 88 * @param cwd current working directory 89 * @returns resolved native modules directory or `null` if it is not found or doesn't exist. 90 */ 91async function resolveNativeModulesDirAsync( 92 nativeModulesDir: string | null | undefined, 93 cwd: string 94): Promise<string | null> { 95 const packageJsonPath = await findUp('package.json', { cwd }); 96 const projectRoot = packageJsonPath != null ? path.join(packageJsonPath, '..') : cwd; 97 const resolvedPath = path.resolve(projectRoot, nativeModulesDir || 'modules'); 98 return fs.existsSync(resolvedPath) ? resolvedPath : null; 99} 100