fix(pwsh): accept link-shaped candidates and sync the README contract

This commit is contained in:
Huanqi Cao
2026-08-12 15:11:20 +08:00
committed by Chinesezjc
parent 7593842ccf
commit 29d420f1ee
5 changed files with 28 additions and 11 deletions

View File

@@ -8,7 +8,7 @@
* @module @deepseek-ai/dsh-pwsh-local/resolve
*/
import { existsSync, lstatSync } from 'node:fs'
import { lstatSync } from 'node:fs'
import { join } from 'node:path'
/**
@@ -36,15 +36,21 @@ export function candidatePwshPaths(env: NodeJS.ProcessEnv = process.env): string
return candidates
}
/** Whether a candidate can be spawned: a real file or a link-shaped reparse point. */
/**
* Whether a candidate can be spawned. lstat opens the entry itself instead of
* following reparse points, so it sees the Store app execution alias where
* stat hits the target's ACL (EACCES); Node reports that alias as a symlink
* on current releases and as a plain file on older ones, and CreateProcess
* resolves either shape. A real directory never matches.
*/
function candidateExists(candidate: string): boolean {
if (existsSync(candidate)) return true
// Microsoft Store app execution aliases are reparse points whose target
// ACL refuses stat(), so existsSync misses them; lstat sees the link
// itself and CreateProcess resolves it when the executor spawns.
try {
return lstatSync(candidate).isSymbolicLink()
const stat = lstatSync(candidate)
return stat.isFile() || stat.isSymbolicLink()
} catch {
// ENOENT (the candidate vanished between listing and probing) is the only
// expected failure; any other error names an unspawnable path, so false
// is the safe answer for it too.
return false
}
}