diff --git a/.zprofile b/.zprofile new file mode 100644 index 0000000..e81c0f8 --- /dev/null +++ b/.zprofile @@ -0,0 +1,7 @@ +# easyzsh: reapply always-on PATH after macOS path_helper +# Login shells only. /etc/zprofile runs path_helper after ~/.zshenv and puts +# Homebrew and system paths ahead of fnm, pyenv, and ~/.local/bin. +# ~/.zshenv prepends are idempotent, so re-sourcing restores that order. +if [[ -x /usr/libexec/path_helper ]]; then + . "${ZDOTDIR:-$HOME}/.zshenv" +fi diff --git a/.zshenv b/.zshenv index 529814d..7c44239 100644 --- a/.zshenv +++ b/.zshenv @@ -1,20 +1,23 @@ # Always-on env for every zsh (interactive, login, agents, zsh -c). -# Machine-local overrides: ~/.config/zsh/local.zsh (never overwritten by deploy). +# Safe to re-source: macOS login shells run this again from ~/.zprofile after +# path_helper reorders PATH. Machine-local overrides: ~/.config/zsh/local.zsh. + +typeset -U path # Homebrew (macOS). cd /tmp so TCC-protected CWDs do not break brew shellenv. -if [[ -x /opt/homebrew/bin/brew ]]; then - eval "$(cd /tmp && /opt/homebrew/bin/brew shellenv)" -elif [[ -x /usr/local/bin/brew ]]; then - eval "$(cd /tmp && /usr/local/bin/brew shellenv)" +# Skip when already initialized (login re-apply, nested zsh). +if [[ -z "$HOMEBREW_PREFIX" ]]; then + if [[ -x /opt/homebrew/bin/brew ]]; then + eval "$(cd /tmp && /opt/homebrew/bin/brew shellenv)" + elif [[ -x /usr/local/bin/brew ]]; then + eval "$(cd /tmp && /usr/local/bin/brew shellenv)" + fi fi # User-local binaries (pipx, uv tools, grok shim, …). # Do not put vendor trees like ~/.grok/bin on PATH wholesale (names like `agent` shadow tools). if [[ -d "$HOME/.local/bin" ]]; then - case ":${PATH}:" in - *":$HOME/.local/bin:"*) ;; - *) PATH="$HOME/.local/bin:${PATH}" ;; - esac + path=("$HOME/.local/bin" ${path:#$HOME/.local/bin}) export PATH fi diff --git a/README.md b/README.md index 227761d..aa3b7aa 100644 --- a/README.md +++ b/README.md @@ -35,15 +35,18 @@ From a git checkout (shell stubs only): | Path | Role | Reinstall | | --- | --- | --- | | `~/.zshenv` | Always-on: Homebrew, `~/.local/bin`, `~/.config/zsh/*.zsh`, then `local.zsh` | Overwritten | +| `~/.zprofile` | Login: re-apply `~/.zshenv` after macOS `path_helper` | Overwritten (extra login lines kept) | | `~/.zshrc` | Interactive: omz, plugins, aliases, fzf, zoxide | Overwritten | | `~/.config/zsh/*.zsh` | Always-on tool fragments (e.g. `fnm.zsh`) | Left alone (via `patch.sh`) | | `~/.config/zsh/local.zsh` | Machine-local overrides (every zsh, including agents) | **Never overwritten** | | `~/.config/zshrc/*.zsh` | Personal/tool overlays before oh-my-zsh (interactive) | Left alone | | `~/.local/bin/` | User tools + shims (`grok` → Grok CLI; `agent` → Cursor when installed) | Left alone except those two shims | -Core deploy does not manage `~/.zprofile`. The pyenv add-on appends one login -hook so macOS `path_helper` cannot outrank pyenv shims. Put other machine hooks -in `local.zsh`. +macOS login shells run `path_helper` after `~/.zshenv`, which would put Homebrew +ahead of fnm, pyenv, and `~/.local/bin`. `~/.zprofile` re-sources `~/.zshenv` +(idempotent prepends) so login and `zsh -c` keep the same PATH. Put other +machine-local env in `local.zsh`; extra login-only lines can stay in +`~/.zprofile` below the easyzsh block. Do **not** put `~/.grok/bin` on PATH. That tree ships an `agent` that shadows Cursor. Grok is the `grok` command (`~/.local/bin/grok`). Cursor is `agent` diff --git a/deploy.sh b/deploy.sh index 2cab472..369490c 100755 --- a/deploy.sh +++ b/deploy.sh @@ -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 \ diff --git a/install.sh b/install.sh index 2903ca0..a745ff0 100755 --- a/install.sh +++ b/install.sh @@ -357,7 +357,8 @@ echo -e "Downloading easyzsh shell stubs\n" for rel in \ deploy.sh \ .zshenv \ - .zshrc + .zshrc \ + .zprofile do download_easyzsh_file "$rel" done diff --git a/install_pyenv.sh b/install_pyenv.sh index 70c2e80..9e7cd9c 100755 --- a/install_pyenv.sh +++ b/install_pyenv.sh @@ -21,13 +21,8 @@ fi # Uncomment the following line if you would like to automatically install it # pyenv install 3.12.0 && pyenv global 3.12.0 -# Always-on fragment: ~/.config/zsh/pyenv.zsh (sourced from ~/.zshenv) +# Always-on fragment: ~/.config/zsh/pyenv.zsh (sourced from ~/.zshenv). +# Login shells re-apply it via ~/.zprofile after macOS path_helper. curl -fsSL https://git.miomio.moe/mio/easyzsh/raw/branch/master/patch.sh | bash -s -- zsh/pyenv -# macOS path_helper runs after ~/.zshenv in login shells, so reapply pyenv afterwards. -PYENV_LOGIN_HOOK='[[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/zsh/pyenv.zsh" ]] && . "${XDG_CONFIG_HOME:-$HOME/.config}/zsh/pyenv.zsh"' -if ! grep -Fqx "$PYENV_LOGIN_HOOK" "$HOME/.zprofile" 2>/dev/null; then - [[ ! -s "$HOME/.zprofile" ]] || printf '\n' >> "$HOME/.zprofile" - printf '%s\n' "$PYENV_LOGIN_HOOK" >> "$HOME/.zprofile" -fi # Drop legacy interactive-only copy if present rm -f "$HOME/.config/zshrc/pyenv.zsh" diff --git a/tests/installers.sh b/tests/installers.sh index f2b89d4..098179d 100755 --- a/tests/installers.sh +++ b/tests/installers.sh @@ -81,11 +81,13 @@ for script in "$ROOT"/*.sh; do bash -n "$script" done -for config in "$ROOT/.zshenv" "$ROOT/.zshrc" "$ROOT"/zshrc/*.zsh "$ROOT"/zsh/*.zsh; do +for config in "$ROOT/.zshenv" "$ROOT/.zshrc" "$ROOT/.zprofile" "$ROOT"/zshrc/*.zsh "$ROOT"/zsh/*.zsh; do [[ -f "$config" ]] || continue zsh -n "$config" done -[[ ! -e "$ROOT/.zprofile" ]] || fail "easyzsh should not ship a managed .zprofile" +[[ -f "$ROOT/.zprofile" ]] || fail "easyzsh should ship a managed .zprofile" +grep -Fqx '# easyzsh: reapply always-on PATH after macOS path_helper' "$ROOT/.zprofile" || + fail ".zprofile is missing the path_helper reapply marker" [[ -f "$ROOT/zsh/fnm.zsh" ]] || fail "always-on fnm fragment is missing" [[ ! -e "$ROOT/zshrc/fnm.zsh" ]] || fail "legacy interactive fnm fragment still exists" [[ -f "$ROOT/zsh/pyenv.zsh" ]] || fail "always-on pyenv fragment is missing" @@ -96,6 +98,7 @@ grep -q 'apt-get install -y unzip' "$ROOT/install_fnm.sh" || fail "Ubuntu unzip 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 '.zprofile' "$ROOT/install.sh" || fail "install.sh does not download .zprofile" grep -Fq 'mktemp "$(dirname "$destination")/.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 '_resolve_trash_cmd' "$ROOT/.zshrc" || fail "trash-backed rm helper is missing" @@ -143,6 +146,8 @@ chmod +x "$deploy_home/.grok/bin/grok" HOME="$deploy_home" bash "$ROOT/deploy.sh" --home "$deploy_home" [[ -f "$deploy_home/.zshenv" ]] || fail "deploy.sh did not install .zshenv" [[ -f "$deploy_home/.zshrc" ]] || fail "deploy.sh did not install .zshrc" +grep -Fqx '# easyzsh: reapply always-on PATH after macOS path_helper' "$deploy_home/.zprofile" || + fail "deploy.sh did not install .zprofile" grep -Fxq '# machine-only' "$deploy_home/.config/zsh/local.zsh" || fail "deploy.sh overwrote local.zsh" [[ ! -e "$deploy_home/.config/zsh/env" ]] || fail "deploy.sh left obsolete env/ tree" @@ -205,34 +210,100 @@ HOME="$pyenv_env_home" XDG_CONFIG_HOME="$pyenv_env_xdg" \ zsh -c '[[ "$(command -v python3)" == "$HOME/.pyenv/shims/python3" ]]' || fail "pyenv Python is not available to non-interactive zsh via .zshenv" -# Login zsh must reapply pyenv after the system profile changes PATH. -pyenv_login_hook=$(sed -n "s/^PYENV_LOGIN_HOOK='\(.*\)'$/\1/p" "$ROOT/install_pyenv.sh") -[[ -n "$pyenv_login_hook" ]] || fail "install_pyenv.sh is missing its login hook" -printf '%s\n' "$pyenv_login_hook" > "$pyenv_env_home/.zprofile" +# Login zsh must reapply always-on PATH after the system profile (path_helper). HOME="$pyenv_env_home" XDG_CONFIG_HOME="$pyenv_env_xdg" \ PYENV_ROOT="$pyenv_env_home/.pyenv" PATH=/usr/bin:/bin \ zsh -lc '[[ "$(command -v python3)" == "$HOME/.pyenv/shims/python3" ]]' || fail "pyenv Python is not restored in login zsh after the system profile" -# A later easyzsh migration must not move the login hook before the system profile. +# A later easyzsh migration still restores pyenv; the old pyenv-only hook is replaced. +pyenv_login_hook='[[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/zsh/pyenv.zsh" ]] && . "${XDG_CONFIG_HOME:-$HOME/.config}/zsh/pyenv.zsh"' +printf '%s\n' "$pyenv_login_hook" > "$pyenv_env_home/.zprofile" HOME="$pyenv_env_home" bash "$ROOT/deploy.sh" --home "$pyenv_env_home" --migrate-zprofile >/dev/null -grep -Fqx "$pyenv_login_hook" "$pyenv_env_home/.zprofile" || - fail "deploy.sh migrated the pyenv login hook out of .zprofile" +grep -Fqx '# easyzsh: reapply always-on PATH after macOS path_helper' "$pyenv_env_home/.zprofile" || + fail "deploy.sh did not replace the pyenv-only login hook with the path_helper reapply" +if grep -Fqx "$pyenv_login_hook" "$pyenv_env_home/.zprofile"; then + fail "deploy.sh left the obsolete pyenv login hook in .zprofile" +fi HOME="$pyenv_env_home" XDG_CONFIG_HOME="$pyenv_env_xdg" \ PYENV_ROOT="$pyenv_env_home/.pyenv" PATH=/usr/bin:/bin \ zsh -lc '[[ "$(command -v python3)" == "$HOME/.pyenv/shims/python3" ]]' || fail "pyenv Python was lost after a later easyzsh migration" -# A comment mentioning the fragment is not an active pyenv login hook. +# Extra login lines are folded into local.zsh on --migrate-zprofile; .zprofile stays managed. profile_comment_home="$TEMP_DIR/profile-comment-home" mkdir -p "$profile_comment_home" printf '%s\n' '# pyenv config: ~/.config/zsh/pyenv.zsh' 'ordinary-login-hook' > "$profile_comment_home/.zprofile" HOME="$profile_comment_home" bash "$ROOT/deploy.sh" --home "$profile_comment_home" --migrate-zprofile >/dev/null -[[ ! -e "$profile_comment_home/.zprofile" ]] || - fail "deploy.sh treated a pyenv path comment as an active login hook" +grep -Fqx '# easyzsh: reapply always-on PATH after macOS path_helper' "$profile_comment_home/.zprofile" || + fail "deploy.sh did not install the managed .zprofile after migrating extra lines" +if grep -Fxq 'ordinary-login-hook' "$profile_comment_home/.zprofile"; then + fail "deploy.sh left migrated login lines in .zprofile" +fi grep -Fxq 'ordinary-login-hook' "$profile_comment_home/.config/zsh/local.zsh" || fail "deploy.sh did not migrate an ordinary profile mentioning pyenv" +# Without --migrate-zprofile, extra login lines stay in .zprofile below the reapply block. +profile_keep_home="$TEMP_DIR/profile-keep-home" +mkdir -p "$profile_keep_home" +printf '%s\n' 'ordinary-login-hook' > "$profile_keep_home/.zprofile" +HOME="$profile_keep_home" bash "$ROOT/deploy.sh" --home "$profile_keep_home" >/dev/null +grep -Fqx '# easyzsh: reapply always-on PATH after macOS path_helper' "$profile_keep_home/.zprofile" || + fail "deploy.sh did not install the managed .zprofile in front of extra login lines" +grep -Fxq 'ordinary-login-hook' "$profile_keep_home/.zprofile" || + fail "deploy.sh dropped extra login lines without --migrate-zprofile" + +# ~/.local/bin must beat path_helper-promoted system binaries in login zsh. +tee_login_home="$TEMP_DIR/tee-login-home" +mkdir -p "$tee_login_home/.local/bin" +printf '%s\n' '#!/bin/sh' 'echo local-tee' >"$tee_login_home/.local/bin/tee" +chmod +x "$tee_login_home/.local/bin/tee" +HOME="$tee_login_home" bash "$ROOT/deploy.sh" --home "$tee_login_home" >/dev/null +HOME="$tee_login_home" PATH=/usr/bin:/bin \ + zsh -c '[[ "$(command -v tee)" == "$HOME/.local/bin/tee" ]]' || + fail "~/.local/bin/tee is not preferred in non-login zsh" +HOME="$tee_login_home" PATH=/usr/bin:/bin \ + zsh -lc '[[ "$(command -v tee)" == "$HOME/.local/bin/tee" ]]' || + fail "~/.local/bin/tee is not restored in login zsh after the system profile" + +# Login zsh must prefer fnm-managed npm over Homebrew/system npm. +fnm_login_home="$TEMP_DIR/fnm-login-home" +mkdir -p "$fnm_login_home/.local/share/fnm/shim/bin" "$fnm_login_home/.config/zsh" +cat >"$fnm_login_home/.local/share/fnm/fnm" <<'EOF' +#!/bin/bash +if [[ "$1" == "env" ]]; then + shim="$HOME/.local/share/fnm/shim" + printf '%s\n' "export PATH=\"${shim}/bin:\$PATH\"" + printf '%s\n' "export FNM_MULTISHELL_PATH=\"${shim}\"" + exit 0 +fi +exit 0 +EOF +chmod +x "$fnm_login_home/.local/share/fnm/fnm" +printf '%s\n' '#!/bin/sh' 'echo fnm-npm' >"$fnm_login_home/.local/share/fnm/shim/bin/npm" +chmod +x "$fnm_login_home/.local/share/fnm/shim/bin/npm" +cp "$ROOT/zsh/fnm.zsh" "$fnm_login_home/.config/zsh/fnm.zsh" +HOME="$fnm_login_home" bash "$ROOT/deploy.sh" --home "$fnm_login_home" >/dev/null +HOME="$fnm_login_home" PATH=/usr/bin:/bin \ + zsh -c '[[ "$(command -v npm)" == "$HOME/.local/share/fnm/shim/bin/npm" ]]' || + fail "fnm npm is not preferred in non-login zsh" +HOME="$fnm_login_home" PATH=/usr/bin:/bin \ + zsh -lc '[[ "$(command -v npm)" == "$HOME/.local/share/fnm/shim/bin/npm" ]]' || + fail "fnm npm is not restored in login zsh after the system profile" + +# Re-sourcing fnm.zsh must reuse the existing multishell instead of stacking PATH. +HOME="$fnm_login_home" PATH=/usr/bin:/bin zsh -c ' + source "$HOME/.config/zsh/fnm.zsh" + first=$FNM_MULTISHELL_PATH + source "$HOME/.config/zsh/fnm.zsh" + [[ $FNM_MULTISHELL_PATH == $first ]] || exit 1 + n=0 + for p in $path; do + [[ $p == "$first/bin" ]] && n=$((n + 1)) + done + (( n == 1 )) +' || fail "fnm.zsh stacked a second multishell on PATH when re-sourced" + # patch.sh path form installs into ~/.config/zsh/ path_patch_home="$TEMP_DIR/path-patch-home" path_bin="$TEMP_DIR/path-bin" diff --git a/zsh/fnm.zsh b/zsh/fnm.zsh index 4604bb0..0222ff6 100644 --- a/zsh/fnm.zsh +++ b/zsh/fnm.zsh @@ -1,10 +1,30 @@ -# fnm (Fast Node Manager) — always-on (sourced from ~/.zshenv) +# fnm (Fast Node Manager) — always-on via ~/.zshenv FNM_PATH="${XDG_DATA_HOME:-$HOME/.local/share}/fnm" -[[ -d "$FNM_PATH" ]] && export PATH="$FNM_PATH:$PATH" +[[ -d "$FNM_PATH" ]] && path=("$FNM_PATH" ${path:#$FNM_PATH}) + if command -v fnm &> /dev/null; then - if [[ -o interactive ]]; then - eval "$(fnm env --use-on-cd --shell zsh)" - else - eval "$(fnm env --shell zsh)" + _easyzsh_fnm_state="${XDG_STATE_HOME:-$HOME/.local/state}/fnm_multishells" + _easyzsh_fnm_ready=0 + if [[ -n "$FNM_MULTISHELL_PATH" && -d "$FNM_MULTISHELL_PATH/bin" ]] \ + && [[ "$FNM_MULTISHELL_PATH" == "$_easyzsh_fnm_state/"* || "$FNM_MULTISHELL_PATH" == "$FNM_PATH/"* ]]; then + if [[ ! -o interactive ]] || (( $+functions[_fnm_autoload_hook] )); then + _easyzsh_fnm_ready=1 + fi fi + if (( _easyzsh_fnm_ready )); then + path=("$FNM_MULTISHELL_PATH/bin" ${path:#$FNM_MULTISHELL_PATH/bin}) + else + _easyzsh_fnm_prev="$FNM_MULTISHELL_PATH" + if [[ -o interactive ]]; then + eval "$(fnm env --use-on-cd --shell zsh)" + else + eval "$(fnm env --shell zsh)" + fi + if [[ -n "$_easyzsh_fnm_prev" ]]; then + path=(${path:#$_easyzsh_fnm_prev/bin}) + fi + unset _easyzsh_fnm_prev + fi + unset _easyzsh_fnm_ready _easyzsh_fnm_state fi +export PATH