mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-06-22 10:58:03 +08:00
fix(install): address Codex review — guard reparse deletes and robust uninstall
- install.ps1 (P1): refuse to delete real files/directories; only remove paths that are actual junctions/symlinks. Closes a data-loss path where Cmd-Uninstall would Remove-Item -Recurse a pre-existing user directory at ~/.understand-anything-plugin (Cmd-Install correctly skipped touching it, but uninstall did not). Cmd-Uninstall and Unlink-Skills now both go through Remove-Reparse, which uses DirectoryInfo.Delete() so a junction's target is never followed. - install.sh (P2): --uninstall no longer exits early when the checkout has been deleted. The per-skill unlinker falls back to scanning the target dir for stale symlinks pointing into the plugin tree, so users can clean up after manually rm -rf'ing the checkout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
8956696228
commit
a25c8e2527
+49
-14
@@ -97,8 +97,34 @@ function Get-SkillNames {
|
||||
Get-ChildItem -Path $root -Directory | Select-Object -ExpandProperty Name
|
||||
}
|
||||
|
||||
function Test-IsReparse([string]$Path) {
|
||||
if (-not (Test-Path $Path)) { return $false }
|
||||
$item = Get-Item -LiteralPath $Path -Force
|
||||
return ($item.LinkType -eq 'Junction' -or $item.LinkType -eq 'SymbolicLink')
|
||||
}
|
||||
|
||||
function Remove-Reparse([string]$Path) {
|
||||
# Removes a junction/symlink without touching its target. Refuses to touch
|
||||
# real files or directories so an existing user folder at the same path is
|
||||
# never destroyed.
|
||||
if (-not (Test-Path $Path)) { return $false }
|
||||
$item = Get-Item -LiteralPath $Path -Force
|
||||
if ($item.LinkType -eq 'Junction' -or $item.LinkType -eq 'SymbolicLink') {
|
||||
$item.Delete()
|
||||
return $true
|
||||
}
|
||||
Write-Warning "Refusing to delete $Path — it is a real file/directory, not a junction/symlink we created. Remove it manually if you intended to."
|
||||
return $false
|
||||
}
|
||||
|
||||
function New-Junction([string]$LinkPath, [string]$TargetPath) {
|
||||
if (Test-Path $LinkPath) { Remove-Item -Force -Recurse $LinkPath }
|
||||
if (Test-Path $LinkPath) {
|
||||
if (Test-IsReparse $LinkPath) {
|
||||
(Get-Item -LiteralPath $LinkPath -Force).Delete()
|
||||
} else {
|
||||
Write-Error "Refusing to overwrite $LinkPath — it is a real file/directory, not a junction. Move or remove it first."
|
||||
}
|
||||
}
|
||||
New-Item -ItemType Junction -Path $LinkPath -Target $TargetPath | Out-Null
|
||||
}
|
||||
|
||||
@@ -125,16 +151,28 @@ function Link-Skills([string]$Target, [string]$Style) {
|
||||
}
|
||||
|
||||
function Unlink-Skills([string]$Target, [string]$Style) {
|
||||
if (-not (Test-Path $Target)) { return }
|
||||
switch ($Style) {
|
||||
'per-skill' {
|
||||
foreach ($skill in Get-SkillNames) {
|
||||
$link = Join-Path $Target $skill
|
||||
if (Test-Path $link) { Remove-Item -Force -Recurse $link }
|
||||
$skillsRoot = Get-SkillsRoot
|
||||
if (Test-Path $skillsRoot) {
|
||||
foreach ($skill in Get-SkillNames) {
|
||||
Remove-Reparse (Join-Path $Target $skill) | Out-Null
|
||||
}
|
||||
} else {
|
||||
# Checkout is gone — scan the target dir for stale links pointing
|
||||
# into our plugin tree so we can still clean up.
|
||||
Get-ChildItem -LiteralPath $Target -Force | ForEach-Object {
|
||||
if ($_.LinkType -eq 'Junction' -or $_.LinkType -eq 'SymbolicLink') {
|
||||
if ($_.Target -match 'understand-anything-plugin[\\/]+skills[\\/]+') {
|
||||
Remove-Reparse $_.FullName | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'folder' {
|
||||
$link = Join-Path $Target 'understand-anything'
|
||||
if (Test-Path $link) { Remove-Item -Force -Recurse $link }
|
||||
Remove-Reparse (Join-Path $Target 'understand-anything') | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,18 +205,15 @@ function Cmd-Install([string]$Id) {
|
||||
|
||||
function Cmd-Uninstall([string]$Id) {
|
||||
$cfg = Resolve-Platform $Id
|
||||
if (-not (Test-Path $RepoDir)) {
|
||||
Write-Host "No checkout found at $RepoDir — nothing to unlink."
|
||||
return
|
||||
}
|
||||
Write-Host "→ Removing skill links for $Id"
|
||||
Unlink-Skills $cfg.Target $cfg.Style
|
||||
if (Test-Path $PluginLink) {
|
||||
Remove-Item -Force -Recurse $PluginLink
|
||||
if (Remove-Reparse $PluginLink) {
|
||||
Write-Host " ✓ removed $PluginLink"
|
||||
}
|
||||
Write-Host "`nThe checkout at $RepoDir was kept (other platforms may still use it)."
|
||||
Write-Host "To remove it: Remove-Item -Recurse -Force '$RepoDir'"
|
||||
if (Test-Path $RepoDir) {
|
||||
Write-Host "`nThe checkout at $RepoDir was kept (other platforms may still use it)."
|
||||
Write-Host "To remove it: Remove-Item -Recurse -Force '$RepoDir'"
|
||||
}
|
||||
}
|
||||
|
||||
function Cmd-Update {
|
||||
|
||||
+22
-11
@@ -136,15 +136,28 @@ link_skills() {
|
||||
|
||||
unlink_skills() {
|
||||
local target="$1" style="$2"
|
||||
[[ -d "$target" ]] || return 0
|
||||
case "$style" in
|
||||
per-skill)
|
||||
local skill
|
||||
while IFS= read -r skill; do
|
||||
rm -f "$target/$skill"
|
||||
done < <(list_skills)
|
||||
if [[ -d "$(skills_root)" ]]; then
|
||||
local skill
|
||||
while IFS= read -r skill; do
|
||||
[[ -L "$target/$skill" ]] && rm -f "$target/$skill"
|
||||
done < <(list_skills)
|
||||
else
|
||||
# Checkout is gone — scan the target dir for stale links pointing into
|
||||
# our plugin tree so we can still clean up.
|
||||
local link resolved
|
||||
for link in "$target"/*; do
|
||||
[[ -L "$link" ]] || continue
|
||||
resolved="$(readlink "$link" 2>/dev/null || true)"
|
||||
[[ "$resolved" == *"/understand-anything-plugin/skills/"* ]] || continue
|
||||
rm -f "$link"
|
||||
done
|
||||
fi
|
||||
;;
|
||||
folder)
|
||||
rm -f "$target/understand-anything"
|
||||
[[ -L "$target/understand-anything" ]] && rm -f "$target/understand-anything"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -186,18 +199,16 @@ cmd_uninstall() {
|
||||
target="$(printf '%s\n' "$row" | cut -d'|' -f2)"
|
||||
style="$(printf '%s\n' "$row" | cut -d'|' -f3)"
|
||||
|
||||
if [[ ! -d "$REPO_DIR" ]]; then
|
||||
printf 'No checkout found at %s — nothing to unlink.\n' "$REPO_DIR"
|
||||
exit 0
|
||||
fi
|
||||
printf -- '→ Removing skill links for %s\n' "$id"
|
||||
unlink_skills "$target" "$style"
|
||||
if [[ -L "$PLUGIN_LINK" ]]; then
|
||||
rm -f "$PLUGIN_LINK"
|
||||
printf ' ✓ removed %s\n' "$PLUGIN_LINK"
|
||||
fi
|
||||
printf '\nThe checkout at %s was kept (other platforms may still use it).\n' "$REPO_DIR"
|
||||
printf 'To remove it: rm -rf "%s"\n' "$REPO_DIR"
|
||||
if [[ -d "$REPO_DIR" ]]; then
|
||||
printf '\nThe checkout at %s was kept (other platforms may still use it).\n' "$REPO_DIR"
|
||||
printf 'To remove it: rm -rf "%s"\n' "$REPO_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_update() {
|
||||
|
||||
Reference in New Issue
Block a user