From 2260d9e4b1caa827dd453a18079c3dda48b2af91 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:59:08 +0000 Subject: [PATCH 1/2] Initial plan From 77051b81e7e50bfe829258fbea5e556389b76fc4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 18:16:21 +0000 Subject: [PATCH 2/2] Fix bugs, improve robustness and macOS compatibility across Dotfiles - Fix ((step++)) -> ((step += 1)) in all 12 install functions (was failing in set -e contexts when step=0) - Add LM Studio CLI PATH ($HOME/.cache/lm-studio/bin) to zsh_postload.zsh - Fix M9 bats test: check zsh_aliases.zsh instead of zsh_postload.zsh for git_clean_branches - Fix bootstrap.sh: add DEST preset check and TTY detection to support non-interactive use - Fix bootstrap.bats I1: use truly empty PATH instead of /usr/bin:/bin (on Ubuntu, /bin == /usr/bin) - Fix xargs -r macOS incompatibility in zsh_aliases.zsh (replace with explicit empty-check guard) - Fix git plugin clone idempotency in installZsh (check dir existence before cloning) - Fix ls glob in doctor.sh: use find instead to avoid errors when no logs exist Co-authored-by: noofreuuuh <1218742+noofreuuuh@users.noreply.github.com> --- bootstrap.sh | 42 +++++++++++++++++++++---------------- config/zsh/zsh_aliases.zsh | 9 +++++--- config/zsh/zsh_postload.zsh | 3 +++ lib/doctor.sh | 2 +- lib/install_core.sh | 21 ++++++++++++------- lib/install_software.sh | 16 +++++++------- tests/bootstrap.bats | 15 +++++++++---- tests/zshrc_config.bats | 4 ++-- 8 files changed, 68 insertions(+), 44 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index 0474d43..94032a9 100644 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -39,25 +39,31 @@ if ! command -v git &>/dev/null; then fi # Resolve installation directory -echo -e " ${BOLD}Installation directory${RESET}" -echo -e " ${DIM}Default: ${DEFAULT_DEST}${RESET}" -echo "" -read -p " Install here? [Y/n] " _confirm -if [[ "$_confirm" =~ ^[Nn]$ ]]; then - while true; do - read -p " Enter path: " _custom - # Expand ~ manually since read doesn't expand it - _custom="${_custom/#\~/$HOME}" - if [[ -n "$_custom" ]]; then - DEST="$_custom" - break - fi - echo -e " ${RED}[ERROR]${RESET} Path cannot be empty." - done -else - DEST="$DEFAULT_DEST" +if [[ -z "${DEST:-}" ]]; then + echo -e " ${BOLD}Installation directory${RESET}" + echo -e " ${DIM}Default: ${DEFAULT_DEST}${RESET}" + echo "" + if [[ -t 0 ]]; then + read -r -p " Install here? [Y/n] " _confirm + else + _confirm="" + fi + if [[ "$_confirm" =~ ^[Nn]$ ]]; then + while true; do + read -r -p " Enter path: " _custom + # Expand ~ manually since read doesn't expand it + _custom="${_custom/#\~/$HOME}" + if [[ -n "$_custom" ]]; then + DEST="$_custom" + break + fi + echo -e " ${RED}[ERROR]${RESET} Path cannot be empty." + done + else + DEST="$DEFAULT_DEST" + fi + echo "" fi -echo "" # Clone or update if [[ -d "$DEST/.git" ]]; then diff --git a/config/zsh/zsh_aliases.zsh b/config/zsh/zsh_aliases.zsh index 67d8c96..2c04a69 100644 --- a/config/zsh/zsh_aliases.zsh +++ b/config/zsh/zsh_aliases.zsh @@ -47,9 +47,12 @@ git_clean_branches() { git fetch "$r" --tags --force --prune --prune-tags done - git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads \ - | awk '$2=="[gone]"{print $1}' \ - | xargs -r git branch -D + local gone_branches + gone_branches=$(git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads \ + | awk '$2=="[gone]"{print $1}') + if [[ -n "$gone_branches" ]]; then + echo "$gone_branches" | xargs git branch -D + fi } alias git-clean-branches='git_clean_branches' diff --git a/config/zsh/zsh_postload.zsh b/config/zsh/zsh_postload.zsh index ab2e927..02a5c09 100644 --- a/config/zsh/zsh_postload.zsh +++ b/config/zsh/zsh_postload.zsh @@ -8,6 +8,9 @@ # PATH: prioritize Homebrew ARM, fallback to Intel if needed export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:$PATH" +# LM Studio CLI +export PATH="$HOME/.cache/lm-studio/bin:$PATH" + # ---- Homebrew ARM (default) ---- if [[ "$(uname -m)" == "arm64" ]] && [[ -x /opt/homebrew/bin/brew ]]; then eval "$(/opt/homebrew/bin/brew shellenv)" diff --git a/lib/doctor.sh b/lib/doctor.sh index 6d8d061..03d2221 100644 --- a/lib/doctor.sh +++ b/lib/doctor.sh @@ -81,7 +81,7 @@ run_doctor() { echo "" echo -e " ${BOLD}Logs${RESET}" local log_count - log_count=$(ls "$LOG_DIR"/*.log 2>/dev/null | wc -l | tr -d ' ') + log_count=$(find "$LOG_DIR" -maxdepth 1 -name "*.log" 2>/dev/null | wc -l | tr -d ' ') echo -e " ${DIM}${log_count} install log(s) in ${LOG_DIR}${RESET}" echo "" diff --git a/lib/install_core.sh b/lib/install_core.sh index a1ce70b..59168d5 100644 --- a/lib/install_core.sh +++ b/lib/install_core.sh @@ -7,7 +7,7 @@ # ============================================================================ installBrew() { - ((step++)) + ((step += 1)) log_step "Install Brew" if [[ "$DRY_RUN" == true ]]; then log_dry "/bin/bash Homebrew install script" @@ -30,7 +30,7 @@ installBrew() { } installGit() { - ((step++)) + ((step += 1)) log_step "Install Git" if [[ "$DRY_RUN" == true ]]; then log_dry "brew install git && git config --global user.name/email" @@ -63,7 +63,7 @@ installGit() { } installZsh() { - ((step++)) + ((step += 1)) log_step "Install ZSH + Oh My Zsh" if [[ "$DRY_RUN" == true ]]; then log_dry "Install Zsh, Oh My Zsh, p10k, plugins, Nerd Font, symlinks + inject dotfiles into ~/.zshrc" @@ -137,13 +137,18 @@ export DOTFILE_PATH="'"${DOTFILE_PATH}"'"\ fi log_info "Installing Zsh plugins..." - git clone https://github.com/zsh-users/zsh-syntax-highlighting.git "${ZSH_CUSTOM:-${HOME}/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting" - git clone https://github.com/zsh-users/zsh-autosuggestions "${ZSH_CUSTOM:-${HOME}/.oh-my-zsh/custom}/plugins/zsh-autosuggestions" + local zsh_custom="${ZSH_CUSTOM:-${HOME}/.oh-my-zsh/custom}" + [ -d "${zsh_custom}/plugins/zsh-syntax-highlighting" ] || \ + git clone https://github.com/zsh-users/zsh-syntax-highlighting.git "${zsh_custom}/plugins/zsh-syntax-highlighting" + [ -d "${zsh_custom}/plugins/zsh-autosuggestions" ] || \ + git clone https://github.com/zsh-users/zsh-autosuggestions "${zsh_custom}/plugins/zsh-autosuggestions" [ "$(uname -s)" == "Darwin" ] && brew_install coreutils - git clone https://github.com/supercrabtree/k "${ZSH_CUSTOM:-${HOME}/.oh-my-zsh/custom}/plugins/k" + [ -d "${zsh_custom}/plugins/k" ] || \ + git clone https://github.com/supercrabtree/k "${zsh_custom}/plugins/k" log_info "Installing PowerLevel10k theme..." - git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k" + [ -d "${zsh_custom}/themes/powerlevel10k" ] || \ + git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "${zsh_custom}/themes/powerlevel10k" log_info "Installing Nerd Font..." if [[ "$(uname -s)" == "Darwin" ]]; then @@ -160,7 +165,7 @@ export DOTFILE_PATH="'"${DOTFILE_PATH}"'"\ } installAsdf() { - ((step++)) + ((step += 1)) log_step "Install ASDF" if [[ "$DRY_RUN" == true ]]; then log_dry "brew install asdf + plugins: nodejs, pnpm, python, java" diff --git a/lib/install_software.sh b/lib/install_software.sh index 182f9cd..23d8756 100644 --- a/lib/install_software.sh +++ b/lib/install_software.sh @@ -7,7 +7,7 @@ # ============================================================================ installSoftwarePro() { - ((step++)) + ((step += 1)) log_step "Install Software: Pro Bundle" if [[ "$DRY_RUN" == true ]]; then log_dry "brew install --cask visual-studio-code iterm2 sublime-text rectangle cakebrew grandperspective spotify vivaldi audio-hijack whatsapp discord + qemu colima docker" @@ -34,7 +34,7 @@ installSoftwarePro() { } installSoftwareDevelopment() { - ((step++)) + ((step += 1)) log_step "Install Software: Development" if [[ "$DRY_RUN" == true ]]; then log_dry "brew install --cask visual-studio-code iterm2 wave sublime-text notion anki + qemu colima docker" @@ -52,7 +52,7 @@ installSoftwareDevelopment() { } installSoftwareLLM() { - ((step++)) + ((step += 1)) log_step "Install Software: LLM Tools" if [[ "$DRY_RUN" == true ]]; then log_dry "brew install --cask lm-studio chatgpt superwhisper opencode opencode-desktop antigravity" @@ -69,7 +69,7 @@ installSoftwareLLM() { } installSoftwareTools() { - ((step++)) + ((step += 1)) log_step "Install Software: Tools" if [[ "$DRY_RUN" == true ]]; then log_dry "brew install --cask rectangle oversight logi-options-plus jdownloader background-music grandperspective pearcleaner clop protonvpn jordanbaird-ice" @@ -90,7 +90,7 @@ installSoftwareTools() { } installSoftwareCommunication() { - ((step++)) + ((step += 1)) log_step "Install Software: Communication" if [[ "$DRY_RUN" == true ]]; then log_dry "brew install --cask audio-hijack slack whatsapp discord signal" @@ -106,7 +106,7 @@ installSoftwareCommunication() { } installSoftwareOffice() { - ((step++)) + ((step += 1)) log_step "Install Software: Office" if [[ "$DRY_RUN" == true ]]; then log_dry "brew install --cask microsoft-office" @@ -118,7 +118,7 @@ installSoftwareOffice() { } installSoftwareGames() { - ((step++)) + ((step += 1)) log_step "Install Software: Games" if [[ "$DRY_RUN" == true ]]; then log_dry "brew install --cask nvidia-geforce-now epic-games steam prismlauncher scummvm obs openemu sony-ps-remote-play moonlight" @@ -138,7 +138,7 @@ installSoftwareGames() { } installSoftwareOthers() { - ((step++)) + ((step += 1)) log_step "Install Software: Others" if [[ "$DRY_RUN" == true ]]; then log_dry "brew install --cask spotify calibre kindle-previewer send-to-kindle hakuneko affinity vivaldi" diff --git a/tests/bootstrap.bats b/tests/bootstrap.bats index cd086a6..9f31fb1 100644 --- a/tests/bootstrap.bats +++ b/tests/bootstrap.bats @@ -13,8 +13,14 @@ BOOTSTRAP="${REPO_ROOT}/bootstrap.sh" # ── Group I: bootstrap.sh non-network logic ──────────────────────────────── @test "I1: bootstrap.sh exits 1 and prints [ERROR] when git is not available" { - # Run bootstrap in a subshell where git is not on PATH - run env PATH="/usr/bin:/bin" bash "${BOOTSTRAP}" 2>&1 || true + # Create a temp empty dir to use as PATH, ensuring git is not found + local empty_path + empty_path=$(mktemp -d) + + # Run bootstrap with an empty PATH so git is not on PATH + run /bin/bash -c "PATH='${empty_path}' /bin/bash '${BOOTSTRAP}'" < /dev/null 2>&1 || true + + rm -rf "$empty_path" # The script should mention git is required and exit non-zero [[ "$output" == *"git is required"* ]] @@ -32,7 +38,8 @@ BOOTSTRAP="${REPO_ROOT}/bootstrap.sh" git_calls="" bash_calls="" - run env DEST="$tmpdir" bash -c " + # Pipe empty stdin so the non-interactive path is taken (skips read prompt) + run bash -c " git() { echo \"GIT:\$*\" # simulate successful pull @@ -45,7 +52,7 @@ BOOTSTRAP="${REPO_ROOT}/bootstrap.sh" export -f git bash DEST='${tmpdir}' source '${BOOTSTRAP}' - " 2>&1 || true + " < /dev/null 2>&1 || true # Should have called git pull, not git clone [[ "$output" == *"pulling latest"* || "$output" == *"GIT:-C"* || "$output" == *"pull"* ]] diff --git a/tests/zshrc_config.bats b/tests/zshrc_config.bats index 5109023..fb7498b 100644 --- a/tests/zshrc_config.bats +++ b/tests/zshrc_config.bats @@ -68,8 +68,8 @@ ALIASES="${REPO_ROOT}/config/zsh/zsh_aliases.zsh" [[ "$output" == *"git-clean-branches"* ]] } -@test "M9: git_clean_branches function is defined in zsh_postload.zsh" { - run grep -n 'git_clean_branches()' "$POSTLOAD" +@test "M9: git_clean_branches function is defined in zsh_aliases.zsh" { + run grep -n 'git_clean_branches()' "$ALIASES" [ "$status" -eq 0 ] }