From 255e20196092e6c8afd6bdd74a17a9e4d718691a Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Fri, 1 May 2026 14:16:30 +0200 Subject: [PATCH 1/2] fix(cli): rewrite pnpm spm node_modules paths --- cli/src/build/request.ts | 9 ++++----- cli/test/test-build-zip-filter.mjs | 13 +++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/cli/src/build/request.ts b/cli/src/build/request.ts index cfb6ba188d..c6c331dbaa 100644 --- a/cli/src/build/request.ts +++ b/cli/src/build/request.ts @@ -1086,13 +1086,12 @@ export async function zipDirectory(projectDir: string, outputPath: string, platf const original = entry.getData().toString('utf-8') let rewritten = original.replace(pnpmPathPattern, 'node_modules/') - // pod install with pnpm resolves symlinks, producing deep relative paths - // like ../../../../../../ios/App/Pods/ (6 levels) instead of ../../../ios/App/Pods/ (3 levels). - // Collapse any excessive ../ before the platform directory back to 3 levels - // (node_modules/@scope/pkg → 3 levels up to project root). + // pnpm can leave deep relative paths in iOS files like Package.swift and Pods output, + // for example ../../../../../node_modules/... instead of ../../../node_modules/... + // Collapse any excessive ../ before project-root ios/ or node_modules/ paths back to 3 levels. if (platform === 'ios') { rewritten = rewritten.replace( - /(?:\.\.\/){4,}(ios\/)/g, + /(?:\.\.\/){4,}(ios\/|node_modules\/)/g, '../../../$1', ) } diff --git a/cli/test/test-build-zip-filter.mjs b/cli/test/test-build-zip-filter.mjs index d04160156b..c0674e9ceb 100644 --- a/cli/test/test-build-zip-filter.mjs +++ b/cli/test/test-build-zip-filter.mjs @@ -450,11 +450,19 @@ await t('generated build zip can pull native pnpm workspace packages from an exp join(projectRoot, 'ios', 'App', 'Podfile'), "platform :ios, '14.0'\npod 'CapacitorCommunityKeepAwake', :path => '../../node_modules/@capacitor-community/keep-awake'\n", ) + writeFile( + join(projectRoot, 'ios', 'App', 'CapApp-SPM', 'Package.swift'), + 'let package = Package(name: "CapApp-SPM", dependencies: [.package(name: "CapacitorCommunityKeepAwake", path: "../../../../../node_modules/.pnpm/@capacitor-community+keep-awake@8.0.0_@capacitor+core@8.2.0/node_modules/@capacitor-community/keep-awake")])\n', + ) writeFile( join(keepAwakePath, 'package.json'), JSON.stringify({ name: '@capacitor-community/keep-awake', version: '8.0.0' }, null, 2), ) + writeFile( + join(keepAwakePath, 'Package.swift'), + 'let package = Package(name: "CapacitorCommunityKeepAwake")\n', + ) writeFile( join(keepAwakePath, 'KeepAwake.podspec'), "Pod::Spec.new do |s|\n s.name = 'KeepAwake'\nend", @@ -476,9 +484,14 @@ await t('generated build zip can pull native pnpm workspace packages from an exp const entries = zip.getEntries().map(entry => entry.entryName).sort() assert.ok(entries.includes('node_modules/@capacitor-community/keep-awake/package.json'), 'missing workspace plugin package.json in zip') + assert.ok(entries.includes('node_modules/@capacitor-community/keep-awake/Package.swift'), 'missing workspace plugin Package.swift in zip') assert.ok(entries.includes('node_modules/@capacitor-community/keep-awake/KeepAwake.podspec'), 'missing workspace plugin podspec in zip') assert.ok(entries.includes('node_modules/@capacitor-community/keep-awake/ios/KeepAwakePlugin.swift'), 'missing workspace plugin ios code in zip') assert.ok(entries.includes('ios/App/Podfile'), 'native platform folder not included') + assert.equal( + zip.readAsText('ios/App/CapApp-SPM/Package.swift'), + 'let package = Package(name: "CapApp-SPM", dependencies: [.package(name: "CapacitorCommunityKeepAwake", path: "../../../node_modules/@capacitor-community/keep-awake")])\n', + ) } finally { rmSync(testRoot, { recursive: true, force: true }) From a37ed9bd3b0c2c4c271986b3cb00b1fdf68bf3e0 Mon Sep 17 00:00:00 2001 From: Martin Donadieu Date: Fri, 1 May 2026 14:34:51 +0200 Subject: [PATCH 2/2] fix(cli): preserve pnpm spm relative depth --- cli/src/build/request.ts | 9 +++++---- cli/test/test-build-zip-filter.mjs | 12 ++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/cli/src/build/request.ts b/cli/src/build/request.ts index c6c331dbaa..4b0612ac1c 100644 --- a/cli/src/build/request.ts +++ b/cli/src/build/request.ts @@ -1086,13 +1086,14 @@ export async function zipDirectory(projectDir: string, outputPath: string, platf const original = entry.getData().toString('utf-8') let rewritten = original.replace(pnpmPathPattern, 'node_modules/') - // pnpm can leave deep relative paths in iOS files like Package.swift and Pods output, - // for example ../../../../../node_modules/... instead of ../../../node_modules/... - // Collapse any excessive ../ before project-root ios/ or node_modules/ paths back to 3 levels. + // pnpm can leave deep relative paths in iOS files like Package.swift and Pods output. + // Collapse any excessive ../ before project-root ios/ or node_modules/ paths back to + // the current zip entry's actual depth. if (platform === 'ios') { + const rootRelativePrefix = '../'.repeat(entry.entryName.split('/').length - 1) rewritten = rewritten.replace( /(?:\.\.\/){4,}(ios\/|node_modules\/)/g, - '../../../$1', + (_match, rootPath: string) => `${rootRelativePrefix}${rootPath}`, ) } diff --git a/cli/test/test-build-zip-filter.mjs b/cli/test/test-build-zip-filter.mjs index c0674e9ceb..b2a7c73638 100644 --- a/cli/test/test-build-zip-filter.mjs +++ b/cli/test/test-build-zip-filter.mjs @@ -201,6 +201,10 @@ await t('generated build zip supports nested Capacitor Podfile paths in monorepo join(testRoot, 'apps/native/ios/App/Podfile'), "platform :ios, '14.0'\npod 'CapacitorApp', :path => '../../../node_modules/@capacitor/app'\n", ) + writeFile( + join(testRoot, 'apps/native/ios/App/CapApp-SPM/Package.swift'), + 'let package = Package(name: "CapApp-SPM", dependencies: [.package(name: "CapacitorApp", path: "../../../../../node_modules/.pnpm/@capacitor+app@6.0.0_@capacitor+core@6.0.0/node_modules/@capacitor/app")])\n', + ) writeFile( join(testRoot, 'apps/native/ios/App/Podfile.lock'), @@ -216,6 +220,10 @@ await t('generated build zip supports nested Capacitor Podfile paths in monorepo join(testRoot, 'node_modules', '@capacitor', 'app', 'CapacitorApp.podspec'), "Pod::Spec.new do |s|\n s.name = 'CapacitorApp'\nend", ) + writeFile( + join(testRoot, 'node_modules', '@capacitor', 'app', 'Package.swift'), + 'let package = Package(name: "CapacitorApp")\n', + ) await zipDirectory(testRoot, zipPath, 'ios', { ios: { @@ -229,6 +237,10 @@ await t('generated build zip supports nested Capacitor Podfile paths in monorepo assert.ok(entries.includes('apps/native/ios/App/Podfile'), 'native platform podfile not included') assert.ok(entries.includes('node_modules/@capacitor/app/package.json'), 'missing plugin package.json in zip') assert.ok(entries.includes('node_modules/@capacitor/app/CapacitorApp.podspec'), 'missing plugin podspec in zip') + assert.equal( + zip.readAsText('apps/native/ios/App/CapApp-SPM/Package.swift'), + 'let package = Package(name: "CapApp-SPM", dependencies: [.package(name: "CapacitorApp", path: "../../../../../node_modules/@capacitor/app")])\n', + ) assert.ok(!entries.includes('apps/native/ios/Podfile.lock'), 'unexpected root lockfile was included') } finally {