1082815dcSEvan Baconimport assert from 'assert'; 2082815dcSEvan Baconimport fs from 'fs'; 3082815dcSEvan Baconimport { sync as globSync } from 'glob'; 4082815dcSEvan Baconimport * as path from 'path'; 5082815dcSEvan Bacon 6*8a424bebSJames Ideimport { ResourceKind } from './Resources'; 7082815dcSEvan Baconimport { UnexpectedError } from '../utils/errors'; 8082815dcSEvan Baconimport { directoryExistsAsync } from '../utils/modules'; 9082815dcSEvan Bacon 10082815dcSEvan Baconexport interface ProjectFile<L extends string = string> { 11082815dcSEvan Bacon path: string; 12082815dcSEvan Bacon language: L; 13082815dcSEvan Bacon contents: string; 14082815dcSEvan Bacon} 15082815dcSEvan Bacon 16082815dcSEvan Baconexport type ApplicationProjectFile = ProjectFile<'java' | 'kt'>; 17082815dcSEvan Baconexport type GradleProjectFile = ProjectFile<'groovy' | 'kt'>; 18082815dcSEvan Bacon 19082815dcSEvan Baconexport function getProjectFilePath(projectRoot: string, name: string): string { 20082815dcSEvan Bacon const filePath = globSync( 21082815dcSEvan Bacon path.join(projectRoot, `android/app/src/main/java/**/${name}.@(java|kt)`) 22082815dcSEvan Bacon )[0]; 23082815dcSEvan Bacon assert( 24082815dcSEvan Bacon filePath, 25082815dcSEvan Bacon `Project file "${name}" does not exist in android project for root "${projectRoot}"` 26082815dcSEvan Bacon ); 27082815dcSEvan Bacon 28082815dcSEvan Bacon return filePath; 29082815dcSEvan Bacon} 30082815dcSEvan Bacon 31082815dcSEvan Baconfunction getLanguage(filePath: string): 'java' | 'groovy' | 'kt' { 32082815dcSEvan Bacon const extension = path.extname(filePath); 33082815dcSEvan Bacon switch (extension) { 34082815dcSEvan Bacon case '.java': 35082815dcSEvan Bacon return 'java'; 36082815dcSEvan Bacon case '.kts': 37082815dcSEvan Bacon case '.kt': 38082815dcSEvan Bacon return 'kt'; 39082815dcSEvan Bacon case '.groovy': 40082815dcSEvan Bacon case '.gradle': 41082815dcSEvan Bacon return 'groovy'; 42082815dcSEvan Bacon default: 43082815dcSEvan Bacon throw new UnexpectedError(`Unexpected Android file extension: ${extension}`); 44082815dcSEvan Bacon } 45082815dcSEvan Bacon} 46082815dcSEvan Bacon 47082815dcSEvan Baconexport function getFileInfo(filePath: string) { 48082815dcSEvan Bacon return { 49082815dcSEvan Bacon path: path.normalize(filePath), 50082815dcSEvan Bacon contents: fs.readFileSync(filePath, 'utf8'), 51082815dcSEvan Bacon language: getLanguage(filePath) as any, 52082815dcSEvan Bacon }; 53082815dcSEvan Bacon} 54082815dcSEvan Bacon 55082815dcSEvan Baconexport async function getMainApplicationAsync( 56082815dcSEvan Bacon projectRoot: string 57082815dcSEvan Bacon): Promise<ApplicationProjectFile> { 58082815dcSEvan Bacon const filePath = getProjectFilePath(projectRoot, 'MainApplication'); 59082815dcSEvan Bacon return getFileInfo(filePath); 60082815dcSEvan Bacon} 61082815dcSEvan Bacon 62082815dcSEvan Baconexport async function getMainActivityAsync(projectRoot: string): Promise<ApplicationProjectFile> { 63082815dcSEvan Bacon const filePath = getProjectFilePath(projectRoot, 'MainActivity'); 64082815dcSEvan Bacon return getFileInfo(filePath); 65082815dcSEvan Bacon} 66082815dcSEvan Bacon 67082815dcSEvan Baconexport function getGradleFilePath(projectRoot: string, gradleName: string): string { 68082815dcSEvan Bacon const groovyPath = path.resolve(projectRoot, `${gradleName}.gradle`); 69082815dcSEvan Bacon const ktPath = path.resolve(projectRoot, `${gradleName}.gradle.kts`); 70082815dcSEvan Bacon 71082815dcSEvan Bacon const isGroovy = fs.existsSync(groovyPath); 72082815dcSEvan Bacon const isKotlin = !isGroovy && fs.existsSync(ktPath); 73082815dcSEvan Bacon 74082815dcSEvan Bacon if (!isGroovy && !isKotlin) { 75082815dcSEvan Bacon throw new Error(`Failed to find '${gradleName}.gradle' file for project: ${projectRoot}.`); 76082815dcSEvan Bacon } 77082815dcSEvan Bacon const filePath = isGroovy ? groovyPath : ktPath; 78082815dcSEvan Bacon return filePath; 79082815dcSEvan Bacon} 80082815dcSEvan Bacon 81082815dcSEvan Baconexport function getProjectBuildGradleFilePath(projectRoot: string): string { 82082815dcSEvan Bacon return getGradleFilePath(path.join(projectRoot, 'android'), 'build'); 83082815dcSEvan Bacon} 84082815dcSEvan Bacon 85082815dcSEvan Baconexport async function getProjectBuildGradleAsync(projectRoot: string): Promise<GradleProjectFile> { 86082815dcSEvan Bacon return getFileInfo(getProjectBuildGradleFilePath(projectRoot)); 87082815dcSEvan Bacon} 88082815dcSEvan Bacon 89082815dcSEvan Baconexport function getSettingsGradleFilePath(projectRoot: string): string { 90082815dcSEvan Bacon return getGradleFilePath(path.join(projectRoot, 'android'), 'settings'); 91082815dcSEvan Bacon} 92082815dcSEvan Bacon 93082815dcSEvan Baconexport async function getSettingsGradleAsync(projectRoot: string): Promise<GradleProjectFile> { 94082815dcSEvan Bacon return getFileInfo(getSettingsGradleFilePath(projectRoot)); 95082815dcSEvan Bacon} 96082815dcSEvan Bacon 97082815dcSEvan Baconexport function getAppBuildGradleFilePath(projectRoot: string): string { 98082815dcSEvan Bacon return getGradleFilePath(path.join(projectRoot, 'android', 'app'), 'build'); 99082815dcSEvan Bacon} 100082815dcSEvan Bacon 101082815dcSEvan Baconexport async function getAppBuildGradleAsync(projectRoot: string): Promise<GradleProjectFile> { 102082815dcSEvan Bacon return getFileInfo(getAppBuildGradleFilePath(projectRoot)); 103082815dcSEvan Bacon} 104082815dcSEvan Bacon 105082815dcSEvan Baconexport async function getProjectPathOrThrowAsync(projectRoot: string): Promise<string> { 106082815dcSEvan Bacon const projectPath = path.join(projectRoot, 'android'); 107082815dcSEvan Bacon if (await directoryExistsAsync(projectPath)) { 108082815dcSEvan Bacon return projectPath; 109082815dcSEvan Bacon } 110082815dcSEvan Bacon throw new Error(`Android project folder is missing in project: ${projectRoot}`); 111082815dcSEvan Bacon} 112082815dcSEvan Bacon 113082815dcSEvan Baconexport async function getAndroidManifestAsync(projectRoot: string): Promise<string> { 114082815dcSEvan Bacon const projectPath = await getProjectPathOrThrowAsync(projectRoot); 115082815dcSEvan Bacon const filePath = path.join(projectPath, 'app/src/main/AndroidManifest.xml'); 116082815dcSEvan Bacon return filePath; 117082815dcSEvan Bacon} 118082815dcSEvan Bacon 119082815dcSEvan Baconexport async function getResourceFolderAsync(projectRoot: string): Promise<string> { 120082815dcSEvan Bacon const projectPath = await getProjectPathOrThrowAsync(projectRoot); 121082815dcSEvan Bacon return path.join(projectPath, `app/src/main/res`); 122082815dcSEvan Bacon} 123082815dcSEvan Bacon 124082815dcSEvan Baconexport async function getResourceXMLPathAsync( 125082815dcSEvan Bacon projectRoot: string, 126082815dcSEvan Bacon { kind = 'values', name }: { kind?: ResourceKind; name: 'colors' | 'strings' | 'styles' | string } 127082815dcSEvan Bacon): Promise<string> { 128082815dcSEvan Bacon const resourcePath = await getResourceFolderAsync(projectRoot); 129082815dcSEvan Bacon 130082815dcSEvan Bacon const filePath = path.join(resourcePath, `${kind}/${name}.xml`); 131082815dcSEvan Bacon return filePath; 132082815dcSEvan Bacon} 133