@smbcloud / cli / commits / 52fbd58

Fix pnpm scope package issue

Seto Elkahfi committed May 30, 2026 at 00:31 UTC 52fbd58a5ea1029ad282414270c68bfdedcba069
2 files changed +44 -8
.agents/skills/smbcloud-deploy-nextjs/SKILL.md
+16 -4
@@ -310,6 +310,13 @@ Fix:
310
311 ### `Cannot find module 'styled-jsx'`, `@swc/helpers`, or other pnpm peer deps
312
313 +A common concrete failure looks like:
314 +
315 +- `code: 'MODULE_NOT_FOUND'`
316 +- `path: '/home/git/apps/web/<app>/node_modules/@swc/helpers'`
317 +
318 +That usually means the standalone upload succeeded, but the top-level hoisted symlink for a scoped package was not recreated on the server.
319 +
320 Cause:
321
322 - pnpm stores peer dependencies inside `node_modules/.pnpm/<pkg>@<version>/node_modules/<pkg>` with internal symlinks that Node's standard `require.resolve()` cannot follow
@@ -318,10 +325,15 @@ Cause:
325
326 Fix:
327
321 -- the CLI deploy script now auto-hoists: after uploading standalone output, it scans `node_modules/.pnpm/*/node_modules/*` and creates symlinks at `node_modules/<pkg>` for any packages not already present
322 -- this restores the resolution structure that pnpm normally provides via its `.pnpm/node_modules/` virtual directory
323 -- no manual intervention needed for new deploys
324 -- to fix a server manually: `find node_modules/.pnpm -mindepth 2 -maxdepth 2 -type d -name node_modules` and symlink each child into the root `node_modules/`
328 +- the CLI deploy script now auto-hoists in two passes:
329 + - first mirror pnpm's virtual directory at `node_modules/.pnpm/node_modules/*`
330 + - then fall back to scanning `node_modules/.pnpm/*/node_modules/*` for anything still missing
331 +- this preserves pnpm's exact resolved package choice and fixes scoped packages like `@swc/helpers` as well as unscoped ones like `styled-jsx`
332 +- no manual intervention needed for new deploys once the updated CLI is used
333 +- to fix a server manually, prefer pnpm's virtual directory as the source of truth:
334 + - create `node_modules/@scope/` directories as needed
335 + - symlink `node_modules/<pkg>` or `node_modules/@scope/<pkg>` to the corresponding entry under `node_modules/.pnpm/node_modules/`
336 + - only if that directory is missing, scan individual `.pnpm/<store-entry>/node_modules/*` folders as a fallback
337
338 ### Wrong port after deploy
339
crates/cli/src/deploy/process_deploy_nextjs_ssr.rs
+28 -4
@@ -456,9 +456,30 @@ ECOSYSTEM_EOF
456
457 # pnpm standalone output buries peer deps inside node_modules/.pnpm/
458 # without the top-level symlinks Node needs for require.resolve().
459 - # Scan the .pnpm store and create flat symlinks. For scoped packages,
460 - # always force-replace partial rsync copies with symlinks to the .pnpm
461 - # store entry that has the most content (not just package.json).
459 + # First mirror pnpm's own virtual node_modules directory when it exists —
460 + # this preserves the exact version selection pnpm already resolved, which is
461 + # especially important for scoped packages like @swc/helpers.
462 + # Then fall back to scanning individual .pnpm store entries for anything the
463 + # virtual directory did not expose.
464 + if [ -d "node_modules/.pnpm/node_modules" ]; then
465 + for pkg_path in node_modules/.pnpm/node_modules/*; do
466 + [ -e "$pkg_path" ] || continue
467 + pkg_name=$(basename "$pkg_path")
468 + if [ "${{pkg_name:0:1}}" = "@" ] && [ -d "$pkg_path" ]; then
469 + mkdir -p "node_modules/$pkg_name"
470 + for sub_path in "$pkg_path"/*; do
471 + [ -e "$sub_path" ] || continue
472 + sub_name=$(basename "$sub_path")
473 + rm -rf "node_modules/$pkg_name/$sub_name"
474 + ln -sfn "$APP_PATH/$sub_path" "node_modules/$pkg_name/$sub_name"
475 + done
476 + else
477 + rm -rf "node_modules/$pkg_name"
478 + ln -sfn "$APP_PATH/$pkg_path" "node_modules/$pkg_name"
479 + fi
480 + done
481 + fi
482 +
483 if [ -d "node_modules/.pnpm" ]; then
484 find node_modules/.pnpm -mindepth 2 -maxdepth 2 -type d -name "node_modules" 2>/dev/null | while read pnpm_nm; do
485 for pkg_path in "$pnpm_nm"/*; do
@@ -484,7 +505,10 @@ ECOSYSTEM_EOF
505 fi
506 done
507 else
487 - [ -e "node_modules/$pkg_name" ] || ln -sfn "$APP_PATH/$pkg_path" "node_modules/$pkg_name"
508 + if [ -e "node_modules/$pkg_name" ]; then
509 + continue
510 + fi
511 + ln -sfn "$APP_PATH/$pkg_path" "node_modules/$pkg_name"
512 fi
513 done
514 done