Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 26 additions & 9 deletions crates/aube/src/commands/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,20 +334,37 @@ pub fn unlink_bins(install_dir: &Path, bin_dir: &Path, bin_names: &[String]) {
let link = bin_dir.join(name);
match std::fs::read_link(&link) {
Ok(target) => {
// Symlink bin: fully resolve and check against
// `install_canon`. Matches the pre-settings behavior.
// Symlink bin: `link_bins` wrote the target as
// `<install_dir>/node_modules/<alias>/<rel>`, so the
// ownership check is textual for the same reason the
// shim branch below is. Canonicalizing first resolves
// through `node_modules/<alias>` and `.aube/<dep_path>`
// into `<cacheDir>/virtual-store/...` whenever the
// global virtual store is on (the default outside CI) —
// that lands outside `install_dir`, the ownership check
// reads the bin as belonging to another install, and
// every global bin leaks as a dangling symlink after
// `remove -g` deletes the install dir.
let absolute = if target.is_absolute() {
target
} else {
bin_dir.join(target)
};
let Some(install_canon) = install_canon.as_ref() else {
continue;
};
let Some(resolved) = std::fs::canonicalize(&absolute).ok() else {
continue;
};
if resolved.starts_with(install_canon) {
let resolved = aube_linker::normalize_path(&absolute);
// Full canonicalization stays as a fallback: a bin
// linked by an older aube (or a target reached through
// a symlinked `install_dir` ancestor) only matches
// once both sides are resolved.
if resolved.starts_with(&install_lex)
|| install_canon
.as_ref()
.is_some_and(|canon| resolved.starts_with(canon))
|| std::fs::canonicalize(&absolute).is_ok_and(|resolved| {
install_canon
.as_ref()
.is_some_and(|canon| resolved.starts_with(canon))
})
{
let _ = std::fs::remove_file(&link);
}
}
Expand Down
6 changes: 6 additions & 0 deletions test/global_install.bats
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ teardown() {
run aube remove -g semver
assert_success
assert_file_not_exists "$AUBE_HOME/semver"
# `assert_file_not_exists` is `[ -f ]`, which follows symlinks — a
# *dangling* symlink passes it. With the global virtual store on, the
# bin's canonical target lives in the shared store rather than under
# the install dir, so the ownership check has to stay textual or the
# symlink survives as a dangle (Discussion #1219).
[ ! -L "$AUBE_HOME/semver" ]

run aube list -g
assert_success
Expand Down
Loading