diff --git a/lib/watch.sh b/lib/watch.sh index 4ac88ef..ce5f8b3 100644 --- a/lib/watch.sh +++ b/lib/watch.sh @@ -219,6 +219,23 @@ with open(sys.argv[1], 'w') as f: _detect_install_mode() { local devflow_path devflow_path="$(command -v devflow 2>/dev/null || echo "")" + + # PATH-independent fallback. cron and launchd fire with a minimal PATH that usually + # lacks ~/.local/bin, so `command -v devflow` is empty and mode wrongly resolves to + # "none" - and _auto_reinstall_check then silently no-ops (the exact reason the launchd + # agent could not auto-update). Resolve the installed launcher directly instead. + # Inspect the BINDIR launcher (not the repo source): a copy install has a real file + # there, a `make link` install has a symlink there - that distinction IS the mode. + if [[ -z "$devflow_path" ]]; then + local cand + for cand in \ + "$([[ "${DEVFLOW_ROOT:-}" == */share/devflow ]] && echo "${DEVFLOW_ROOT%/share/devflow}/bin/devflow")" \ + "${HOME}/.local/bin/devflow" \ + "/usr/local/bin/devflow" \ + "/opt/homebrew/bin/devflow"; do + [[ -n "$cand" && -e "$cand" ]] && { devflow_path="$cand"; break; } + done + fi [[ -z "$devflow_path" ]] && echo "none" && return # Resolve the real path for Homebrew detection @@ -550,9 +567,10 @@ _watch_launchd_plist() { echo "${HOME}/Library/LaunchAgents/$(_watch_launchd_lab # _watch_install_launchd — write + (re)load the LaunchAgent. _watch_install_launchd() { local project_dir="$1" devflow_bin="$2" - local label plist uid cal m + local label plist uid cal m bindir label="$(_watch_launchd_label "$project_dir")" plist="$(_watch_launchd_plist "$project_dir")" + bindir="$(dirname "$devflow_bin")" # so the agent's minimal PATH still finds devflow mkdir -p "${HOME}/Library/LaunchAgents" "${HOME}/.devflow" cal="" @@ -577,6 +595,10 @@ _watch_install_launchd() { StartCalendarInterval ${cal} + EnvironmentVariables + + PATH${bindir}:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + RunAtLoad WorkingDirectory${project_dir} StandardOutPath${HOME}/.devflow/watch.log diff --git a/tests/unit/watch.bats b/tests/unit/watch.bats index a27526a..cce320b 100644 --- a/tests/unit/watch.bats +++ b/tests/unit/watch.bats @@ -233,9 +233,12 @@ EOF assert_output "brew" } -@test "detect_install_mode returns none when devflow not found" { +@test "detect_install_mode returns none when devflow not found anywhere" { + # none now requires BOTH: not on PATH AND no launcher at any known location + # (the pathless fallback resolves ~/.local/bin etc.), so use a clean empty HOME. + export HOME="${BATS_TEST_TMPDIR}/empty-home"; mkdir -p "$HOME" rm -f "${MOCK_DIR}/devflow" - PATH="${MOCK_DIR}" run _detect_install_mode + DEVFLOW_ROOT="" PATH="${MOCK_DIR}" run _detect_install_mode assert_success assert_output "none" } @@ -363,3 +366,41 @@ EOF assert_output --partial "LaunchAgent removed" [ ! -f "$plist" ] || fail "plist not removed" } + +# ── _detect_install_mode under a minimal PATH (launchd/cron) ──── + +@test "detect_install_mode resolves WITHOUT PATH: copy install (regular file at BINDIR)" { + export HOME="${BATS_TEST_TMPDIR}/dh-copy"; mkdir -p "$HOME/.local/bin" + printf '#!/bin/bash\n' > "$HOME/.local/bin/devflow"; chmod +x "$HOME/.local/bin/devflow" + PATH="/usr/bin:/bin" run _detect_install_mode + assert_success + assert_output "install" +} + +@test "detect_install_mode resolves WITHOUT PATH: link (symlink at BINDIR)" { + export HOME="${BATS_TEST_TMPDIR}/dh-link"; mkdir -p "$HOME/.local/bin" + printf '#!/bin/bash\n' > "$HOME/real-df"; chmod +x "$HOME/real-df" + ln -sf "$HOME/real-df" "$HOME/.local/bin/devflow" + PATH="/usr/bin:/bin" run _detect_install_mode + assert_success + assert_output "link" +} + +@test "detect_install_mode resolves WITHOUT PATH via DEVFLOW_ROOT for a custom prefix" { + export HOME="${BATS_TEST_TMPDIR}/dh-none"; mkdir -p "$HOME" # no ~/.local/bin/devflow + local pfx="${BATS_TEST_TMPDIR}/opt/df"; mkdir -p "$pfx/bin" "$pfx/share/devflow" + printf '#!/bin/bash\n' > "$pfx/bin/devflow"; chmod +x "$pfx/bin/devflow" + DEVFLOW_ROOT="$pfx/share/devflow" PATH="/usr/bin:/bin" run _detect_install_mode + assert_success + assert_output "install" +} + +@test "launchd plist injects PATH so the agent's minimal env can find devflow" { + export HOME="${BATS_TEST_TMPDIR}/lah-path"; mkdir -p "$HOME" + printf '#!/bin/bash\nexit 0\n' > "${MOCK_DIR}/launchctl"; chmod +x "${MOCK_DIR}/launchctl" + _watch_install_launchd "/tmp/pp" "/Users/x/.local/bin/devflow" + local plist; plist="$(_watch_launchd_plist /tmp/pp)" + run cat "$plist" + assert_output --partial "EnvironmentVariables" + assert_output --partial "PATH/Users/x/.local/bin:" +}