diff --git a/eslint.config.cjs b/eslint.config.cjs new file mode 100644 index 0000000..0752f1f --- /dev/null +++ b/eslint.config.cjs @@ -0,0 +1,14 @@ +const ionic = require('@ionic/eslint-config/recommended'); + +module.exports = [ + { + ignores: [ + '**/dist/**', + // lint TypeScript only + '**/*.js', + '**/*.mjs', + '**/*.cjs', + ], + }, + ...ionic, +]; diff --git a/package.json b/package.json index 63c4771..869510c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "lint": "npm run eslint && npm run prettier -- --check", "fmt": "npm run eslint -- --fix && npm run prettier -- --write", - "eslint": "eslint . --ext ts", + "eslint": "eslint .", "prettier": "prettier \"**/*.{css,html,js,mjs,ts,json}\"", "build": "npm run clean && tsc", "clean": "rimraf ./dist", @@ -30,19 +30,17 @@ "author": "Ionic Team ", "license": "MIT", "prettier": "@ionic/prettier-config", - "eslintConfig": { - "extends": "@ionic/eslint-config/recommended" - }, "bugs": { "url": "https://github.com/ionic-team/migrate-capacitor-plugin/issues" }, "homepage": "https://github.com/ionic-team/migrate-capacitor-plugin#readme", "devDependencies": { - "@ionic/eslint-config": "^0.4.0", + "@ionic/eslint-config": "^0.5.0", "@ionic/prettier-config": "^4.0.0", "@types/fs-extra": "^11.0.4", "@types/node": "^24.10.1", - "eslint": "^8.57.0", + "@types/semver": "^7.7.1", + "eslint": "^10.0.0", "prettier": "^3.6.2", "typescript": "^5.9.3" }, @@ -54,4 +52,4 @@ "rimraf": "^6.1.0", "semver": "^7.7.3" } -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 12fa32d..d7beeb9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,9 +20,52 @@ const gradleVersion = '9.5.1'; const AGPVersion = '9.2.1'; const gmsVersion = '4.5.0'; const docgenVersion = '^0.3.1'; -const eslintVersion = '^8.57.1'; -const ionicEslintVersion = '^0.4.0'; +const eslintVersion = '^10.0.0'; +const ionicEslintVersion = '^0.5.0'; const ionicPrettierVersion = '^4.0.0'; + +// Flat config has no equivalent of .eslintignore, so its entries move into the config. +// Keep them: a plugin that excluded docs/ or website/ should not start linting them. +function eslintIgnoresFrom(eslintIgnorePath: string): string[] { + const entries = existsSync(eslintIgnorePath) + ? readFileSync(eslintIgnorePath, 'utf-8') + .split('\n') + .map((line) => line.trim()) + .filter((line) => line.length > 0 && !line.startsWith('#')) + : ['build', 'dist', 'example-app']; + + return entries.map((entry) => { + if (entry.endsWith('/**')) { + return entry; + } + const bare = entry.replace(/\/$/, ''); + const name = bare.split('/').pop() ?? bare; + // a dot anywhere but the front means it is a file, not a directory + if (name.indexOf('.') > 0) { + return bare; + } + return bare.includes('/') ? `${bare}/**` : `**/${bare}/**`; + }); +} + +function eslintConfigCjsFor(ignores: string[]): string { + const lines = ignores.map((entry) => ` '${entry}',`).join('\n'); + return `const ionic = require('@ionic/eslint-config/recommended'); + +module.exports = [ + { + ignores: [ +${lines} + // the old lint script passed --ext ts, which flat config ignores + '**/*.js', + '**/*.mjs', + '**/*.cjs', + ], + }, + ...ionic, +]; +`; +} const ionicSwiftlintVersion = '^2.0.0'; const prettierJavaVersion = '^2.7.7'; const prettierVersion = '^3.6.2'; @@ -82,11 +125,17 @@ export const run = async (): Promise => { pluginJSON.peerDependencies[dep] = `>=${coreVersion}`; } } + let eslintUpdated = false; if (pluginJSON.devDependencies?.['@ionic/eslint-config']) { pluginJSON.devDependencies['@ionic/eslint-config'] = ionicEslintVersion; if (pluginJSON.devDependencies?.['eslint']) { pluginJSON.devDependencies['eslint'] = eslintVersion; } + delete pluginJSON.eslintConfig; + if (pluginJSON.scripts?.['eslint']) { + pluginJSON.scripts['eslint'] = pluginJSON.scripts['eslint'].replace(' --ext ts', ''); + } + eslintUpdated = true; } if (pluginJSON.devDependencies?.['@ionic/swiftlint-config']) { pluginJSON.devDependencies['@ionic/swiftlint-config'] = ionicSwiftlintVersion; @@ -165,6 +214,20 @@ export const run = async (): Promise => { writeFileSync(packageJson, packageJsonText, 'utf-8'); + if (eslintUpdated) { + const ignores = eslintIgnoresFrom(join(dir, '.eslintignore')); + // ESLint 10 reads neither of these, so leaving them behind is just dead config + const stale = ['.eslintignore', '.eslintrc', '.eslintrc.js', '.eslintrc.cjs', '.eslintrc.json', '.eslintrc.yml']; + for (const file of stale.map((name) => join(dir, name))) { + if (existsSync(file)) { + removeSync(file); + } + } + if (!['eslint.config.js', 'eslint.config.mjs', 'eslint.config.cjs'].some((file) => existsSync(join(dir, file)))) { + writeFileSync(join(dir, 'eslint.config.cjs'), eslintConfigCjsFor(ignores), 'utf-8'); + } + } + rimraf.sync(join(dir, 'node_modules/@capacitor')); rimraf.sync(join(dir, 'package-lock.json')); @@ -173,7 +236,7 @@ export const run = async (): Promise => { ...opts, cwd: dir, }); - } catch (e: any) { + } catch { logger.warn('npm install failed, please, install the dependencies using your package manager of choice'); } @@ -238,6 +301,13 @@ export const run = async (): Promise => { logger.info('Plugin migrated to Capacitor 9!'); + if (eslintUpdated) { + logger.info(''); + logger.info('⚠️ Note: ESLint has been updated to v10 and @ionic/eslint-config to v1, which uses flat config.'); + logger.info('An eslint.config.cjs file was added; .eslintignore and the eslintConfig block in package.json were removed.'); + logger.info('Running lint may surface new reports; see https://github.com/ionic-team/eslint-config#migrating-from-04-or-earlier'); + } + if (prettierUpdatedFromV2) { logger.info(''); logger.info('⚠️ Note: Prettier has been updated from v2 to v3.');