From 044dc9181beae6a2b56a8e13a8b2acaa65c0824c Mon Sep 17 00:00:00 2001 From: Matthew Dolan Date: Mon, 2 Mar 2026 20:51:58 -0800 Subject: [PATCH 1/2] Add dol utility for dotfiles install and updates --- README.md | 14 + home/.zshrc | 32 +- home/bin/agents-check-for-upgrade.sh | 49 ++- home/bin/agents-upgrade.sh | 37 ++- home/bin/dol | 406 +++++++++++++++++++++++++ home/bin/dotfiles-check-for-upgrade.sh | 49 ++- home/bin/dotfiles-upgrade.sh | 38 ++- install.sh | 68 +---- tests/dol.bats | 25 ++ 9 files changed, 549 insertions(+), 169 deletions(-) create mode 100755 home/bin/dol create mode 100644 tests/dol.bats diff --git a/README.md b/README.md index 2e975a3..6b387f7 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,20 @@ curl -fsSL https://matthewdolan.github.io/dotfiles/install.sh | bash When executed via `curl` or from outside the repository, the installer clones this repository to `~/.dotfiles` before continuing. +## `dol` command + +After installation, use `dol` to manage dotfiles and agents updates: + +```bash +dol install +dol check +dol update +dol agents check +dol agents update +``` + +Legacy commands (`install.sh`, `dotfiles-upgrade.sh`, `dotfiles-check-for-upgrade.sh`, `agents-upgrade.sh`, and `agents-check-for-upgrade.sh`) are still available as compatibility wrappers and forward to `dol`. + ## Testing Activate the included [Hermit](https://github.com/cashapp/hermit) environment and run the tests with: diff --git a/home/.zshrc b/home/.zshrc index c8693b5..5b5db50 100644 --- a/home/.zshrc +++ b/home/.zshrc @@ -108,43 +108,33 @@ if [[ "${DOLAN_USE_HERMIT:-false}" == "true" ]]; then fi fi -# Check for dotfiles upgrades (once every 24 hours) -_dotfiles_check_for_upgrade() { - local stamp_file="${HOME}/.dotfiles-last-update-check" +# Check for dotfiles/agents upgrades (once every 24 hours). +_dol_check_for_upgrade() { + local stamp_file="${HOME}/.dol-last-update-check" local now - now="$(date +%s)" local last_check=0 + now="$(date +%s)" if [[ -f "${stamp_file}" ]]; then last_check="$(cat "${stamp_file}")" fi if (( now - last_check >= 86400 )); then echo "${now}" > "${stamp_file}" - dotfiles-check-for-upgrade.sh - fi -} -_dotfiles_check_for_upgrade - -# Check for agents upgrades (once every 24 hours) -_agents_check_for_upgrade() { - local stamp_file="${HOME}/.agents-last-update-check" - local now - now="$(date +%s)" - local last_check=0 - if [[ -f "${stamp_file}" ]]; then - last_check="$(cat "${stamp_file}")" - fi + if command -v dol >/dev/null 2>&1; then + dol check + return 0 + fi - if (( now - last_check >= 86400 )); then - echo "${now}" > "${stamp_file}" + # Backward-compatible fallback before dol is symlinked into PATH. + dotfiles-check-for-upgrade.sh if command -v agents-check-for-upgrade.sh >/dev/null 2>&1; then agents-check-for-upgrade.sh fi fi } -_agents_check_for_upgrade +_dol_check_for_upgrade # Source 1Password plugins if available if [[ -f "${HOME}/.config/op/plugins.sh" ]]; then diff --git a/home/bin/agents-check-for-upgrade.sh b/home/bin/agents-check-for-upgrade.sh index d14243e..91bb046 100755 --- a/home/bin/agents-check-for-upgrade.sh +++ b/home/bin/agents-check-for-upgrade.sh @@ -1,39 +1,28 @@ #!/bin/zsh set -euo pipefail -agents_dir="${HOME}/.agents" +WRAPPER_SCRIPT_SOURCE="$0" -# Skip silently when agents repo is not installed. -if [[ ! -d "${agents_dir}" ]]; then - exit 0 -fi +resolve_wrapper_dotfiles_dir() { + local script_source="${WRAPPER_SCRIPT_SOURCE}" + local source_dir -# Verify it's a git repository. -if ! git -C "${agents_dir}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo "Error: ${agents_dir} is not a git repository." >&2 - exit 1 -fi + if [[ "${script_source}" != */* ]]; then + script_source="$(command -v -- "${script_source}")" + fi -# Fetch latest from remote; exit silently on network failure. -if ! git -C "${agents_dir}" fetch --quiet 2>/dev/null; then - exit 0 -fi + while [[ -L "${script_source}" ]]; do + source_dir="$(cd -P "$(dirname "${script_source}")" && pwd)" + script_source="$(readlink "${script_source}")" + if [[ "${script_source}" != /* ]]; then + script_source="${source_dir}/${script_source}" + fi + done -# Compare local HEAD to upstream. -local_head="$(git -C "${agents_dir}" rev-parse HEAD)" -remote_head="$(git -C "${agents_dir}" rev-parse '@{u}' 2>/dev/null)" || exit 0 + cd -P "$(dirname "${script_source}")/../.." && pwd +} -if [[ "${local_head}" == "${remote_head}" ]]; then - exit 0 -fi +dotfiles_dir="$(resolve_wrapper_dotfiles_dir)" -# Show what's new. -echo "Agents updates available:" -git -C "${agents_dir}" log --oneline HEAD.."@{u}" -echo "" - -# Prompt for upgrade. -read -r -p "Upgrade agents? [y/N] " response -if [[ "${response}" =~ ^[Yy]$ ]]; then - agents-upgrade.sh -fi +echo "agents-check-for-upgrade.sh is deprecated; forwarding to 'dol agents check'." +exec "${dotfiles_dir}/home/bin/dol" agents check "$@" diff --git a/home/bin/agents-upgrade.sh b/home/bin/agents-upgrade.sh index 010581d..3c930ca 100755 --- a/home/bin/agents-upgrade.sh +++ b/home/bin/agents-upgrade.sh @@ -1,15 +1,28 @@ #!/bin/zsh set -euo pipefail -agents_dir="${HOME}/.agents" - -# Verify it's a git repository. -if ! git -C "${agents_dir}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo "Error: ${agents_dir} is not a git repository." >&2 - exit 1 -fi - -echo "Upgrading agents from ${agents_dir}..." -git -C "${agents_dir}" pull --ff-only -"${agents_dir}/install.sh" -echo "Agents upgraded successfully." +WRAPPER_SCRIPT_SOURCE="$0" + +resolve_wrapper_dotfiles_dir() { + local script_source="${WRAPPER_SCRIPT_SOURCE}" + local source_dir + + if [[ "${script_source}" != */* ]]; then + script_source="$(command -v -- "${script_source}")" + fi + + while [[ -L "${script_source}" ]]; do + source_dir="$(cd -P "$(dirname "${script_source}")" && pwd)" + script_source="$(readlink "${script_source}")" + if [[ "${script_source}" != /* ]]; then + script_source="${source_dir}/${script_source}" + fi + done + + cd -P "$(dirname "${script_source}")/../.." && pwd +} + +dotfiles_dir="$(resolve_wrapper_dotfiles_dir)" + +echo "agents-upgrade.sh is deprecated; forwarding to 'dol agents update'." +exec "${dotfiles_dir}/home/bin/dol" agents update "$@" diff --git a/home/bin/dol b/home/bin/dol new file mode 100755 index 0000000..0ddda8d --- /dev/null +++ b/home/bin/dol @@ -0,0 +1,406 @@ +#!/bin/zsh +set -euo pipefail + +DOL_ASSUME_YES=false +DOL_DOTFILES_DIR="" +DOTFILES_UPDATES_AVAILABLE=false +AGENTS_UPDATES_AVAILABLE=false +DOL_SCRIPT_SOURCE="$0" + +print_usage() { + cat <<'USAGE' +Usage: + dol install [--yes] + dol update [--yes] + dol check [--yes] + dol agents install [--yes] + dol agents update [--yes] + dol agents check [--yes] + dol help +USAGE +} + +resolve_script_dir() { + local script_source="${DOL_SCRIPT_SOURCE}" + local source_dir + + if [[ "${script_source}" != */* ]]; then + script_source="$(command -v -- "${script_source}")" + fi + + while [[ -L "${script_source}" ]]; do + source_dir="$(cd -P "$(dirname "${script_source}")" && pwd)" + script_source="$(readlink "${script_source}")" + if [[ "${script_source}" != /* ]]; then + script_source="${source_dir}/${script_source}" + fi + done + + cd -P "$(dirname "${script_source}")" && pwd +} + +resolve_dotfiles_dir() { + local script_dir + local candidate + + script_dir="$(resolve_script_dir)" + candidate="$(cd "${script_dir}/../.." && pwd)" + + if [[ -d "${candidate}/home" && -f "${candidate}/install.sh" ]]; then + echo "${candidate}" + return 0 + fi + + echo "Error: unable to locate dotfiles repository from ${script_dir}." >&2 + return 1 +} + +ensure_dotfiles_repo() { + if ! git -C "${DOL_DOTFILES_DIR}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "Error: ${DOL_DOTFILES_DIR} is not a git repository." >&2 + return 1 + fi +} + +ensure_clean_dotfiles_repo() { + local repo_status + + repo_status="$(git -C "${DOL_DOTFILES_DIR}" status --porcelain)" + if [[ -n "${repo_status}" ]]; then + cat >&2 <<'EOF' +Refusing to update dotfiles because the working tree has local changes. +Please commit, stash, or discard your changes and run `dol update` again. +EOF + return 1 + fi +} + +confirm_action() { + local prompt="$1" + local response + + if [[ "${DOL_ASSUME_YES}" == "true" ]]; then + return 0 + fi + + if [[ ! -t 0 || ! -t 1 ]]; then + return 1 + fi + + printf "%s [y/N] " "${prompt}" + if ! read -r response; then + return 1 + fi + + [[ "${response}" =~ ^[Yy]$ ]] +} + +parse_common_flags() { + DOL_ASSUME_YES=false + + while [[ $# -gt 0 ]]; do + case "$1" in + --yes|-y) + DOL_ASSUME_YES=true + ;; + -h|--help) + print_usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + print_usage >&2 + exit 1 + ;; + esac + shift + done +} + +run_dotfiles_install() { + local home_src + local home_dst + local os_name + + ensure_dotfiles_repo + + echo "Setting up your Computer..." + + if [[ ! -d "${HOME}/Developer" ]]; then + echo "Creating a Developer folder..." + mkdir -p "${HOME}/Developer" + fi + + if [[ ! -d "${HOME}/.oh-my-zsh" ]]; then + echo "Installing oh-my-zsh..." + curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh | sh -s -- --keep-zshrc + fi + + if [[ "${DOLAN_USE_HERMIT:-false}" == "true" ]]; then + if [[ ! -d "${HOME}/bin/hermit" ]]; then + echo "Installing hermit..." + curl -fsSL https://github.com/cashapp/hermit/releases/download/stable/install.sh | /bin/bash + fi + fi + + home_src="${DOL_DOTFILES_DIR}/home" + home_dst="${HOME}" + + echo "Symlinking files from ${home_src} to ${home_dst}..." + + find "${home_src}" -type f -print0 | while IFS= read -r -d '' file; do + local dest_dir + local dest_file + + dest_dir="$(dirname "${file#"${home_src}/"}")" + echo " Symlinking ${file#"${home_src}/"} to \$HOME/${dest_dir}..." + mkdir -p "${home_dst}/${dest_dir}" + dest_file="${home_dst}/${dest_dir}/$(basename "${file}")" + + if [[ -e "${dest_file}" && ! -L "${dest_file}" ]]; then + mkdir -p "${home_dst}/.old/${dest_dir}" + mv "${dest_file}" "${home_dst}/.old/${dest_dir}/$(basename "${file}")" + fi + + ln -sf "${file}" "${dest_file}" + done + + os_name="$(uname)" + if [[ "${os_name}" == "Darwin" ]]; then + echo "Installing Mac OS Software..." + + if ! command -v brew >/dev/null 2>&1; then + echo " Installing Homebrew..." + curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | /bin/bash + fi + + echo " Showing hidden files in Finder..." + defaults write com.apple.finder AppleShowAllFiles TRUE + killall Finder + fi + + . "${DOL_DOTFILES_DIR}/agents/install.sh" +} + +run_agents_install() { + . "${DOL_DOTFILES_DIR}/agents/install.sh" +} + +run_dotfiles_update() { + ensure_dotfiles_repo + ensure_clean_dotfiles_repo + + echo "Upgrading dotfiles from ${DOL_DOTFILES_DIR}..." + git -C "${DOL_DOTFILES_DIR}" pull --ff-only + run_dotfiles_install + echo "Dotfiles upgraded successfully." +} + +run_agents_update() { + local agents_dir="${HOME}/.agents" + + if ! git -C "${agents_dir}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "Error: ${agents_dir} is not a git repository." >&2 + return 1 + fi + + echo "Upgrading agents from ${agents_dir}..." + git -C "${agents_dir}" pull --ff-only + "${agents_dir}/install.sh" + echo "Agents upgraded successfully." +} + +check_dotfiles_updates() { + local local_head + local remote_head + + DOTFILES_UPDATES_AVAILABLE=false + ensure_dotfiles_repo + + if ! git -C "${DOL_DOTFILES_DIR}" fetch --quiet 2>/dev/null; then + return 0 + fi + + local_head="$(git -C "${DOL_DOTFILES_DIR}" rev-parse HEAD)" + remote_head="$(git -C "${DOL_DOTFILES_DIR}" rev-parse "@{u}" 2>/dev/null)" || return 0 + + if [[ "${local_head}" == "${remote_head}" ]]; then + return 0 + fi + + DOTFILES_UPDATES_AVAILABLE=true + echo "Dotfiles updates available:" + git -C "${DOL_DOTFILES_DIR}" log --oneline HEAD.."@{u}" + echo "" +} + +check_agents_updates() { + local agents_dir="${HOME}/.agents" + local local_head + local remote_head + + AGENTS_UPDATES_AVAILABLE=false + + if [[ ! -d "${agents_dir}" ]]; then + return 0 + fi + + if ! git -C "${agents_dir}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "Error: ${agents_dir} is not a git repository." >&2 + return 1 + fi + + if ! git -C "${agents_dir}" fetch --quiet 2>/dev/null; then + return 0 + fi + + local_head="$(git -C "${agents_dir}" rev-parse HEAD)" + remote_head="$(git -C "${agents_dir}" rev-parse "@{u}" 2>/dev/null)" || return 0 + + if [[ "${local_head}" == "${remote_head}" ]]; then + return 0 + fi + + AGENTS_UPDATES_AVAILABLE=true + echo "Agents updates available:" + git -C "${agents_dir}" log --oneline HEAD.."@{u}" + echo "" +} + +apply_available_updates() { + if [[ "${DOTFILES_UPDATES_AVAILABLE}" == "true" ]]; then + run_dotfiles_update + fi + + if [[ "${AGENTS_UPDATES_AVAILABLE}" == "true" ]]; then + run_agents_update + fi +} + +command_install() { + run_dotfiles_install +} + +command_update() { + run_dotfiles_update +} + +command_check() { + local confirm_status=1 + + check_dotfiles_updates + check_agents_updates + + if [[ "${DOTFILES_UPDATES_AVAILABLE}" != "true" && "${AGENTS_UPDATES_AVAILABLE}" != "true" ]]; then + return 0 + fi + + if [[ "${DOL_ASSUME_YES}" == "true" ]]; then + apply_available_updates + return 0 + fi + + set +e + confirm_action "Upgrade available updates now?" + confirm_status=$? + set -e + if [[ "${confirm_status}" -eq 0 ]]; then + apply_available_updates + fi +} + +command_agents_install() { + run_agents_install +} + +command_agents_update() { + run_agents_update +} + +command_agents_check() { + local confirm_status=1 + + check_agents_updates + + if [[ "${AGENTS_UPDATES_AVAILABLE}" != "true" ]]; then + return 0 + fi + + if [[ "${DOL_ASSUME_YES}" == "true" ]]; then + run_agents_update + return 0 + fi + + set +e + confirm_action "Upgrade agents now?" + confirm_status=$? + set -e + if [[ "${confirm_status}" -eq 0 ]]; then + run_agents_update + fi +} + +main() { + local command + local subcommand + + DOL_DOTFILES_DIR="$(resolve_dotfiles_dir)" + command="${1:-help}" + if [[ $# -gt 0 ]]; then + shift + fi + + case "${command}" in + help|-h|--help) + print_usage + ;; + install) + parse_common_flags "$@" + command_install + ;; + update) + parse_common_flags "$@" + command_update + ;; + check) + parse_common_flags "$@" + command_check + ;; + agents) + subcommand="${1:-help}" + if [[ $# -gt 0 ]]; then + shift + fi + + case "${subcommand}" in + install) + parse_common_flags "$@" + command_agents_install + ;; + update) + parse_common_flags "$@" + command_agents_update + ;; + check) + parse_common_flags "$@" + command_agents_check + ;; + help|-h|--help) + print_usage + ;; + *) + echo "Unknown agents subcommand: ${subcommand}" >&2 + print_usage >&2 + return 1 + ;; + esac + ;; + *) + echo "Unknown command: ${command}" >&2 + print_usage >&2 + return 1 + ;; + esac +} + +main "$@" diff --git a/home/bin/dotfiles-check-for-upgrade.sh b/home/bin/dotfiles-check-for-upgrade.sh index 322eef7..3a05648 100755 --- a/home/bin/dotfiles-check-for-upgrade.sh +++ b/home/bin/dotfiles-check-for-upgrade.sh @@ -1,39 +1,28 @@ #!/bin/zsh set -euo pipefail -# Resolve the dotfiles directory by following this script's symlink -script_source="${0}" -if [[ -L "${script_source}" ]]; then - script_source="$(readlink "${script_source}")" -fi -dotfiles_dir="$(cd "$(dirname "${script_source}")/../.." && pwd)" +WRAPPER_SCRIPT_SOURCE="$0" -# Verify it's a git repository -if ! git -C "${dotfiles_dir}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo "Error: ${dotfiles_dir} is not a git repository." >&2 - exit 1 -fi +resolve_wrapper_dotfiles_dir() { + local script_source="${WRAPPER_SCRIPT_SOURCE}" + local source_dir -# Fetch latest from remote; exit silently on network failure -if ! git -C "${dotfiles_dir}" fetch --quiet 2>/dev/null; then - exit 0 -fi + if [[ "${script_source}" != */* ]]; then + script_source="$(command -v -- "${script_source}")" + fi -# Compare local HEAD to upstream -local_head="$(git -C "${dotfiles_dir}" rev-parse HEAD)" -remote_head="$(git -C "${dotfiles_dir}" rev-parse "@{u}" 2>/dev/null)" || exit 0 + while [[ -L "${script_source}" ]]; do + source_dir="$(cd -P "$(dirname "${script_source}")" && pwd)" + script_source="$(readlink "${script_source}")" + if [[ "${script_source}" != /* ]]; then + script_source="${source_dir}/${script_source}" + fi + done -if [[ "${local_head}" == "${remote_head}" ]]; then - exit 0 -fi + cd -P "$(dirname "${script_source}")/../.." && pwd +} -# Show what's new -echo "Dotfiles updates available:" -git -C "${dotfiles_dir}" log --oneline HEAD.."@{u}" -echo "" +dotfiles_dir="$(resolve_wrapper_dotfiles_dir)" -# Prompt for upgrade -read -r -p "Upgrade dotfiles? [y/N] " response -if [[ "${response}" =~ ^[Yy]$ ]]; then - dotfiles-upgrade.sh -fi +echo "dotfiles-check-for-upgrade.sh is deprecated; forwarding to 'dol check'." +exec "${dotfiles_dir}/home/bin/dol" check "$@" diff --git a/home/bin/dotfiles-upgrade.sh b/home/bin/dotfiles-upgrade.sh index a30b992..4d8f051 100755 --- a/home/bin/dotfiles-upgrade.sh +++ b/home/bin/dotfiles-upgrade.sh @@ -1,20 +1,28 @@ #!/bin/zsh set -euo pipefail -# Resolve the dotfiles directory by following this script's symlink -script_source="${0}" -if [[ -L "${script_source}" ]]; then - script_source="$(readlink "${script_source}")" -fi -dotfiles_dir="$(cd "$(dirname "${script_source}")/../.." && pwd)" +WRAPPER_SCRIPT_SOURCE="$0" -# Verify it's a git repository -if ! git -C "${dotfiles_dir}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo "Error: ${dotfiles_dir} is not a git repository." >&2 - exit 1 -fi +resolve_wrapper_dotfiles_dir() { + local script_source="${WRAPPER_SCRIPT_SOURCE}" + local source_dir -echo "Upgrading dotfiles from ${dotfiles_dir}..." -git -C "${dotfiles_dir}" pull --ff-only -"${dotfiles_dir}/install.sh" -echo "Dotfiles upgraded successfully." + if [[ "${script_source}" != */* ]]; then + script_source="$(command -v -- "${script_source}")" + fi + + while [[ -L "${script_source}" ]]; do + source_dir="$(cd -P "$(dirname "${script_source}")" && pwd)" + script_source="$(readlink "${script_source}")" + if [[ "${script_source}" != /* ]]; then + script_source="${source_dir}/${script_source}" + fi + done + + cd -P "$(dirname "${script_source}")/../.." && pwd +} + +dotfiles_dir="$(resolve_wrapper_dotfiles_dir)" + +echo "dotfiles-upgrade.sh is deprecated; forwarding to 'dol update'." +exec "${dotfiles_dir}/home/bin/dol" update "$@" diff --git a/install.sh b/install.sh index e2f98f2..195b9d9 100755 --- a/install.sh +++ b/install.sh @@ -3,77 +3,23 @@ set -euo pipefail repo_url="https://github.com/MatthewDolan/dotfiles.git" -# Determine where this script lives on disk +# Determine where this script lives on disk. script_dir="$(cd "$(dirname "$0")" && pwd)" -# If the script isn't part of a git repository, clone and re-run from ~/.dotfiles +# If the script isn't part of a git repository, clone and run from ~/.dotfiles. if ! git -C "${script_dir}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then target="${HOME}/.dotfiles" echo "Cloning dotfiles repository to ${target}..." if [[ ! -d "${target}" ]]; then git clone --depth=1 "${repo_url}" "${target}" fi - cd "${target}" - exec ./install.sh "$@" -fi - -cd "${script_dir}" - -echo "Setting up your Computer..." - -# create developer directory -if [[ ! -d "${HOME}/Developer" ]]; then - echo "Creating a Developer folder..." - mkdir -p "${HOME}/Developer" -fi - -# install oh-my-zsh -if [[ ! -d "${HOME}/.oh-my-zsh" ]]; then - echo "Installing oh-my-zsh..." - curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh | sh -s -- --keep-zshrc -fi - -# install hermit -if [[ "${DOLAN_USE_HERMIT:-false}" == "true" ]]; then - if [[ ! -d "${HOME}/bin/hermit" ]]; then - echo "Installing hermit..." - curl -fsSL https://github.com/cashapp/hermit/releases/download/stable/install.sh | /bin/bash - fi -fi - -# symlink files from `./home` to `$HOME` -home_src="${script_dir}/home" -home_dst="${HOME}" - -echo "Symlinking files from ${home_src} to ${home_dst}..." - -# iterate through all files in $home_src and its subdirectories -find "${home_src}" -type f -print0 | while IFS= read -r -d '' file; do - dest_dir="$(dirname "${file#"${home_src}/"}")" - echo " Symlinking ${file#"${home_src}/"} to \$HOME/${dest_dir}..." - mkdir -p "${home_dst}/${dest_dir}" - dest_file="${home_dst}/${dest_dir}/$(basename "${file}")" - if [[ -e "${dest_file}" && ! -L "${dest_file}" ]]; then - mkdir -p "${home_dst}/.old/${dest_dir}" - mv "${dest_file}" "${home_dst}/.old/${dest_dir}/$(basename "${file}")" - fi - ln -sf "${file}" "${dest_file}" -done - -# install mac os x specific programs -os_name=$(uname) -if [[ "${os_name}" == "Darwin" ]]; then - echo "Installing Mac OS Software..." - if ! command -v brew >/dev/null; then - echo " Installing Homebrew..." - curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | /bin/bash + if ! git -C "${target}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "Error: ${target} exists but is not a git repository." >&2 + exit 1 fi - echo " Showing hidden files in Finder..." - defaults write com.apple.finder AppleShowAllFiles TRUE - killall Finder + exec "${target}/home/bin/dol" install "$@" fi -# Keep agents bootstrap logic in a dedicated script for readability. -. "${script_dir}/agents/install.sh" +exec "${script_dir}/home/bin/dol" install "$@" diff --git a/tests/dol.bats b/tests/dol.bats new file mode 100644 index 0000000..ba48ca5 --- /dev/null +++ b/tests/dol.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats +# shellcheck disable=SC2154 + +repo_root="$(git rev-parse --show-toplevel)" + +dol_help_prints_usage() { + run "${repo_root}/home/bin/dol" help + [[ "${status}" -eq 0 ]] + [[ "${output}" == *"dol install [--yes]"* ]] + [[ "${output}" == *"dol agents check [--yes]"* ]] +} + +dol_update_fails_on_dirty_working_tree() { + local dirty_file="${repo_root}/.dol-test-dirty.$$" + + touch "${dirty_file}" + run "${repo_root}/home/bin/dol" update + rm -f "${dirty_file}" + + [[ "${status}" -eq 1 ]] + [[ "${output}" == *"working tree has local changes"* ]] +} + +bats_test_function --description "dol help prints usage" -- dol_help_prints_usage +bats_test_function --description "dol update fails on dirty working tree" -- dol_update_fails_on_dirty_working_tree From 01283c8c98cb6e9c6e2692ae2cc5c98a14d62b25 Mon Sep 17 00:00:00 2001 From: Matthew Dolan Date: Mon, 2 Mar 2026 20:53:47 -0800 Subject: [PATCH 2/2] Make dol bats tests shell-portable in CI --- tests/dol.bats | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/dol.bats b/tests/dol.bats index ba48ca5..57146a5 100644 --- a/tests/dol.bats +++ b/tests/dol.bats @@ -3,8 +3,17 @@ repo_root="$(git rev-parse --show-toplevel)" +run_dol() { + if command -v zsh >/dev/null 2>&1; then + run zsh "${repo_root}/home/bin/dol" "$@" + return + fi + + run bash "${repo_root}/home/bin/dol" "$@" +} + dol_help_prints_usage() { - run "${repo_root}/home/bin/dol" help + run_dol help [[ "${status}" -eq 0 ]] [[ "${output}" == *"dol install [--yes]"* ]] [[ "${output}" == *"dol agents check [--yes]"* ]] @@ -14,7 +23,7 @@ dol_update_fails_on_dirty_working_tree() { local dirty_file="${repo_root}/.dol-test-dirty.$$" touch "${dirty_file}" - run "${repo_root}/home/bin/dol" update + run_dol update rm -f "${dirty_file}" [[ "${status}" -eq 1 ]]