import { ExpoConfig } from '@expo/config-types' ; import Debug from 'debug' ; import fs from 'fs-extra' ; import { sync as globSync } from 'glob' ; import path from 'path' ; import { ConfigPlugin } from '../Plugin.types' ; import { createAndroidManifestPlugin , withAppBuildGradle } from '../plugins/android-plugins' ; import { withDangerousMod } from '../plugins/withDangerousMod' ; import { directoryExistsAsync } from '../utils/modules' ; import { addWarningAndroid } from '../utils/warnings' ; import { AndroidManifest } from './Manifest' ; import { getAppBuildGradleFilePath , getProjectFilePath } from './Paths' ; const debug = Debug ( 'expo:config-plugins:android:package' ) ; export const withPackageManifest = createAndroidManifestPlugin ( setPackageInAndroidManifest , 'withPackageManifest' ) ; export const withPackageGradle = config => { return withAppBuildGradle ( config , config => { if ( config . modResults . language === 'groovy' ) { config . modResults . contents = setPackageInBuildGradle ( config , config . modResults . contents ) ; } else { addWarningAndroid ( 'android.package' , `Cannot automatically configure app build.gradle if it's not groovy` ) ; } return config ; } ) ; } ; export const withPackageRefactor = config => { return withDangerousMod ( config , [ 'android' , async ( config ) => { await renamePackageOnDisk ( config , config . modRequest . projectRoot ) ; return config ; } , ] ) ; } ; export function getPackage ( config ) { return config . android ?. package ?? null ; } function getPackageRoot ( projectRoot , type ) { return path . join ( projectRoot , 'android' , 'app' , 'src' , type , 'java' ) ; } function getCurrentPackageName ( projectRoot , packageRoot ) { const mainApplication = getProjectFilePath ( projectRoot , 'MainApplication' ) ; const packagePath = path . dirname ( mainApplication ) ; const packagePathParts = path . relative ( packageRoot , packagePath ) . split ( path . sep ) . filter ( Boolean ) ; return packagePathParts . join ( '.' ) ; } function getCurrentPackageForProjectFile ( projectRoot , packageRoot , fileName , type ) { const filePath = globSync ( path . join ( projectRoot , `android/app/src/${ type }/java/**/${ fileName }.@(java|kt)` ) ) [ 0 ] ; if ( ! filePath ) { return null ; } const packagePath = path . dirname ( filePath ) ; const packagePathParts = path . relative ( packageRoot , packagePath ) . split ( path . sep ) . filter ( Boolean ) ; return packagePathParts . join ( '.' ) ; } function getCurrentPackageNameForType ( projectRoot , type ) { const packageRoot = getPackageRoot ( projectRoot , type as any ) ; if ( type === 'main' ) { return getCurrentPackageName ( projectRoot , packageRoot ) ; } return getCurrentPackageForProjectFile ( projectRoot , packageRoot , '*' , type ) ; } export async function renamePackageOnDisk ( config , projectRoot ) { const newPackageName = getPackage ( config ) ; if ( newPackageName === null ) { return ; } for ( const type of [ 'main' , 'debug' ] ) { await renamePackageOnDiskForType ( { projectRoot , type , packageName : newPackageName } ) ; } } export async function renamePackageOnDiskForType ( { projectRoot , type , packageName , } ) { if ( ! packageName ) { return ; } const currentPackageName = getCurrentPackageNameForType ( projectRoot , type ) ; debug ( `Found package "${ currentPackageName }" for type "${ type }"` ) ; if ( ! currentPackageName || currentPackageName === packageName ) { return ; } debug ( `Refactor "${ currentPackageName }" to "${ packageName }" for type "${ type }"` ) ; const packageRoot = getPackageRoot ( projectRoot , type as any ) ; if ( ! ( await directoryExistsAsync ( packageRoot ) ) ) { debug ( `- skipping refactor of missing directory: ${ packageRoot }` ) ; return ; } const currentPackagePath = path . join ( packageRoot , ... currentPackageName . split ( '.' ) ) ; const newPackagePath = path . join ( packageRoot , ... packageName . split ( '.' ) ) ; fs . mkdirpSync ( newPackagePath ) ; globSync ( '**/*' , { cwd : currentPackagePath } ) . forEach ( relativePath => { const filepath = path . join ( currentPackagePath , relativePath ) ; if ( fs . lstatSync ( filepath ) . isFile ( ) ) { fs . moveSync ( filepath , path . join ( newPackagePath , relativePath ) ) ; } else { fs . mkdirpSync ( filepath ) ; } } ) ; const oldPathParts = currentPackageName . split ( '.' ) ; while ( oldPathParts . length ) { const pathToCheck = path . join ( packageRoot , ... oldPathParts ) ; try { const files = fs . readdirSync ( pathToCheck ) ; if ( files . length === 0 ) { fs . rmdirSync ( pathToCheck ) ; } } finally { oldPathParts . pop ( ) ; } } const filesToUpdate = [ ... globSync ( '**/*' , { cwd : newPackagePath , absolute : true } ) ] ; if ( type === 'main' ) { filesToUpdate . push ( path . join ( projectRoot , 'android' , 'app' , 'BUCK' ) ) ; } filesToUpdate . forEach ( ( filepath ) => { try { if ( fs . lstatSync ( filepath ) . isFile ( ) ) { let contents = fs . readFileSync ( filepath ) . toString ( ) ; contents = contents . replace ( new RegExp ( currentPackageName ! , 'g' ) , packageName ) ; fs . writeFileSync ( filepath , contents ) ; } } catch ( e ) { debug ( `Error updating "${ filepath }" for type "${ type }"` ) ; } } ) ; } export function setPackageInBuildGradle ( config , buildGradle ) { const packageName = getPackage ( config ) ; if ( packageName === null ) { return buildGradle ; } const pattern = new RegExp ( `applicationId ['"].*['"]` ) ; return buildGradle . replace ( pattern , `applicationId '${ packageName }'` ) ; } export function setPackageInAndroidManifest ( config , androidManifest ) { const packageName = getPackage ( config ) ; if ( packageName ) { androidManifest . manifest . $ . package = packageName ; } else { delete androidManifest . manifest . $ . package ; } return androidManifest ; } export async function getApplicationIdAsync ( projectRoot ) { const buildGradlePath = getAppBuildGradleFilePath ( projectRoot ) ; if ( ! ( await fs . pathExists ( buildGradlePath ) ) ) { return null ; } const buildGradle = await fs . readFile ( buildGradlePath , 'utf8' ) ; const matchResult = buildGradle . match ( /applicationId ['"](.*)['"]/ ) ; return matchResult ?. [ 1 ] ?? null ; }