Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c226537
chore(cli): Update plugins with breaking changes for Cap 8 migrate
OS-pedrogustavobilro Nov 6, 2025
94023e9
chore(cli): Update Capacitor major and deployment targets
OS-pedrogustavobilro Nov 6, 2025
485281f
chore(cli): update google maps default versions
OS-pedrogustavobilro Nov 6, 2025
5dd652a
chore(cli): Add density to AndroidManifest.xml im migrate command
OS-pedrogustavobilro Nov 6, 2025
cf5b1b0
chore(cli): Update gradle version for migrate command
OS-pedrogustavobilro Nov 6, 2025
12f10d7
chore(cli): Update capacitor update link for official plugins
OS-pedrogustavobilro Nov 6, 2025
1905659
chore(cli): Add step to migrate app/build.gradle
OS-pedrogustavobilro Nov 6, 2025
dae7205
chore(cli): Update android dependency versions for migrate command
OS-pedrogustavobilro Nov 7, 2025
426e434
chore: run fmt for cli
OS-pedrogustavobilro Nov 7, 2025
4dad6ee
Merge branch 'main' into feat/RMET-4539/cli-migrate-cap8
OS-pedrogustavobilro Nov 7, 2025
d477b35
chore(cli): Update androidx exif version for migrate command
OS-pedrogustavobilro Nov 10, 2025
6955aab
chore(cli): Remove old method to add namespace from manifest
OS-pedrogustavobilro Nov 10, 2025
d2cdcb6
Merge branch 'main' into feat/RMET-4539/cli-migrate-cap8
OS-pedrogustavobilro Nov 10, 2025
5961084
chore(cli): Add additional plugins with breaking changes
OS-pedrogustavobilro Nov 10, 2025
e3e1fb8
Merge branch 'main' into feat/RMET-4539/cli-migrate-cap8
OS-pedrogustavobilro Nov 10, 2025
5a1b938
Merge branch 'main' into feat/RMET-4539/cli-migrate-cap8
OS-pedrogustavobilro Nov 11, 2025
8ec0779
chore(cli): Fail migrate if density cannot be added to Android Manifest
OS-pedrogustavobilro Nov 11, 2025
91fe0e2
chore(cli): Log error instead of failing command
OS-pedrogustavobilro Nov 12, 2025
9b4a66b
chore(cli): Replace mention of Ionic VS Code extension
OS-pedrogustavobilro Nov 12, 2025
e67dfc7
Merge branch 'main' into feat/RMET-4539/cli-migrate-cap8
markemer Nov 12, 2025
2b6a007
Merge branch 'main' into feat/RMET-4539/cli-migrate-cap8
OS-pedrogustavobilro Nov 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions cli/src/android/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
} from '../plugin';
import type { Plugin } from '../plugin';
import { copy as copyTask } from '../tasks/copy';
import { patchOldCapacitorPlugins } from '../tasks/migrate';
import { readdirp, convertToUnixPath } from '../util/fs';
import { resolveNode } from '../util/node';
import { extractTemplate } from '../util/template';
Expand All @@ -39,7 +38,6 @@ export async function updateAndroid(config: Config): Promise<void> {
await writePluginsJson(config, capacitorPlugins);
await removePluginsNativeFiles(config);
const cordovaPlugins = plugins.filter((p) => getPluginType(p, platform) === PluginType.Cordova);
await patchOldCapacitorPlugins(config);
if (cordovaPlugins.length > 0) {
await copyPluginsNativeFiles(config, cordovaPlugins);
}
Expand Down
183 changes: 55 additions & 128 deletions cli/src/tasks/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ import { join } from 'path';
import { rimraf } from 'rimraf';
import { coerce, gte, lt } from 'semver';

import { getAndroidPlugins } from '../android/common';
import c from '../colors';
import { getCoreVersion, runTask, checkJDKMajorVersion } from '../common';
import type { Config } from '../definitions';
import { fatal } from '../errors';
import { getMajoriOSVersion } from '../ios/common';
import { logger, logPrompt, logSuccess } from '../log';
import { getPlugins } from '../plugin';
import { deleteFolderRecursive } from '../util/fs';
import { resolveNode } from '../util/node';
import { checkPackageManager } from '../util/spm';
import { runCommand } from '../util/subprocess';
import { extractTemplate } from '../util/template';
Expand Down Expand Up @@ -46,10 +43,10 @@ const plugins = [
'@capacitor/text-zoom',
'@capacitor/toast',
];
const coreVersion = '^7.0.0';
const pluginVersion = '^7.0.0';
const gradleVersion = '8.11.1';
const iOSVersion = '14';
const coreVersion = 'next';
const pluginVersion = 'next';
const gradleVersion = '8.14.3';
const iOSVersion = '15';
let installFailed = false;

export async function migrateCommand(config: Config, noprompt: boolean, packagemanager: string): Promise<void> {
Expand All @@ -58,14 +55,14 @@ export async function migrateCommand(config: Config, noprompt: boolean, packagem
}

const capMajor = await checkCapacitorMajorVersion(config);
if (capMajor < 6) {
fatal('Migrate can only be used on Capacitor 6, please use the CLI in Capacitor 6 to upgrade to 6 first');
if (capMajor < 7) {
fatal('Migrate can only be used on Capacitor 7, please use the CLI in Capacitor 7 to upgrade to 7 first');
}

const jdkMajor = await checkJDKMajorVersion();

if (jdkMajor < 21) {
logger.warn('Capacitor 7 requires JDK 21 or higher. Some steps may fail.');
logger.warn('Capacitor 8 requires JDK 21 or higher. Some steps may fail.');
}

const variablesAndClasspaths:
Expand All @@ -92,7 +89,7 @@ export async function migrateCommand(config: Config, noprompt: boolean, packagem

const { migrateconfirm } = noprompt
? { migrateconfirm: 'y' }
: await logPrompt(`Capacitor 7 sets a deployment target of iOS ${iOSVersion} and Android 15 (SDK 35). \n`, {
: await logPrompt(`Capacitor 8 sets a deployment target of iOS ${iOSVersion} and Android 16 (SDK 36). \n`, {
type: 'text',
name: 'migrateconfirm',
message: `Are you sure you want to migrate? (Y/n)`,
Expand Down Expand Up @@ -188,8 +185,8 @@ export async function migrateCommand(config: Config, noprompt: boolean, packagem
}

if (allDependencies['@capacitor/android'] && existsSync(config.android.platformDirAbs)) {
// AndroidManifest.xml add navigation"
await runTask(`Migrating AndroidManifest.xml by adding navigation to Activity configChanges.`, () => {
// AndroidManifest.xml add "density"
await runTask(`Migrating AndroidManifest.xml by adding density to Activity configChanges.`, () => {
return updateAndroidManifest(join(config.android.srcMainDirAbs, 'AndroidManifest.xml'));
});

Expand Down Expand Up @@ -222,10 +219,14 @@ export async function migrateCommand(config: Config, noprompt: boolean, packagem
} else {
logger.warn('Skipped upgrading gradle wrapper files');
}
await runTask(`Migrating build.gradle file.`, () => {
await runTask(`Migrating root build.gradle file.`, () => {
return updateBuildGradle(join(config.android.platformDirAbs, 'build.gradle'), variablesAndClasspaths);
});

await runTask(`Migrating app build.gradle file.`, () => {
return updateAppBuildGradle(join(config.android.appDirAbs, 'build.gradle'));
});

// Variables gradle
await runTask(`Migrating variables.gradle file.`, () => {
return (async (): Promise<void> => {
Expand Down Expand Up @@ -274,18 +275,18 @@ export async function migrateCommand(config: Config, noprompt: boolean, packagem
}
}
const pluginVariables: { [key: string]: string } = {
firebaseMessagingVersion: '24.1.0',
firebaseMessagingVersion: '25.0.1',
playServicesLocationVersion: '21.3.0',
androidxBrowserVersion: '1.8.0',
androidxMaterialVersion: '1.12.0',
androidxExifInterfaceVersion: '1.3.7',
androidxCoreKTXVersion: '1.12.0',
googleMapsPlayServicesVersion: '18.2.0',
googleMapsUtilsVersion: '3.8.2',
googleMapsKtxVersion: '5.0.0',
googleMapsUtilsKtxVersion: '5.0.0',
kotlinxCoroutinesVersion: '1.7.3',
coreSplashScreenVersion: '1.0.1',
androidxBrowserVersion: '1.9.0',
androidxMaterialVersion: '1.13.0',
androidxExifInterfaceVersion: '1.4.1',
androidxCoreKTXVersion: '1.17.0',
googleMapsPlayServicesVersion: '19.2.0',
googleMapsUtilsVersion: '3.19.1',
googleMapsKtxVersion: '5.2.1',
googleMapsUtilsKtxVersion: '5.2.1',
kotlinxCoroutinesVersion: '1.10.2',
coreSplashScreenVersion: '1.2.0',
};
for (const variable of Object.keys(pluginVariables)) {
await updateFile(config, variablesPath, `${variable} = '`, `'`, pluginVariables[variable], true);
Expand All @@ -294,14 +295,6 @@ export async function migrateCommand(config: Config, noprompt: boolean, packagem
});

rimraf.sync(join(config.android.appDirAbs, 'build'));

if (!installFailed) {
await runTask('Migrating package from Manifest to build.gradle in Capacitor plugins', () => {
return patchOldCapacitorPlugins(config);
});
} else {
logger.warn('Skipped migrating package from Manifest to build.gradle in Capacitor plugins');
}
}

// Write all breaking changes
Expand Down Expand Up @@ -373,11 +366,15 @@ async function installLatestLibs(dependencyManager: string, runInstall: boolean,

async function writeBreakingChanges() {
const breaking = [
'@capacitor/app',
'@capacitor/device',
'@capacitor/haptics',
'@capacitor/action-sheet',
'@capacitor/barcode-scanner',
'@capacitor/browser',
'@capacitor/camera',
'@capacitor/google-maps',
'@capacitor/push-notifications',
'@capacitor/screen-orientation',
'@capacitor/splash-screen',
'@capacitor/statusbar',
'@capacitor/status-bar',
];
const broken = [];
for (const lib of breaking) {
Expand All @@ -387,7 +384,7 @@ async function writeBreakingChanges() {
}
if (broken.length > 0) {
logger.info(
`IMPORTANT: Review https://capacitorjs.com/docs/next/updating/7-0#plugins for breaking changes in these plugins that you use: ${broken.join(
`IMPORTANT: Review https://capacitorjs.com/docs/next/updating/8-0#plugins for breaking changes in these plugins that you use: ${broken.join(
', ',
)}.`,
);
Expand Down Expand Up @@ -464,59 +461,6 @@ async function updateGradleWrapperFiles(platformDir: string) {
);
}

async function movePackageFromManifestToBuildGradle(manifestFilename: string, buildGradleFilename: string) {
const manifestText = readFile(manifestFilename);
const buildGradleText = readFile(buildGradleFilename);

if (!manifestText) {
logger.error(`Could not read ${manifestFilename}. Check its permissions and if it exists.`);
return;
}

if (!buildGradleText) {
logger.error(`Could not read ${buildGradleFilename}. Check its permissions and if it exists.`);
return;
}

const namespaceExists = new RegExp(/\s+namespace\s+/).test(buildGradleText);
if (namespaceExists) {
logger.error('Found namespace in build.gradle already, skipping migration');
return;
}

let packageName: string;
const manifestRegEx = new RegExp(/package="([^"]+)"/);
const manifestResults = manifestRegEx.exec(manifestText);

if (manifestResults === null) {
logger.error(`Unable to update Android Manifest. Package not found.`);
return;
} else {
packageName = manifestResults[1];
}

let manifestReplaced = manifestText;

manifestReplaced = manifestReplaced.replace(manifestRegEx, '');

if (manifestText == manifestReplaced) {
logger.error(`Unable to update Android Manifest: no changes were detected in Android Manifest file`);
return;
}

let buildGradleReplaced = buildGradleText;

buildGradleReplaced = setAllStringIn(buildGradleText, 'android {', '\n', `\n namespace "${packageName}"`);

if (buildGradleText == buildGradleReplaced) {
logger.error(`Unable to update buildGradleText: no changes were detected in Android Manifest file`);
return;
}

writeFileSync(manifestFilename, manifestReplaced, 'utf-8');
writeFileSync(buildGradleFilename, buildGradleReplaced, 'utf-8');
}

async function updateBuildGradle(
filename: string,
variablesAndClasspaths: {
Expand Down Expand Up @@ -548,6 +492,24 @@ async function updateBuildGradle(
writeFileSync(filename, replaced, 'utf-8');
}

async function updateAppBuildGradle(filename: string) {
const txt = readFile(filename);
if (!txt) {
return;
}
let replaced = txt;

const gradlePproperties = ['compileSdk', 'namespace', 'ignoreAssetsPattern'];
for (const prop of gradlePproperties) {
// Use updated Groovy DSL syntax with " = " assignment
const regex = new RegExp(`(^\\s*${prop})\\s+(?!=)(.+)$`, 'gm');
replaced = replaced.replace(regex, (_match, key, value) => {
return `${key} = ${value.trim()}`;
});
}
writeFileSync(filename, replaced, 'utf-8');
}

async function updateFile(
config: Config,
filename: string,
Expand Down Expand Up @@ -621,48 +583,13 @@ async function updateAndroidManifest(filename: string) {
return;
}

if (txt.includes('navigation')) {
if (txt.includes('|density')) {
return; // Probably already updated
}
const replaced = txt.replace(
'android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"',
'android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"',
'android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"',
);

writeFileSync(filename, replaced, 'utf-8');
}

export async function patchOldCapacitorPlugins(config: Config): Promise<void[]> {
const allPlugins = await getPlugins(config, 'android');
const androidPlugins = await getAndroidPlugins(allPlugins);
return await Promise.all(
androidPlugins.map(async (p) => {
if (p.manifest?.android?.src) {
const buildGradlePath = resolveNode(config.app.rootDir, p.id, p.manifest.android.src, 'build.gradle');
const manifestPath = resolveNode(
config.app.rootDir,
p.id,
p.manifest.android.src,
'src',
'main',
'AndroidManifest.xml',
);
if (buildGradlePath && manifestPath) {
const gradleContent = readFile(buildGradlePath);
if (!gradleContent?.includes('namespace')) {
if (plugins.includes(p.id)) {
logger.warn(
`You are using an outdated version of ${p.id}, update the plugin to version ${pluginVersion}`,
);
} else {
logger.warn(
`${p.id}@${p.version} doesn't officially support Capacitor ${coreVersion} yet, doing our best moving it's package to build.gradle so it builds`,
);
}
movePackageFromManifestToBuildGradle(manifestPath, buildGradlePath);
}
}
}
}),
);
}
Loading