1082815dcSEvan Baconimport { existsSync, readFileSync } from 'fs'; 2082815dcSEvan Baconimport { sync as globSync } from 'glob'; 3082815dcSEvan Baconimport * as path from 'path'; 4082815dcSEvan Bacon 5*8a424bebSJames Ideimport * as Entitlements from './Entitlements'; 6082815dcSEvan Baconimport { UnexpectedError } from '../utils/errors'; 7082815dcSEvan Baconimport { addWarningIOS } from '../utils/warnings'; 8082815dcSEvan Bacon 9082815dcSEvan Baconconst ignoredPaths = ['**/@(Carthage|Pods|vendor|node_modules)/**']; 10082815dcSEvan Bacon 11082815dcSEvan Baconinterface ProjectFile<L extends string = string> { 12082815dcSEvan Bacon path: string; 13082815dcSEvan Bacon language: L; 14082815dcSEvan Bacon contents: string; 15082815dcSEvan Bacon} 16082815dcSEvan Bacon 17082815dcSEvan Bacontype AppleLanguage = 'objc' | 'objcpp' | 'swift'; 18082815dcSEvan Bacon 19082815dcSEvan Baconexport type AppDelegateProjectFile = ProjectFile<AppleLanguage>; 20082815dcSEvan Bacon 21082815dcSEvan Baconexport function getAppDelegateHeaderFilePath(projectRoot: string): string { 22082815dcSEvan Bacon const [using, ...extra] = globSync('ios/*/AppDelegate.h', { 23082815dcSEvan Bacon absolute: true, 24082815dcSEvan Bacon cwd: projectRoot, 25082815dcSEvan Bacon ignore: ignoredPaths, 26082815dcSEvan Bacon }); 27082815dcSEvan Bacon 28082815dcSEvan Bacon if (!using) { 29082815dcSEvan Bacon throw new UnexpectedError( 30082815dcSEvan Bacon `Could not locate a valid AppDelegate header at root: "${projectRoot}"` 31082815dcSEvan Bacon ); 32082815dcSEvan Bacon } 33082815dcSEvan Bacon 34082815dcSEvan Bacon if (extra.length) { 35082815dcSEvan Bacon warnMultipleFiles({ 36082815dcSEvan Bacon tag: 'app-delegate-header', 37082815dcSEvan Bacon fileName: 'AppDelegate', 38082815dcSEvan Bacon projectRoot, 39082815dcSEvan Bacon using, 40082815dcSEvan Bacon extra, 41082815dcSEvan Bacon }); 42082815dcSEvan Bacon } 43082815dcSEvan Bacon 44082815dcSEvan Bacon return using; 45082815dcSEvan Bacon} 46082815dcSEvan Bacon 47082815dcSEvan Baconexport function getAppDelegateFilePath(projectRoot: string): string { 48082815dcSEvan Bacon const [using, ...extra] = globSync('ios/*/AppDelegate.@(m|mm|swift)', { 49082815dcSEvan Bacon absolute: true, 50082815dcSEvan Bacon cwd: projectRoot, 51082815dcSEvan Bacon ignore: ignoredPaths, 52082815dcSEvan Bacon }); 53082815dcSEvan Bacon 54082815dcSEvan Bacon if (!using) { 55082815dcSEvan Bacon throw new UnexpectedError(`Could not locate a valid AppDelegate at root: "${projectRoot}"`); 56082815dcSEvan Bacon } 57082815dcSEvan Bacon 58082815dcSEvan Bacon if (extra.length) { 59082815dcSEvan Bacon warnMultipleFiles({ 60082815dcSEvan Bacon tag: 'app-delegate', 61082815dcSEvan Bacon fileName: 'AppDelegate', 62082815dcSEvan Bacon projectRoot, 63082815dcSEvan Bacon using, 64082815dcSEvan Bacon extra, 65082815dcSEvan Bacon }); 66082815dcSEvan Bacon } 67082815dcSEvan Bacon 68082815dcSEvan Bacon return using; 69082815dcSEvan Bacon} 70082815dcSEvan Bacon 71082815dcSEvan Baconexport function getAppDelegateObjcHeaderFilePath(projectRoot: string): string { 72082815dcSEvan Bacon const [using, ...extra] = globSync('ios/*/AppDelegate.h', { 73082815dcSEvan Bacon absolute: true, 74082815dcSEvan Bacon cwd: projectRoot, 75082815dcSEvan Bacon ignore: ignoredPaths, 76082815dcSEvan Bacon }); 77082815dcSEvan Bacon 78082815dcSEvan Bacon if (!using) { 79082815dcSEvan Bacon throw new UnexpectedError(`Could not locate a valid AppDelegate.h at root: "${projectRoot}"`); 80082815dcSEvan Bacon } 81082815dcSEvan Bacon 82082815dcSEvan Bacon if (extra.length) { 83082815dcSEvan Bacon warnMultipleFiles({ 84082815dcSEvan Bacon tag: 'app-delegate-objc-header', 85082815dcSEvan Bacon fileName: 'AppDelegate.h', 86082815dcSEvan Bacon projectRoot, 87082815dcSEvan Bacon using, 88082815dcSEvan Bacon extra, 89082815dcSEvan Bacon }); 90082815dcSEvan Bacon } 91082815dcSEvan Bacon 92082815dcSEvan Bacon return using; 93082815dcSEvan Bacon} 94082815dcSEvan Bacon 95082815dcSEvan Baconfunction getLanguage(filePath: string): AppleLanguage { 96082815dcSEvan Bacon const extension = path.extname(filePath); 97082815dcSEvan Bacon switch (extension) { 98082815dcSEvan Bacon case '.mm': 99082815dcSEvan Bacon return 'objcpp'; 100082815dcSEvan Bacon case '.m': 101082815dcSEvan Bacon case '.h': 102082815dcSEvan Bacon return 'objc'; 103082815dcSEvan Bacon case '.swift': 104082815dcSEvan Bacon return 'swift'; 105082815dcSEvan Bacon default: 106082815dcSEvan Bacon throw new UnexpectedError(`Unexpected iOS file extension: ${extension}`); 107082815dcSEvan Bacon } 108082815dcSEvan Bacon} 109082815dcSEvan Bacon 110082815dcSEvan Baconexport function getFileInfo(filePath: string) { 111082815dcSEvan Bacon return { 112082815dcSEvan Bacon path: path.normalize(filePath), 113082815dcSEvan Bacon contents: readFileSync(filePath, 'utf8'), 114082815dcSEvan Bacon language: getLanguage(filePath), 115082815dcSEvan Bacon }; 116082815dcSEvan Bacon} 117082815dcSEvan Bacon 118082815dcSEvan Baconexport function getAppDelegate(projectRoot: string): AppDelegateProjectFile { 119082815dcSEvan Bacon const filePath = getAppDelegateFilePath(projectRoot); 120082815dcSEvan Bacon return getFileInfo(filePath); 121082815dcSEvan Bacon} 122082815dcSEvan Bacon 123082815dcSEvan Baconexport function getSourceRoot(projectRoot: string): string { 124082815dcSEvan Bacon const appDelegate = getAppDelegate(projectRoot); 125082815dcSEvan Bacon return path.dirname(appDelegate.path); 126082815dcSEvan Bacon} 127082815dcSEvan Bacon 128082815dcSEvan Baconexport function findSchemePaths(projectRoot: string): string[] { 129082815dcSEvan Bacon return globSync('ios/*.xcodeproj/xcshareddata/xcschemes/*.xcscheme', { 130082815dcSEvan Bacon absolute: true, 131082815dcSEvan Bacon cwd: projectRoot, 132082815dcSEvan Bacon ignore: ignoredPaths, 133082815dcSEvan Bacon }); 134082815dcSEvan Bacon} 135082815dcSEvan Bacon 136082815dcSEvan Baconexport function findSchemeNames(projectRoot: string): string[] { 137082815dcSEvan Bacon const schemePaths = findSchemePaths(projectRoot); 138082815dcSEvan Bacon return schemePaths.map((schemePath) => path.parse(schemePath).name); 139082815dcSEvan Bacon} 140082815dcSEvan Bacon 141082815dcSEvan Baconexport function getAllXcodeProjectPaths(projectRoot: string): string[] { 142082815dcSEvan Bacon const iosFolder = 'ios'; 143082815dcSEvan Bacon const pbxprojPaths = globSync('ios/**/*.xcodeproj', { cwd: projectRoot, ignore: ignoredPaths }) 144082815dcSEvan Bacon .filter( 145082815dcSEvan Bacon (project) => !/test|example|sample/i.test(project) || path.dirname(project) === iosFolder 146082815dcSEvan Bacon ) 147082815dcSEvan Bacon // sort alphabetically to ensure this works the same across different devices (Fail in CI (linux) without this) 148082815dcSEvan Bacon .sort() 149082815dcSEvan Bacon .sort((a, b) => { 150082815dcSEvan Bacon const isAInIos = path.dirname(a) === iosFolder; 151082815dcSEvan Bacon const isBInIos = path.dirname(b) === iosFolder; 152082815dcSEvan Bacon // preserve previous sort order 153082815dcSEvan Bacon if ((isAInIos && isBInIos) || (!isAInIos && !isBInIos)) { 154082815dcSEvan Bacon return 0; 155082815dcSEvan Bacon } 156082815dcSEvan Bacon return isAInIos ? -1 : 1; 157082815dcSEvan Bacon }); 158082815dcSEvan Bacon 159082815dcSEvan Bacon if (!pbxprojPaths.length) { 160082815dcSEvan Bacon throw new UnexpectedError( 161082815dcSEvan Bacon `Failed to locate the ios/*.xcodeproj files relative to path "${projectRoot}".` 162082815dcSEvan Bacon ); 163082815dcSEvan Bacon } 164082815dcSEvan Bacon return pbxprojPaths.map((value) => path.join(projectRoot, value)); 165082815dcSEvan Bacon} 166082815dcSEvan Bacon 167082815dcSEvan Bacon/** 168082815dcSEvan Bacon * Get the pbxproj for the given path 169082815dcSEvan Bacon */ 170082815dcSEvan Baconexport function getXcodeProjectPath(projectRoot: string): string { 171082815dcSEvan Bacon const [using, ...extra] = getAllXcodeProjectPaths(projectRoot); 172082815dcSEvan Bacon 173082815dcSEvan Bacon if (extra.length) { 174082815dcSEvan Bacon warnMultipleFiles({ 175082815dcSEvan Bacon tag: 'xcodeproj', 176082815dcSEvan Bacon fileName: '*.xcodeproj', 177082815dcSEvan Bacon projectRoot, 178082815dcSEvan Bacon using, 179082815dcSEvan Bacon extra, 180082815dcSEvan Bacon }); 181082815dcSEvan Bacon } 182082815dcSEvan Bacon 183082815dcSEvan Bacon return using; 184082815dcSEvan Bacon} 185082815dcSEvan Bacon 186082815dcSEvan Baconexport function getAllPBXProjectPaths(projectRoot: string): string[] { 187082815dcSEvan Bacon const projectPaths = getAllXcodeProjectPaths(projectRoot); 188082815dcSEvan Bacon const paths = projectPaths 189082815dcSEvan Bacon .map((value) => path.join(value, 'project.pbxproj')) 190082815dcSEvan Bacon .filter((value) => existsSync(value)); 191082815dcSEvan Bacon 192082815dcSEvan Bacon if (!paths.length) { 193082815dcSEvan Bacon throw new UnexpectedError( 194082815dcSEvan Bacon `Failed to locate the ios/*.xcodeproj/project.pbxproj files relative to path "${projectRoot}".` 195082815dcSEvan Bacon ); 196082815dcSEvan Bacon } 197082815dcSEvan Bacon return paths; 198082815dcSEvan Bacon} 199082815dcSEvan Bacon 200082815dcSEvan Baconexport function getPBXProjectPath(projectRoot: string): string { 201082815dcSEvan Bacon const [using, ...extra] = getAllPBXProjectPaths(projectRoot); 202082815dcSEvan Bacon 203082815dcSEvan Bacon if (extra.length) { 204082815dcSEvan Bacon warnMultipleFiles({ 205082815dcSEvan Bacon tag: 'project-pbxproj', 206082815dcSEvan Bacon fileName: 'project.pbxproj', 207082815dcSEvan Bacon projectRoot, 208082815dcSEvan Bacon using, 209082815dcSEvan Bacon extra, 210082815dcSEvan Bacon }); 211082815dcSEvan Bacon } 212082815dcSEvan Bacon 213082815dcSEvan Bacon return using; 214082815dcSEvan Bacon} 215082815dcSEvan Bacon 216082815dcSEvan Baconexport function getAllInfoPlistPaths(projectRoot: string): string[] { 217082815dcSEvan Bacon const paths = globSync('ios/*/Info.plist', { 218082815dcSEvan Bacon absolute: true, 219082815dcSEvan Bacon cwd: projectRoot, 220082815dcSEvan Bacon ignore: ignoredPaths, 221082815dcSEvan Bacon }).sort( 222082815dcSEvan Bacon // longer name means more suffixes, we want the shortest possible one to be first. 223082815dcSEvan Bacon (a, b) => a.length - b.length 224082815dcSEvan Bacon ); 225082815dcSEvan Bacon 226082815dcSEvan Bacon if (!paths.length) { 227082815dcSEvan Bacon throw new UnexpectedError( 228082815dcSEvan Bacon `Failed to locate Info.plist files relative to path "${projectRoot}".` 229082815dcSEvan Bacon ); 230082815dcSEvan Bacon } 231082815dcSEvan Bacon return paths; 232082815dcSEvan Bacon} 233082815dcSEvan Bacon 234082815dcSEvan Baconexport function getInfoPlistPath(projectRoot: string): string { 235082815dcSEvan Bacon const [using, ...extra] = getAllInfoPlistPaths(projectRoot); 236082815dcSEvan Bacon 237082815dcSEvan Bacon if (extra.length) { 238082815dcSEvan Bacon warnMultipleFiles({ 239082815dcSEvan Bacon tag: 'info-plist', 240082815dcSEvan Bacon fileName: 'Info.plist', 241082815dcSEvan Bacon projectRoot, 242082815dcSEvan Bacon using, 243082815dcSEvan Bacon extra, 244082815dcSEvan Bacon }); 245082815dcSEvan Bacon } 246082815dcSEvan Bacon 247082815dcSEvan Bacon return using; 248082815dcSEvan Bacon} 249082815dcSEvan Bacon 250082815dcSEvan Baconexport function getAllEntitlementsPaths(projectRoot: string): string[] { 251082815dcSEvan Bacon const paths = globSync('ios/*/*.entitlements', { 252082815dcSEvan Bacon absolute: true, 253082815dcSEvan Bacon cwd: projectRoot, 254082815dcSEvan Bacon ignore: ignoredPaths, 255082815dcSEvan Bacon }); 256082815dcSEvan Bacon return paths; 257082815dcSEvan Bacon} 258082815dcSEvan Bacon 259082815dcSEvan Bacon/** 260082815dcSEvan Bacon * @deprecated: use Entitlements.getEntitlementsPath instead 261082815dcSEvan Bacon */ 262082815dcSEvan Baconexport function getEntitlementsPath(projectRoot: string): string | null { 263082815dcSEvan Bacon return Entitlements.getEntitlementsPath(projectRoot); 264082815dcSEvan Bacon} 265082815dcSEvan Bacon 266082815dcSEvan Baconexport function getSupportingPath(projectRoot: string): string { 267082815dcSEvan Bacon return path.resolve(projectRoot, 'ios', path.basename(getSourceRoot(projectRoot)), 'Supporting'); 268082815dcSEvan Bacon} 269082815dcSEvan Bacon 270082815dcSEvan Baconexport function getExpoPlistPath(projectRoot: string): string { 271082815dcSEvan Bacon const supportingPath = getSupportingPath(projectRoot); 272082815dcSEvan Bacon return path.join(supportingPath, 'Expo.plist'); 273082815dcSEvan Bacon} 274082815dcSEvan Bacon 275082815dcSEvan Baconfunction warnMultipleFiles({ 276082815dcSEvan Bacon tag, 277082815dcSEvan Bacon fileName, 278082815dcSEvan Bacon projectRoot, 279082815dcSEvan Bacon using, 280082815dcSEvan Bacon extra, 281082815dcSEvan Bacon}: { 282082815dcSEvan Bacon tag: string; 283082815dcSEvan Bacon fileName: string; 284082815dcSEvan Bacon projectRoot?: string; 285082815dcSEvan Bacon using: string; 286082815dcSEvan Bacon extra: string[]; 287082815dcSEvan Bacon}) { 288082815dcSEvan Bacon const usingPath = projectRoot ? path.relative(projectRoot, using) : using; 289082815dcSEvan Bacon const extraPaths = projectRoot ? extra.map((v) => path.relative(projectRoot, v)) : extra; 290082815dcSEvan Bacon addWarningIOS( 291082815dcSEvan Bacon `paths-${tag}`, 292082815dcSEvan Bacon `Found multiple ${fileName} file paths, using "${usingPath}". Ignored paths: ${JSON.stringify( 293082815dcSEvan Bacon extraPaths 294082815dcSEvan Bacon )}` 295082815dcSEvan Bacon ); 296082815dcSEvan Bacon} 297