fix(shell): restore always-on PATH after macOS path_helper

path_helper runs after ~/.zshenv in login shells and puts Homebrew ahead of
fnm, pyenv, and ~/.local/bin. Re-source ~/.zshenv from ~/.zprofile and make
prepends idempotent so login and zsh -c resolve the same binaries.
This commit is contained in:
Frank Qing committed 2026-09-07 22:07:58 +08:00
1 parent 82c6a57c3a
commit 34d11b5aa4
8 files changed
+198 -73

No files matched your search

+60 -35
View File
@@ -1,13 +1,15 @@
#!/usr/bin/env bash
# Install easyzsh shell stubs into a home directory.
# Overwrites: ~/.zshenv ~/.zshrc
# Installs: ~/.zprofile (re-applies ~/.zshenv after macOS path_helper)
# Never overwrites: ~/.config/zsh/local.zsh ~/.config/zshrc/** ~/.local/bin/**
# Does not manage ~/.zprofile (machine/login hooks belong in local.zsh).
# Extra ~/.zprofile lines stay as login-only hooks unless --migrate-zprofile
# folds them into local.zsh.
#
# Usage:
# ./deploy.sh
# ./deploy.sh --home /path/to/home
# ./deploy.sh --migrate-zprofile # fold real ~/.zprofile into local.zsh, then remove stub
# ./deploy.sh --migrate-zprofile # fold extra ~/.zprofile lines into local.zsh
set -euo pipefail
@@ -58,6 +60,9 @@ file_has_content() {
[[ -f "$1" ]] && grep -q '[^[:space:]]' "$1"
}
ZPROFILE_MARKER='# easyzsh: reapply always-on PATH after macOS path_helper'
PYENV_LOGIN_HOOK='[[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/zsh/pyenv.zsh" ]] && . "${XDG_CONFIG_HOME:-$HOME/.config}/zsh/pyenv.zsh"'
is_easyzsh_zprofile_stub() {
local f="$1"
[[ -f "$f" ]] || return 1
@@ -68,9 +73,25 @@ is_easyzsh_zprofile_stub() {
grep -Eq 'easyzsh|PATH lives in|Leave this file empty|prefer.*local\.zsh' "$f"
}
has_pyenv_login_hook() {
local hook='[[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/zsh/pyenv.zsh" ]] && . "${XDG_CONFIG_HOME:-$HOME/.config}/zsh/pyenv.zsh"'
[[ -f "$1" ]] && grep -Fqx "$hook" "$1"
is_pyenv_only_zprofile() {
local f="$1"
local body
[[ -f "$f" ]] || return 1
body=$(grep -Ev '^[[:space:]]*(#|$)' "$f" || true)
[[ "$body" == "$PYENV_LOGIN_HOOK" ]]
}
# Lines that are not the managed path_helper block or the old pyenv login hook.
zprofile_extras() {
local f="$1"
[[ -f "$f" ]] || return 0
awk -v marker="$ZPROFILE_MARKER" -v hook="$PYENV_LOGIN_HOOK" '
$0 == marker { in_block=1; next }
in_block && $0 == "fi" { in_block=0; next }
in_block { next }
$0 == hook { next }
{ print }
' "$f"
}
echo "Deploying easyzsh shell stubs into $TARGET_HOME"
@@ -104,45 +125,49 @@ if [[ ! -f "$LOCAL_ZSH" ]]; then
rm -f "$tmp"
fi
# Optional: fold a real (non-stub) ~/.zprofile into local.zsh.
if [[ "$MIGRATE_ZPROFILE" -eq 1 && -f "$ZPROFILE" ]]; then
if has_pyenv_login_hook "$ZPROFILE"; then
# ponytail: keep the whole profile; split migration only if per-line ownership is needed.
echo "Keeping ~/.zprofile: contains the pyenv login hook"
elif is_easyzsh_zprofile_stub "$ZPROFILE"; then
echo "Removing easyzsh ~/.zprofile stub (hooks live in local.zsh / .zshenv)"
rm -f "$ZPROFILE"
elif [[ ! -f "$LOCAL_ZSH" ]]; then
zprofile_extract=$(mktemp)
awk '
/Created by `pipx`/ { skip_pipx=1; next }
skip_pipx && /local\/bin/ { skip_pipx=0; next }
skip_pipx { next }
{ print }
' "$ZPROFILE" >"$zprofile_extract"
if file_has_content "$zprofile_extract"; then
cp "$zprofile_extract" "$LOCAL_ZSH"
echo "Migrated ~/.zprofile body -> ~/.config/zsh/local.zsh"
rm -f "$ZPROFILE"
echo "Removed ~/.zprofile after migration"
fi
rm -f "$zprofile_extract"
elif is_easyzsh_zprofile_stub "$ZPROFILE"; then
rm -f "$ZPROFILE"
fi
# ~/.zprofile re-applies ~/.zshenv after macOS path_helper.
# Replace comment-only stubs and the old pyenv-only hook. Keep other login
# lines, or fold them into local.zsh when --migrate-zprofile is set.
zprofile_extras_file=$(mktemp)
: >"$zprofile_extras_file"
if [[ -f "$ZPROFILE" ]] && ! is_easyzsh_zprofile_stub "$ZPROFILE" && ! is_pyenv_only_zprofile "$ZPROFILE"; then
zprofile_extras "$ZPROFILE" | awk '
/Created by `pipx`/ { skip_pipx=1; next }
skip_pipx && /local\/bin/ { skip_pipx=0; next }
skip_pipx { next }
{ print }
' >"$zprofile_extras_file"
fi
# Always drop the comment-only easyzsh stub if present (even without --migrate).
if is_easyzsh_zprofile_stub "$ZPROFILE"; then
rm -f "$ZPROFILE"
echo "Removed empty easyzsh ~/.zprofile stub"
if [[ "$MIGRATE_ZPROFILE" -eq 1 ]] && file_has_content "$zprofile_extras_file"; then
if [[ -f "$LOCAL_ZSH" ]]; then
printf '\n' >>"$LOCAL_ZSH"
cat "$zprofile_extras_file" >>"$LOCAL_ZSH"
else
cp "$zprofile_extras_file" "$LOCAL_ZSH"
fi
echo "Migrated extra ~/.zprofile lines -> ~/.config/zsh/local.zsh"
: >"$zprofile_extras_file"
fi
backup_file "${TARGET_HOME}/.zshenv"
backup_file "${TARGET_HOME}/.zshrc"
backup_file "$ZPROFILE"
install -m 644 "${ROOT}/.zshenv" "${TARGET_HOME}/.zshenv"
install -m 644 "${ROOT}/.zshrc" "${TARGET_HOME}/.zshrc"
if file_has_content "$zprofile_extras_file"; then
{
cat "${ROOT}/.zprofile"
printf '\n'
cat "$zprofile_extras_file"
} >"$ZPROFILE"
echo "Installed ~/.zprofile (kept extra login lines)"
else
install -m 644 "${ROOT}/.zprofile" "$ZPROFILE"
echo "Installed ~/.zprofile"
fi
rm -f "$zprofile_extras_file"
# Remove obsolete managed-tree layout and examples from earlier deploys.
/bin/rm -rf \