11const assert = require ( 'node:assert' ) ;
22const { execFileSync } = require ( 'node:child_process' ) ;
3- const { rmSync, mkdirSync, lstatSync, unlinkSync } = require ( 'node:fs' ) ;
3+ const { rmSync, mkdirSync, lstatSync, realpathSync } = require ( 'node:fs' ) ;
44const { join } = require ( 'node:path' ) ;
55const { createRequire } = require ( 'node:module' ) ;
66
@@ -21,16 +21,24 @@ for (const searchDir of cwdRequire.resolve.paths(packageName) || []) {
2121
2222assert ( entryPath , `Package "${ packageName } " is not installed. It must be installed before it can be replaced.` ) ;
2323
24- if ( lstatSync ( entryPath ) . isSymbolicLink ( ) ) {
25- unlinkSync ( entryPath ) ;
26- } else {
27- rmSync ( entryPath , { recursive : true , force : true } ) ;
28- }
24+ const targetPath = ( ( ) => {
25+ if ( ! lstatSync ( entryPath ) . isSymbolicLink ( ) ) {
26+ return entryPath ;
27+ }
28+
29+ // If it's a symlink (pnpm), replace the target content to preserve the resolution chain.
30+ // pnpm places dependencies as siblings of the target, so the symlink must stay intact.
31+ const resolved = realpathSync ( entryPath ) ;
32+ assert ( resolved . startsWith ( process . cwd ( ) + '/' ) , `Symlink target "${ resolved } " resolves outside the project` ) ;
33+ return resolved ;
34+
35+ } ) ( ) ;
2936
30- mkdirSync ( entryPath , { recursive : true } ) ;
37+ rmSync ( targetPath , { recursive : true , force : true } ) ;
38+ mkdirSync ( targetPath , { recursive : true } ) ;
3139
3240const listing = execFileSync ( 'tar' , [ '-tzf' , packageFile ] ) . toString ( ) ;
3341const unsafeEntry = listing . split ( '\n' ) . find ( e => e . includes ( '..' ) || e . startsWith ( '/' ) ) ;
3442assert ( ! unsafeEntry , `Archive contains unsafe path: ${ unsafeEntry } ` ) ;
3543
36- execFileSync ( 'tar' , [ '-xzf' , packageFile , '-C' , entryPath , '--strip-components=1' ] ) ;
44+ execFileSync ( 'tar' , [ '-xzf' , packageFile , '-C' , targetPath , '--strip-components=1' ] ) ;
0 commit comments