Skip to content
Open
Changes from all commits
Commits
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
79 changes: 40 additions & 39 deletions scripts/check-capacitor-plugin-wiring.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,26 @@
* node scripts/check-capacitor-plugin-wiring.mjs --dir path # checks given plugin dir
*/

import fs from "node:fs";
import path from "node:path";
import fs from 'node:fs';
import path from 'node:path';

const SKIP_DIRS = new Set([
"node_modules",
"dist",
"build",
".build",
".gradle",
"Pods",
"DerivedData",
".swiftpm",
".git",
'node_modules',
'dist',
'build',
'.build',
'.gradle',
'Pods',
'DerivedData',
'.swiftpm',
'.git',
]);

function readText(p) {
try {
return fs.readFileSync(p, "utf8");
return fs.readFileSync(p, 'utf8');
} catch {
return "";
return '';
}
}

Expand Down Expand Up @@ -90,8 +90,8 @@ function parseArgs(argv) {
const out = { dir: process.cwd() };
for (let i = 2; i < argv.length; i++) {
const a = argv[i];
if (a === "--dir" || a === "--pluginDir") {
out.dir = path.resolve(argv[++i] || ".");
if (a === '--dir' || a === '--pluginDir') {
out.dir = path.resolve(argv[++i] || '.');
continue;
}
}
Expand All @@ -100,7 +100,7 @@ function parseArgs(argv) {

const args = parseArgs(process.argv);
const pluginDir = args.dir;
const pkgPath = path.join(pluginDir, "package.json");
const pkgPath = path.join(pluginDir, 'package.json');

if (!exists(pkgPath)) {
console.error(`[wiring] ERROR: missing package.json in ${pluginDir}`);
Expand All @@ -115,9 +115,9 @@ try {
process.exit(2);
}

const cap = typeof pkg.capacitor === "object" && pkg.capacitor ? pkg.capacitor : {};
const supportsAndroid = typeof cap.android === "object" && cap.android;
const supportsIos = typeof cap.ios === "object" && cap.ios;
const cap = typeof pkg.capacitor === 'object' && pkg.capacitor ? pkg.capacitor : {};
const supportsAndroid = typeof cap.android === 'object' && cap.android;
const supportsIos = typeof cap.ios === 'object' && cap.ios;

// Not a Capacitor plugin package (e.g. meta/workspace package).
// We only enforce wiring rules for actual plugin packages declaring a `capacitor` config.
Expand All @@ -126,10 +126,10 @@ if (!supportsAndroid && !supportsIos) {
}

// ---------------- JS (registerPlugin) ----------------
const jsSrcDir = path.join(pluginDir, "src");
let jsName = "";
const jsSrcDir = path.join(pluginDir, 'src');
let jsName = '';
if (exists(jsSrcDir)) {
const jsFiles = walkFiles(jsSrcDir, [".ts", ".js"]);
const jsFiles = walkFiles(jsSrcDir, ['.ts', '.js']);
const reRegister = /registerPlugin(?:<[^>]*>)?\(\s*['"]([^'"]+)['"]/;
for (const f of jsFiles) {
const m = reRegister.exec(readText(f));
Expand All @@ -143,12 +143,12 @@ if (exists(jsSrcDir)) {
// ---------------- Android (@CapacitorPlugin) ----------------
let androidNames = [];
if (supportsAndroid) {
const androidMain = path.join(pluginDir, "android", "src", "main");
const files = walkFiles(androidMain, [".java", ".kt"]);
const androidMain = path.join(pluginDir, 'android', 'src', 'main');
const files = walkFiles(androidMain, ['.java', '.kt']);
const foundAnnotations = [];
for (const f of files) {
const txt = readText(f);
if (!txt.includes("@CapacitorPlugin")) continue;
if (!txt.includes('@CapacitorPlugin')) continue;
foundAnnotations.push(f);
const m =
/@CapacitorPlugin\(\s*name\s*=\s*"([^"]+)"/.exec(txt) ||
Expand All @@ -160,7 +160,7 @@ if (supportsAndroid) {
// Enforce explicit name attribute to prevent silent class-name drift.
if (foundAnnotations.length && !androidNames.length) {
console.error(
`[wiring] ERROR: Android has @CapacitorPlugin but none specify name = \"...\". Add an explicit name to the plugin class.`
`[wiring] ERROR: Android has @CapacitorPlugin but none specify name = \"...\". Add an explicit name to the plugin class.`,
);
process.exit(1);
}
Expand All @@ -169,13 +169,13 @@ if (supportsAndroid) {
// ---------------- iOS (jsName) ----------------
let iosJsNames = [];
if (supportsIos) {
const iosDir = path.join(pluginDir, "ios");
const scanRoot = exists(path.join(iosDir, "Sources")) ? path.join(iosDir, "Sources") : iosDir;
const swiftFiles = walkFiles(scanRoot, [".swift"]);
const iosDir = path.join(pluginDir, 'ios');
const scanRoot = exists(path.join(iosDir, 'Sources')) ? path.join(iosDir, 'Sources') : iosDir;
const swiftFiles = walkFiles(scanRoot, ['.swift']);
const reJsName = /\bjsName\s*=\s*"([^"]+)"/g;
for (const f of swiftFiles) {
const txt = readText(f);
if (!txt.includes("jsName")) continue;
if (!txt.includes('jsName')) continue;
let m;
while ((m = reJsName.exec(txt))) iosJsNames.push(m[1]);
}
Expand All @@ -186,12 +186,12 @@ if (supportsIos) {
function parsePodspecName(podspecPath) {
const txt = readText(podspecPath);
const m = /\bs\.name\s*=\s*'([^']+)'/.exec(txt);
return m ? m[1] : "";
return m ? m[1] : '';
}

function parseSpmNames(packageSwiftPath) {
const txt = readText(packageSwiftPath);
const pkg = /Package\(\s*name\s*:\s*"([^"]+)"/.exec(txt)?.[1] || "";
const pkg = /Package\(\s*name\s*:\s*"([^"]+)"/.exec(txt)?.[1] || '';
const libs = [];
const reLib = /\.library\(\s*name\s*:\s*"([^"]+)"/g;
let m;
Expand All @@ -207,7 +207,7 @@ if (!jsName) {
}

if (supportsAndroid) {
if (!androidNames.length) errors.push("Android: missing @CapacitorPlugin(name = \"...\")");
if (!androidNames.length) errors.push('Android: missing @CapacitorPlugin(name = "...")');
if (jsName && androidNames.length && androidNames.some((n) => n !== jsName)) {
errors.push(`Android: @CapacitorPlugin(name)=${JSON.stringify(androidNames)} != JS registerPlugin=${jsName}`);
}
Expand All @@ -221,15 +221,16 @@ if (supportsIos) {

const podspecs = fs
.readdirSync(pluginDir, { withFileTypes: true })
.filter((e) => e.isFile() && e.name.endsWith(".podspec"))
.filter((e) => e.isFile() && e.name.endsWith('.podspec'))
.map((e) => path.join(pluginDir, e.name))
.sort();
if (!podspecs.length) errors.push("iOS: missing *.podspec at plugin root");
if (podspecs.length > 1) errors.push(`iOS: multiple podspecs at plugin root: ${podspecs.map((p) => path.basename(p))}`);
if (!podspecs.length) errors.push('iOS: missing *.podspec at plugin root');
if (podspecs.length > 1)
errors.push(`iOS: multiple podspecs at plugin root: ${podspecs.map((p) => path.basename(p))}`);

const pkgSwift = path.join(pluginDir, "Package.swift");
const pkgSwift = path.join(pluginDir, 'Package.swift');
if (!exists(pkgSwift)) {
errors.push("iOS: missing Package.swift at plugin root");
errors.push('iOS: missing Package.swift at plugin root');
} else if (podspecs.length) {
const podName = parsePodspecName(podspecs[0]);
const { pkgName, libNames } = parseSpmNames(pkgSwift);
Expand All @@ -245,7 +246,7 @@ if (supportsIos) {
}

if (errors.length) {
const relDir = path.relative(process.cwd(), pluginDir) || ".";
const relDir = path.relative(process.cwd(), pluginDir) || '.';
console.error(`[wiring] FAIL in ${relDir}`);
for (const e of errors) console.error(`- ${e}`);
process.exit(1);
Expand Down