const { withAppBuildGradle, withProjectBuildGradle, withSettingsGradle, withDangerousMod, withAndroidManifest, } = require('@expo/config-plugins'); const fs = require('fs'); const path = require('path'); const withHuaweiPush = (config, options = {}) => { const { jpushVersion = '5.8.0' } = options; config = withSettingsGradle(config, (config) => { const contents = config.modResults.contents; // Add Huawei Maven repo to pluginManagement.repositories for buildscript resolution. // NOTE: We intentionally do NOT add `plugins { id 'com.huawei.agconnect' ... }` here. // The Huawei agcp plugin transitively depends on agconnect-apms-plugin:1.6.2.300, // which depends on AGP 4.0.1. That old AGP references BuildCompletionListener (removed // from Gradle 8.x+), causing NoClassDefFoundError at build time. Instead, the plugin // is applied via buildscript classpath in build.gradle with the apms-plugin excluded. if (!contents.includes('developer.huawei.com/repo')) { const repoLine = ` maven { url 'https://developer.huawei.com/repo/' }`; const jigLine = ` maven { url 'https://developer.jiguang.cn/maven' }`; const hasPluginManagement = contents.includes('pluginManagement'); const hasRepositoriesInPM = /pluginManagement\s*\{[\s\S]*?repositories\s*\{/.test(contents); if (hasRepositoriesInPM) { config.modResults.contents = contents.replace( /(pluginManagement\s*\{[\s\S]*?repositories\s*\{)/, `$1\n ${repoLine}\n ${jigLine}` ); } else if (hasPluginManagement) { config.modResults.contents = contents.replace( /(pluginManagement\s*\{)/, `$1\n repositories {\n gradlePluginPortal()\n google()\n mavenCentral()\n ${repoLine}\n ${jigLine}\n }` ); } else { config.modResults.contents = `pluginManagement {\n repositories {\n ${repoLine}\n ${jigLine}\n }\n}\n\n` + config.modResults.contents; } } // Add Jiguang Maven repo to dependencyResolutionManagement (for jcore/jpush SDK) if (!contents.includes('developer.jiguang.cn/maven')) { const depMgmtPattern = /dependencyResolutionManagement\s*\{[\s\S]*?repositories\s*\{/; if (depMgmtPattern.test(config.modResults.contents)) { config.modResults.contents = config.modResults.contents.replace( depMgmtPattern, `$&\n maven { url 'https://developer.jiguang.cn/maven' }` ); } } // Ensure Huawei Maven repo in dependencyResolutionManagement (Gradle 9+) if (!config.modResults.contents.match(/dependencyResolutionManagement[\s\S]*developer\.huawei\.com\/repo/)) { const depMgmtPattern = /dependencyResolutionManagement\s*\{[\s\S]*?repositories\s*\{/; if (depMgmtPattern.test(config.modResults.contents)) { config.modResults.contents = config.modResults.contents.replace( depMgmtPattern, `$&\n maven { url 'https://developer.huawei.com/repo/' }` ); } } return config; }); config = withProjectBuildGradle(config, (config) => { const contents = config.modResults.contents; if (!contents.includes('developer.huawei.com/repo')) { config.modResults.contents = contents.replace( /(repositories\s*\{)/g, (match) => { return match + `\n maven { url 'https://developer.huawei.com/repo/' }`; } ); } return config; }); config = withProjectBuildGradle(config, (config) => { const contents = config.modResults.contents; // Add Huawei AGConnect classpath with apms-plugin excluded. // The apms-plugin transitively depends on AGP 4.0.1 which is incompatible // with Gradle 8.14+ (references removed BuildCompletionListener class). if (!contents.includes('com.huawei.agconnect:agcp')) { if (contents.includes('buildscript')) { const depsPattern = /(buildscript\s*\{[\s\S]*?dependencies\s*\{)/; if (depsPattern.test(contents)) { config.modResults.contents = contents.replace( depsPattern, `$1\n classpath('com.huawei.agconnect:agcp:1.9.1.301') {\n exclude group: 'com.huawei.agconnect', module: 'agconnect-apms-plugin'\n }` ); } } } // Set explicit AGP version (needed by Huawei plugin validation) if (!contents.includes('com.android.tools.build:gradle:8.12.0')) { config.modResults.contents = config.modResults.contents.replace( /classpath\(['"]com\.android\.tools\.build:gradle['"]\)/, "classpath('com.android.tools.build:gradle:8.12.0')" ); } // Google Services plugin classpath — required for Firebase init via google-services.json. if (!contents.includes('com.google.gms:google-services')) { const depsPattern = /(buildscript\s*\{[\s\S]*?dependencies\s*\{[\s\S]*?classpath\(['"]org\.jetbrains\.kotlin:kotlin-gradle-plugin['"]\))/; if (depsPattern.test(config.modResults.contents)) { config.modResults.contents = config.modResults.contents.replace( depsPattern, `$1\n classpath('com.google.gms:google-services:4.4.4')` ); } } if (!config.modResults.contents.includes('developer.huawei.com/repo')) { const bsRepoPattern = /(buildscript\s*\{[\s\S]*?repositories\s*\{)/; if (bsRepoPattern.test(config.modResults.contents)) { config.modResults.contents = config.modResults.contents.replace( bsRepoPattern, `$1\n maven { url 'https://developer.huawei.com/repo/' }` ); } } return config; }); config = withAppBuildGradle(config, (config) => { const contents = config.modResults.contents; // Apply Google Services plugin (Firebase) before the Huawei plugin. // The googleServicesFile in app.json only sets up the file copy + plugin // when run through `expo prebuild`, but it does not coexist with this // Huawei plugin reliably, so we apply it explicitly here. if (!contents.includes('com.google.gms.google-services')) { const reactApply = /apply plugin:\s*['"]com\.facebook\.react['"]\s*\n/; if (reactApply.test(contents)) { config.modResults.contents = contents.replace( reactApply, `apply plugin: "com.facebook.react"\napply plugin: 'com.google.gms.google-services'\n` ); } else { config.modResults.contents = `apply plugin: 'com.google.gms.google-services'\n` + contents; } } if (!contents.includes('com.huawei.agconnect')) { const applyPluginPattern = /apply plugin:\s*['"]com\.google\.gms\.google-services['"]/; if (applyPluginPattern.test(contents)) { config.modResults.contents = contents.replace( applyPluginPattern, `apply plugin: 'com.google.gms.google-services'\napply plugin: 'com.huawei.agconnect'` ); } else { const lastApplyPlugin = /apply plugin:[^\n]*(?:\n)/g; const matches = contents.match(lastApplyPlugin); if (matches && matches.length > 0) { const lastMatch = matches[matches.length - 1]; const lastIndex = contents.lastIndexOf(lastMatch); config.modResults.contents = contents.slice(0, lastIndex + lastMatch.length) + "apply plugin: 'com.huawei.agconnect'\n" + contents.slice(lastIndex + lastMatch.length); } else { config.modResults.contents = contents + "\napply plugin: 'com.huawei.agconnect'\n"; } } } if (!contents.includes('cn.jiguang.sdk.plugin:huawei')) { const depsPattern = /dependencies\s*\{/; if (depsPattern.test(config.modResults.contents)) { config.modResults.contents = config.modResults.contents.replace( depsPattern, `dependencies {\n implementation 'cn.jiguang.sdk.plugin:huawei:${jpushVersion}'` ); } } return config; }); config = withDangerousMod(config, [ 'android', async (config) => { const appDir = path.join(config.modRequest.platformProjectRoot, 'app'); const srcFile = path.join(config.modRequest.projectRoot, 'plugins', 'agconnect-services.json'); const destFile = path.join(appDir, 'agconnect-services.json'); if (fs.existsSync(srcFile)) { fs.copyFileSync(srcFile, destFile); console.log('[withHuaweiPush] copied agconnect-services.json to android/app/'); } else { console.warn('[withHuaweiPush] agconnect-services.json not found at project root'); } // Copy google-services.json to android/app/ so the Google Services // Gradle plugin can find it. Without this, FCM / Firebase init fails // at runtime with "default FirebaseApp is not initialized". const gmsSrc = path.join(config.modRequest.projectRoot, 'google-services.json'); const gmsDest = path.join(appDir, 'google-services.json'); if (fs.existsSync(gmsSrc)) { fs.copyFileSync(gmsSrc, gmsDest); console.log('[withHuaweiPush] copied google-services.json to android/app/'); } else { console.warn('[withHuaweiPush] google-services.json not found at project root'); } // RN 0.85.3 ships Gradle 9.3.1 in its template, but Kotlin 2.1.20 is incompatible // with Gradle 9.x (CompilerEnvironment ClassNotFoundException). Downgrade to 8.14.5. const wrapperFile = path.join(config.modRequest.platformProjectRoot, 'gradle', 'wrapper', 'gradle-wrapper.properties'); if (fs.existsSync(wrapperFile)) { let wrapper = fs.readFileSync(wrapperFile, 'utf8'); if (wrapper.includes('gradle-9.')) { wrapper = wrapper.replace(/gradle-[\d.]+-bin\.zip/, 'gradle-8.14.5-bin.zip'); fs.writeFileSync(wrapperFile, wrapper); console.log('[withHuaweiPush] downgraded Gradle to 8.14.5 for Kotlin 2.1.20 compatibility'); } } // Ensure local.properties with SDK path exists (gets cleared on prebuild --clean) const localPropsFile = path.join(config.modRequest.platformProjectRoot, 'local.properties'); if (!fs.existsSync(localPropsFile)) { const homeDir = process.env.USERPROFILE || process.env.HOME || ''; const sdkPath = path.join(homeDir, 'AppData', 'Local', 'Android', 'Sdk'); if (fs.existsSync(sdkPath)) { fs.writeFileSync(localPropsFile, `sdk.dir=${sdkPath.replace(/\\/g, '\\\\')}\n`); console.log('[withHuaweiPush] created local.properties with SDK path'); } } return config; }, ]); config = withAndroidManifest(config, (config) => { return config; }); return config; }; module.exports = withHuaweiPush;