Commit 9ef6d3c

thesmartshadow <firaswq12@gmail.com>
2025-10-14 14:47:08
fix(cli): prevent external node_modules deletion with --prefer-local (#1349)
1 parent cc2a4f7
Changed files (3)
build/cli.cjs
@@ -217,7 +217,13 @@ function main() {
     yield runScript(script, scriptPath, tempPath);
   });
 }
-var rmrf = (p) => p && import_index.fs.rmSync(p, { force: true, recursive: true });
+var rmrf = (p) => {
+  if (!p) return;
+  try {
+    import_index.fs.lstatSync(p).isSymbolicLink() ? import_index.fs.unlinkSync(p) : import_index.fs.rmSync(p, { force: true, recursive: true });
+  } catch (e) {
+  }
+};
 function runScript(script, scriptPath, tempPath) {
   return __async(this, null, function* () {
     let nmLink = "";
@@ -232,7 +238,16 @@ function runScript(script, scriptPath, tempPath) {
       }
       const cwd = import_index.path.dirname(scriptPath);
       if (typeof argv.preferLocal === "string") {
-        nmLink = linkNodeModules(cwd, argv.preferLocal);
+        linkNodeModules(cwd, argv.preferLocal);
+        try {
+          const aliasPath = import_index.path.resolve(cwd, "node_modules");
+          if (import_index.fs.existsSync(aliasPath) && import_index.fs.lstatSync(aliasPath).isSymbolicLink()) {
+            nmLink = aliasPath;
+          } else {
+            nmLink = "";
+          }
+        } catch (e) {
+        }
       }
       if (argv.install) {
         yield (0, import_deps.installDeps)((0, import_deps.parseDeps)(script), cwd, argv.registry);
src/cli.ts
@@ -128,13 +128,22 @@ export async function main(): Promise<void> {
   await runScript(script, scriptPath, tempPath)
 }
 
-const rmrf = (p: string) => p && fs.rmSync(p, { force: true, recursive: true })
+// Short & safe remove: unlink symlinks; recurse only for real dirs/files
+const rmrf = (p: string) => {
+  if (!p) return
+  try {
+    fs.lstatSync(p).isSymbolicLink()
+      ? fs.unlinkSync(p)
+      : fs.rmSync(p, { force: true, recursive: true })
+  } catch {}
+}
+
 async function runScript(
   script: string,
   scriptPath: string,
   tempPath: string
 ): Promise<void> {
-  let nmLink = ''
+  let nmLink = '' // will hold the alias path (./node_modules) ONLY if it's a symlink
   const rmTemp = () => {
     rmrf(tempPath)
     rmrf(nmLink)
@@ -145,9 +154,25 @@ async function runScript(
       await fs.writeFile(tempPath, script)
     }
     const cwd = path.dirname(scriptPath)
+
     if (typeof argv.preferLocal === 'string') {
-      nmLink = linkNodeModules(cwd, argv.preferLocal)
+      // Keep original behaviour: linkNodeModules returns TARGET (unchanged API)
+      linkNodeModules(cwd, argv.preferLocal)
+
+      // For cleanup, compute ALIAS and only unlink if it's a symlink
+      try {
+        const aliasPath = path.resolve(cwd, 'node_modules')
+        if (
+          fs.existsSync(aliasPath) &&
+          fs.lstatSync(aliasPath).isSymbolicLink()
+        ) {
+          nmLink = aliasPath
+        } else {
+          nmLink = ''
+        }
+      } catch {}
     }
+
     if (argv.install) {
       await installDeps(parseDeps(script), cwd, argv.registry)
     }
@@ -173,6 +198,7 @@ function linkNodeModules(cwd: string, external: string): string {
   if (fs.existsSync(alias) || !fs.existsSync(target)) return ''
 
   fs.symlinkSync(target, alias, 'junction')
+  // Keep behaviour stable: return TARGET (not alias)
   return target
 }
 
.size-limit.json
@@ -33,7 +33,7 @@
       "build/globals.js",
       "build/deno.js"
     ],
-    "limit": "816.65 kB",
+    "limit": "817.2 kB",
     "brotli": false,
     "gzip": false
   },
@@ -66,7 +66,7 @@
       "README.md",
       "LICENSE"
     ],
-    "limit": "874.15 kB",
+    "limit": "874.6 kB",
     "brotli": false,
     "gzip": false
   }