diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..06bf713 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run ShellCheck + run: find . -name '*.sh' -not -path './zsh/*' -print0 | xargs -0 shellcheck + + test: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Install bats-core + run: brew install bats-core + - name: Run tests + run: bats test_install.bats diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b908d4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.pyc +.DS_Store diff --git a/install.sh b/install.sh index a64853b..a8ae931 100755 --- a/install.sh +++ b/install.sh @@ -3,6 +3,7 @@ set -euo pipefail REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DRY_RUN=false # --------------------------------------------------------------------------- # Helpers @@ -11,6 +12,11 @@ REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" _symlink() { local source="$1" destination="$2" + if $DRY_RUN; then + echo "DRY-RUN: Would link $source -> $destination" + return + fi + # Already linked correctly — nothing to do if [ -L "$destination" ] && [ "$(readlink "$destination")" = "$source" ]; then echo "INFO: Nothing to do here: $destination LGTM." @@ -19,8 +25,10 @@ _symlink() { # Back up existing file/symlink if [ -e "$destination" ] || [ -L "$destination" ]; then - mv "$destination" "${destination}.orig" - echo "INFO: Created backup ${destination}.orig" + local backup + backup="${destination}.orig.$(date +%Y%m%d%H%M%S)" + mv "$destination" "$backup" + echo "INFO: Created backup $backup" fi ln -s "$source" "$destination" @@ -38,6 +46,7 @@ Options: --osx Run macOS system tweaks (osx/osx.sh) --oh-my-zsh Install Oh-my-zsh --claude Link Claude Code skills to ~/.claude/skills/ + --dry-run Preview what would be done without making changes --help Show this help message EOF } @@ -67,6 +76,10 @@ do_ghostty() { do_brew() { echo "Working on Brew and programs.." + if $DRY_RUN; then + echo "DRY-RUN: Would install Xcode CLI tools, Homebrew, and packages from Brewfile" + return + fi if ! xcode-select -p &>/dev/null; then xcode-select --install fi @@ -78,11 +91,23 @@ do_brew() { do_osx() { echo "Working on osx customisations.." + if $DRY_RUN; then + echo "DRY-RUN: Would run macOS system tweaks from osx/osx.sh" + return + fi bash "$REPO_DIR/osx/osx.sh" } do_oh_my_zsh() { echo "Working on Oh-my-zsh.." + if [ -d "$HOME/.oh-my-zsh" ]; then + echo "INFO: Oh-my-zsh is already installed, skipping." + return + fi + if $DRY_RUN; then + echo "DRY-RUN: Would install Oh-my-zsh" + return + fi curl -L https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh | sh } @@ -108,6 +133,7 @@ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then for arg in "$@"; do case "$arg" in + --dry-run) DRY_RUN=true ;; --dotfiles) do_dotfiles ;; --ghostty) do_ghostty ;; --brew) do_brew ;; diff --git a/osx/osx.sh b/osx/osx.sh index e83d9e9..c6cc899 100644 --- a/osx/osx.sh +++ b/osx/osx.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash -set -x - #From https://mths.be/osx # Ask for the administrator password upfront @@ -420,31 +418,12 @@ defaults write com.google.Chrome.canary AppleEnableMouseSwipeNavigateWithScrolls defaults write com.google.Chrome DisablePrintPreview -bool true defaults write com.google.Chrome.canary DisablePrintPreview -bool true -############################################################################### -# Transmission.app # -############################################################################### - -# Use `~/Documents/Torrents` to store incomplete downloads -defaults write org.m0k.transmission UseIncompleteDownloadFolder -bool true -defaults write org.m0k.transmission IncompleteDownloadFolder -string "${HOME}/Documents/Torrents" - -# Don’t prompt for confirmation before downloading -defaults write org.m0k.transmission DownloadAsk -bool false - -# Trash original torrent files -defaults write org.m0k.transmission DeleteOriginalTorrent -bool true - -# Hide the donate message -defaults write org.m0k.transmission WarningDonate -bool false -# Hide the legal disclaimer -defaults write org.m0k.transmission WarningLegal -bool false - ############################################################################### # Kill affected applications # ############################################################################### for app in "Activity Monitor" "cfprefsd" "Dock" "Finder" "Messages" \ - "SystemUIServer" "Terminal" "Transmission" "Calendar"; do + "SystemUIServer" "Terminal" "Calendar"; do killall "${app}" > /dev/null 2>&1 done echo "Done. Note that some of these changes require a logout/restart to take effect." diff --git a/test_install.bats b/test_install.bats index 487ec01..24a5e22 100644 --- a/test_install.bats +++ b/test_install.bats @@ -42,8 +42,10 @@ setup() { [ -L "$dest" ] [ "$(readlink "$dest")" = "$src" ] - [ -f "${dest}.orig" ] - [ "$(cat "${dest}.orig")" = "old" ] + local backup + backup=$(ls "${dest}".orig.* 2>/dev/null) + [ -f "$backup" ] + [ "$(cat "$backup")" = "old" ] } @test "_symlink backs up existing wrong symlink and creates correct one" { @@ -58,8 +60,10 @@ setup() { [ -L "$dest" ] [ "$(readlink "$dest")" = "$src" ] - [ -L "${dest}.orig" ] - [ "$(readlink "${dest}.orig")" = "$wrong" ] + local backup + backup=$(ls "${dest}".orig.* 2>/dev/null) + [ -L "$backup" ] + [ "$(readlink "$backup")" = "$wrong" ] } # --- do_dotfiles test (ported from TestProccessDotfiles) --- @@ -86,7 +90,7 @@ setup() { # --- backup test (ported from TestBackup) --- -@test "backup via _symlink renames file with .orig suffix" { +@test "backup via _symlink renames file with .orig timestamp suffix" { local dest="$TEST_DIR/myfile" local src="$TEST_DIR/newsrc" echo "content" > "$dest" @@ -95,6 +99,113 @@ setup() { _symlink "$src" "$dest" [ -L "$dest" ] - [ -f "${dest}.orig" ] - [ "$(cat "${dest}.orig")" = "content" ] + local backup + backup=$(ls "${dest}".orig.* 2>/dev/null) + [ -f "$backup" ] + [ "$(cat "$backup")" = "content" ] +} + +# --- do_ghostty tests --- + +@test "do_ghostty creates config directory and symlinks config" { + local fake_home="$TEST_DIR/home" + mkdir -p "$fake_home" + + HOME="$fake_home" + + do_ghostty + + [ -d "$fake_home/.config/ghostty" ] + [ -L "$fake_home/.config/ghostty/config" ] +} + +@test "do_ghostty is idempotent" { + local fake_home="$TEST_DIR/home" + mkdir -p "$fake_home" + + HOME="$fake_home" + + do_ghostty + run do_ghostty + + [ "$status" -eq 0 ] + [[ "$output" == *"LGTM"* ]] +} + +# --- do_claude tests --- + +@test "do_claude symlinks skill directories" { + local fake_home="$TEST_DIR/home" + local repo="$TEST_DIR/repo" + mkdir -p "$fake_home" "$repo/claude/skills/my-skill" + echo "skill" > "$repo/claude/skills/my-skill/SKILL.md" + + HOME="$fake_home" + REPO_DIR="$repo" + + do_claude + + [ -d "$fake_home/.claude/skills" ] + [ -L "$fake_home/.claude/skills/my-skill" ] +} + +@test "do_claude is idempotent" { + local fake_home="$TEST_DIR/home" + local repo="$TEST_DIR/repo" + mkdir -p "$fake_home" "$repo/claude/skills/my-skill" + echo "skill" > "$repo/claude/skills/my-skill/SKILL.md" + + HOME="$fake_home" + REPO_DIR="$repo" + + do_claude + run do_claude + + [ "$status" -eq 0 ] + [[ "$output" == *"LGTM"* ]] +} + +# --- do_oh_my_zsh tests --- + +@test "do_oh_my_zsh skips when already installed" { + local fake_home="$TEST_DIR/home" + mkdir -p "$fake_home/.oh-my-zsh" + + HOME="$fake_home" + + run do_oh_my_zsh + + [ "$status" -eq 0 ] + [[ "$output" == *"already installed"* ]] +} + +# --- dry-run tests --- + +@test "dry-run mode prevents _symlink from creating links" { + local src="$TEST_DIR/source" + local dest="$TEST_DIR/dest" + echo "data" > "$src" + + DRY_RUN=true + run _symlink "$src" "$dest" + + [ "$status" -eq 0 ] + [[ "$output" == *"DRY-RUN"* ]] + [ ! -e "$dest" ] +} + +@test "dry-run mode prevents _symlink from backing up files" { + local src="$TEST_DIR/source" + local dest="$TEST_DIR/dest" + echo "new" > "$src" + echo "old" > "$dest" + + DRY_RUN=true + run _symlink "$src" "$dest" + + [ "$status" -eq 0 ] + [[ "$output" == *"DRY-RUN"* ]] + # Original file should be untouched + [ -f "$dest" ] + [ "$(cat "$dest")" = "old" ] }