240 lines
9.9 KiB
JavaScript
240 lines
9.9 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 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}`
|
|
);
|
|
} else if (hasPluginManagement) {
|
|
config.modResults.contents = contents.replace(
|
|
/(pluginManagement\s*\{)/,
|
|
`$1\n repositories {\n gradlePluginPortal()\n google()\n mavenCentral()\n ${repoLine}\n }`
|
|
);
|
|
} else {
|
|
config.modResults.contents =
|
|
`pluginManagement {\n repositories {\n ${repoLine}\n }\n}\n\n` +
|
|
config.modResults.contents;
|
|
}
|
|
}
|
|
|
|
// 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;
|