1082815dcSEvan Baconimport { ModConfig } from '@expo/config-plugins'; 2082815dcSEvan Baconimport JsonFile, { JSONObject } from '@expo/json-file'; 3082815dcSEvan Baconimport fs from 'fs'; 4082815dcSEvan Baconimport { sync as globSync } from 'glob'; 5082815dcSEvan Baconimport path from 'path'; 6082815dcSEvan Baconimport resolveFrom from 'resolve-from'; 7082815dcSEvan Baconimport semver from 'semver'; 8082815dcSEvan Baconimport slugify from 'slugify'; 9082815dcSEvan Bacon 10082815dcSEvan Baconimport { 11082815dcSEvan Bacon AppJSONConfig, 12082815dcSEvan Bacon ConfigFilePaths, 13082815dcSEvan Bacon ExpoConfig, 14082815dcSEvan Bacon GetConfigOptions, 15082815dcSEvan Bacon PackageJSONConfig, 16082815dcSEvan Bacon Platform, 17082815dcSEvan Bacon ProjectConfig, 18082815dcSEvan Bacon ProjectTarget, 19082815dcSEvan Bacon WriteConfigOptions, 20082815dcSEvan Bacon} from './Config.types'; 21082815dcSEvan Baconimport { getDynamicConfig, getStaticConfig } from './getConfig'; 22*97e1898cSEvan Baconimport { getExpoSDKVersion } from './getExpoSDKVersion'; 23082815dcSEvan Baconimport { withConfigPlugins } from './plugins/withConfigPlugins'; 24082815dcSEvan Baconimport { withInternal } from './plugins/withInternal'; 25082815dcSEvan Baconimport { getRootPackageJsonPath } from './resolvePackageJson'; 26082815dcSEvan Bacon 27082815dcSEvan Bacontype SplitConfigs = { expo: ExpoConfig; mods: ModConfig }; 28082815dcSEvan Bacon 29082815dcSEvan Bacon/** 30082815dcSEvan Bacon * If a config has an `expo` object then that will be used as the config. 31082815dcSEvan Bacon * This method reduces out other top level values if an `expo` object exists. 32082815dcSEvan Bacon * 33082815dcSEvan Bacon * @param config Input config object to reduce 34082815dcSEvan Bacon */ 35082815dcSEvan Baconfunction reduceExpoObject(config?: any): SplitConfigs { 36082815dcSEvan Bacon if (!config) return config === undefined ? null : config; 37082815dcSEvan Bacon 38082815dcSEvan Bacon const { mods, ...expo } = config.expo ?? config; 39082815dcSEvan Bacon 40082815dcSEvan Bacon return { 41082815dcSEvan Bacon expo, 42082815dcSEvan Bacon mods, 43082815dcSEvan Bacon }; 44082815dcSEvan Bacon} 45082815dcSEvan Bacon 46082815dcSEvan Bacon/** 47082815dcSEvan Bacon * Get all platforms that a project is currently capable of running. 48082815dcSEvan Bacon * 49082815dcSEvan Bacon * @param projectRoot 50082815dcSEvan Bacon * @param exp 51082815dcSEvan Bacon */ 52082815dcSEvan Baconfunction getSupportedPlatforms(projectRoot: string): Platform[] { 53082815dcSEvan Bacon const platforms: Platform[] = []; 54082815dcSEvan Bacon if (resolveFrom.silent(projectRoot, 'react-native')) { 55082815dcSEvan Bacon platforms.push('ios', 'android'); 56082815dcSEvan Bacon } 57082815dcSEvan Bacon if (resolveFrom.silent(projectRoot, 'react-native-web')) { 58082815dcSEvan Bacon platforms.push('web'); 59082815dcSEvan Bacon } 60082815dcSEvan Bacon return platforms; 61082815dcSEvan Bacon} 62082815dcSEvan Bacon 63082815dcSEvan Bacon/** 64082815dcSEvan Bacon * Evaluate the config for an Expo project. 65082815dcSEvan Bacon * If a function is exported from the `app.config.js` then a partial config will be passed as an argument. 66082815dcSEvan Bacon * The partial config is composed from any existing app.json, and certain fields from the `package.json` like name and description. 67082815dcSEvan Bacon * 68082815dcSEvan Bacon * If options.isPublicConfig is true, the Expo config will include only public-facing options (omitting private keys). 69082815dcSEvan Bacon * The resulting config should be suitable for hosting or embedding in a publicly readable location. 70082815dcSEvan Bacon * 71082815dcSEvan Bacon * **Example** 72082815dcSEvan Bacon * ```js 73082815dcSEvan Bacon * module.exports = function({ config }) { 74082815dcSEvan Bacon * // mutate the config before returning it. 75082815dcSEvan Bacon * config.slug = 'new slug' 76082815dcSEvan Bacon * return { expo: config }; 77082815dcSEvan Bacon * } 78082815dcSEvan Bacon * ``` 79082815dcSEvan Bacon * 80082815dcSEvan Bacon * **Supports** 81082815dcSEvan Bacon * - `app.config.ts` 82082815dcSEvan Bacon * - `app.config.js` 83082815dcSEvan Bacon * - `app.config.json` 84082815dcSEvan Bacon * - `app.json` 85082815dcSEvan Bacon * 86082815dcSEvan Bacon * @param projectRoot the root folder containing all of your application code 87082815dcSEvan Bacon * @param options enforce criteria for a project config 88082815dcSEvan Bacon */ 89082815dcSEvan Baconexport function getConfig(projectRoot: string, options: GetConfigOptions = {}): ProjectConfig { 90082815dcSEvan Bacon const paths = getConfigFilePaths(projectRoot); 91082815dcSEvan Bacon 92082815dcSEvan Bacon const rawStaticConfig = paths.staticConfigPath ? getStaticConfig(paths.staticConfigPath) : null; 93082815dcSEvan Bacon // For legacy reasons, always return an object. 94082815dcSEvan Bacon const rootConfig = (rawStaticConfig || {}) as AppJSONConfig; 95082815dcSEvan Bacon const staticConfig = reduceExpoObject(rawStaticConfig) || {}; 96082815dcSEvan Bacon 97082815dcSEvan Bacon // Can only change the package.json location if an app.json or app.config.json exists 98082815dcSEvan Bacon const [packageJson, packageJsonPath] = getPackageJsonAndPath(projectRoot); 99082815dcSEvan Bacon 100082815dcSEvan Bacon function fillAndReturnConfig(config: SplitConfigs, dynamicConfigObjectType: string | null) { 101082815dcSEvan Bacon const configWithDefaultValues = { 102082815dcSEvan Bacon ...ensureConfigHasDefaultValues({ 103082815dcSEvan Bacon projectRoot, 104082815dcSEvan Bacon exp: config.expo, 105082815dcSEvan Bacon pkg: packageJson, 106082815dcSEvan Bacon skipSDKVersionRequirement: options.skipSDKVersionRequirement, 107082815dcSEvan Bacon paths, 108082815dcSEvan Bacon packageJsonPath, 109082815dcSEvan Bacon }), 110082815dcSEvan Bacon mods: config.mods, 111082815dcSEvan Bacon dynamicConfigObjectType, 112082815dcSEvan Bacon rootConfig, 113082815dcSEvan Bacon dynamicConfigPath: paths.dynamicConfigPath, 114082815dcSEvan Bacon staticConfigPath: paths.staticConfigPath, 115082815dcSEvan Bacon }; 116082815dcSEvan Bacon 117082815dcSEvan Bacon if (options.isModdedConfig) { 118082815dcSEvan Bacon // @ts-ignore: Add the mods back to the object. 119082815dcSEvan Bacon configWithDefaultValues.exp.mods = config.mods ?? null; 120082815dcSEvan Bacon } 121082815dcSEvan Bacon 122082815dcSEvan Bacon // Apply static json plugins, should be done after _internal 123082815dcSEvan Bacon configWithDefaultValues.exp = withConfigPlugins( 124082815dcSEvan Bacon configWithDefaultValues.exp, 125082815dcSEvan Bacon !!options.skipPlugins 126082815dcSEvan Bacon ); 127082815dcSEvan Bacon 128082815dcSEvan Bacon if (!options.isModdedConfig) { 129082815dcSEvan Bacon // @ts-ignore: Delete mods added by static plugins when they won't have a chance to be evaluated 130082815dcSEvan Bacon delete configWithDefaultValues.exp.mods; 131082815dcSEvan Bacon } 132082815dcSEvan Bacon 133082815dcSEvan Bacon if (options.isPublicConfig) { 134*97e1898cSEvan Bacon // TODD(EvanBacon): Drop plugins array after it's been resolved. 135*97e1898cSEvan Bacon 136082815dcSEvan Bacon // Remove internal values with references to user's file paths from the public config. 137082815dcSEvan Bacon delete configWithDefaultValues.exp._internal; 138082815dcSEvan Bacon 139082815dcSEvan Bacon if (configWithDefaultValues.exp.hooks) { 140082815dcSEvan Bacon delete configWithDefaultValues.exp.hooks; 141082815dcSEvan Bacon } 142082815dcSEvan Bacon if (configWithDefaultValues.exp.ios?.config) { 143082815dcSEvan Bacon delete configWithDefaultValues.exp.ios.config; 144082815dcSEvan Bacon } 145082815dcSEvan Bacon if (configWithDefaultValues.exp.android?.config) { 146082815dcSEvan Bacon delete configWithDefaultValues.exp.android.config; 147082815dcSEvan Bacon } 148082815dcSEvan Bacon 149082815dcSEvan Bacon delete configWithDefaultValues.exp.updates?.codeSigningCertificate; 150082815dcSEvan Bacon delete configWithDefaultValues.exp.updates?.codeSigningMetadata; 151082815dcSEvan Bacon } 152082815dcSEvan Bacon 153082815dcSEvan Bacon return configWithDefaultValues; 154082815dcSEvan Bacon } 155082815dcSEvan Bacon 156082815dcSEvan Bacon // Fill in the static config 157082815dcSEvan Bacon function getContextConfig(config: SplitConfigs) { 158082815dcSEvan Bacon return ensureConfigHasDefaultValues({ 159082815dcSEvan Bacon projectRoot, 160082815dcSEvan Bacon exp: config.expo, 161082815dcSEvan Bacon pkg: packageJson, 162082815dcSEvan Bacon skipSDKVersionRequirement: true, 163082815dcSEvan Bacon paths, 164082815dcSEvan Bacon packageJsonPath, 165082815dcSEvan Bacon }).exp; 166082815dcSEvan Bacon } 167082815dcSEvan Bacon 168082815dcSEvan Bacon if (paths.dynamicConfigPath) { 169082815dcSEvan Bacon // No app.config.json or app.json but app.config.js 170082815dcSEvan Bacon const { exportedObjectType, config: rawDynamicConfig } = getDynamicConfig( 171082815dcSEvan Bacon paths.dynamicConfigPath, 172082815dcSEvan Bacon { 173082815dcSEvan Bacon projectRoot, 174082815dcSEvan Bacon staticConfigPath: paths.staticConfigPath, 175082815dcSEvan Bacon packageJsonPath, 176082815dcSEvan Bacon config: getContextConfig(staticConfig), 177082815dcSEvan Bacon } 178082815dcSEvan Bacon ); 179082815dcSEvan Bacon // Allow for the app.config.js to `export default null;` 180082815dcSEvan Bacon // Use `dynamicConfigPath` to detect if a dynamic config exists. 181082815dcSEvan Bacon const dynamicConfig = reduceExpoObject(rawDynamicConfig) || {}; 182082815dcSEvan Bacon return fillAndReturnConfig(dynamicConfig, exportedObjectType); 183082815dcSEvan Bacon } 184082815dcSEvan Bacon 185082815dcSEvan Bacon // No app.config.js but json or no config 186082815dcSEvan Bacon return fillAndReturnConfig(staticConfig || {}, null); 187082815dcSEvan Bacon} 188082815dcSEvan Bacon 189082815dcSEvan Baconexport function getPackageJson(projectRoot: string): PackageJSONConfig { 190082815dcSEvan Bacon const [pkg] = getPackageJsonAndPath(projectRoot); 191082815dcSEvan Bacon return pkg; 192082815dcSEvan Bacon} 193082815dcSEvan Bacon 194082815dcSEvan Baconfunction getPackageJsonAndPath(projectRoot: string): [PackageJSONConfig, string] { 195082815dcSEvan Bacon const packageJsonPath = getRootPackageJsonPath(projectRoot); 196082815dcSEvan Bacon return [JsonFile.read(packageJsonPath), packageJsonPath]; 197082815dcSEvan Bacon} 198082815dcSEvan Bacon 199082815dcSEvan Bacon/** 200082815dcSEvan Bacon * Get the static and dynamic config paths for a project. Also accounts for custom paths. 201082815dcSEvan Bacon * 202082815dcSEvan Bacon * @param projectRoot 203082815dcSEvan Bacon */ 204082815dcSEvan Baconexport function getConfigFilePaths(projectRoot: string): ConfigFilePaths { 205082815dcSEvan Bacon return { 206082815dcSEvan Bacon dynamicConfigPath: getDynamicConfigFilePath(projectRoot), 207082815dcSEvan Bacon staticConfigPath: getStaticConfigFilePath(projectRoot), 208082815dcSEvan Bacon }; 209082815dcSEvan Bacon} 210082815dcSEvan Bacon 211082815dcSEvan Baconfunction getDynamicConfigFilePath(projectRoot: string): string | null { 212082815dcSEvan Bacon for (const fileName of ['app.config.ts', 'app.config.js']) { 213082815dcSEvan Bacon const configPath = path.join(projectRoot, fileName); 214082815dcSEvan Bacon if (fs.existsSync(configPath)) { 215082815dcSEvan Bacon return configPath; 216082815dcSEvan Bacon } 217082815dcSEvan Bacon } 218082815dcSEvan Bacon return null; 219082815dcSEvan Bacon} 220082815dcSEvan Bacon 221082815dcSEvan Baconfunction getStaticConfigFilePath(projectRoot: string): string | null { 222082815dcSEvan Bacon for (const fileName of ['app.config.json', 'app.json']) { 223082815dcSEvan Bacon const configPath = path.join(projectRoot, fileName); 224082815dcSEvan Bacon if (fs.existsSync(configPath)) { 225082815dcSEvan Bacon return configPath; 226082815dcSEvan Bacon } 227082815dcSEvan Bacon } 228082815dcSEvan Bacon return null; 229082815dcSEvan Bacon} 230082815dcSEvan Bacon 231082815dcSEvan Bacon/** 232082815dcSEvan Bacon * Attempt to modify an Expo project config. 233082815dcSEvan Bacon * This will only fully work if the project is using static configs only. 234082815dcSEvan Bacon * Otherwise 'warn' | 'fail' will return with a message about why the config couldn't be updated. 235082815dcSEvan Bacon * The potentially modified config object will be returned for testing purposes. 236082815dcSEvan Bacon * 237082815dcSEvan Bacon * @param projectRoot 238082815dcSEvan Bacon * @param modifications modifications to make to an existing config 239082815dcSEvan Bacon * @param readOptions options for reading the current config file 240082815dcSEvan Bacon * @param writeOptions If true, the static config file will not be rewritten 241082815dcSEvan Bacon */ 242082815dcSEvan Baconexport async function modifyConfigAsync( 243082815dcSEvan Bacon projectRoot: string, 244082815dcSEvan Bacon modifications: Partial<ExpoConfig>, 245082815dcSEvan Bacon readOptions: GetConfigOptions = {}, 246082815dcSEvan Bacon writeOptions: WriteConfigOptions = {} 247082815dcSEvan Bacon): Promise<{ 248082815dcSEvan Bacon type: 'success' | 'warn' | 'fail'; 249082815dcSEvan Bacon message?: string; 250082815dcSEvan Bacon config: AppJSONConfig | null; 251082815dcSEvan Bacon}> { 252082815dcSEvan Bacon const config = getConfig(projectRoot, readOptions); 253082815dcSEvan Bacon if (config.dynamicConfigPath) { 254082815dcSEvan Bacon // We cannot automatically write to a dynamic config. 255082815dcSEvan Bacon /* Currently we should just use the safest approach possible, informing the user that they'll need to manually modify their dynamic config. 256082815dcSEvan Bacon 257082815dcSEvan Bacon if (config.staticConfigPath) { 258082815dcSEvan Bacon // Both a dynamic and a static config exist. 259082815dcSEvan Bacon if (config.dynamicConfigObjectType === 'function') { 260082815dcSEvan Bacon // The dynamic config exports a function, this means it possibly extends the static config. 261082815dcSEvan Bacon } else { 262082815dcSEvan Bacon // Dynamic config ignores the static config, there isn't a reason to automatically write to it. 263082815dcSEvan Bacon // Instead we should warn the user to add values to their dynamic config. 264082815dcSEvan Bacon } 265082815dcSEvan Bacon } 266082815dcSEvan Bacon */ 267082815dcSEvan Bacon return { 268082815dcSEvan Bacon type: 'warn', 269082815dcSEvan Bacon message: `Cannot automatically write to dynamic config at: ${path.relative( 270082815dcSEvan Bacon projectRoot, 271082815dcSEvan Bacon config.dynamicConfigPath 272082815dcSEvan Bacon )}`, 273082815dcSEvan Bacon config: null, 274082815dcSEvan Bacon }; 275082815dcSEvan Bacon } else if (config.staticConfigPath) { 276082815dcSEvan Bacon // Static with no dynamic config, this means we can append to the config automatically. 277082815dcSEvan Bacon let outputConfig: AppJSONConfig; 278082815dcSEvan Bacon // If the config has an expo object (app.json) then append the options to that object. 279082815dcSEvan Bacon if (config.rootConfig.expo) { 280082815dcSEvan Bacon outputConfig = { 281082815dcSEvan Bacon ...config.rootConfig, 282082815dcSEvan Bacon expo: { ...config.rootConfig.expo, ...modifications }, 283082815dcSEvan Bacon }; 284082815dcSEvan Bacon } else { 285082815dcSEvan Bacon // Otherwise (app.config.json) just add the config modification to the top most level. 286082815dcSEvan Bacon outputConfig = { ...config.rootConfig, ...modifications }; 287082815dcSEvan Bacon } 288082815dcSEvan Bacon if (!writeOptions.dryRun) { 289082815dcSEvan Bacon await JsonFile.writeAsync(config.staticConfigPath, outputConfig, { json5: false }); 290082815dcSEvan Bacon } 291082815dcSEvan Bacon return { type: 'success', config: outputConfig }; 292082815dcSEvan Bacon } 293082815dcSEvan Bacon 294082815dcSEvan Bacon return { type: 'fail', message: 'No config exists', config: null }; 295082815dcSEvan Bacon} 296082815dcSEvan Bacon 297082815dcSEvan Baconfunction ensureConfigHasDefaultValues({ 298082815dcSEvan Bacon projectRoot, 299082815dcSEvan Bacon exp, 300082815dcSEvan Bacon pkg, 301082815dcSEvan Bacon paths, 302082815dcSEvan Bacon packageJsonPath, 303082815dcSEvan Bacon skipSDKVersionRequirement = false, 304082815dcSEvan Bacon}: { 305082815dcSEvan Bacon projectRoot: string; 306082815dcSEvan Bacon exp: Partial<ExpoConfig> | null; 307082815dcSEvan Bacon pkg: JSONObject; 308082815dcSEvan Bacon skipSDKVersionRequirement?: boolean; 309082815dcSEvan Bacon paths?: ConfigFilePaths; 310082815dcSEvan Bacon packageJsonPath?: string; 311082815dcSEvan Bacon}): { exp: ExpoConfig; pkg: PackageJSONConfig } { 312082815dcSEvan Bacon if (!exp) { 313082815dcSEvan Bacon exp = {}; 314082815dcSEvan Bacon } 315082815dcSEvan Bacon exp = withInternal(exp as any, { 316082815dcSEvan Bacon projectRoot, 317082815dcSEvan Bacon ...(paths ?? {}), 318082815dcSEvan Bacon packageJsonPath, 319082815dcSEvan Bacon }); 320082815dcSEvan Bacon // Defaults for package.json fields 321082815dcSEvan Bacon const pkgName = typeof pkg.name === 'string' ? pkg.name : path.basename(projectRoot); 322082815dcSEvan Bacon const pkgVersion = typeof pkg.version === 'string' ? pkg.version : '1.0.0'; 323082815dcSEvan Bacon 324082815dcSEvan Bacon const pkgWithDefaults = { ...pkg, name: pkgName, version: pkgVersion }; 325082815dcSEvan Bacon 326082815dcSEvan Bacon // Defaults for app.json/app.config.js fields 327082815dcSEvan Bacon const name = exp.name ?? pkgName; 328082815dcSEvan Bacon const slug = exp.slug ?? slugify(name.toLowerCase()); 329082815dcSEvan Bacon const version = exp.version ?? pkgVersion; 330082815dcSEvan Bacon let description = exp.description; 331082815dcSEvan Bacon if (!description && typeof pkg.description === 'string') { 332082815dcSEvan Bacon description = pkg.description; 333082815dcSEvan Bacon } 334082815dcSEvan Bacon 335082815dcSEvan Bacon const expWithDefaults = { ...exp, name, slug, version, description }; 336082815dcSEvan Bacon 337082815dcSEvan Bacon let sdkVersion; 338082815dcSEvan Bacon try { 339082815dcSEvan Bacon sdkVersion = getExpoSDKVersion(projectRoot, expWithDefaults); 340082815dcSEvan Bacon } catch (error) { 341082815dcSEvan Bacon if (!skipSDKVersionRequirement) throw error; 342082815dcSEvan Bacon } 343082815dcSEvan Bacon 344082815dcSEvan Bacon let platforms = exp.platforms; 345082815dcSEvan Bacon if (!platforms) { 346082815dcSEvan Bacon platforms = getSupportedPlatforms(projectRoot); 347082815dcSEvan Bacon } 348082815dcSEvan Bacon 349082815dcSEvan Bacon return { 350082815dcSEvan Bacon exp: { ...expWithDefaults, sdkVersion, platforms }, 351082815dcSEvan Bacon pkg: pkgWithDefaults, 352082815dcSEvan Bacon }; 353082815dcSEvan Bacon} 354082815dcSEvan Bacon 355082815dcSEvan Baconconst DEFAULT_BUILD_PATH = `web-build`; 356082815dcSEvan Bacon 357082815dcSEvan Baconexport function getWebOutputPath(config: { [key: string]: any } = {}): string { 358082815dcSEvan Bacon if (process.env.WEBPACK_BUILD_OUTPUT_PATH) { 359082815dcSEvan Bacon return process.env.WEBPACK_BUILD_OUTPUT_PATH; 360082815dcSEvan Bacon } 361082815dcSEvan Bacon const expo = config.expo || config || {}; 362082815dcSEvan Bacon return expo?.web?.build?.output || DEFAULT_BUILD_PATH; 363082815dcSEvan Bacon} 364082815dcSEvan Bacon 365082815dcSEvan Baconexport function getNameFromConfig(exp: Record<string, any> = {}): { 366082815dcSEvan Bacon appName?: string; 367082815dcSEvan Bacon webName?: string; 368082815dcSEvan Bacon} { 369082815dcSEvan Bacon // For RN CLI support 370082815dcSEvan Bacon const appManifest = exp.expo || exp; 371082815dcSEvan Bacon const { web = {} } = appManifest; 372082815dcSEvan Bacon 373082815dcSEvan Bacon // rn-cli apps use a displayName value as well. 374082815dcSEvan Bacon const appName = exp.displayName || appManifest.displayName || appManifest.name; 375082815dcSEvan Bacon const webName = web.name || appName; 376082815dcSEvan Bacon 377082815dcSEvan Bacon return { 378082815dcSEvan Bacon appName, 379082815dcSEvan Bacon webName, 380082815dcSEvan Bacon }; 381082815dcSEvan Bacon} 382082815dcSEvan Bacon 383082815dcSEvan Baconexport function getDefaultTarget( 384082815dcSEvan Bacon projectRoot: string, 385082815dcSEvan Bacon exp?: Pick<ExpoConfig, 'sdkVersion'> 386082815dcSEvan Bacon): ProjectTarget { 387082815dcSEvan Bacon exp ??= getConfig(projectRoot, { skipSDKVersionRequirement: true }).exp; 388082815dcSEvan Bacon 389082815dcSEvan Bacon // before SDK 37, always default to managed to preserve previous behavior 390082815dcSEvan Bacon if (exp.sdkVersion && exp.sdkVersion !== 'UNVERSIONED' && semver.lt(exp.sdkVersion, '37.0.0')) { 391082815dcSEvan Bacon return 'managed'; 392082815dcSEvan Bacon } 393082815dcSEvan Bacon return isBareWorkflowProject(projectRoot) ? 'bare' : 'managed'; 394082815dcSEvan Bacon} 395082815dcSEvan Bacon 396082815dcSEvan Baconfunction isBareWorkflowProject(projectRoot: string): boolean { 397082815dcSEvan Bacon const [pkg] = getPackageJsonAndPath(projectRoot); 398082815dcSEvan Bacon 399*97e1898cSEvan Bacon // TODO: Drop this 400082815dcSEvan Bacon if (pkg.dependencies && pkg.dependencies.expokit) { 401082815dcSEvan Bacon return false; 402082815dcSEvan Bacon } 403082815dcSEvan Bacon 404082815dcSEvan Bacon const xcodeprojFiles = globSync('ios/**/*.xcodeproj', { 405082815dcSEvan Bacon absolute: true, 406082815dcSEvan Bacon cwd: projectRoot, 407082815dcSEvan Bacon }); 408082815dcSEvan Bacon if (xcodeprojFiles.length) { 409082815dcSEvan Bacon return true; 410082815dcSEvan Bacon } 411082815dcSEvan Bacon const gradleFiles = globSync('android/**/*.gradle', { 412082815dcSEvan Bacon absolute: true, 413082815dcSEvan Bacon cwd: projectRoot, 414082815dcSEvan Bacon }); 415082815dcSEvan Bacon if (gradleFiles.length) { 416082815dcSEvan Bacon return true; 417082815dcSEvan Bacon } 418082815dcSEvan Bacon 419082815dcSEvan Bacon return false; 420082815dcSEvan Bacon} 421082815dcSEvan Bacon 422082815dcSEvan Bacon/** 423082815dcSEvan Bacon * Return a useful name describing the project config. 424082815dcSEvan Bacon * - dynamic: app.config.js 425082815dcSEvan Bacon * - static: app.json 426082815dcSEvan Bacon * - custom path app config relative to root folder 427082815dcSEvan Bacon * - both: app.config.js or app.json 428082815dcSEvan Bacon */ 429082815dcSEvan Baconexport function getProjectConfigDescription(projectRoot: string): string { 430082815dcSEvan Bacon const paths = getConfigFilePaths(projectRoot); 431082815dcSEvan Bacon return getProjectConfigDescriptionWithPaths(projectRoot, paths); 432082815dcSEvan Bacon} 433082815dcSEvan Bacon 434082815dcSEvan Bacon/** 435082815dcSEvan Bacon * Returns a string describing the configurations used for the given project root. 436082815dcSEvan Bacon * Will return null if no config is found. 437082815dcSEvan Bacon * 438082815dcSEvan Bacon * @param projectRoot 439082815dcSEvan Bacon * @param projectConfig 440082815dcSEvan Bacon */ 441082815dcSEvan Baconexport function getProjectConfigDescriptionWithPaths( 442082815dcSEvan Bacon projectRoot: string, 443082815dcSEvan Bacon projectConfig: ConfigFilePaths 444082815dcSEvan Bacon): string { 445082815dcSEvan Bacon if (projectConfig.dynamicConfigPath) { 446082815dcSEvan Bacon const relativeDynamicConfigPath = path.relative(projectRoot, projectConfig.dynamicConfigPath); 447082815dcSEvan Bacon if (projectConfig.staticConfigPath) { 448082815dcSEvan Bacon return `${relativeDynamicConfigPath} or ${path.relative( 449082815dcSEvan Bacon projectRoot, 450082815dcSEvan Bacon projectConfig.staticConfigPath 451082815dcSEvan Bacon )}`; 452082815dcSEvan Bacon } 453082815dcSEvan Bacon return relativeDynamicConfigPath; 454082815dcSEvan Bacon } else if (projectConfig.staticConfigPath) { 455082815dcSEvan Bacon return path.relative(projectRoot, projectConfig.staticConfigPath); 456082815dcSEvan Bacon } 457082815dcSEvan Bacon // If a config doesn't exist, our tooling will generate a static app.json 458082815dcSEvan Bacon return 'app.json'; 459082815dcSEvan Bacon} 460082815dcSEvan Bacon 461082815dcSEvan Baconexport * from './Config.types'; 462