New Phase 1b in tests/run-tests.fish: catches a bare C1-shadowed-command call in a functions/*.fish body with no matching uses-shadow(name) or self-limiting(name) in that function's own CLASSIFICATION header. This is exactly the check discussed after the rm and cd audits -- runtime auto-unwrapping isn't viable in fish (there's no hook finer than shadowing itself, and rewriting behavior invisibly at runtime is its own footgun); a static lint using the CLASSIFICATION tag as the declared-intentional marker is. Scoped to functions/*.fish only: the one-function-per-file convention there makes body extraction exact with no block-depth parser needed. Added a new self-limiting(name) tag to the schema for the case a bare call is safe not because the caller did anything, but because the shadow's own logic already neutralizes the override: rm's and mkdir's flag checks (verified precisely -- rm falls back to command rm for any flag except a bare -r/-R/--recursive alone, which still routes to trash; mkdir falls back to command mkdir -p for any flag, no exception), and grep/fgrep/egrep/dir/vdir/cat's own tty auto-detection (--color=auto, and bat's default color behavior -- verified byte-identical to stock cat when piped, since bat also auto-disables highlighting on a non-terminal). Explicit and durable rather than a silent lint exemption: if a shadow's bypass condition is ever weakened, every self-limiting site is one grep away instead of silently wrong. Running the first draft of the lint surfaced three more real bugs, none previously audited: - config-help.fish's --man pager path checks `type -q less` (proving it wants the real less binary specifically, for less-only -R/+N flag syntax) then called it bare, routing through our own $PAGER -> ov -> less -> more -> cat fallback chain instead -- which could hand those less-specific flags to a completely different program. Now command less. - _fish_deps_install.fish and _fish_deps_update.fish's binary-upgrade paths cp a freshly downloaded binary over an already-installed one with no existence guard -- the update flow's target is guaranteed to already exist. Our cp shadow forces -i unconditionally (a plain alias, not flag-aware like rm's), so this would hang waiting on a confirmation prompt in any non-interactive run. Now command cp. Same two files' lazydocker install path piped curl output into bare bash, invoking our shell-switch wrapper instead of a plain subshell. Now command bash. - agents-init.fish's AGENTS.md/CLAUDE.md relocation calls mv bare in four places; each is already guarded by a preceding test -f check on the destination, so the -i alias was unlikely to ever fire in practice, but explicit command mv removes the reliance on that guard entirely rather than leaving it as the only thing standing between a file move and an unattended hang. The remaining ~65 flagged call sites across ~24 files were reviewed individually and tagged self-limiting(rm)/self-limiting(mkdir) (verified flagged with -f/-rf or -p) and self-limiting(grep)/ self-limiting(cat) (verified piped, captured, or -q/-c; none display color to a human), plus uses-shadow(ls) for two existence-check-only calls (cffetch.fish, ffetch.fish) whose output is redirected to /dev/null.
264 lines
9.0 KiB
Fish
264 lines
9.0 KiB
Fish
# Copyright (C) 2026 Rootiest
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
# CLASSIFICATION
|
|
# self-limiting(rm), bypasses-shadow(cp,bash), destructive, network
|
|
#
|
|
# SYNOPSIS
|
|
# _fish_deps_update
|
|
#
|
|
# DESCRIPTION
|
|
# Updates all currently installed fish shell dependencies using their
|
|
# preferred method. Priority order: cargo, then system PM, then special
|
|
# installers (fzf-update, fisher, pipx). Always updates fisher plugins first.
|
|
#
|
|
# EXAMPLE
|
|
# _fish_deps_update
|
|
function _fish_deps_update
|
|
_fish_deps_catalog
|
|
|
|
set -l pm (_fish_deps_detect_pm)
|
|
set -l updated_any 0
|
|
|
|
# Fisher plugins — always update if fisher is present
|
|
if type -q fisher
|
|
echo "Updating fisher plugins..."
|
|
fisher update
|
|
set updated_any 1
|
|
end
|
|
|
|
set -l i 1
|
|
for bin in $_fdc_bins
|
|
# Skip fisher itself (handled above) and tools that aren't installed.
|
|
# command -q (not type -q): ignore wrapper functions shadowing the name.
|
|
if test "$bin" = fisher; or not command -q $bin
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
set -l cargo_crate $_fdc_cargo[$i]
|
|
set -l pm_pkg $_fdc_pm[$i]
|
|
set -l special $_fdc_special[$i]
|
|
|
|
# yay: update via paru if available, else system PM
|
|
if test "$special" = yay-build
|
|
if type -q paru
|
|
echo "Updating $bin..."
|
|
paru -S --noconfirm yay
|
|
set updated_any 1
|
|
else if test -n "$pm_pkg"; and test -n "$pm"
|
|
echo "Updating $bin..."
|
|
_fish_deps_pm_upgrade $pm_pkg
|
|
set updated_any 1
|
|
end
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
# cargo: update via rustup
|
|
if test "$special" = rustup-installer
|
|
if type -q rustup
|
|
echo "Updating $bin..."
|
|
rustup update
|
|
set updated_any 1
|
|
end
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
# fzf: always use fzf-update (git-based)
|
|
if test "$special" = fzf-update
|
|
echo "Updating $bin..."
|
|
fzf-update
|
|
set updated_any 1
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
# ov: prefer go install (always fetches latest); fall back to system PM
|
|
if test "$special" = go-ov
|
|
if type -q go
|
|
echo "Updating $bin..."
|
|
go install github.com/noborus/ov@latest
|
|
set updated_any 1
|
|
else if test -n "$pm_pkg"; and test -n "$pm"
|
|
echo "Updating $bin (go unavailable, using system PM)..."
|
|
_fish_deps_pm_upgrade $pm_pkg
|
|
set updated_any 1
|
|
end
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
# lazydocker: re-run the official install/update script
|
|
if test "$special" = curl-lazydocker
|
|
echo "Updating $bin..."
|
|
curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | command bash
|
|
set updated_any 1
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
# marktext: AUR where it exists, else refresh the AppImage. Only an
|
|
# AppImage we own is refreshed -- a distro-packaged marktext belongs
|
|
# to that package manager, and ~/.local/bin/marktext would shadow it.
|
|
if test "$special" = marktext-release
|
|
if type -q paru
|
|
echo "Updating $bin..."
|
|
paru -S --noconfirm marktext-bin
|
|
set updated_any 1
|
|
else if type -q yay
|
|
echo "Updating $bin..."
|
|
yay -S --noconfirm marktext-bin
|
|
set updated_any 1
|
|
else if test -f "$HOME/.local/bin/marktext"
|
|
echo "Updating $bin..."
|
|
_fish_deps_marktext_appimage
|
|
set updated_any 1
|
|
end
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
# wakatime: re-download the binary from github releases
|
|
if test "$special" = wakatime-binary
|
|
echo "Updating $bin..."
|
|
set -l _arch (uname -m)
|
|
switch $_arch
|
|
case x86_64
|
|
set _arch amd64
|
|
case aarch64 arm64
|
|
set _arch arm64
|
|
case armv7l
|
|
set _arch arm
|
|
case '*'
|
|
set _arch amd64
|
|
end
|
|
set -l _zip "wakatime-cli-linux-$_arch.zip"
|
|
set -l _bin_src "wakatime-cli-linux-$_arch"
|
|
set -l _wt_bin "$HOME/.config/wakatime/wakatime"
|
|
set -l _tmpdir (mktemp -d)
|
|
curl -L "https://github.com/wakatime/wakatime-cli/releases/latest/download/$_zip" \
|
|
-o "$_tmpdir/$_zip"
|
|
and unzip -o "$_tmpdir/$_zip" -d "$_tmpdir"
|
|
and command cp "$_tmpdir/$_bin_src" "$_wt_bin"
|
|
and chmod +x "$_wt_bin"
|
|
rm -rf "$_tmpdir"
|
|
and set updated_any 1
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
# win32yank: re-download the binary from github releases (WSL2 only;
|
|
# only reached if a copy is already on PATH, so no WSL check needed)
|
|
if test "$special" = win32yank-release
|
|
echo "Updating $bin..."
|
|
set -l _zip win32yank-x64.zip
|
|
set -l _tmpdir (mktemp -d)
|
|
curl -fL "https://github.com/equalsraf/win32yank/releases/latest/download/$_zip" \
|
|
-o "$_tmpdir/$_zip"
|
|
and unzip -o "$_tmpdir/$_zip" -d "$_tmpdir"
|
|
and command cp "$_tmpdir/win32yank.exe" "$HOME/.local/bin/win32yank.exe"
|
|
and chmod +x "$HOME/.local/bin/win32yank.exe"
|
|
set -l _up_status $status
|
|
rm -rf "$_tmpdir"
|
|
test $_up_status -eq 0
|
|
and set updated_any 1
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
# pipx tools
|
|
if test "$special" = pipx
|
|
if type -q pipx
|
|
echo "Updating $bin..."
|
|
pipx upgrade $bin
|
|
set updated_any 1
|
|
end
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
# uv: use built-in self-updater
|
|
if test "$special" = curl-uv
|
|
echo "Updating $bin..."
|
|
uv self update
|
|
set updated_any 1
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
# fish: prefer build from source via git + cargo; fall back to PM
|
|
if test "$special" = git-cargo-fish
|
|
if type -q cargo; and type -q uv
|
|
echo "Updating $bin..."
|
|
set -l _tmpdir (mktemp -d)
|
|
set -l _build_ok 0
|
|
git clone https://github.com/fish-shell/fish-shell "$_tmpdir"
|
|
and begin
|
|
set -l _tag (git -C "$_tmpdir" tag --list 'fish-*' --sort=version:refname | tail -1)
|
|
test -n "$_tag"; and git -C "$_tmpdir" checkout "$_tag"
|
|
true
|
|
end
|
|
and pushd "$_tmpdir"
|
|
and uv run --no-managed-python cargo install --path .
|
|
and set _build_ok 1
|
|
popd 2>/dev/null
|
|
rm -rf "$_tmpdir"
|
|
if test $_build_ok -eq 1
|
|
set updated_any 1
|
|
set_color yellow
|
|
echo " Fish updated — restart your shell to use the new version."
|
|
set_color normal
|
|
end
|
|
else if test -n "$pm_pkg"; and test -n "$pm"
|
|
echo "Updating $bin (cargo/uv unavailable, using system PM)..."
|
|
_fish_deps_pm_upgrade $pm_pkg
|
|
set updated_any 1
|
|
set_color yellow
|
|
echo " Fish updated — restart your shell to use the new version."
|
|
set_color normal
|
|
else
|
|
set_color yellow
|
|
echo " fish: cannot update — install cargo and uv to build from source"
|
|
set_color normal
|
|
end
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
# curl-installer tools (starship etc.): re-run install script, which upgrades in place
|
|
if test "$special" = curl-installer
|
|
if test "$bin" = starship
|
|
echo "Updating $bin..."
|
|
curl -sS https://starship.rs/install.sh | sh -s -- --yes
|
|
set updated_any 1
|
|
end
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
# Cargo: prefer for Rust tools
|
|
if test -n "$cargo_crate"; and type -q cargo
|
|
echo "Updating $bin..."
|
|
cargo install --force $cargo_crate
|
|
set updated_any 1
|
|
set i (math $i + 1)
|
|
continue
|
|
end
|
|
|
|
# System PM fallback
|
|
if test -n "$pm_pkg"; and test -n "$pm"
|
|
echo "Updating $bin..."
|
|
_fish_deps_pm_upgrade $pm_pkg
|
|
set updated_any 1
|
|
end
|
|
|
|
set i (math $i + 1)
|
|
end
|
|
|
|
if test $updated_any -eq 0
|
|
echo "Nothing to update."
|
|
end
|
|
end
|