Skip to content
This repository was archived by the owner on May 1, 2026. It is now read-only.

fix: handle pnpm node_modules paths in native build zip#502

Merged
riderx merged 2 commits into
mainfrom
fix/pnpm-node-modules-in-build-zip
Feb 21, 2026
Merged

fix: handle pnpm node_modules paths in native build zip#502
riderx merged 2 commits into
mainfrom
fix/pnpm-node-modules-in-build-zip

Conversation

@WcaleNieWolny
Copy link
Copy Markdown
Contributor

@WcaleNieWolny WcaleNieWolny commented Feb 21, 2026

Summary

  • Bug: build request uploaded empty node_modules when the project used pnpm (or had stale pnpm-generated Capacitor config files), causing CI builds to fail with missing dependency errors. iOS builds also failed due to pnpm paths embedded deep in Pods project files.
  • Root cause: capacitor.settings.gradle, Podfile, and CocoaPods-generated files (project.pbxproj, .xcconfig, Manifest.lock) contain pnpm store paths like node_modules/.pnpm/@capacitor+android@8.1.0.../node_modules/@capacitor/android/capacitor. The CLI's extractNativeDependencies used these raw .pnpm/... paths as package identifiers, which never matched the actual directory structure during zip traversal — resulting in zero node_modules files being included. Additionally, pod install with pnpm resolves symlinks and bakes the real .pnpm store depth into relative paths in project.pbxproj group entries (e.g. ../../../../../../ios/App/Pods/ instead of ../../../ios/App/Pods/), causing Xcode to look for files at wrong paths on CI.

Changes

  1. extractNativeDependencies — For all platforms (Android gradle, iOS Podfile, iOS SPM), normalize captured paths by stripping the .pnpm/.../node_modules/ prefix to get the real package name. Also handle @capacitor/android's special /capacitor suffix.

  2. zipDirectory — pnpm text rewrite — Scan ALL text-based zip entries (by file extension) for pnpm store paths and rewrite them to standard flat node_modules/ paths. This covers Podfile, Podfile.lock, Manifest.lock, project.pbxproj, .xcconfig files, settings.gradle, etc.

  3. zipDirectory — iOS deep relative path fix — Collapse excessive ../ sequences before ios/ in pbxproj group paths (from 6+ levels back down to 3 levels). These deep relative paths are generated by pod install resolving pnpm symlinks and don't contain .pnpm/ text, so the first regex misses them.

Testing

  • ✅ npm + build request --platform android → build succeeded on CI
  • ✅ pnpm + build request --platform android → build succeeded on CI
  • ✅ npm + build request --platform ios → build succeeded on CI
  • ✅ pnpm + build request --platform ios → build succeeded on CI

Summary by CodeRabbit

  • Bug Fixes
    • Fixed native dependency path normalization for iOS and Android to ensure consistent dependency paths.
    • Rewrote package references inside build archives to standardize node_modules paths across platform config files.
    • Updated packaging flow to apply path rewrites and platform-specific collapsing before finalizing archives.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 21, 2026

📝 Walkthrough

Walkthrough

The PR normalizes native dependency paths (pnpm/store and node_modules prefixes) for iOS (SPM/CocoaPods) and Android (Gradle) during native dependency discovery, and rewrites pnpm-style paths to standard node_modules references in text-based build files inside the zip before finalizing the archive.

Changes

Cohort / File(s) Summary
Path normalization & zip rewriting
src/build/request.ts
Normalize native dependency paths by trimming any prefix up to the last node_modules/ for iOS SPM and CocoaPods and for Android Gradle (also stripping platform suffixes like android/capacitor); after zipping, rewrite pnpm store references (node_modules/.pnpm/.../node_modules/) to node_modules/ across relevant text-based files and collapse excessive relative ios/ path segments.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI build flow
    participant Parser as Native path parsers (SPM / CocoaPods / Gradle)
    participant Rewriter as Zip content rewriter
    participant Zip as Zip packaging service

    CLI->>Parser: Scan project files for native dependency references
    Parser-->>CLI: Emit normalized package paths (trim up to last node_modules/, strip platform suffixes)
    CLI->>Zip: Collect files into temporary archive
    Zip->>Rewriter: Provide text entries for inspection
    Rewriter-->>Zip: Replace pnpm store paths with standard `node_modules/`, collapse ios relative paths
    Zip->>CLI: Finalize and return normalized archive
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hop through pnpm and node_modules lanes,
Trimming paths, neatening build-time chains.
iOS pods and Android gradle too,
Rewritten paths make the archive true.
A tiny carrot of tidy code—hooray! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: handle pnpm node_modules paths in native build zip' accurately captures the main change: handling pnpm-style paths in the native build zip process.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/pnpm-node-modules-in-build-zip

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/build/request.ts (1)

769-787: zip.updateFile API confirmed valid — overall rewrite logic is correct.

updateFile(entry, content) is listed in the official adm-zip wiki API, and the updateFile method takes the filename and new content as a Buffer, matching the call on line 777.

One minor observation: rewriteFileInZip silently returns when the entry isn't found (if (!entry) return). This is correct for legitimately absent files (e.g., Podfile on an SPM project), but it equally silences a wrong-entry-path bug — the zip would be uploaded with unrewritten pnpm paths and CI builds would fail with the same symptom this PR fixes. Adding a verbose-mode log for the no-op case would make that failure path diagnosable:

🔍 Suggested verbose logging (optional)
 const rewriteFileInZip = (zipEntryPath: string) => {
   const entry = zip.getEntry(zipEntryPath)
-  if (!entry)
-    return
+  if (!entry) {
+    if (verbose)
+      log.info(`[pnpm rewrite] entry not found in zip: ${zipEntryPath}`)
+    return
+  }
   const original = entry.getData().toString('utf-8')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/build/request.ts` around lines 769 - 787, The rewriteFileInZip helper
quietly returns when zip.getEntry(zipEntryPath) yields no entry, which hides
cases where an expected file (e.g., `${platformDir}/Podfile`) is missing and
pnpm path rewrites never run; update rewriteFileInZip to log a verbose/warn
message when entry is falsy (including the attempted zipEntryPath and platform)
so CI diagnostics show why pnpmPathPattern rewrites didn’t occur, while
preserving the existing behavior of using pnpmPathPattern and calling
zip.updateFile(Buffer) when replacements happen.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/build/request.ts`:
- Around line 769-787: The rewriteFileInZip helper quietly returns when
zip.getEntry(zipEntryPath) yields no entry, which hides cases where an expected
file (e.g., `${platformDir}/Podfile`) is missing and pnpm path rewrites never
run; update rewriteFileInZip to log a verbose/warn message when entry is falsy
(including the attempted zipEntryPath and platform) so CI diagnostics show why
pnpmPathPattern rewrites didn’t occur, while preserving the existing behavior of
using pnpmPathPattern and calling zip.updateFile(Buffer) when replacements
happen.

The previous commit only rewrote specific files (Podfile, settings.gradle).
This was insufficient because pnpm paths also leak into Podfile.lock,
Manifest.lock, Pods.xcodeproj/project.pbxproj, and .xcconfig files.

Additionally, pod install with pnpm resolves symlinks and bakes the real
.pnpm store depth into relative paths in project.pbxproj group entries
(e.g. ../../../../../../ios/App/Pods/ instead of ../../../ios/App/Pods/).
These don't contain .pnpm text so the first regex misses them, and they
resolve to wrong locations on CI runners with different workspace depths.

Fix:
- Scan ALL text-based zip entries (by extension) instead of hardcoded paths.
- Collapse excessive ../ sequences before ios/ in pbxproj to 3 levels.
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/build/request.ts (1)

779-781: Two minor clarity issues: variable shadow and redundant Podfile guard.

  1. const basename at line 780 shadows the basename function imported from node:path — use a distinct name (e.g., entryBasename).
  2. Including '' in textExtensions already admits all extensionless files (including Podfile), making the explicit basename !== 'Podfile' guard a dead-code branch. Either drop '' from the set and rely solely on the Podfile name check, or drop the name check and document that '' intentionally covers all extensionless files.
♻️ Proposed cleanup
-    const ext = entry.entryName.includes('.') ? `.${entry.entryName.split('.').pop()}` : ''
-    const basename = entry.entryName.split('/').pop() || ''
-    if (!textExtensions.has(ext) && basename !== 'Podfile')
+    const entryBasename = entry.entryName.split('/').pop() || ''
+    const ext = entryBasename.includes('.') ? `.${entryBasename.split('.').pop()}` : ''
+    if (!textExtensions.has(ext) && entryBasename !== 'Podfile')
       continue

And remove '' from textExtensions so only the explicit Podfile catch handles extensionless files:

   const textExtensions = new Set([
-    '',
     '.gradle',
     ...
   ])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/build/request.ts` around lines 779 - 781, Rename the local const basename
to entryBasename (it currently shadows the imported basename from node:path) and
update its usages (e.g., where you check for 'Podfile'); also remove the
empty-string entry ('') from the textExtensions set and rely on the explicit
basename === 'Podfile' check to allow extensionless Podfile files, updating any
comments to document that extensionless files are only permitted via the Podfile
name guard.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/build/request.ts`:
- Around line 772-775: The Set initialization for textExtensions mixes inline
and multi-line items and violates antfu/consistent-list-newline; update the new
Set([...]) expression (symbol: textExtensions) so each string element is on its
own line inside the array literal (one item per line, including the empty string
''), maintaining the same items and commas, so the linter passes.

---

Nitpick comments:
In `@src/build/request.ts`:
- Around line 779-781: Rename the local const basename to entryBasename (it
currently shadows the imported basename from node:path) and update its usages
(e.g., where you check for 'Podfile'); also remove the empty-string entry ('')
from the textExtensions set and rely on the explicit basename === 'Podfile'
check to allow extensionless Podfile files, updating any comments to document
that extensionless files are only permitted via the Podfile name guard.

Comment thread src/build/request.ts
Comment on lines +772 to +775
const textExtensions = new Set([
'', '.gradle', '.swift', '.json', '.lock', '.xml', '.properties',
'.pbxproj', '.xcconfig', '.plist', '.podspec', '.rb', '.yaml', '.yml',
])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

ESLint antfu/consistent-list-newline error blocks linting CI.

The array inside new Set([...]) mixes inline and multi-line layout. Each item needs its own line.

🔧 Proposed fix
-  const textExtensions = new Set([
-    '', '.gradle', '.swift', '.json', '.lock', '.xml', '.properties',
-    '.pbxproj', '.xcconfig', '.plist', '.podspec', '.rb', '.yaml', '.yml',
-  ])
+  const textExtensions = new Set([
+    '',
+    '.gradle',
+    '.swift',
+    '.json',
+    '.lock',
+    '.xml',
+    '.properties',
+    '.pbxproj',
+    '.xcconfig',
+    '.plist',
+    '.podspec',
+    '.rb',
+    '.yaml',
+    '.yml',
+  ])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const textExtensions = new Set([
'', '.gradle', '.swift', '.json', '.lock', '.xml', '.properties',
'.pbxproj', '.xcconfig', '.plist', '.podspec', '.rb', '.yaml', '.yml',
])
const textExtensions = new Set([
'',
'.gradle',
'.swift',
'.json',
'.lock',
'.xml',
'.properties',
'.pbxproj',
'.xcconfig',
'.plist',
'.podspec',
'.rb',
'.yaml',
'.yml',
])
🧰 Tools
🪛 ESLint

[error] 773-773: Should have line breaks between items, in node ArrayExpression

(antfu/consistent-list-newline)


[error] 773-773: Should have line breaks between items, in node ArrayExpression

(antfu/consistent-list-newline)


[error] 773-773: Should have line breaks between items, in node ArrayExpression

(antfu/consistent-list-newline)


[error] 773-773: Should have line breaks between items, in node ArrayExpression

(antfu/consistent-list-newline)


[error] 773-773: Should have line breaks between items, in node ArrayExpression

(antfu/consistent-list-newline)


[error] 773-773: Should have line breaks between items, in node ArrayExpression

(antfu/consistent-list-newline)


[error] 774-774: Should have line breaks between items, in node ArrayExpression

(antfu/consistent-list-newline)


[error] 774-774: Should have line breaks between items, in node ArrayExpression

(antfu/consistent-list-newline)


[error] 774-774: Should have line breaks between items, in node ArrayExpression

(antfu/consistent-list-newline)


[error] 774-774: Should have line breaks between items, in node ArrayExpression

(antfu/consistent-list-newline)


[error] 774-774: Should have line breaks between items, in node ArrayExpression

(antfu/consistent-list-newline)


[error] 774-774: Should have line breaks between items, in node ArrayExpression

(antfu/consistent-list-newline)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/build/request.ts` around lines 772 - 775, The Set initialization for
textExtensions mixes inline and multi-line items and violates
antfu/consistent-list-newline; update the new Set([...]) expression (symbol:
textExtensions) so each string element is on its own line inside the array
literal (one item per line, including the empty string ''), maintaining the same
items and commas, so the linter passes.

@riderx riderx merged commit ed38b31 into main Feb 21, 2026
19 checks passed
@riderx riderx deleted the fix/pnpm-node-modules-in-build-zip branch March 17, 2026 14:53
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants