Files
easyzsh/tests/installers.sh
T
Jeffrey ed5365859c fix(zshenv): put ~/.local/bin on PATH for every zsh
Non-interactive agent shells (Codex, Claude) skip .zshrc and never saw
user-local tools installed via pipx/uv/grok. Add ~/.local/bin in
.zshenv with an idempotent guard, and source optional
~/.config/zshenv.local for machine-local early env.
2026-08-06 14:25:52 +08:00

110 lines
5.2 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT
fail() {
echo "FAIL: $*" >&2
exit 1
}
assert_download_failure_is_reported() {
local home="$TEMP_DIR/home-${1%.sh}"
mkdir -p "$home"
if HOME="$home" PATH="$TEMP_DIR/bin:/usr/bin:/bin" /bin/bash "$ROOT/$1" >"$home/output" 2>&1; then
fail "$1 returned success after curl failed"
fi
}
mkdir -p "$TEMP_DIR/bin"
ln -s /usr/bin/false "$TEMP_DIR/bin/curl"
ln -s /usr/bin/true "$TEMP_DIR/bin/unzip"
for script in install_fnm.sh install_nvm.sh install_pyenv.sh install_merlin_worker.sh; do
assert_download_failure_is_reported "$script"
done
patch_home="$TEMP_DIR/patch-home"
mkdir -p "$patch_home/.config/zshrc"
cp "$ROOT/README.md" "$patch_home/.config/zshrc/missing.zsh"
if HOME="$patch_home" PATH="$TEMP_DIR/bin:/usr/bin:/bin" /bin/bash "$ROOT/patch.sh" missing >/dev/null 2>&1; then
fail "patch.sh returned success after curl failed"
fi
cmp -s "$ROOT/README.md" "$patch_home/.config/zshrc/missing.zsh" ||
fail "patch.sh replaced an existing config after curl failed"
if HOME="$patch_home" PATH="$TEMP_DIR/bin:/usr/bin:/bin" /bin/bash "$ROOT/patch.sh" ../invalid >/dev/null 2>&1; then
fail "patch.sh accepted an unsafe config name"
fi
main_bin="$TEMP_DIR/main-bin"
main_home="$TEMP_DIR/main-home"
mkdir -p "$main_bin" "$main_home"
for command_name in zsh git curl tar fc-cache eza zoxide autoconf trash; do
ln -s /usr/bin/true "$main_bin/$command_name"
done
ln -s /usr/bin/false "$main_bin/wget"
cp "$ROOT/README.md" "$main_home/.zshrc"
printf '%s\n' 'existing-zshenv' >"$main_home/.zshenv"
if HOME="$main_home" PATH="$main_bin:/usr/bin:/bin" /bin/bash "$ROOT/install.sh" --non-interactive >/dev/null 2>&1; then
fail "install.sh returned success after the zshrc download failed"
fi
cmp -s "$ROOT/README.md" "$main_home/.zshrc" ||
fail "install.sh replaced .zshrc after its download failed"
printf '%s\n' 'existing-zshenv' | cmp -s - "$main_home/.zshenv" ||
fail "install.sh replaced .zshenv after a download failed"
fnm_home="$TEMP_DIR/fnm-home"
mkdir -p "$fnm_home/.local/share/fnm"
ln -s /usr/bin/true "$fnm_home/.local/share/fnm/fnm"
HOME="$fnm_home" PATH=/usr/bin:/bin zsh -c 'source "$1"; command -v fnm >/dev/null' zsh "$ROOT/zshrc/fnm.zsh" ||
fail "fnm is not restored to PATH in a new Zsh session"
for script in "$ROOT"/*.sh; do
bash -n "$script"
done
for config in "$ROOT/.zshenv" "$ROOT/.zshrc" "$ROOT"/zshrc/*.zsh; do
zsh -n "$config"
done
grep -q 'NVM_VERSION="v0.40.4"' "$ROOT/install_nvm.sh" || fail "nvm version is stale"
grep -q 'apt-get install -y unzip' "$ROOT/install_fnm.sh" || fail "Ubuntu unzip installation is missing"
grep -q 'tail -1 || true' "$ROOT/install_merlin_worker.sh" || fail "a missing PYTHONPATH remains fatal"
grep -Fq 'mktemp "$HOME/.zshrc.download.' "$ROOT/install.sh" || fail ".zshrc is not downloaded on its target filesystem"
grep -Fq 'mktemp "$HOME/.zshenv.download.' "$ROOT/install.sh" || fail ".zshenv is not downloaded on its target filesystem"
grep -Fq 'mktemp "$HOME/.config/.easyzsh-' "$ROOT/patch.sh" || fail "patch downloads can be sourced while incomplete"
grep -Fq '"$ZSH_CONFIGS_DIR"/*(DN)' "$ROOT/.zshrc" || fail "existing extensionless Zsh configs are ignored"
grep -Fq "alias rm='/usr/bin/trash'" "$ROOT/.zshrc" || fail "macOS rm does not use the native Trash"
grep -Fq "alias rm='gio trash'" "$ROOT/.zshrc" || fail "Linux rm does not use the desktop Trash"
grep -Fq 'libglib2.0-bin' "$ROOT/install.sh" || fail "Ubuntu Trash support is not installed"
grep -Fq 'cp -Pp "$HOME/.zshrc"' "$ROOT/install.sh" || fail ".zshrc symlinks are not preserved in backups"
grep -Fq '[ -L "$HOME/.zshrc" ]' "$ROOT/install.sh" || fail "broken .zshrc symlinks are not backed up"
grep -Fq 'cp -Pp "$HOME/.zshenv"' "$ROOT/install.sh" || fail ".zshenv symlinks are not preserved in backups"
grep -Fq '[ -L "$HOME/.zshenv" ]' "$ROOT/install.sh" || fail "broken .zshenv symlinks are not backed up"
grep -Fq 'brew shellenv' "$ROOT/.zshenv" || fail "Homebrew is not initialized in .zshenv"
grep -Fq '/usr/local/bin/brew' "$ROOT/.zshenv" || fail "Intel Homebrew path is missing from .zshenv"
grep -Fq '$HOME/.local/bin' "$ROOT/.zshenv" || fail "~/.local/bin is not added in .zshenv"
grep -Fq 'zshenv.local' "$ROOT/.zshenv" || fail "machine-local zshenv.local hook is missing"
if grep -Fq 'brew shellenv' "$ROOT/.zshrc"; then
fail "Homebrew still initializes from interactive-only .zshrc"
fi
# Non-interactive zsh must resolve ~/.local/bin without sourcing .zshrc.
local_bin_home="$TEMP_DIR/local-bin-home"
mkdir -p "$local_bin_home/.local/bin"
printf '%s\n' '#!/bin/sh' 'echo ok' >"$local_bin_home/.local/bin/easyzsh-path-probe"
chmod +x "$local_bin_home/.local/bin/easyzsh-path-probe"
HOME="$local_bin_home" PATH=/usr/bin:/bin \
zsh -c '. "$1"; command -v easyzsh-path-probe >/dev/null' zsh "$ROOT/.zshenv" ||
fail "~/.local/bin is not available to non-interactive zsh via .zshenv"
[[ ! -e "$ROOT/install/fnm.sh" ]] || fail "vendored fnm installer still exists"
if grep -Eq '^[[:space:]]*export MLX_USER_TOKEN=' "$ROOT"/zshrc/*.zsh; then
fail "a static MLX user token remains"
fi
echo "installer checks passed"