const { withDangerousMod } = require('@expo/config-plugins'); const fs = require('fs'); const path = require('path'); // 荣耀厂商推送通道的 Expo config plugin. // // 客户端职责(依据极光官方文档 https://docs.jiguang.cn/jpush/client/Android/android_3rd_guide): // 1) 添加 cn.jiguang.sdk.plugin:honor 依赖 // 2) 注入 manifestPlaceholders: HONOR_APPID // 3) 添加荣耀 Maven 仓库 https://developer.hihonor.com/repo 到 settings.gradle 和根 build.gradle // 关键: 必须同时加到 settings.gradle 的 dependencyResolutionManagement 块(CI build 走这条路径), // 和根 build.gradle 的 buildscript+allprojects(本地 build 兜底)。 // 4) 添加 Proguard 规则 (com.hihonor.push.** 保留 + 5 条 -keepattributes) // // 实现说明:所有文件改动统一用 withDangerousMod 直接读写磁盘。 // 原因:Expo SDK 56+ 的 withSettingsGradle / withProjectBuildGradle / withAppBuildGradle hook 链中, // 第二个以后的 plugin 写回 modResults.contents 会被静默忽略。withDangerousMod 走文件系统串行执行, // 多 vendor plugin 共存可靠。 const withHonorPush = (config, options = {}) => { const { jpushVersion = '6.1.0', appId = '104562917', } = options; const honorRepoUrl = 'https://developer.hihonor.com/repo'; config = withDangerousMod(config, [ 'android', async (config) => { const settingsGradlePath = path.join( config.modRequest.platformProjectRoot, 'settings.gradle' ); const rootBuildGradlePath = path.join( config.modRequest.platformProjectRoot, 'build.gradle' ); const appBuildGradlePath = path.join( config.modRequest.platformProjectRoot, 'app', 'build.gradle' ); const proguardFilePath = path.join( config.modRequest.platformProjectRoot, 'app', 'proguard-rules.pro' ); // --- 1. settings.gradle: pluginManagement + dependencyResolutionManagement --- if (fs.existsSync(settingsGradlePath)) { let settings = fs.readFileSync(settingsGradlePath, 'utf-8'); let settingsChanged = false; const repoLine = ` maven { url '${honorRepoUrl}' }`; // pluginManagement.repositories if (!settings.match(/pluginManagement\s*\{[\s\S]*?developer\.hihonor\.com\/repo/)) { const hasRepositoriesInPM = /pluginManagement\s*\{[\s\S]*?repositories\s*\{/.test(settings); if (hasRepositoriesInPM) { settings = settings.replace( /(pluginManagement\s*\{[\s\S]*?repositories\s*\{)/, `$1\n ${repoLine}` ); settingsChanged = true; } else if (settings.includes('pluginManagement')) { settings = settings.replace( /(pluginManagement\s*\{)/, `$1\n repositories {\n gradlePluginPortal()\n google()\n mavenCentral()\n ${repoLine}\n }` ); settingsChanged = true; } else { settings = `pluginManagement {\n repositories {\n ${repoLine}\n }\n}\n\n` + settings; settingsChanged = true; } } // dependencyResolutionManagement (Gradle 7+, takes precedence over allprojects in CI) if (!settings.match(/dependencyResolutionManagement[\s\S]*developer\.hihonor\.com\/repo/)) { const depMgmtPattern = /dependencyResolutionManagement\s*\{[\s\S]*?repositories\s*\{/; if (depMgmtPattern.test(settings)) { settings = settings.replace( depMgmtPattern, `$&\n maven { url '${honorRepoUrl}' }` ); settingsChanged = true; } else { // Block doesn't exist — fallback: create with Huawei + Honor + standard repos. const block = `\ndependencyResolutionManagement {\n repositories {\n maven { url 'https://developer.huawei.com/repo/' }\n maven { url '${honorRepoUrl}' }\n google()\n mavenCentral()\n maven { url 'https://www.jitpack.io' }\n }\n}\n`; const pluginsBlockEnd = /(\nplugins\s*\{[\s\S]*?\n\})/; if (pluginsBlockEnd.test(settings)) { settings = settings.replace(pluginsBlockEnd, `$1\n${block}`); } else { settings = settings + block; } settingsChanged = true; } } if (settingsChanged) { fs.writeFileSync(settingsGradlePath, settings); console.log('[withHonorPush] updated settings.gradle (pluginManagement + dependencyResolutionManagement)'); } } // --- 2. 根 build.gradle: buildscript + allprojects repositories --- if (fs.existsSync(rootBuildGradlePath)) { let gradle = fs.readFileSync(rootBuildGradlePath, 'utf-8'); let changed = false; const repoLine = ` maven { url '${honorRepoUrl}' }`; if (!gradle.match(/buildscript\s*\{[\s\S]*?developer\.hihonor\.com\/repo/)) { const bsRepoPattern = /(buildscript\s*\{[\s\S]*?repositories\s*\{)/; if (bsRepoPattern.test(gradle)) { gradle = gradle.replace(bsRepoPattern, `$1\n${repoLine}`); changed = true; } } if (!gradle.match(/allprojects\s*\{[\s\S]*?developer\.hihonor\.com\/repo/)) { const allProjectsPattern = /(allprojects\s*\{[\s\S]*?repositories\s*\{)/; if (allProjectsPattern.test(gradle)) { gradle = gradle.replace(allProjectsPattern, `$1\n${repoLine}`); changed = true; } else { const allProjectsBlock = `\nallprojects {\n repositories {${repoLine}\n }\n}\n`; gradle = gradle + allProjectsBlock; changed = true; } } if (changed) { fs.writeFileSync(rootBuildGradlePath, gradle); console.log('[withHonorPush] updated root build.gradle (buildscript + allprojects repos)'); } } // --- 3. app/build.gradle: AAR 依赖 + manifestPlaceholders --- if (fs.existsSync(appBuildGradlePath)) { let gradle = fs.readFileSync(appBuildGradlePath, 'utf-8'); let changed = false; if (!gradle.includes('cn.jiguang.sdk.plugin:honor')) { const depsPattern = /dependencies\s*\{/; if (depsPattern.test(gradle)) { gradle = gradle.replace( depsPattern, `dependencies {\n implementation 'cn.jiguang.sdk.plugin:honor:${jpushVersion}'` ); changed = true; } } const manifestPlaceholderRegex = /manifestPlaceholders\s*=\s*\[([\s\S]*?)\]/; const match = gradle.match(manifestPlaceholderRegex); const requiredPlaceholders = { HONOR_APPID: appId, }; if (match) { const existingBlock = match[1]; const existingEntries = {}; const entryRegex = /(\w+)\s*:\s*"([^"]*)"/g; let entryMatch; while ((entryMatch = entryRegex.exec(existingBlock)) !== null) { existingEntries[entryMatch[1]] = entryMatch[2]; } let placeholdersChanged = false; for (const [key, value] of Object.entries(requiredPlaceholders)) { if (existingEntries[key] !== value) { existingEntries[key] = value; placeholdersChanged = true; } } if (placeholdersChanged) { const entriesStr = Object.entries(existingEntries) .map(([k, v]) => `${k}: "${v}"`) .join(',\n '); const newBlock = `\n manifestPlaceholders = [\n ${entriesStr}\n ]`; gradle = gradle.replace(manifestPlaceholderRegex, newBlock.trim()); changed = true; } } else if (gradle.includes('REACT_NATIVE_RELEASE_LEVEL')) { const entriesStr = Object.entries(requiredPlaceholders) .map(([k, v]) => `${k}: "${v}"`) .join(',\n '); const block = `\n manifestPlaceholders = [\n ${entriesStr}\n ]\n`; const lines = gradle.split('\n'); for (let i = 0; i < lines.length; i++) { if (lines[i].includes('REACT_NATIVE_RELEASE_LEVEL')) { lines.splice(i + 1, 0, block); break; } } gradle = lines.join('\n'); changed = true; } if (changed) { fs.writeFileSync(appBuildGradlePath, gradle); console.log('[withHonorPush] updated app/build.gradle (AAR + manifestPlaceholders)'); } } // --- 4. proguard-rules.pro --- if (fs.existsSync(proguardFilePath)) { let proguard = fs.readFileSync(proguardFilePath, 'utf-8'); if (!proguard.includes('-keep class com.hihonor.push.**')) { const honorRules = ` # JPush Honor vendor channel — keep rules (https://docs.jiguang.cn/jpush/client/Android/android_3rd_guide) -ignorewarnings -keepattributes *Annotation* -keepattributes Exceptions -keepattributes InnerClasses -keepattributes Signature -keepattributes SourceFile,LineNumberTable -keep class com.hihonor.push.**{*;} `; fs.writeFileSync(proguardFilePath, proguard + honorRules); console.log('[withHonorPush] appended Honor Proguard rules to proguard-rules.pro'); } } return config; }, ]); return config; }; module.exports = withHonorPush;