Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
248a4b7
feat: migrate from cap 7 to cap 8
ItsChaceD Nov 6, 2025
0a61107
add kotlin 2.2 migration
ItsChaceD Nov 10, 2025
de0954e
fix propName deprecations and simplify kotlinOptions replacement
ItsChaceD Nov 11, 2025
33fadb4
change plugin version to 8.0.0
ItsChaceD Nov 11, 2025
d83c2ea
update lib versions, package version, and replace compileSdkVersion
ItsChaceD Nov 12, 2025
4334174
update core version to next
ItsChaceD Nov 12, 2025
ddb2804
remove compileSdkVersion logic
ItsChaceD Nov 12, 2025
a5e9677
update android lib versions
ItsChaceD Nov 12, 2025
a4d9370
revert core version to alpha
ItsChaceD Nov 12, 2025
ee5c903
add carat back in
ItsChaceD Nov 13, 2025
402c007
optionally set kotlin version
ItsChaceD Nov 13, 2025
7895323
change kotlin start
ItsChaceD Nov 14, 2025
cddf24f
update npm dependencies
ItsChaceD Nov 14, 2025
c0ea9d0
update to beta
ItsChaceD Nov 14, 2025
8505134
fix kotlin version regex and update npm dependencies in migrate script
ItsChaceD Nov 19, 2025
9bbd468
change to 8.0.0 and cleanup prettier logic
ItsChaceD Nov 20, 2025
592e401
update package.json version
OS-ruimoreiramendes Nov 27, 2025
c71a2d5
added missing variables
OS-ruimoreiramendes Nov 27, 2025
79d2753
update missing variables
OS-ruimoreiramendes Nov 27, 2025
b6659fa
remove unnecessary properties
OS-ruimoreiramendes Nov 27, 2025
2ab8393
fix updateKotlinOptions to remove whitespace and update JVM version
OS-ruimoreiramendes Nov 28, 2025
5ae21b7
update logger.info message in updateKotlinOptions
OS-ruimoreiramendes Dec 2, 2025
187033b
added new regex to update kotlin_version
OS-ruimoreiramendes Dec 2, 2025
c973568
added new regex to update kotlin_version
OS-ruimoreiramendes Dec 2, 2025
8794872
update typescript and types/node
OS-ruimoreiramendes Dec 2, 2025
2aa42c6
change typeScript and types/node update logic
OS-ruimoreiramendes Dec 2, 2025
e60e5fb
remove types/node check
OS-ruimoreiramendes Dec 2, 2025
1b6e88f
update @capacitor/docgen version
OS-ruimoreiramendes Dec 3, 2025
fd9e010
fix kotlin-stdlib transform and add new tranform for kotlin-gradle-pl…
OS-ruimoreiramendes Dec 3, 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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Migrate Capacitor Plugin

Migrate a Capacitor 6 plugin to Capacitor 7.
Migrate a Capacitor 7 plugin to Capacitor 8.

## Usage

```
npx @capacitor/plugin-migration-v6-to-v7@latest
npx @capacitor/plugin-migration-v7-to-v8@latest
```
22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@capacitor/plugin-migration-v6-to-v7",
"version": "0.0.7",
"description": "Utility to help migrate Capacitor 6 plugins to Capacitor 7",
"name": "@capacitor/plugin-migration-v7-to-v8",
"version": "0.0.1",
"description": "Utility to help migrate Capacitor 7 plugins to Capacitor 8",
"main": "./dist/index.js",
"scripts": {
"lint": "npm run eslint && npm run prettier -- --check",
Expand All @@ -18,7 +18,7 @@
"url": "git+https://github.com/ionic-team/migrate-capacitor-plugin.git"
},
"bin": {
"plugin-migration-v6-to-v7": "bin/migrate-capacitor-plugin"
"plugin-migration-v7-to-v8": "bin/migrate-capacitor-plugin"
},
"files": [
"bin/",
Expand All @@ -41,17 +41,17 @@
"@ionic/eslint-config": "^0.4.0",
"@ionic/prettier-config": "^4.0.0",
"@types/fs-extra": "^11.0.4",
"@types/node": "^20.17.6",
"@types/node": "^24.10.1",
"eslint": "^8.57.0",
"prettier": "^3.3.3",
"typescript": "^5.0.4"
"prettier": "^3.6.2",
"typescript": "^5.9.3"
},
"dependencies": {
"@ionic/cli-framework-output": "^2.2.8",
"@ionic/utils-subprocess": "^3.0.1",
"fs-extra": "^11.2.0",
"fs-extra": "^11.3.2",
"kleur": "^4.1.5",
"rimraf": "^6.0.1",
"semver": "^7.6.3"
"rimraf": "^6.1.0",
"semver": "^7.7.3"
}
}
}
202 changes: 163 additions & 39 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,48 @@ import { rimraf } from 'rimraf';
import { logger } from './log';
import { runCommand } from './subprocess';

const coreVersion = '7.0.0';
const gradleVersion = '8.11.1';
const AGPVersion = '8.7.2';
const gmsVersion = '4.4.2';
const kotlinVersion = '1.9.25';
const docgenVersion = '^0.3.0';
const eslintVersion = '^8.57.0';
const coreVersion = '8.0.0';
const gradleVersion = '8.14.3';
const AGPVersion = '8.13.0';
const gmsVersion = '4.4.4';
const kotlinVersion = '2.2.20';
const docgenVersion = '^0.3.1';
const eslintVersion = '^8.57.1';
const ionicEslintVersion = '^0.4.0';
const ionicPrettierVersion = '^4.0.0';
const ionicSwiftlintVersion = '^2.0.0';
const prettierJavaVersion = '^2.6.6';
const prettierVersion = '^3.4.2';
const rimrafVersion = '^6.0.1';
const rollupVersion = '^4.30.1';
const prettierJavaVersion = '^2.7.7';
const prettierVersion = '^3.6.2';
const rimrafVersion = '^6.1.0';
const rollupVersion = '^4.53.2';
const typesNodeVersion = '^24.10.1';
const typeScriptVersion = '^5.9.3';
let updatePrettierJava = false;
const variables = {
minSdkVersion: 23,
compileSdkVersion: 35,
targetSdkVersion: 35,
androidxActivityVersion: '1.9.2',
androidxAppCompatVersion: '1.7.0',
androidxCoordinatorLayoutVersion: '1.2.0',
androidxCoreVersion: '1.15.0',
androidxFragmentVersion: '1.8.4',
firebaseMessagingVersion: '24.1.0',
minSdkVersion: 24,
compileSdkVersion: 36,
targetSdkVersion: 36,
androidxActivityVersion: '1.11.0',
androidxAppCompatVersion: '1.7.1',
androidxCoordinatorLayoutVersion: '1.3.0',
androidxCoreVersion: '1.17.0',
androidxFragmentVersion: '1.8.9',
firebaseMessagingVersion: '25.0.1',
playServicesLocationVersion: '21.3.0',
androidxBrowserVersion: '1.8.0',
androidxMaterialVersion: '1.12.0',
androidxExifInterfaceVersion: '1.3.7',
coreSplashScreenVersion: '1.0.1',
androidxWebkitVersion: '1.12.1',
androidxBrowserVersion: '1.9.0',
androidxMaterialVersion: '1.13.0',
androidxExifInterfaceVersion: '1.4.1',
coreSplashScreenVersion: '1.2.0',
androidxWebkitVersion: '1.14.0',
junitVersion: '4.13.2',
androidxJunitVersion: '1.2.1',
androidxEspressoCoreVersion: '3.6.1',
androidxJunitVersion: '1.3.0',
androidxEspressoCoreVersion: '3.7.0',
androidxCoreKTXVersion: '1.17.0',
googleMapsPlayServicesVersion: '19.2.0',
googleMapsUtilsVersion: '3.19.1',
googleMapsKtxVersion: '5.2.1',
googleMapsUtilsKtxVersion: '5.2.1',
kotlinxCoroutinesVersion: '1.10.2',
};

process.on('unhandledRejection', (error) => {
Expand Down Expand Up @@ -93,8 +101,23 @@ export const run = async (): Promise<void> => {
if (pluginJSON.devDependencies?.['rollup']) {
pluginJSON.devDependencies['rollup'] = rollupVersion;
}
if (pluginJSON.devDependencies?.['typescript']) {
pluginJSON.devDependencies['typescript'] = typeScriptVersion;
}
if (pluginJSON.devDependencies?.['@types/node']) {
pluginJSON.devDependencies['@types/node'] = typesNodeVersion;
}

let prettierUpdatedFromV2 = false;
if (pluginJSON.devDependencies?.['@ionic/prettier-config']) {
const existingPrettierVersion = pluginJSON.devDependencies?.['prettier'];
if (existingPrettierVersion) {
const semver = await import('semver');
const cleanVersion = semver.coerce(existingPrettierVersion);
if (cleanVersion && semver.lt(cleanVersion, '3.0.0')) {
prettierUpdatedFromV2 = true;
}
}
pluginJSON.devDependencies['@ionic/prettier-config'] = ionicPrettierVersion;
pluginJSON.devDependencies['prettier'] = prettierVersion;
pluginJSON.devDependencies['prettier-plugin-java'] = prettierJavaVersion;
Expand All @@ -105,8 +128,8 @@ export const run = async (): Promise<void> => {
pluginJSON.devDependencies['@capacitor/docgen'] = docgenVersion;
}

if (pluginJSON.version.startsWith('6.')) {
pluginJSON.version = '7.0.0';
if (pluginJSON.version?.startsWith('7.')) {
pluginJSON.version = '8.0.0';
}

await writeJSON(packageJson, pluginJSON, { spaces: 2 });
Expand Down Expand Up @@ -196,10 +219,10 @@ export const run = async (): Promise<void> => {
join(iosDir, 'Plugin.xcodeproj', 'project.pbxproj'),
'IPHONEOS_DEPLOYMENT_TARGET = ',
';',
'14.0',
'15.0',
);
await updateFile(join(iosDir, 'Podfile'), `platform :ios, '`, `'`, '14.0');
await updateFile(join(dir, 'Package.swift'), '[.iOS(.v', ')],', '14');
await updateFile(join(iosDir, 'Podfile'), `platform :ios, '`, `'`, '15.0');
await updateFile(join(dir, 'Package.swift'), '[.iOS(.v', ')],', '15');
await updateFile(
join(dir, 'Package.swift'),
'.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git",',
Expand All @@ -210,7 +233,13 @@ export const run = async (): Promise<void> => {
}
}

logger.info('Plugin migrated to Capacitor 7!');
logger.info('Plugin migrated to Capacitor 8!');

if (prettierUpdatedFromV2) {
logger.info('');
logger.info('⚠️ Note: Prettier has been updated from v2 to v3.');
logger.info('We recommend running your formatting and linting scripts to ensure your codebase is formatted correctly.');
}
};

function updatePodspec(dir: string, pluginJSON: any) {
Expand All @@ -220,7 +249,7 @@ function updatePodspec(dir: string, pluginJSON: any) {
return false;
}
txt = txt.replace('s.ios.deployment_target =', 's.ios.deployment_target =');
txt = txt.replace(`s.ios.deployment_target = '13.0'`, `s.ios.deployment_target = '14.0'`);
txt = txt.replace(`s.ios.deployment_target = '14.0'`, `s.ios.deployment_target = '15.0'`);
writeFileSync(podspecFile, txt, { encoding: 'utf-8' });
}

Expand Down Expand Up @@ -280,23 +309,118 @@ async function updateBuildGradle(
}
}
}

const kotlinRegex = /ext\.kotlin_version\s*=\s*(?:project\.hasProperty\("kotlin_version"\)\s*\?\s*rootProject\.ext\.kotlin_version\s*:\s*)?['"]([^'"]+)['"]/;
gradleFile = gradleFile.replace(kotlinRegex, () => {
logger.info(`Set kotlin_version = ${kotlinVersion}.`);
return `ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '${kotlinVersion}'`;
});

const blockKotlinRegex = /ext\s*\{[\s\S]*?\}/gm;
gradleFile = gradleFile.replace(blockKotlinRegex, (block) => {
return block.replace(
/kotlin_version\s*=\s*['"][^'"]+['"]|kotlin_version\s*=\s*project\.hasProperty\("kotlin_version"\)\s*\?\s*rootProject\.ext\.kotlin_version\s*:\s*['"][^'"]+['"]/,
`kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '${kotlinVersion}'`,
);
});

const hasKotlinVersion = gradleFile.includes('kotlin_version');
const versionToUse = hasKotlinVersion ? '$kotlin_version' : kotlinVersion;
gradleFile = setAllStringIn(
gradleFile,
`ext.kotlin_version = `,
`\n`,
`project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '${kotlinVersion}'`,
`implementation "org.jetbrains.kotlin:kotlin-stdlib`,
`"`,
`:${versionToUse}`,
);

gradleFile = setAllStringIn(
gradleFile,
`implementation "org.jetbrains.kotlin:kotlin-stdlib`,
`classpath "org.jetbrains.kotlin:kotlin-gradle-plugin`,
`"`,
`:$kotlin_version`,
`:${versionToUse}`,
);

gradleFile = updateDeprecatedPropertySyntax(gradleFile);
gradleFile = updateKotlinOptions(gradleFile);

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

function updateDeprecatedPropertySyntax(gradleFile: string): string {
const propertiesToUpdate = [
'namespace',
'compileSdk',
'url',
'abortOnError',
];

let result = gradleFile;

for (const prop of propertiesToUpdate) {
const regex = new RegExp(`\\b(${prop})[ \\t]+([^=\\t{:])`, 'g');
result = result.replace(regex, '$1 = $2');
}

return result;
}

function updateKotlinOptions(gradleFile: string): string {
const kotlinOptionsRegex = /kotlinOptions\s*\{\s*jvmTarget\s*=\s*['"](\d+\.?\d*)['"][\s\S]*?\}/;
const match = kotlinOptionsRegex.exec(gradleFile);

if (!match) {
return gradleFile;
}

const jvmTargetValue = `JVM_21`;

let result = gradleFile;

if (!result.includes('import org.jetbrains.kotlin.gradle.dsl.JvmTarget')) {
const firstNonCommentLine = result.split('\n').findIndex((line) => {
if (!line) return false;
const trimmed = line.trim();
return trimmed.length > 0 && !trimmed.startsWith('//') && !trimmed.startsWith('/*');
});
const lines = result.split('\n');
if (firstNonCommentLine >= 0) {
lines.splice(firstNonCommentLine, 0, 'import org.jetbrains.kotlin.gradle.dsl.JvmTarget', '');
result = lines.join('\n');
}
}

result = result.replace(/\n\s*kotlinOptions\s*\{[\s\S]*?\}\s*\n/g, '\n');

const kotlinBlockRegex = /\nkotlin\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/;
const kotlinBlockMatch = kotlinBlockRegex.exec(result);

const compilerOptionsBlock = ` compilerOptions {\n jvmTarget = JvmTarget.${jvmTargetValue}\n }`;

if (kotlinBlockMatch) {
// Kotlin block exists, add compilerOptions to it if not already present
if (!kotlinBlockMatch[0].includes('compilerOptions')) {
const kotlinBlockContent = kotlinBlockMatch[1];
const existingContent = kotlinBlockContent.trimEnd();
const newKotlinBlock = `\nkotlin {${existingContent}\n${compilerOptionsBlock}\n}`;
result = result.replace(kotlinBlockRegex, newKotlinBlock);
}
} else {
// No kotlin block exists, create one after the android block
const androidBlockRegex = /android\s*\{[\s\S]*?\n\}/;
const androidMatch = androidBlockRegex.exec(result);

if (androidMatch) {
const insertPosition = androidMatch.index + androidMatch[0].length;
const kotlinBlock = `\n\nkotlin {\n${compilerOptionsBlock}\n}`;
result = result.slice(0, insertPosition) + kotlinBlock + result.slice(insertPosition);
}
}

logger.info('Updated kotlinOptions to compilerOptions for Kotlin 2');

return result;
}

function readFile(filename: string): string | undefined {
try {
if (!existsSync(filename)) {
Expand Down