Files
frontend/plugins/withHuaweiPush.js
lafay edde8f1c30
Some checks failed
Frontend CI / ota (android) (push) Successful in 1m41s
Frontend CI / ota (ios) (push) Successful in 1m36s
Frontend CI / build-and-push-web (push) Successful in 10m38s
Frontend CI / build-android-apk (push) Failing after 16m7s
fix(huawei-push): simplify Jiguang Maven repository injection
Clean up duplicate and malformed Jiguang repo addition code in the Huawei push plugin. The previous implementation had redundant logic for injecting the repository into dependencyResolutionManagement, resulting in duplicate entries and syntax errors. Simplified to directly append the maven URL using the `$&` backreference pattern for cleaner, more maintainable code.
2026-06-15 23:05:55 +08:00

252 lines
10 KiB
JavaScript

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;