From 2e9b9a34fdcf1d35ca90c7046e1c5f111631e0f9 Mon Sep 17 00:00:00 2001 From: Tom Riglar Date: Fri, 19 Jun 2026 12:01:28 +0100 Subject: [PATCH] installers: detect shadowing dcd and auto-persist PATH on Unix A prior `npm install -g @devicecloud.dev/dcd` is never removed by the binary installers, so the two coexist and PATH order decides the winner. On Windows the appended install dir loses to npm's earlier bin entry, silently shadowing the freshly installed binary. - install.sh: scan PATH for a dcd outside the install dir and warn to `npm uninstall -g`; auto-append the PATH line to the shell rc (zsh/bash/profile), de-duped on re-install, with the manual hint as a fallback. - install.ps1: warn via Get-Command when a different dcd is already on PATH (the registry change isn't visible in-session, so any hit is a pre-existing install). Co-Authored-By: Claude Opus 4.8 --- install.ps1 | 18 +++++++++++ install.sh | 86 +++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 91 insertions(+), 13 deletions(-) diff --git a/install.ps1 b/install.ps1 index b6db89a..f96cbb4 100644 --- a/install.ps1 +++ b/install.ps1 @@ -91,3 +91,21 @@ if (-not $alreadyOnPath) { Write-Host "Installed: $(& "$InstallDir\dcd.exe" --version)" Write-Host ' Try: dcd --help' } + +# --- warn about a conflicting (shadowing) install --- +# A leftover `npm install -g @devicecloud.dev/dcd` resolves earlier on PATH than +# the appended install dir, so it would keep shadowing this binary. Get-Command +# reads the current session PATH (which doesn't include the registry change we +# just made), so any hit here is a different, pre-existing dcd. +$target = Join-Path $InstallDir 'dcd.exe' +$existing = Get-Command dcd -All -ErrorAction SilentlyContinue | + Where-Object { $_.Source -and ($_.Source -ine $target) } | + Select-Object -First 1 +if ($existing) { + Write-Host '' + Write-Host '! Another dcd is already on your PATH:' + Write-Host " $($existing.Source)" + Write-Host " This is usually a previous 'npm install -g @devicecloud.dev/dcd', which" + Write-Host ' can shadow this binary depending on PATH order. Remove it with:' + Write-Host ' npm uninstall -g @devicecloud.dev/dcd' +} diff --git a/install.sh b/install.sh index d213ebb..81b2b96 100755 --- a/install.sh +++ b/install.sh @@ -23,6 +23,42 @@ info() { printf '%s\n' "$1" } +# Find a dcd on PATH other than the one we just installed — usually a leftover +# `npm install -g @devicecloud.dev/dcd` that can shadow this binary. Runs in a +# subshell so the temporary IFS change never leaks back to the caller. +find_conflicting_dcd() { + ( + IFS=: + for dir in $PATH; do + [ -n "$dir" ] || continue + if [ "$dir" != "$INSTALL_DIR" ] && [ -x "$dir/dcd" ]; then + printf '%s\n' "$dir/dcd" + exit 0 + fi + done + exit 1 + ) +} + +# Pick the shell rc file to persist PATH into, based on the login shell. +rc_file() { + case "${SHELL:-}" in + *zsh) + printf '%s\n' "${ZDOTDIR:-$HOME}/.zshrc" + ;; + *bash) + if [ -f "$HOME/.bashrc" ]; then + printf '%s\n' "$HOME/.bashrc" + else + printf '%s\n' "$HOME/.bash_profile" + fi + ;; + *) + printf '%s\n' "$HOME/.profile" + ;; + esac +} + main() { DOWNLOAD_BASE="${DCD_DOWNLOAD_BASE:-https://get.devicecloud.dev}" INSTALL_DIR="${DCD_INSTALL_DIR:-$HOME/.dcd/bin}" @@ -95,24 +131,48 @@ main() { mv "$tmp" "$INSTALL_DIR/dcd" trap - EXIT # tmp has been moved; nothing to clean up - # --- PATH hint --- + # --- PATH setup --- + path_line="export PATH=\"$INSTALL_DIR:\$PATH\"" case ":$PATH:" in - *":$INSTALL_DIR:"*) + *":$INSTALL_DIR:"*) on_path=1 ;; + *) on_path=0 ;; + esac + + info "" + info "✓ Installed dcd $version to $INSTALL_DIR/dcd" + + if [ "$on_path" -eq 0 ]; then + rc=$(rc_file) + if [ -f "$rc" ] && grep -Fq "$INSTALL_DIR" "$rc" 2>/dev/null; then + # rc already references the dir (e.g. a re-install); don't duplicate it. info "" - info "✓ Installed: $($INSTALL_DIR/dcd --version 2>/dev/null || echo "$version")" - info " Try: dcd --help" - ;; - *) + info " $INSTALL_DIR is already configured in $rc." + info " Restart your shell, or run: $path_line" + elif printf '\n# dcd\n%s\n' "$path_line" >> "$rc" 2>/dev/null; then info "" - info "✓ Installed dcd $version to $INSTALL_DIR/dcd" + info " Added $INSTALL_DIR to your PATH in $rc." + info " Restart your shell, or run: $path_line" + else info "" info " $INSTALL_DIR is not on your PATH. Add this to your shell rc:" - info " export PATH=\"$INSTALL_DIR:\$PATH\"" - info "" - info " Then restart your shell, or run:" - info " export PATH=\"$INSTALL_DIR:\$PATH\"" - ;; - esac + info " $path_line" + fi + fi + + # --- warn about a conflicting (shadowing) install --- + if conflict=$(find_conflicting_dcd); then + info "" + info "! Another dcd is already on your PATH:" + info " $conflict" + info " This is usually a previous 'npm install -g @devicecloud.dev/dcd', which" + info " can shadow this binary depending on PATH order. Remove it with:" + info " npm uninstall -g @devicecloud.dev/dcd" + fi + + if [ "$on_path" -eq 1 ]; then + info "" + info " Try: dcd --help" + fi } main "$@"