Skip to content

Commit 63980ce

Browse files
committed
feat: install to user-writable PATH directory without sudo
Scan PATH for user-writable directories before falling back to creating ~/.local/bin. Detect shell profile for PATH update instructions when needed. No sudo required for installation.
1 parent 313ff93 commit 63980ce

1 file changed

Lines changed: 125 additions & 19 deletions

File tree

install.sh

Lines changed: 125 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,124 @@ check_version_exists() {
9898
fi
9999
}
100100

101+
# Check if a directory is in the current PATH
102+
is_in_path() {
103+
case ":$PATH:" in
104+
*":$1:"*) return 0 ;;
105+
*) return 1 ;;
106+
esac
107+
}
108+
109+
# Find the best user-writable install directory
110+
# Priority: user-writable directories already in PATH, then well-known user dirs
111+
find_install_dir() {
112+
# Well-known user-level bin directories, in order of preference
113+
local candidates="
114+
$HOME/.local/bin
115+
$HOME/bin
116+
$HOME/.cargo/bin
117+
$HOME/go/bin
118+
$HOME/.local/share/bin
119+
"
120+
121+
# 1. Check if any well-known user directory is already in PATH and writable
122+
for dir in $candidates; do
123+
if is_in_path "$dir" && [ -d "$dir" ] && [ -w "$dir" ]; then
124+
debug "Found user-writable directory in PATH: $dir"
125+
echo "$dir"
126+
return
127+
fi
128+
done
129+
130+
# 2. Scan PATH for any other user-writable directory (under $HOME)
131+
local IFS=':'
132+
for dir in $PATH; do
133+
case "$dir" in
134+
"$HOME"*)
135+
if [ -d "$dir" ] && [ -w "$dir" ]; then
136+
debug "Found user-writable directory in PATH: $dir"
137+
echo "$dir"
138+
return
139+
fi
140+
;;
141+
esac
142+
done
143+
unset IFS
144+
145+
# 3. Check /usr/local/bin if writable (common on macOS)
146+
if [ -w "/usr/local/bin" ]; then
147+
debug "Using writable /usr/local/bin"
148+
echo "/usr/local/bin"
149+
return
150+
fi
151+
152+
# 4. Create ~/.local/bin (XDG standard, most shells source it)
153+
local fallback="$HOME/.local/bin"
154+
mkdir -p "$fallback"
155+
156+
# Check if it's already in PATH after creation
157+
if is_in_path "$fallback"; then
158+
debug "Created $fallback (already in PATH)"
159+
else
160+
debug "Created $fallback (not yet in PATH)"
161+
fi
162+
163+
echo "$fallback"
164+
}
165+
166+
# Detect the user's shell profile file
167+
detect_shell_profile() {
168+
local shell_name
169+
shell_name=$(basename "${SHELL:-/bin/sh}")
170+
171+
case "$shell_name" in
172+
zsh)
173+
if [ -f "$HOME/.zshrc" ]; then
174+
echo "$HOME/.zshrc"
175+
else
176+
echo "$HOME/.zprofile"
177+
fi
178+
;;
179+
bash)
180+
if [ -f "$HOME/.bashrc" ]; then
181+
echo "$HOME/.bashrc"
182+
elif [ -f "$HOME/.bash_profile" ]; then
183+
echo "$HOME/.bash_profile"
184+
else
185+
echo "$HOME/.profile"
186+
fi
187+
;;
188+
fish)
189+
echo "$HOME/.config/fish/config.fish"
190+
;;
191+
*)
192+
echo "$HOME/.profile"
193+
;;
194+
esac
195+
}
196+
197+
# Suggest how to add a directory to PATH
198+
suggest_path_update() {
199+
local dir="$1"
200+
local profile
201+
profile=$(detect_shell_profile)
202+
local shell_name
203+
shell_name=$(basename "${SHELL:-/bin/sh}")
204+
205+
warn "Add $dir to your PATH by running:"
206+
if [ "$shell_name" = "fish" ]; then
207+
warn " fish_add_path $dir"
208+
else
209+
warn " echo 'export PATH=\"$dir:\$PATH\"' >> $profile"
210+
fi
211+
warn "Then restart your shell or run:"
212+
if [ "$shell_name" = "fish" ]; then
213+
warn " source $profile"
214+
else
215+
warn " source $profile"
216+
fi
217+
}
218+
101219
# Download and install
102220
install_binary() {
103221
local tmp_dir="/tmp/ee-install-$$"
@@ -127,20 +245,9 @@ install_binary() {
127245
# Make binary executable
128246
chmod +x "$tmp_dir/$BINARY_NAME$BINARY_SUFFIX"
129247

130-
# Determine install location
248+
# Determine install location — prefer user-writable directories already in PATH
131249
local install_dir
132-
if [ -w "/usr/local/bin" ]; then
133-
install_dir="/usr/local/bin"
134-
elif [ -d "$HOME/.local/bin" ]; then
135-
install_dir="$HOME/.local/bin"
136-
mkdir -p "$install_dir"
137-
elif [ -d "$HOME/bin" ]; then
138-
install_dir="$HOME/bin"
139-
else
140-
install_dir="$HOME/.local/bin"
141-
mkdir -p "$install_dir"
142-
warn "Created directory $install_dir - make sure it's in your PATH"
143-
fi
250+
install_dir=$(find_install_dir)
144251

145252
# Install binary
146253
log "Installing to $install_dir/$BINARY_NAME$BINARY_SUFFIX..."
@@ -153,16 +260,15 @@ install_binary() {
153260

154261
# Verify installation
155262
if "$install_dir/$BINARY_NAME$BINARY_SUFFIX" --version >/dev/null 2>&1; then
156-
log "ee $VERSION installed successfully!"
157-
log "📍 Location: $install_dir/$BINARY_NAME$BINARY_SUFFIX"
263+
log "ee $VERSION installed successfully!"
264+
log "Location: $install_dir/$BINARY_NAME$BINARY_SUFFIX"
158265

159266
# Check if binary is in PATH
160267
if command -v "$BINARY_NAME" >/dev/null 2>&1; then
161-
log "🎉 You can now use 'ee' from anywhere!"
268+
log "You can now use 'ee' from anywhere!"
162269
else
163-
warn "⚠️ $install_dir is not in your PATH"
164-
warn "Add this to your shell profile:"
165-
warn "export PATH=\"$install_dir:\$PATH\""
270+
warn "$install_dir is not in your PATH"
271+
suggest_path_update "$install_dir"
166272
fi
167273

168274
log ""

0 commit comments

Comments
 (0)