Repository metrics
- Stars
- (7,323 個のスター)
- PR merge metrics
- (平均マージ 17h 30m) (30d で 21 merged PRs)
説明
Capacitor Version
💊 Capacitor Doctor 💊
Latest Dependencies:
@capacitor/cli: 8.4.2 @capacitor/core: 8.4.2 @capacitor/android: 8.4.2 @capacitor/ios: 8.4.2
Installed Dependencies:
@capacitor/android: 8.4.2 @capacitor/cli: 8.4.2 @capacitor/core: 8.4.2 @capacitor/ios: 8.4.2
Other API Details
pnpm 11.x
node v24.x
Platforms Affected
- iOS
- Android
- Web
Current Behavior
Follow-up to #7606. That issue was about capacitor:sync:before/capacitor:sync:after hooks running once per plugin (from inside node_modules) instead of once at the repo root when an nx.json is present. It looks like this was since addressed by having runHooks invoke runPlatformHook once for the app root and once per resolved plugin (dist/common.js):
async function runHooks(config, platformName, dir, hook) {
await runPlatformHook(config, platformName, dir, hook);
const allPlugins = await getPlugins(config, platformName);
for (const p of allPlugins) {
await runPlatformHook(config, platformName, p.rootPath, hook);
}
}
However, runPlatformHook itself still has the Nx-monorepo special-case from the same era, and it now breaks per-plugin hooks entirely:
async function runPlatformHook(config, platformName, platformDir, hook) {
let pkg;
if (isNXMonorepo(platformDir)) {
pkg = await readJSON(join(findNXMonorepoRoot(platformDir), 'package.json'));
} else {
pkg = await readJSON(join(platformDir, 'package.json'));
}
const cmd = pkg.scripts?.[hook];
if (!cmd) {
return;
}
...
}
isNXMonorepo/findNXMonorepoRoot (dist/util/monorepotools.js) walk up from platformDir looking for the nearest nx.json. When platformDir is a plugin's install path deep in node_modules (e.g. node_modules/.pnpm/@capgo+capacitor-social-login@8.3.38.../node_modules/@capgo/capacitor-social-login), that walk-up still terminates at the workspace's nx.json, so the function reads the workspace root package.json for the hook name — never the plugin's own package.json — regardless of which platformDir was passed in for the per-plugin loop above.
Net effect: in any Nx workspace, a plugin that ships its own capacitor:sync:before/capacitor:sync:after hook in its own package.json has that hook silently ignored (if (!cmd) return;, no warning), because the CLI looks it up in the wrong file.
It's not just plugins — the app's own hook is redirected too, and that's the part that doesn't make sense. runHooks is called from tasks/sync.js as:
await runHooks(config, platformName, config.app.rootDir, 'capacitor:sync:before');
config.app.rootDir is already correctly scoped to the Capacitor app being synced (e.g. apps/mobile in an Nx layout, not the workspace root) — this is exactly the directory cap sync is being run against, and it's the same directory that already holds all the app's other Capacitor-related npm scripts (sync:android, build:android, configure:android, etc.). The non-monorepo branch of runPlatformHook would use it correctly (readJSON(join(platformDir, 'package.json'))).
But because isNXMonorepo(platformDir) only checks "is there an nx.json somewhere above this directory", it fires for config.app.rootDir too (an Nx workspace's app package is, by definition, below the root nx.json) and redirects the lookup up to the workspace root package.json — a file that in a real Nx monorepo is shared across dozens of unrelated apps and libraries, and has no natural connection to any single app's Capacitor hooks. There's no fallback semantics here (e.g. "use the root only if the app itself has no matching script") — it's an unconditional redirect away from a directory that was already correct.
Concretely, in our repo (apps/mobile is the Capacitor app, nx.json/package.json live at the monorepo root several directories above): the CLI currently resolves capacitor:sync:before against the top-level workspace package.json — the one shared by every other Nx project in the repo — instead of apps/mobile/package.json, which is where such a hook would actually belong and where all our other Capacitor scripts already live. We would never want to add a Capacitor sync hook to the workspace root; it would be meaningless there (which app would it even apply to?).
Expected Behavior
runPlatformHook should read platformDir/package.json directly — whether platformDir is the app's own root (config.app.rootDir) or a plugin's own root (p.rootPath) — regardless of whether the workspace happens to be an Nx monorepo. The Nx-monorepo special case doesn't appear to serve a real purpose here: platformDir is always already the correct, specific directory to look in (that's the whole point of passing it in per-app / per-plugin), so redirecting to a distant shared root can only ever pick the wrong file. If there's a specific historical reason for the Nx-root redirect, it should at minimum be a fallback (only consulted when platformDir itself has no matching script), not the default/only lookup path.
Project Reproduction
Minimal repro shape (based on gabides/capacitor-sample-app-sync-issue from #7606, updated for the new per-plugin behavior):
- Create an Nx workspace (
nx.jsonat the root) with pnpm/npm workspaces, with the Capacitor app living in a subdirectory (e.g.apps/mobile), as is the standard Nx layout. - Add a
capacitor:sync:beforescript to the app's ownpackage.json(apps/mobile/package.json) — this is the natural, expected place for it, right alongside the app's other Capacitor scripts (sync:android,build:android, etc.). - Also link in a Capacitor plugin that defines its own
capacitor:sync:beforescript in its ownpackage.json. - Leave the workspace root
package.json(/package.json) with nocapacitor:sync:beforescript — there's no reason it should have one; it isn't specific to any single app. - Run
npx cap syncfromapps/mobile. - Observe: neither the app's own hook nor the plugin's hook runs, because both lookups get redirected to the workspace root
package.json, which has nothing under that key.
Real-world instance we hit: @capgo/capacitor-social-login ships "capacitor:sync:before": "node scripts/configure-dependencies.js" in its own package.json to conditionally enable/disable native provider dependencies (Google/Facebook/Apple SDKs) in its podspec/gradle.properties based on capacitor.config.ts. In our Nx + pnpm workspace (app at apps/mobile, workspace root several directories up) this hook never fires during cap sync, so the plugin always ships all provider SDKs regardless of config. And if we wanted to add our own app-level capacitor:sync:before/capacitor:sync:after hook, we'd have to put it in apps/mobile/package.json to match the rest of the app's scripts — but the CLI would ignore it there and look at the monorepo root instead.
Project Reproduction
https://github.com/LouisTrezzini/capacitor-cli-nx-repro
Additional Information
Workaround we're using until this is fixed: after cap sync, we manually reconstruct the same CAPACITOR_CONFIG/CAPACITOR_PLATFORM_NAME env vars Capacitor would have passed (via cap config --json, extracting .app.extConfig) and invoke the plugin's hook script directly, with cwd set to the plugin's install directory rather than relying on cap sync to do it.