11import fs from "node:fs/promises" ;
2+ import fsSync from "node:fs" ;
3+ import os from "node:os" ;
24import path from "node:path" ;
35import { describe , expect , it } from "vitest" ;
46import { withTempDir } from "../test-helpers/temp-dir.js" ;
7+
8+ const canCreateDirectorySymlinks = ( ( ) => {
9+ const probeDir = fsSync . mkdtempSync ( path . join ( os . tmpdir ( ) , "openclaw-install-safe-dir-symlink-probe-" ) ) ;
10+ const targetDir = path . join ( probeDir , "target" ) ;
11+ const linkDir = path . join ( probeDir , "link" ) ;
12+ try {
13+ fsSync . mkdirSync ( targetDir ) ;
14+ fsSync . symlinkSync ( targetDir , linkDir , process . platform === "win32" ? "junction" : "dir" ) ;
15+ return true ;
16+ } catch {
17+ return false ;
18+ } finally {
19+ fsSync . rmSync ( probeDir , { recursive : true , force : true } ) ;
20+ }
21+ } ) ( ) ;
522import {
623 assertCanonicalPathWithinBase ,
724 packageNameMatchesId ,
@@ -154,13 +171,13 @@ describe("assertCanonicalPathWithinBase", () => {
154171 } ) ;
155172 } ) ;
156173
157- it . runIf ( process . platform !== "win32" ) (
174+ it . skipIf ( ! canCreateDirectorySymlinks ) (
158175 "rejects symlinked candidate directories that escape the base" ,
159176 async ( ) => {
160177 await withTempDir ( { prefix : "openclaw-install-safe-" } , async ( baseDir ) => {
161178 await withTempDir ( { prefix : "openclaw-install-safe-outside-" } , async ( outsideDir ) => {
162179 const linkDir = path . join ( baseDir , "alias" ) ;
163- await fs . symlink ( outsideDir , linkDir ) ;
180+ await fs . symlink ( outsideDir , linkDir , process . platform === "win32" ? "junction" : "dir" ) ;
164181 await expect (
165182 assertCanonicalPathWithinBase ( {
166183 baseDir,
@@ -173,14 +190,14 @@ describe("assertCanonicalPathWithinBase", () => {
173190 } ,
174191 ) ;
175192
176- it . runIf ( process . platform !== "win32" ) (
193+ it . skipIf ( ! canCreateDirectorySymlinks ) (
177194 "accepts symlinked base directories when the target stays in the real base" ,
178195 async ( ) => {
179196 await withTempDir ( { prefix : "openclaw-install-safe-" } , async ( parentDir ) => {
180197 const realBaseDir = path . join ( parentDir , "real-base" ) ;
181198 const symlinkBaseDir = path . join ( parentDir , "base-link" ) ;
182199 await fs . mkdir ( realBaseDir , { recursive : true } ) ;
183- await fs . symlink ( realBaseDir , symlinkBaseDir ) ;
200+ await fs . symlink ( realBaseDir , symlinkBaseDir , process . platform === "win32" ? "junction" : "dir" ) ;
184201 await expect (
185202 assertCanonicalPathWithinBase ( {
186203 baseDir : symlinkBaseDir ,
@@ -192,7 +209,7 @@ describe("assertCanonicalPathWithinBase", () => {
192209 } ,
193210 ) ;
194211
195- it . runIf ( process . platform !== "win32" ) (
212+ it . skipIf ( ! canCreateDirectorySymlinks ) (
196213 "rejects nested symlinked candidate directories" ,
197214 async ( ) => {
198215 await withTempDir ( { prefix : "openclaw-install-safe-" } , async ( parentDir ) => {
@@ -201,7 +218,7 @@ describe("assertCanonicalPathWithinBase", () => {
201218 const nestedSymlinkDir = path . join ( realBaseDir , "nested-link" ) ;
202219 await fs . mkdir ( realBaseDir , { recursive : true } ) ;
203220 await fs . mkdir ( nestedRealDir , { recursive : true } ) ;
204- await fs . symlink ( nestedRealDir , nestedSymlinkDir ) ;
221+ await fs . symlink ( nestedRealDir , nestedSymlinkDir , process . platform === "win32" ? "junction" : "dir" ) ;
205222 await expect (
206223 assertCanonicalPathWithinBase ( {
207224 baseDir : realBaseDir ,
0 commit comments