From 0c8e93622d32a3c32bfc25a3c0cfc07be7d09e74 Mon Sep 17 00:00:00 2001 From: Sal <59910950+ss-o@users.noreply.github.com> Date: Sun, 20 Sep 2026 08:01:48 +0100 Subject: [PATCH] fix(installer): carry explicit paths into loader --- public/checksum.txt | 2 +- public/sh/install.sh | 53 ++++++++++++++- tests/installers.sh | 158 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 3 deletions(-) diff --git a/public/checksum.txt b/public/checksum.txt index 3e47e88..0647175 100644 --- a/public/checksum.txt +++ b/public/checksum.txt @@ -1,4 +1,4 @@ 6de66efba021ebcf462e7672577d9f6876f13db55691f04e7009ff7e78d658b8 public/sh/install_zpmod.sh -2308d14ff74b9bb5928c4d0abfb9ae0d4268cd253fb871cd3d23d4bbbede6b10 public/sh/install.sh +a85ba6f5f6ebfa62412b49be978213a4a625307649de64f69b1bf335fdcf49e9 public/sh/install.sh 08cc893ceb982fc99d17db1966c6c30790cc571e16e4f5392352d995f5252952 public/sh/sync-init.sh c979e39748d1d86ace17a61ff2b1bf6e1224a43291c25bf7fad985d1e9e11af1 public/zsh/init.zsh diff --git a/public/sh/install.sh b/public/sh/install.sh index a7a0f36..88d75af 100755 --- a/public/sh/install.sh +++ b/public/sh/install.sh @@ -4,8 +4,12 @@ set -eu -WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/zi-install.XXXXXX")" || exit 1 -trap 'rm -rf "${WORKDIR:?}"' EXIT INT TERM +# Preserve whether the caller selected either checkout component. The loader +# stays dynamically resolved unless an explicit value must survive startup. +ZI_HOME_EXPLICIT=0 +[ -z "${ZI_HOME-}" ] || ZI_HOME_EXPLICIT=1 +ZI_BIN_DIR_NAME_EXPLICIT=0 +[ -z "${ZI_BIN_DIR_NAME-}" ] || ZI_BIN_DIR_NAME_EXPLICIT=1 ZOPT="" AOPT="" BOPT="main" @@ -47,6 +51,25 @@ case "${BOPT}" in ;; esac +case "${ZDOTDIR-}" in +"" | /*) ;; +*) + printf '%s\n' "-- ERROR -- ZDOTDIR must be an absolute path when set: ${ZDOTDIR}" >&2 + exit 1 + ;; +esac + +case "${ZI_HOME-}" in +"" | /*) ;; +*) + printf '%s\n' "-- ERROR -- ZI_HOME must be an absolute path when set: ${ZI_HOME}" >&2 + exit 1 + ;; +esac + +WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/zi-install.XXXXXX")" || exit 1 +trap 'rm -rf "${WORKDIR:?}"' EXIT INT TERM + SCRIPT_DIR="" LOCAL_INIT_ZSH="" LOCAL_INSTALL_ZPMOD="" @@ -109,6 +132,14 @@ is_absolute_path() { esac } +zsh_single_quote() { + # Single-quoted Zsh text is inert; represent an embedded quote by ending the + # quote, escaping one literal quote, and reopening it. + printf "'" + printf '%s' "$1" | command sed "s/'/'\\\\''/g" + printf "'" +} + zi_home_has_installation() { [ -f "$1/bin/zi.zsh" ] || [ -d "$1/plugins" ] || @@ -173,6 +204,16 @@ if [ -z "${ZI_BIN_DIR_NAME-}" ]; then ZI_BIN_DIR_NAME="bin" fi +ZI_LOADER_PATHS_EXPLICIT=0 +ZI_LOADER_HOME_TEXT="" +ZI_LOADER_BIN_TEXT="" +if [ "${AOPT}" = loader ] && + { [ "${ZI_HOME_EXPLICIT}" -eq 1 ] || [ "${ZI_BIN_DIR_NAME_EXPLICIT}" -eq 1 ]; }; then + ZI_LOADER_PATHS_EXPLICIT=1 + ZI_LOADER_HOME_TEXT="$(zsh_single_quote "${ZI_HOME}")" + ZI_LOADER_BIN_TEXT="$(zsh_single_quote "${ZI_HOME}/${ZI_BIN_DIR_NAME}")" +fi + if ! test -d "${ZI_HOME}"; then command mkdir -p "${ZI_HOME}" command chmod 700 "${ZI_HOME}" @@ -295,6 +336,14 @@ if [[ -n \${XDG_CONFIG_HOME:-} && \${XDG_CONFIG_HOME} == /* ]]; then else ZI_LOADER_CONFIG_HOME="\${HOME}/.config/zi" fi +EOF + if [ "${ZI_LOADER_PATHS_EXPLICIT}" -eq 1 ]; then + command printf '%s\n' \ + 'typeset -gA ZI' \ + "ZI[HOME_DIR]=${ZI_LOADER_HOME_TEXT}" \ + "ZI[BIN_DIR]=${ZI_LOADER_BIN_TEXT}" >>"${THE_ZDOTDIR}/.zshrc" + fi + command cat <<-EOF >>"${THE_ZDOTDIR}/.zshrc" if [[ -r "\${ZI_LOADER_CONFIG_HOME}/init.zsh" ]]; then source "\${ZI_LOADER_CONFIG_HOME}/init.zsh" && zzinit fi diff --git a/tests/installers.sh b/tests/installers.sh index 5b4c5b3..4d1a582 100755 --- a/tests/installers.sh +++ b/tests/installers.sh @@ -908,6 +908,160 @@ test_sync_init() { pass "sync-init fixtures" } +test_loader_default_paths_remain_dynamic() { + home="${TMP_ROOT}/loader-dynamic-home" + config="${TMP_ROOT}/loader-dynamic-config" + data="${TMP_ROOT}/loader-dynamic-data" + command mkdir -p "${home}" "${config}" "${data}" + + HOME="${home}" \ + ZDOTDIR="${home}" \ + XDG_CONFIG_HOME="${config}" \ + XDG_DATA_HOME="${data}" \ + ZI_SRC_TEST_ROOT="${ROOT}" \ + PATH="${FAKE_BIN}:${PATH}" \ + sh "${ROOT}/public/sh/install.sh" -a loader >/dev/null + + if grep -F 'ZI[HOME_DIR]=' "${home}/.zshrc" >/dev/null 2>&1; then + fail "default loader install pinned a dynamically resolved home" + fi + pass "default loader paths remain dynamically resolved" +} + +test_loader_carries_explicit_paths() { + home="${TMP_ROOT}/loader-explicit-home" + config="${TMP_ROOT}/loader-explicit-config" + data="${TMP_ROOT}/loader-explicit-data" + explicit="${TMP_ROOT}/loader explicit's root" + bin_name="custom \$(touch pwned) ' bin" + runtime_work="${TMP_ROOT}/loader-runtime-work" + values_log="${TMP_ROOT}/loader-explicit-values" + command mkdir -p "${home}" "${config}" "${data}" "${runtime_work}" + + HOME="${home}" \ + ZDOTDIR="${home}" \ + XDG_CONFIG_HOME="${config}" \ + XDG_DATA_HOME="${data}" \ + ZI_HOME="${explicit}" \ + ZI_BIN_DIR_NAME="${bin_name}" \ + ZI_SRC_TEST_ROOT="${ROOT}" \ + PATH="${FAKE_BIN}:${PATH}" \ + sh "${ROOT}/public/sh/install.sh" -a loader >/dev/null + + [ -f "${explicit}/${bin_name}/zi.zsh" ] || fail "loader install did not use the explicit checkout path" + contains "${home}/.zshrc" 'typeset -gA ZI' + + ( + cd "${runtime_work}" || exit 1 + HOME="${home}" \ + XDG_CONFIG_HOME="${config}" \ + XDG_DATA_HOME="${data}" \ + zsh -f -c ' + source "$1" + print -r -- "home:${ZI[HOME_DIR]}" + print -r -- "bin:${ZI[BIN_DIR]}" + print -r -- "layout:${ZI[HOME_LAYOUT]}" + ' zsh "${home}/.zshrc" + ) >"${values_log}" + + contains "${values_log}" "home:${explicit}" + contains "${values_log}" "bin:${explicit}/${bin_name}" + contains "${values_log}" 'layout:explicit' + [ ! -e "${runtime_work}/pwned" ] || fail "explicit loader path executed generated Zsh" + [ ! -e "${data}/zi/bin/zi.zsh" ] || fail "loader startup cloned a second checkout" + pass "loader carries explicit home and bin paths into startup safely" +} + +test_loader_carries_explicit_bin_name() { + home="${TMP_ROOT}/loader-bin-home" + config="${TMP_ROOT}/loader-bin-config" + data="${TMP_ROOT}/loader-bin-data" + bin_name="custom bin" + values_log="${TMP_ROOT}/loader-bin-values" + command mkdir -p "${home}" "${config}" "${data}" + + HOME="${home}" \ + ZDOTDIR="${home}" \ + XDG_CONFIG_HOME="${config}" \ + XDG_DATA_HOME="${data}" \ + ZI_BIN_DIR_NAME="${bin_name}" \ + ZI_SRC_TEST_ROOT="${ROOT}" \ + PATH="${FAKE_BIN}:${PATH}" \ + sh "${ROOT}/public/sh/install.sh" -a loader >/dev/null + + HOME="${home}" \ + XDG_CONFIG_HOME="${config}" \ + XDG_DATA_HOME="${data}" \ + zsh -f -c ' + source "$1" + print -r -- "home:${ZI[HOME_DIR]}" + print -r -- "bin:${ZI[BIN_DIR]}" + ' zsh "${home}/.zshrc" >"${values_log}" + + contains "${values_log}" "home:${data}/zi" + contains "${values_log}" "bin:${data}/zi/${bin_name}" + [ ! -e "${data}/zi/bin/zi.zsh" ] || fail "custom bin startup cloned into the default bin" + pass "loader carries an explicit bin name with the resolved home" +} + +test_relative_installer_paths_are_rejected() { + zdot_home="${TMP_ROOT}/relative-zdot-home" + zdot_config="${TMP_ROOT}/relative-zdot-config" + zdot_data="${TMP_ROOT}/relative-zdot-data" + zdot_work="${TMP_ROOT}/relative-zdot-work" + zdot_err="${TMP_ROOT}/relative-zdot-err" + command mkdir -p "${zdot_home}" "${zdot_work}" + + set +e + ( + cd "${zdot_work}" || exit 1 + HOME="${zdot_home}" \ + ZDOTDIR="relative-zdotdir" \ + XDG_CONFIG_HOME="${zdot_config}" \ + XDG_DATA_HOME="${zdot_data}" \ + ZI_SRC_TEST_ROOT="${ROOT}" \ + PATH="${FAKE_BIN}:${PATH}" \ + sh "${ROOT}/public/sh/install.sh" -a loader >/dev/null 2>"${zdot_err}" + ) + exit_code="$?" + set -e + + [ "${exit_code}" -ne 0 ] || fail "install.sh accepted a relative ZDOTDIR" + contains "${zdot_err}" 'ZDOTDIR must be an absolute path when set: relative-zdotdir' + [ ! -e "${zdot_config}" ] || fail "relative ZDOTDIR rejection wrote loader configuration" + [ ! -e "${zdot_data}" ] || fail "relative ZDOTDIR rejection created a checkout" + [ ! -e "${zdot_work}/relative-zdotdir" ] || fail "relative ZDOTDIR rejection created the relative path" + + zi_home="${TMP_ROOT}/relative-zi-home" + zi_config="${TMP_ROOT}/relative-zi-config" + zi_data="${TMP_ROOT}/relative-zi-data" + zi_work="${TMP_ROOT}/relative-zi-work" + zi_err="${TMP_ROOT}/relative-zi-err" + command mkdir -p "${zi_home}" "${zi_work}" + + set +e + ( + cd "${zi_work}" || exit 1 + HOME="${zi_home}" \ + ZDOTDIR="${zi_home}" \ + XDG_CONFIG_HOME="${zi_config}" \ + XDG_DATA_HOME="${zi_data}" \ + ZI_HOME="relative-zi-root" \ + ZI_SRC_TEST_ROOT="${ROOT}" \ + PATH="${FAKE_BIN}:${PATH}" \ + sh "${ROOT}/public/sh/install.sh" -a loader >/dev/null 2>"${zi_err}" + ) + exit_code="$?" + set -e + + [ "${exit_code}" -ne 0 ] || fail "install.sh accepted a relative ZI_HOME" + contains "${zi_err}" 'ZI_HOME must be an absolute path when set: relative-zi-root' + [ ! -e "${zi_config}" ] || fail "relative ZI_HOME rejection wrote loader configuration" + [ ! -e "${zi_data}" ] || fail "relative ZI_HOME rejection created a default checkout" + [ ! -e "${zi_work}/relative-zi-root" ] || fail "relative ZI_HOME rejection created the relative checkout" + pass "relative ZDOTDIR and ZI_HOME are rejected before persistent mutation" +} + check_syntax check_checksums test_init_defaults_are_single_arguments @@ -920,6 +1074,10 @@ test_init_uses_private_tempdir test_init_path_resolution write_fake_tools test_loader_install +test_loader_default_paths_remain_dynamic +test_loader_carries_explicit_paths +test_loader_carries_explicit_bin_name +test_relative_installer_paths_are_rejected test_xdg_data_home_install test_legacy_home_install test_relative_xdg_fallback_install