From e1246ce063a4f000f3c8527097ca2be144e985b9 Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:15:29 +0200 Subject: [PATCH 01/23] =?UTF-8?q?=F0=9F=90=9B=20fix:=20align=20setup=20con?= =?UTF-8?q?tract=20and=20completion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 7 ++- configs/zsh/completions/_mac | 2 + docs/setup/setup.md | 99 +++++++++++++++++++++--------------- scripts/setup.sh | 46 +++++++++++++++++ 4 files changed, 111 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 30a8c22..149e6e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,7 @@ jobs: "$(find /tmp/lychee -type f -name lychee -print -quit)" \ /usr/local/bin/lychee - echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" - name: Check commit format run: bash scripts/check-commit.sh @@ -77,3 +77,8 @@ jobs: - name: Run repository quality checks run: pre-commit run --all-files --show-diff-on-failure + + - name: Verify generated Zsh completion + run: | + bash scripts/generate-zsh-completion.sh + git diff --exit-code -- configs/zsh/completions/_mac diff --git a/configs/zsh/completions/_mac b/configs/zsh/completions/_mac index 977678b..ea5ef5d 100644 --- a/configs/zsh/completions/_mac +++ b/configs/zsh/completions/_mac @@ -9,6 +9,8 @@ _mac() { 'help:List available commands.' 'doctor:Run system diagnostics for the macOS development setup.' 'setup:Install and configure the macOS development setup.' + 'uninstall:Remove the mac CLI symlink and managed shell PATH entry.' + 'update:Update the mac CLI from its git repository.' ) if (( CURRENT == 2 )); then diff --git a/docs/setup/setup.md b/docs/setup/setup.md index b2bc254..4966b90 100644 --- a/docs/setup/setup.md +++ b/docs/setup/setup.md @@ -1,25 +1,26 @@ -# Global setup script +# Setup command -The repository provides a single entry point for applying the supported setup steps: +The supported entry point for applying the macOS development environment is: -~~~text -scripts/setup.sh +~~~bash +mac setup ~~~ -The script coordinates the existing installation and configuration scripts without replacing their individual documentation. +The CLI delegates to `scripts/setup.sh`, which keeps the same options for direct +maintenance use. ## Usage -Display the available options: +Display the available CLI commands: ~~~bash -./scripts/setup.sh --help +mac help ~~~ -Run every supported step: +Install or reapply the default setup: ~~~bash -./scripts/setup.sh --all +mac setup ~~~ ## Profiles @@ -39,59 +40,76 @@ Use the minimal profile for a smaller command-line setup: mac setup --profile minimal ~~~ +Preview the setup without changing the machine: + +~~~bash +mac setup --profile minimal --dry-run +~~~ + Profiles are defined under: ~~~text profiles//Brewfile ~~~ -To add a new profile, create a new directory under `profiles/` and add a -`Brewfile`. The CLI discovers profile names from that directory. +To add a new profile, create a new directory under `profiles/`, add a +`Brewfile`, and allow it in `scripts/lib/profiles.sh`. -Run only selected steps: +## Supported steps + +### Homebrew ~~~bash -./scripts/setup.sh --homebrew -./scripts/setup.sh --vscode -./scripts/setup.sh --keyboard -./scripts/setup.sh --macos -./scripts/setup.sh --warp +mac setup --profile full ~~~ -Several options can be combined: +Installs the dependencies declared by the selected profile Brewfile. + +### Git ~~~bash -./scripts/setup.sh --homebrew --vscode --warp +mac setup ~~~ -## Supported steps +Adds the repository Git configuration as a managed global include. -### Homebrew +### Zsh ~~~bash -./scripts/setup.sh --homebrew +mac setup ~~~ -Installs the dependencies declared in the repository `Brewfile`. +Applies the curated Zsh files and installs the generated `mac` completion. -### Visual Studio Code +## Optional tools -~~~bash -./scripts/setup.sh --vscode -~~~ +Some versioned assets are intentionally installed by dedicated scripts instead +of the default `mac setup` flow. + +### Visual Studio Code extensions -Installs the recommended VS Code extensions listed in: +Install the recommended extensions listed in: ~~~text configs/vscode/extensions.txt ~~~ -Optional extensions remain excluded from the global setup. +with: + +~~~bash +./scripts/install-vscode-extensions.sh +~~~ + +Optional extensions remain explicit: + +~~~bash +./scripts/install-vscode-extensions.sh --with-optional +~~~ ### French OSS keyboard layout ~~~bash -./scripts/setup.sh --keyboard +./scripts/install-keyboard-layout.sh ~~~ Installs the versioned French OSS keyboard layout bundle. @@ -103,27 +121,24 @@ A logout and login are still required before macOS reloads the layout. ### macOS defaults ~~~bash -./scripts/setup.sh --macos +./scripts/apply-macos-defaults.sh ~~~ Applies the curated Finder, Dock, screenshot, keyboard, and text-substitution preferences. ### Warp -~~~bash -./scripts/setup.sh --warp -~~~ - -Copies the versioned Warp configuration to: +The versioned Warp configuration is stored at: ~~~text -~/.warp/settings.toml +configs/warp/settings.toml ~~~ -An existing configuration is backed up first under: +Install it manually after reviewing the file: -~~~text -~/Documents/Backups/warp +~~~bash +mkdir -p "$HOME/.warp" +cp configs/warp/settings.toml "$HOME/.warp/settings.toml" ~~~ Warp should be restarted after applying the configuration. @@ -150,10 +165,10 @@ Display its help output: ./scripts/setup.sh --help ~~~ -Test an idempotent step: +Test the dry-run path: ~~~bash -./scripts/setup.sh --vscode +./scripts/setup.sh --profile minimal --dry-run ~~~ ## Rollback diff --git a/scripts/setup.sh b/scripts/setup.sh index 3e9fc8d..81e5bdd 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -19,6 +19,52 @@ source "$SCRIPT_DIR/lib/logging.sh" source "$SCRIPT_DIR/lib/profiles.sh" REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +print_usage() { + log_line "Usage: scripts/setup.sh [--profile full|minimal] [--dry-run]" + log_line "Profiles: $(profile_list "$REPO_DIR")" +} + +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + --profile) + PROFILE="${2:-}" + if [ -z "$PROFILE" ] || [ "${PROFILE#--}" != "$PROFILE" ]; then + error "Missing value for --profile" + print_usage >&2 + exit 1 + fi + shift 2 + ;; + --profile=*) + PROFILE="${1#*=}" + if [ -z "$PROFILE" ]; then + error "Missing value for --profile" + print_usage >&2 + exit 1 + fi + shift + ;; + --dry-run) + DRY_RUN="true" + shift + ;; + --help|-h) + print_usage + exit 0 + ;; + *) + error "Unknown option: $1" + print_usage >&2 + exit 1 + ;; + esac + done +} + +parse_args "$@" + PROFILE="${PROFILE:-$(profile_default)}" BREWFILE="$(profile_brewfile "$REPO_DIR" "$PROFILE")" From 21f8dace3bac1df476c1ae7e6117c4b4b77044ae Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:17:24 +0200 Subject: [PATCH 02/23] =?UTF-8?q?=F0=9F=90=9B=20fix:=20make=20setup=20file?= =?UTF-8?q?=20writes=20safer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/git/git.md | 3 ++- docs/zsh/zsh.md | 29 +++++++++++----------- scripts/bootstrap.sh | 56 +++-------------------------------------- scripts/git.sh | 7 +++++- scripts/zsh.sh | 59 ++++++++++++++++++++++++++++---------------- 5 files changed, 63 insertions(+), 91 deletions(-) diff --git a/docs/git/git.md b/docs/git/git.md index 9fdc5f0..d90401f 100644 --- a/docs/git/git.md +++ b/docs/git/git.md @@ -162,7 +162,8 @@ git config --global --includes --get-regexp \ Remove the include from the global Git configuration: ~~~bash -git config --global --unset-all include.path +git config --global --fixed-value --unset-all include.path \ + "$HOME/Documents/Projects/mac-dev-setup/configs/git/.gitconfig" ~~~ Review the global configuration afterward: diff --git a/docs/zsh/zsh.md b/docs/zsh/zsh.md index d913e9e..6587ad0 100644 --- a/docs/zsh/zsh.md +++ b/docs/zsh/zsh.md @@ -191,28 +191,27 @@ zsh -ic 'typeset -p POWERLEVEL9K_MODE' A final cold-start test should also be performed by closing the terminal application completely, reopening it, and confirming that the prompt and shell configuration load correctly. -## Rollback +## Backups and rollback -Before replacing an existing Zsh configuration, create backups of the current files: +`mac setup` backs up existing Zsh files before replacing them when the current +file differs from the versioned file. -```bash -cp "$HOME/.zprofile" "$HOME/.zprofile.backup" 2>/dev/null || true -cp "$HOME/.zshrc" "$HOME/.zshrc.backup" 2>/dev/null || true -cp "$HOME/.zsh_plugins.txt" "$HOME/.zsh_plugins.txt.backup" 2>/dev/null || true -cp "$HOME/.p10k.zsh" "$HOME/.p10k.zsh.backup" 2>/dev/null || true -cp "$HOME/.shell/alias.sh" "$HOME/.shell/alias.sh.backup" 2>/dev/null || true +Backups are stored under: + +```text +~/Documents/Backups/mac-dev-setup/zsh ``` -To restore the previous configuration, move the backup files back to their original locations: +To restore a previous configuration, copy the relevant backup back to its +original path. For example: ```bash -[[ -f "$HOME/.zprofile.backup" ]] && mv "$HOME/.zprofile.backup" "$HOME/.zprofile" -[[ -f "$HOME/.zshrc.backup" ]] && mv "$HOME/.zshrc.backup" "$HOME/.zshrc" -[[ -f "$HOME/.zsh_plugins.txt.backup" ]] && mv "$HOME/.zsh_plugins.txt.backup" "$HOME/.zsh_plugins.txt" -[[ -f "$HOME/.p10k.zsh.backup" ]] && mv "$HOME/.p10k.zsh.backup" "$HOME/.p10k.zsh" -[[ -f "$HOME/.shell/alias.sh.backup" ]] && mv "$HOME/.shell/alias.sh.backup" "$HOME/.shell/alias.sh" +cp "$HOME/Documents/Backups/mac-dev-setup/zsh/.zshrc..backup" \ + "$HOME/.zshrc" ``` -Only restore files whose backup exists. Review machine-specific settings in `$HOME/.shell/local.zsh` separately, because this file is not managed by the repository. +Only restore files whose backup exists. Review machine-specific settings in +`$HOME/.shell/local.zsh` separately, because this file is not managed by the +repository. After restoring the files, close the terminal application completely and reopen it to perform a cold-start test. diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index fabe5b3..82daf45 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -2,57 +2,7 @@ set -euo pipefail -# IMPORTANT: always run from repo root -cd "$(dirname "$0")/.." +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -echo "๐Ÿš€ MacDevSetup bootstrap starting..." - -# ---------------------------- -# 1. Check Homebrew -# ---------------------------- -if ! command -v brew >/dev/null 2>&1; then - echo "๐Ÿ“ฆ Homebrew not found. Installing..." - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -fi - -echo "โœ… Homebrew OK" - -# ---------------------------- -# 2. Update Brew -# ---------------------------- -echo "๐Ÿ”„ Updating Homebrew..." -brew update - -# ---------------------------- -# 3. Install Brewfiles (split) -# ---------------------------- -echo "๐Ÿ“ฆ Installing base..." -brew bundle --file=brewfiles/Brewfile.base - -echo "๐Ÿ“ฆ Installing dev..." -brew bundle --file=brewfiles/Brewfile.dev - -echo "๐Ÿ“ฆ Installing casks..." -brew bundle --file=brewfiles/Brewfile.casks - -# ---------------------------- -# 4. Basic verification -# ---------------------------- -echo "๐Ÿงช Running sanity checks..." - -command -v git >/dev/null && echo "โœ” git" -command -v jq >/dev/null && echo "โœ” jq" -command -v orbctl >/dev/null && echo "โœ” orbctl" -command -v gitleaks >/dev/null && echo "โœ” gitleaks" -command -v shellcheck >/dev/null && echo "โœ” shellcheck" - -if [ -d "/Applications/OrbStack.app" ]; then - echo "โœ” OrbStack installed" -else - echo "โš  OrbStack missing" -fi - -echo "๐Ÿงช Running pre/post verification..." -./scripts/verify.sh - -echo "๐ŸŽ‰ Bootstrap completed successfully!" +printf 'MacDevSetup bootstrap now delegates to scripts/setup.sh.\n' +exec bash "$SCRIPT_DIR/setup.sh" "$@" diff --git a/scripts/git.sh b/scripts/git.sh index edaa449..f2ebd00 100755 --- a/scripts/git.sh +++ b/scripts/git.sh @@ -12,7 +12,12 @@ source "$SCRIPT_DIR/lib/logging.sh" info "[GIT] Setup starting" if [ -f "$GITCONFIG" ]; then - git config --global --no-includes --unset-all include.path 2>/dev/null || true + if git config --global --get-all include.path 2>/dev/null | grep -Fx "$GITCONFIG" >/dev/null; then + git config --global --fixed-value --unset-all include.path "$GITCONFIG" 2>/dev/null \ + || git config --global --unset-all include.path "$GITCONFIG" 2>/dev/null \ + || true + fi + git config --global --no-includes --add include.path "$GITCONFIG" success "gitconfig applied" else diff --git a/scripts/zsh.sh b/scripts/zsh.sh index 2fdb90a..2cd6b38 100755 --- a/scripts/zsh.sh +++ b/scripts/zsh.sh @@ -5,37 +5,54 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" ZSH_CONFIG_DIR="$REPO_DIR/configs/zsh" +BACKUP_DIR="$HOME/Documents/Backups/mac-dev-setup/zsh" # shellcheck source=scripts/lib/logging.sh source "$SCRIPT_DIR/lib/logging.sh" info "[ZSH] Setup starting" -if [ -f "$ZSH_CONFIG_DIR/.zprofile" ]; then - cp "$ZSH_CONFIG_DIR/.zprofile" ~/.zprofile - success ".zprofile applied" -fi +backup_target_if_needed() { + source_file="$1" + target_file="$2" + label="$3" -if [ -f "$ZSH_CONFIG_DIR/.zshrc" ]; then - cp "$ZSH_CONFIG_DIR/.zshrc" ~/.zshrc - success ".zshrc applied" -fi + if [ ! -f "$target_file" ] || cmp -s "$source_file" "$target_file"; then + return 0 + fi -if [ -f "$ZSH_CONFIG_DIR/.zsh_plugins.txt" ]; then - cp "$ZSH_CONFIG_DIR/.zsh_plugins.txt" ~/.zsh_plugins.txt - success ".zsh_plugins.txt applied" -fi + mkdir -p "$BACKUP_DIR" + backup_file="$BACKUP_DIR/$(basename "$target_file").$(date +%Y%m%d-%H%M%S).backup" + cp -p "$target_file" "$backup_file" + info "Backed up existing $label to $backup_file" +} -if [ -f "$ZSH_CONFIG_DIR/.p10k.zsh" ]; then - cp "$ZSH_CONFIG_DIR/.p10k.zsh" ~/.p10k.zsh - success "p10k config applied" -fi +install_file() { + source_file="$1" + target_file="$2" + label="$3" -if [ -f "$ZSH_CONFIG_DIR/alias.sh" ]; then - mkdir -p ~/.shell - cp "$ZSH_CONFIG_DIR/alias.sh" ~/.shell/alias.sh - success "zsh aliases applied" -fi + if [ ! -f "$source_file" ]; then + return 0 + fi + + mkdir -p "$(dirname "$target_file")" + backup_target_if_needed "$source_file" "$target_file" "$label" + + if [ -f "$target_file" ] && cmp -s "$source_file" "$target_file"; then + success "$label already up to date" + return 0 + fi + + cp "$source_file" "$target_file" + success "$label applied" +} + +install_file "$ZSH_CONFIG_DIR/.zprofile" "$HOME/.zprofile" ".zprofile" +install_file "$ZSH_CONFIG_DIR/.zshrc" "$HOME/.zshrc" ".zshrc" +install_file "$ZSH_CONFIG_DIR/.zsh_plugins.txt" "$HOME/.zsh_plugins.txt" ".zsh_plugins.txt" +install_file "$ZSH_CONFIG_DIR/.p10k.zsh" "$HOME/.p10k.zsh" "p10k config" +install_file "$ZSH_CONFIG_DIR/alias.sh" "$HOME/.shell/alias.sh" "zsh aliases" if [ -x "$REPO_DIR/scripts/generate-zsh-completion.sh" ]; then bash "$REPO_DIR/scripts/generate-zsh-completion.sh" From dffd9e6d508ecb8c1478d1d5bbb28b4de0ae48d2 Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:19:03 +0200 Subject: [PATCH 03/23] =?UTF-8?q?=F0=9F=94=A7=20chore:=20consolidate=20bre?= =?UTF-8?q?wfiles=20and=20add=20cli=20smoke=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .husky/pre-commit | 3 + Brewfile | 121 +------------------------------------- brew/Brewfile | 120 ------------------------------------- docs/homebrew/homebrew.md | 10 +++- docs/releases/v1.0.0.md | 2 +- package.json | 2 +- scripts/test-cli.sh | 40 +++++++++++++ 7 files changed, 54 insertions(+), 244 deletions(-) mode change 100644 => 100755 .husky/pre-commit mode change 100644 => 120000 Brewfile delete mode 100644 brew/Brewfile create mode 100755 scripts/test-cli.sh diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 index 72c4429..3efeaa1 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1,4 @@ +#!/usr/bin/env sh + npm test +pre-commit run --all-files diff --git a/Brewfile b/Brewfile deleted file mode 100644 index bd376c3..0000000 --- a/Brewfile +++ /dev/null @@ -1,120 +0,0 @@ -# Taps - -tap "symfony-cli/tap" - -# CLI tools - -# Plugin manager for zsh, inspired by antigen and antibody -brew "antidote" -# Shell extension to jump to frequently used directories -brew "autojump" -# Bourne-Again SHell, a UNIX command interpreter -brew "bash" -# Clone of cat(1) with syntax highlighting and Git integration -brew "bat" -# Top-like interface for container metrics -brew "ctop" -# Disk Usage/Free Utility - a better 'df' alternative -brew "duf" -# More intuitive version of du in rust -brew "dust" -# Syntax-highlighting pager for git and diff output -brew "git-delta" -# Alternative to top/htop -brew "glances" -# Clone of ls with colorful output, file type icons, and more -brew "lsd" -# SMTP command-line test tool -brew "swaks" -# Send macOS User Notifications from the command-line -brew "terminal-notifier" -# Simplified and community-driven man pages -brew "tldr" -# Program that allows you to count code, quickly -brew "tokei" -# Display directories as trees (with optional color/HTML output) -brew "tree" - -# Dev tools - -# Run your GitHub Actions locally -brew "act" -# Static checker for GitHub Actions workflow files -brew "actionlint" -# Dependency Manager for PHP -brew "composer" -# Tool to verify that your files are in harmony with your .editorconfig -brew "editorconfig-checker" -# GitHub command-line tool -brew "gh" -# Audit git repos for secrets -brew "gitleaks" -# Smarter Dockerfile linter to validate best practices -brew "hadolint" -# Postgres C API library -brew "libpq" -# Fast, async, resource-friendly link checker -brew "lychee" -# Fast, flexible, config-based cli for linting Markdown/CommonMark files -brew "markdownlint-cli2" -# Open-source, cross-platform JavaScript runtime environment -brew "node" -# General-purpose scripting language -brew "php" -# Framework for managing multi-language pre-commit hooks -brew "pre-commit" -# Static analysis and lint tool, for (ba)sh scripts -brew "shellcheck" -# Symfony CLI helps Symfony developers manage projects, from local code to remote infrastructure -brew "symfony-cli/tap/symfony-cli", trusted: true -# Extremely fast Python package installer and resolver, written in Rust -brew "uv" - -# GUI apps - -# Cross platform SQL editor and database management app -cask "beekeeper-studio" -# Menu bar usage monitor for Codex and Claude -cask "codexbar" -# Menu bar manager -cask "jordanbaird-ice" -# Keyboard customiser -cask "karabiner-elements" -# Password manager compatible with KeePass -cask "keeweb" -# Replacement for Docker Desktop -cask "orbstack" -# Utility to uninstall apps and remove leftover files from old/uninstalled apps -cask "pearcleaner" -# System monitor for the menu bar -cask "stats" -# Unicode keyboard layout editor -cask "ukelele" -# Configurator of compatible keyboards in real time -cask "vial" -# Open-source code editor -cask "visual-studio-code" -# Rust-based terminal -cask "warp" - -# Editor extensions - -vscode "anthropic.claude-code" -vscode "avaly.restore-git-branch-tabs-improved" -vscode "bmewburn.vscode-intelephense-client" -vscode "christian-kohler.path-intellisense" -vscode "eamodio.gitlens" -vscode "github.vscode-github-actions" -vscode "hogashi.crontab-syntax-highlight" -vscode "mechatroner.rainbow-csv" -vscode "mehedidracula.php-namespace-resolver" -vscode "mikestead.dotenv" -vscode "monokai.theme-monokai-pro-vscode" -vscode "redhat.vscode-yaml" -vscode "sanderronde.phpstan-vscode" -vscode "whatwedo.twig" -vscode "xdebug.php-debug" - -# Runtime-managed tools - -uv "claude-monitor" diff --git a/Brewfile b/Brewfile new file mode 120000 index 0000000..02b8b8e --- /dev/null +++ b/Brewfile @@ -0,0 +1 @@ +profiles/full/Brewfile \ No newline at end of file diff --git a/brew/Brewfile b/brew/Brewfile deleted file mode 100644 index bd376c3..0000000 --- a/brew/Brewfile +++ /dev/null @@ -1,120 +0,0 @@ -# Taps - -tap "symfony-cli/tap" - -# CLI tools - -# Plugin manager for zsh, inspired by antigen and antibody -brew "antidote" -# Shell extension to jump to frequently used directories -brew "autojump" -# Bourne-Again SHell, a UNIX command interpreter -brew "bash" -# Clone of cat(1) with syntax highlighting and Git integration -brew "bat" -# Top-like interface for container metrics -brew "ctop" -# Disk Usage/Free Utility - a better 'df' alternative -brew "duf" -# More intuitive version of du in rust -brew "dust" -# Syntax-highlighting pager for git and diff output -brew "git-delta" -# Alternative to top/htop -brew "glances" -# Clone of ls with colorful output, file type icons, and more -brew "lsd" -# SMTP command-line test tool -brew "swaks" -# Send macOS User Notifications from the command-line -brew "terminal-notifier" -# Simplified and community-driven man pages -brew "tldr" -# Program that allows you to count code, quickly -brew "tokei" -# Display directories as trees (with optional color/HTML output) -brew "tree" - -# Dev tools - -# Run your GitHub Actions locally -brew "act" -# Static checker for GitHub Actions workflow files -brew "actionlint" -# Dependency Manager for PHP -brew "composer" -# Tool to verify that your files are in harmony with your .editorconfig -brew "editorconfig-checker" -# GitHub command-line tool -brew "gh" -# Audit git repos for secrets -brew "gitleaks" -# Smarter Dockerfile linter to validate best practices -brew "hadolint" -# Postgres C API library -brew "libpq" -# Fast, async, resource-friendly link checker -brew "lychee" -# Fast, flexible, config-based cli for linting Markdown/CommonMark files -brew "markdownlint-cli2" -# Open-source, cross-platform JavaScript runtime environment -brew "node" -# General-purpose scripting language -brew "php" -# Framework for managing multi-language pre-commit hooks -brew "pre-commit" -# Static analysis and lint tool, for (ba)sh scripts -brew "shellcheck" -# Symfony CLI helps Symfony developers manage projects, from local code to remote infrastructure -brew "symfony-cli/tap/symfony-cli", trusted: true -# Extremely fast Python package installer and resolver, written in Rust -brew "uv" - -# GUI apps - -# Cross platform SQL editor and database management app -cask "beekeeper-studio" -# Menu bar usage monitor for Codex and Claude -cask "codexbar" -# Menu bar manager -cask "jordanbaird-ice" -# Keyboard customiser -cask "karabiner-elements" -# Password manager compatible with KeePass -cask "keeweb" -# Replacement for Docker Desktop -cask "orbstack" -# Utility to uninstall apps and remove leftover files from old/uninstalled apps -cask "pearcleaner" -# System monitor for the menu bar -cask "stats" -# Unicode keyboard layout editor -cask "ukelele" -# Configurator of compatible keyboards in real time -cask "vial" -# Open-source code editor -cask "visual-studio-code" -# Rust-based terminal -cask "warp" - -# Editor extensions - -vscode "anthropic.claude-code" -vscode "avaly.restore-git-branch-tabs-improved" -vscode "bmewburn.vscode-intelephense-client" -vscode "christian-kohler.path-intellisense" -vscode "eamodio.gitlens" -vscode "github.vscode-github-actions" -vscode "hogashi.crontab-syntax-highlight" -vscode "mechatroner.rainbow-csv" -vscode "mehedidracula.php-namespace-resolver" -vscode "mikestead.dotenv" -vscode "monokai.theme-monokai-pro-vscode" -vscode "redhat.vscode-yaml" -vscode "sanderronde.phpstan-vscode" -vscode "whatwedo.twig" -vscode "xdebug.php-debug" - -# Runtime-managed tools - -uv "claude-monitor" diff --git a/docs/homebrew/homebrew.md b/docs/homebrew/homebrew.md index 30de46a..2ea1f03 100644 --- a/docs/homebrew/homebrew.md +++ b/docs/homebrew/homebrew.md @@ -6,7 +6,12 @@ The goal is not to reproduce every package currently installed on a machine, but ## Brewfile -The root `Brewfile` contains only packages that have been manually reviewed, tested, and accepted for this setup. +`profiles/full/Brewfile` contains the complete package inventory that has been +manually reviewed, tested, and accepted for this setup. + +The root `Brewfile` is a compatibility link to `profiles/full/Brewfile`, so +existing `brew bundle --file=Brewfile` commands continue to work without +duplicating the inventory. It intentionally excludes packages that may still be installed locally but are no longer considered part of the curated environment. @@ -129,4 +134,5 @@ Remove a cask with: brew uninstall --cask ``` -After uninstalling a package, remove its entry from the Brewfile and run the verification command again. +After uninstalling a package, remove its entry from `profiles/full/Brewfile` +and run the verification command again. diff --git a/docs/releases/v1.0.0.md b/docs/releases/v1.0.0.md index 206d3bb..574e988 100644 --- a/docs/releases/v1.0.0.md +++ b/docs/releases/v1.0.0.md @@ -32,7 +32,7 @@ This release establishes the repository as a production-ready macOS development ## Homebrew environment -- [x] The root `Brewfile` remains the default full package inventory. +- [x] The root `Brewfile` remains a compatibility entry for the full package inventory. - [x] Profile-specific Brewfiles are available for full and minimal setups. - [x] Homebrew package inventory documentation is up to date. - [x] Accepted command-line and application tools are documented. diff --git a/package.json b/package.json index e935bb9..13a22fb 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "doc": "docs" }, "scripts": { - "test": "echo \"no tests yet\"", + "test": "bash scripts/test-cli.sh", "prepare": "husky" }, "repository": { diff --git a/scripts/test-cli.sh b/scripts/test-cli.sh new file mode 100755 index 0000000..573a24a --- /dev/null +++ b/scripts/test-cli.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +assert_contains() { + haystack="$1" + needle="$2" + + if ! printf '%s\n' "$haystack" | grep -F "$needle" >/dev/null; then + printf 'Expected output to contain: %s\n' "$needle" >&2 + exit 1 + fi +} + +help_output="$(bash "$REPO_DIR/scripts/cli.sh" help)" +assert_contains "$help_output" "doctor" +assert_contains "$help_output" "setup" +assert_contains "$help_output" "uninstall" +assert_contains "$help_output" "update" + +setup_help_output="$(bash "$REPO_DIR/scripts/setup.sh" --help)" +assert_contains "$setup_help_output" "Usage: scripts/setup.sh" + +setup_dry_run_output="$(bash "$REPO_DIR/scripts/cli.sh" setup --profile minimal --dry-run)" +assert_contains "$setup_dry_run_output" "Dry run mode activated" + +if bash "$REPO_DIR/scripts/cli.sh" updte >/tmp/mac-dev-setup-cli-test.out 2>&1; then + printf 'Expected an unknown command to fail.\n' >&2 + exit 1 +fi +assert_contains "$(cat /tmp/mac-dev-setup-cli-test.out)" "mac update" +rm -f /tmp/mac-dev-setup-cli-test.out + +bash "$REPO_DIR/scripts/generate-zsh-completion.sh" >/dev/null +git -C "$REPO_DIR" diff --exit-code -- configs/zsh/completions/_mac >/dev/null + +printf 'CLI smoke tests passed.\n' From 284c4c269f31e5d5810726c3d225c51d66c22512 Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:20:18 +0200 Subject: [PATCH 04/23] =?UTF-8?q?=F0=9F=93=9D=20docs:=20clarify=20reposito?= =?UTF-8?q?ry=20architecture=20and=20release=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 +++++---- ROADMAP.md | 24 ++++++------ docs/architecture/current-architecture.md | 48 +++++++++++++++++++++++ docs/releases/v1.0.0.md | 6 ++- package.json | 2 +- 5 files changed, 77 insertions(+), 22 deletions(-) create mode 100644 docs/architecture/current-architecture.md diff --git a/README.md b/README.md index e8a8fb9..9331f8b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MacDevSetup -Production-ready macOS development setup managed through a small `mac` CLI. +Curated macOS development setup managed through a small `mac` CLI. MacDevSetup installs a curated Homebrew environment, applies Git and Zsh configuration, and provides repeatable commands for setup, diagnostics, updates, @@ -31,7 +31,7 @@ installed: mac help ``` -## Set Up Your Mac +## Setup Install the default development environment: @@ -52,7 +52,7 @@ Preview a setup command without changing your system: mac setup --profile minimal --dry-run ``` -During setup, MacDevSetup: +Setup: - installs Homebrew if it is missing; - installs packages from the selected profile; @@ -64,9 +64,10 @@ During setup, MacDevSetup: `minimal` installs the core command-line environment for shell, Git, and project maintenance. -`full` installs everything in the curated developer environment, including -language tooling, quality tools, GUI applications, VS Code extensions, and -container/database utilities. +`full` installs the complete curated developer environment, including language +tooling, quality tools, GUI applications, VS Code extensions, and +container/database utilities. The root `Brewfile` is a compatibility link to +`profiles/full/Brewfile`. ## CLI Usage @@ -131,14 +132,16 @@ MacDevSetup may manage these user-level files: - `~/.zsh/completions/_mac` - global Git `include.path` -Review or back up existing shell files before running `mac setup` if you already -maintain custom Zsh configuration. +Existing shell files are backed up before replacement when they differ from the +versioned files. Backups are written under +`~/Documents/Backups/mac-dev-setup/zsh`. ## Documentation Detailed tool documentation is available in [`docs`](docs). Start with: - [`docs/setup/setup.md`](docs/setup/setup.md) +- [`docs/architecture/current-architecture.md`](docs/architecture/current-architecture.md) - [`docs/troubleshooting/troubleshooting.md`](docs/troubleshooting/troubleshooting.md) - [`docs/homebrew/homebrew.md`](docs/homebrew/homebrew.md) - [`docs/zsh/zsh.md`](docs/zsh/zsh.md) diff --git a/ROADMAP.md b/ROADMAP.md index b828ff1..0e8aa77 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,26 +1,28 @@ # Roadmap -## v0.3.x โ€” Stable foundation +## Current foundation -- Brewfile architecture -- bootstrap system -- CI validation -- hardening checks +- Profile-based Brewfile architecture +- `mac` CLI setup, doctor, update, and uninstall commands +- Repository quality validation through `pre-commit` +- macOS CI validation and hardening checks -## v0.4.0 โ€” Smart layer +## Next - Smart Layer - machine audit system - missing tools detection - Brewfile drift analysis -## v0.5.0 โ€” Automation +## Later - Automation - auto-update Brewfile - dependency health check - cleanup suggestions -## v1.0.0 โ€” Stable OSS release (released 2026-06-17) +## Stable OSS Release -- fully documented system -- community contributions -- cross-machine reproducibility +- complete release checklist +- green GitHub Actions on `main` +- published `v1.0.0` tag and GitHub release +- community contribution path +- cross-machine reproducibility notes diff --git a/docs/architecture/current-architecture.md b/docs/architecture/current-architecture.md new file mode 100644 index 0000000..992a78d --- /dev/null +++ b/docs/architecture/current-architecture.md @@ -0,0 +1,48 @@ +# Current architecture + +MacDevSetup is organized around a small shell CLI and a profile-based package +inventory. + +## Entry points + +- `install.sh` installs or updates the checkout and creates the `mac` symlink. +- `scripts/cli.sh` dispatches `mac` commands. +- `scripts/commands/` contains the public command implementations. +- `scripts/setup.sh` is the internal setup runner used by `mac setup`. + +## Package profiles + +Homebrew packages are declared under `profiles//Brewfile`. + +- `profiles/minimal/Brewfile` contains the smaller command-line setup. +- `profiles/full/Brewfile` contains the complete curated workstation setup. +- The root `Brewfile` links to `profiles/full/Brewfile` for compatibility with + common `brew bundle --file=Brewfile` workflows. + +Profile names are validated in `scripts/lib/profiles.sh`. + +## Managed user files + +`mac setup` may install versioned Git and Zsh configuration. + +Zsh files are copied from `configs/zsh/` to the user's home directory. Existing +files are backed up first when their content differs from the versioned files. + +Git is configured through a global `include.path` entry that points at +`configs/git/.gitconfig`. Other global includes are left untouched. + +## Generated files + +`configs/zsh/completions/_mac` is generated from the command registry by +`scripts/generate-zsh-completion.sh`. + +The CI workflow checks that the committed completion file is up to date. + +## Quality gates + +Local and CI validation are centered on: + +- `npm test` for quick CLI smoke tests; +- `pre-commit run --all-files` for formatting, markdown, links, secrets, and + workflow linting; +- macOS workflows for Homebrew and setup validation. diff --git a/docs/releases/v1.0.0.md b/docs/releases/v1.0.0.md index 574e988..8b8d84a 100644 --- a/docs/releases/v1.0.0.md +++ b/docs/releases/v1.0.0.md @@ -1,8 +1,10 @@ # Version 1.0.0 release criteria -Version 1.0.0 is the first stable release of MacDevSetup. +Version 1.0.0 is the planned first stable release of MacDevSetup. -This release establishes the repository as a production-ready macOS development setup with a documented CLI, selectable setup profiles, update and uninstall flows, and quality checks suitable for ongoing maintenance. +This release is intended to establish the repository as a maintained macOS +development setup with a documented CLI, selectable setup profiles, update and +uninstall flows, and quality checks suitable for ongoing maintenance. ## Repository quality diff --git a/package.json b/package.json index 13a22fb..1bcc122 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mac-dev-setup", "version": "1.0.0", - "description": "![macOS](https://img.shields.io/badge/macOS-dev_environment-blue) ![status](https://img.shields.io/badge/status-stable-green) ![brew](https://img.shields.io/badge/homebrew-based-orange)", + "description": "Curated macOS development setup managed through a small mac CLI.", "main": "index.js", "directories": { "doc": "docs" From e538b2de20c6703885d01a928980e43ed4fa9def Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:21:21 +0200 Subject: [PATCH 05/23] =?UTF-8?q?=F0=9F=91=B7=20ci:=20simplify=20macos=20v?= =?UTF-8?q?alidation=20workflows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci-macos.yml | 38 ++++++--------------------------- .github/workflows/hardening.yml | 7 ++++-- docs/github-actions/ci.md | 14 ++++++++++++ 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 848b879..42bc72e 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -1,4 +1,4 @@ -name: macOS CI - Bootstrap Test +name: macOS CI on: pull_request: @@ -11,15 +11,9 @@ jobs: runs-on: macos-latest steps: - # ---------------------------- - # Checkout - # ---------------------------- - name: Checkout repository uses: actions/checkout@v4 - # ---------------------------- - # Ensure Homebrew exists - # ---------------------------- - name: Check Homebrew run: | if ! command -v brew >/dev/null 2>&1; then @@ -27,36 +21,18 @@ jobs: exit 1 fi - # ---------------------------- - # Make scripts executable - # ---------------------------- - name: Prepare scripts run: chmod +x scripts/*.sh - # ---------------------------- - # Run bootstrap (MAIN TEST) - # ---------------------------- - - name: Run setup script - run: | - ./scripts/setup.sh - - # ---------------------------- - # Validate Brewfile - # ---------------------------- - - name: Validate Brewfile - run: | - brew bundle check --file=profiles/full/Brewfile + - name: Verify setup CLI contract + run: bash scripts/cli.sh setup --profile minimal --dry-run - - name: Run bootstrap (real test) - run: | - chmod +x scripts/*.sh - ./scripts/setup.sh + - name: Install full profile + run: brew bundle --file=profiles/full/Brewfile - - name: Re-run bootstrap (idempotence check) - run: | - ./scripts/setup.sh + - name: Apply setup once + run: CI=true bash scripts/setup.sh --profile full - name: Validate Brew state run: | - brew list brew bundle check --file=profiles/full/Brewfile diff --git a/.github/workflows/hardening.yml b/.github/workflows/hardening.yml index 09d0e47..5f4288e 100644 --- a/.github/workflows/hardening.yml +++ b/.github/workflows/hardening.yml @@ -12,9 +12,12 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Install Homebrew + - name: Check Homebrew run: | - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + if ! command -v brew >/dev/null 2>&1; then + echo "Homebrew not found on macOS runner" + exit 1 + fi - name: Run hardening checks run: bash scripts/hardening.sh diff --git a/docs/github-actions/ci.md b/docs/github-actions/ci.md index c2e3743..049fe25 100644 --- a/docs/github-actions/ci.md +++ b/docs/github-actions/ci.md @@ -78,6 +78,20 @@ The current workflow validates: - EditorConfig compliance; - GitHub Actions workflow syntax. +It also verifies that the generated Zsh completion committed at +`configs/zsh/completions/_mac` is up to date. + +## macOS workflows + +Additional macOS workflows validate platform-specific behavior: + +- `.github/workflows/brewfile.yml` installs the full Homebrew profile and runs + verification plus hardening checks. +- `.github/workflows/ci-macos.yml` checks the setup CLI contract, installs the + full profile once, applies setup once, and validates the resulting Brew state. +- `.github/workflows/hardening.yml` runs the hardening gate on a macOS runner + without reinstalling Homebrew. + ## Pre-commit cache GitHub Actions caches pre-commit environments under: From bab4795807f67dcc527fc5a7e8289fbb6c563b6e Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:21:59 +0200 Subject: [PATCH 06/23] =?UTF-8?q?=F0=9F=90=9B=20fix:=20resolve=20script=20?= =?UTF-8?q?paths=20from=20repository=20root?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/install-keyboard-layout.sh | 4 +++- scripts/verify.sh | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/install-keyboard-layout.sh b/scripts/install-keyboard-layout.sh index 9c7ea47..6ecde56 100755 --- a/scripts/install-keyboard-layout.sh +++ b/scripts/install-keyboard-layout.sh @@ -2,7 +2,9 @@ set -euo pipefail -SOURCE_DIR="configs/keyboard/Francais-OSS-Mac.bundle" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +SOURCE_DIR="$REPO_DIR/configs/keyboard/Francais-OSS-Mac.bundle" TARGET_DIR="$HOME/Library/Keyboard Layouts/Francais-OSS-Mac.bundle" if [[ ! -d "$SOURCE_DIR" ]]; then diff --git a/scripts/verify.sh b/scripts/verify.sh index 623dcfb..028380b 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -51,6 +51,6 @@ fi echo "โœ” No forbidden tools detected" echo "๐Ÿ” Running hardening layer..." -./scripts/hardening.sh +bash "$SCRIPT_DIR/hardening.sh" echo "โœ… All checks passed!" From d6debd2023632a9424a1dbb07cd35244ad4646b6 Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:39:56 +0200 Subject: [PATCH 07/23] =?UTF-8?q?=F0=9F=90=9B=20fix(cli):=20handle=20--hel?= =?UTF-8?q?p=20in=20mac=20setup=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mac setup --help / -h previously failed with "Unknown option" because the command wrapper did not recognise the help flags, while scripts/setup.sh --help worked. Add the missing case so the CLI contract is consistent, and cover it in the smoke tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/commands/setup.sh | 4 ++++ scripts/test-cli.sh | 3 +++ 2 files changed, 7 insertions(+) diff --git a/scripts/commands/setup.sh b/scripts/commands/setup.sh index ece3d78..411fa6f 100755 --- a/scripts/commands/setup.sh +++ b/scripts/commands/setup.sh @@ -44,6 +44,10 @@ main() { DRY_RUN="true" shift ;; + --help|-h) + print_usage + exit 0 + ;; *) error "Unknown option: $1" print_usage >&2 diff --git a/scripts/test-cli.sh b/scripts/test-cli.sh index 573a24a..ed757d3 100755 --- a/scripts/test-cli.sh +++ b/scripts/test-cli.sh @@ -24,6 +24,9 @@ assert_contains "$help_output" "update" setup_help_output="$(bash "$REPO_DIR/scripts/setup.sh" --help)" assert_contains "$setup_help_output" "Usage: scripts/setup.sh" +mac_setup_help_output="$(bash "$REPO_DIR/scripts/cli.sh" setup --help)" +assert_contains "$mac_setup_help_output" "Usage: mac setup" + setup_dry_run_output="$(bash "$REPO_DIR/scripts/cli.sh" setup --profile minimal --dry-run)" assert_contains "$setup_dry_run_output" "Dry run mode activated" From 6e7c235202c0c4e5dbfcb2fefea5e9842b543aec Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:40:32 +0200 Subject: [PATCH 08/23] =?UTF-8?q?=F0=9F=90=9B=20fix(cli):=20handle=20--hel?= =?UTF-8?q?p=20in=20mac=20doctor=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mac doctor ignored all arguments, so mac doctor --help actually ran the full diagnostic (including brew doctor) instead of printing usage. Parse arguments and expose a read-only --help/-h flag, rejecting unknown options. Covered by the CLI smoke tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/commands/doctor.sh | 24 ++++++++++++++++++++++++ scripts/test-cli.sh | 3 +++ 2 files changed, 27 insertions(+) diff --git a/scripts/commands/doctor.sh b/scripts/commands/doctor.sh index b69d4db..2bf0491 100755 --- a/scripts/commands/doctor.sh +++ b/scripts/commands/doctor.sh @@ -9,7 +9,31 @@ REPO_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" # shellcheck source=scripts/lib/logging.sh source "$REPO_DIR/scripts/lib/logging.sh" +print_usage() { + log_line "Usage: mac doctor [--help]" + log_line "" + log_line "Run read-only diagnostics for the macOS development setup." +} + +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + --help|-h) + print_usage + exit 0 + ;; + *) + error "Unknown option: $1" + print_usage >&2 + exit 1 + ;; + esac + done +} + main() { + parse_args "$@" + info "Mac Doctor - System diagnostics" log_section "System" diff --git a/scripts/test-cli.sh b/scripts/test-cli.sh index ed757d3..caa27f8 100755 --- a/scripts/test-cli.sh +++ b/scripts/test-cli.sh @@ -27,6 +27,9 @@ assert_contains "$setup_help_output" "Usage: scripts/setup.sh" mac_setup_help_output="$(bash "$REPO_DIR/scripts/cli.sh" setup --help)" assert_contains "$mac_setup_help_output" "Usage: mac setup" +mac_doctor_help_output="$(bash "$REPO_DIR/scripts/cli.sh" doctor --help)" +assert_contains "$mac_doctor_help_output" "Usage: mac doctor" + setup_dry_run_output="$(bash "$REPO_DIR/scripts/cli.sh" setup --profile minimal --dry-run)" assert_contains "$setup_dry_run_output" "Dry run mode activated" From d1dbf10a177a617ccd8634dd1a90d1ef6b43be77 Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:42:54 +0200 Subject: [PATCH 09/23] =?UTF-8?q?=F0=9F=90=9B=20fix(cli):=20make=20mac=20d?= =?UTF-8?q?octor=20exit=20non-zero=20on=20failed=20checks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mac doctor printed "brew missing" or "mac CLI missing" but always ended with "Doctor done" and exit 0, which is misleading for CI and automation. Track required-tool failures and return a non-zero status while keeping brew warnings non-fatal. A stubbed-PATH smoke test covers the failure path. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/commands/doctor.sh | 47 ++++++++++++++++++++------------------ scripts/test-cli.sh | 18 +++++++++++++++ 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/scripts/commands/doctor.sh b/scripts/commands/doctor.sh index 2bf0491..cd22d2e 100755 --- a/scripts/commands/doctor.sh +++ b/scripts/commands/doctor.sh @@ -31,6 +31,20 @@ parse_args() { done } +DOCTOR_STATUS=0 + +check_command() { + command_name="$1" + label="$2" + + if command -v "$command_name" >/dev/null; then + success "$label installed" + else + error "$label missing" + DOCTOR_STATUS=1 + fi +} + main() { parse_args "$@" @@ -42,23 +56,9 @@ main() { log_section "Tools" - if command -v brew >/dev/null; then - success "brew installed" - else - error "brew missing" - fi - - if command -v git >/dev/null; then - success "git installed" - else - error "git missing" - fi - - if command -v zsh >/dev/null; then - success "zsh installed" - else - error "zsh missing" - fi + check_command brew "brew" + check_command git "git" + check_command zsh "zsh" log_section "Homebrew" @@ -72,14 +72,17 @@ main() { log_section "mac CLI" - if command -v mac >/dev/null; then - success "mac CLI OK" + check_command mac "mac CLI" + + log_line "" + + if [ "$DOCTOR_STATUS" -eq 0 ]; then + success "Doctor done" else - error "mac CLI missing" + error "Doctor found problems" fi - log_line "" - success "Doctor done" + return "$DOCTOR_STATUS" } main "$@" diff --git a/scripts/test-cli.sh b/scripts/test-cli.sh index caa27f8..190d057 100755 --- a/scripts/test-cli.sh +++ b/scripts/test-cli.sh @@ -40,6 +40,24 @@ fi assert_contains "$(cat /tmp/mac-dev-setup-cli-test.out)" "mac update" rm -f /tmp/mac-dev-setup-cli-test.out +doctor_bin="$(mktemp -d)" +for bootstrap_tool in bash dirname; do + ln -s "$(command -v "$bootstrap_tool")" "$doctor_bin/$bootstrap_tool" +done +for stub in sw_vers uname brew git zsh; do + printf '#!/bin/sh\nexit 0\n' >"$doctor_bin/$stub" + chmod +x "$doctor_bin/$stub" +done +# mac is intentionally absent so the diagnostic must report a failure. +if PATH="$doctor_bin" bash "$REPO_DIR/scripts/commands/doctor.sh" >/tmp/mac-dev-setup-doctor.out 2>&1; then + printf 'Expected doctor to exit non-zero when a required tool is missing.\n' >&2 + rm -rf "$doctor_bin" /tmp/mac-dev-setup-doctor.out + exit 1 +fi +assert_contains "$(cat /tmp/mac-dev-setup-doctor.out)" "mac CLI missing" +assert_contains "$(cat /tmp/mac-dev-setup-doctor.out)" "Doctor found problems" +rm -rf "$doctor_bin" /tmp/mac-dev-setup-doctor.out + bash "$REPO_DIR/scripts/generate-zsh-completion.sh" >/dev/null git -C "$REPO_DIR" diff --exit-code -- configs/zsh/completions/_mac >/dev/null From fbcb9325cd0f4f365c6d1628250b7a53444dece2 Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:43:56 +0200 Subject: [PATCH 10/23] =?UTF-8?q?=F0=9F=90=9B=20fix(setup):=20write=20setu?= =?UTF-8?q?p=20log=20to=20an=20absolute=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/setup.sh created ./logs/setup.log relative to the current directory, so running "mac setup" from another project polluted that project with a logs/ folder. Write to a user-level location ($HOME/Library/Logs/mac-dev-setup by default, overridable via MAC_DEV_SETUP_LOG_DIR) and assert the absence of CWD pollution in tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/setup.sh | 8 ++++++-- scripts/test-cli.sh | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/scripts/setup.sh b/scripts/setup.sh index 81e5bdd..27c68de 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -89,10 +89,14 @@ fi # ---------------------------- # LOGS # ---------------------------- -mkdir -p logs -LOG_FILE="logs/setup.log" +# Use an absolute, user-level location so the log never lands in whatever +# directory the user happened to run "mac setup" from. +LOG_DIR="${MAC_DEV_SETUP_LOG_DIR:-$HOME/Library/Logs/mac-dev-setup}" +mkdir -p "$LOG_DIR" +LOG_FILE="$LOG_DIR/setup.log" exec > >(tee -a "$LOG_FILE") 2>&1 +info "Logging to $LOG_FILE" # ---------------------------- # EXECUTION diff --git a/scripts/test-cli.sh b/scripts/test-cli.sh index 190d057..f7a5eb4 100755 --- a/scripts/test-cli.sh +++ b/scripts/test-cli.sh @@ -40,6 +40,23 @@ fi assert_contains "$(cat /tmp/mac-dev-setup-cli-test.out)" "mac update" rm -f /tmp/mac-dev-setup-cli-test.out +log_test_dir="$(mktemp -d)" +( + cd "$log_test_dir" + bash "$REPO_DIR/scripts/setup.sh" --profile minimal --dry-run >/dev/null +) +if [ -e "$log_test_dir/logs" ]; then + printf 'setup.sh must not create a logs directory in the current directory.\n' >&2 + rm -rf "$log_test_dir" + exit 1 +fi +rm -rf "$log_test_dir" + +if grep -Eq 'mkdir -p logs|"logs/setup\.log"' "$REPO_DIR/scripts/setup.sh"; then + printf 'setup.sh must use an absolute log path, not a relative logs/ directory.\n' >&2 + exit 1 +fi + doctor_bin="$(mktemp -d)" for bootstrap_tool in bash dirname; do ln -s "$(command -v "$bootstrap_tool")" "$doctor_bin/$bootstrap_tool" From 1fbf8b16c158893817619033b3665f19d92df2f7 Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:44:24 +0200 Subject: [PATCH 11/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(git):=20rel?= =?UTF-8?q?y=20on=20managed=20include=20for=20init.defaultBranch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git.sh wrote init.defaultBranch directly into the global git config, outside the managed include.path. The same setting already lives in configs/git/.gitconfig, so the direct write was both redundant and an intrusive change to the user's global config. Drop it and let the include be the single source of truth. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/git.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/git.sh b/scripts/git.sh index f2ebd00..bd40407 100755 --- a/scripts/git.sh +++ b/scripts/git.sh @@ -24,7 +24,4 @@ else warn "$GITCONFIG not found" fi -# Optional: global identity (important) -git config --global init.defaultBranch main - success "[GIT] Done" From b39e8b08061beb3c3a7e07c0e5d6dca93c13b4ae Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:44:46 +0200 Subject: [PATCH 12/23] =?UTF-8?q?=F0=9F=90=9B=20fix(zsh):=20stop=20regener?= =?UTF-8?q?ating=20completion=20during=20mac=20setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zsh.sh regenerated configs/zsh/completions/_mac on every "mac setup", mutating a versioned file inside the installed checkout (which breaks on read-only or otherwise pinned installs). The completion is already generated and committed during development and verified up to date in CI, so setup now installs the committed file as-is. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/zsh.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/zsh.sh b/scripts/zsh.sh index 2cd6b38..c3e56ad 100755 --- a/scripts/zsh.sh +++ b/scripts/zsh.sh @@ -54,10 +54,9 @@ install_file "$ZSH_CONFIG_DIR/.zsh_plugins.txt" "$HOME/.zsh_plugins.txt" ".zsh_p install_file "$ZSH_CONFIG_DIR/.p10k.zsh" "$HOME/.p10k.zsh" "p10k config" install_file "$ZSH_CONFIG_DIR/alias.sh" "$HOME/.shell/alias.sh" "zsh aliases" -if [ -x "$REPO_DIR/scripts/generate-zsh-completion.sh" ]; then - bash "$REPO_DIR/scripts/generate-zsh-completion.sh" -fi - +# The completion file is generated and committed during development (and +# verified in CI), so setup installs the versioned copy as-is instead of +# regenerating it inside the install tree. if [ -d "$ZSH_CONFIG_DIR/completions" ]; then mkdir -p ~/.zsh/completions cp "$ZSH_CONFIG_DIR"/completions/* ~/.zsh/completions/ From 9c80d11b64abf46ecd8466b92643f5774a17577f Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:45:42 +0200 Subject: [PATCH 13/23] =?UTF-8?q?=E2=9C=A8=20feat(uninstall):=20cover=20al?= =?UTF-8?q?l=20managed=20files=20in=20--remove-config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README documents seven MacDevSetup-managed files, but --remove-config only handled the git include, alias.sh and the zsh completion. Extend remove_config to the managed zsh dotfiles (.zprofile, .zshrc, .zsh_plugins.txt, .p10k.zsh) using the existing remove_if_identical guard, so files are removed only when they match the versioned copy and left untouched (with a warning) otherwise. Covered by a dry-run smoke test against a temporary HOME. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/commands/uninstall.sh | 4 ++++ scripts/test-cli.sh | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/scripts/commands/uninstall.sh b/scripts/commands/uninstall.sh index 6f86453..b3f15da 100755 --- a/scripts/commands/uninstall.sh +++ b/scripts/commands/uninstall.sh @@ -181,6 +181,10 @@ remove_git_include() { remove_config() { remove_git_include + remove_if_identical "$REPO_DIR/configs/zsh/.zprofile" "$HOME/.zprofile" ".zprofile" + remove_if_identical "$REPO_DIR/configs/zsh/.zshrc" "$HOME/.zshrc" ".zshrc" + remove_if_identical "$REPO_DIR/configs/zsh/.zsh_plugins.txt" "$HOME/.zsh_plugins.txt" ".zsh_plugins.txt" + remove_if_identical "$REPO_DIR/configs/zsh/.p10k.zsh" "$HOME/.p10k.zsh" "p10k config" remove_if_identical "$REPO_DIR/configs/zsh/alias.sh" "$HOME/.shell/alias.sh" "zsh aliases" remove_if_identical "$REPO_DIR/configs/zsh/completions/_mac" "$HOME/.zsh/completions/_mac" "zsh completion" } diff --git a/scripts/test-cli.sh b/scripts/test-cli.sh index f7a5eb4..add06c4 100755 --- a/scripts/test-cli.sh +++ b/scripts/test-cli.sh @@ -57,6 +57,14 @@ if grep -Eq 'mkdir -p logs|"logs/setup\.log"' "$REPO_DIR/scripts/setup.sh"; then exit 1 fi +uninstall_home="$(mktemp -d)" +cp "$REPO_DIR/configs/zsh/.zprofile" "$uninstall_home/.zprofile" +printf 'unmanaged content\n' >"$uninstall_home/.zshrc" +uninstall_config_output="$(HOME="$uninstall_home" bash "$REPO_DIR/scripts/commands/uninstall.sh" --remove-config --dry-run 2>&1)" +assert_contains "$uninstall_config_output" "Would remove .zprofile" +assert_contains "$uninstall_config_output" ".zshrc differs" +rm -rf "$uninstall_home" + doctor_bin="$(mktemp -d)" for bootstrap_tool in bash dirname; do ln -s "$(command -v "$bootstrap_tool")" "$doctor_bin/$bootstrap_tool" From b5066d97f600e767e0152d5054d568e48bcba9ec Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:46:48 +0200 Subject: [PATCH 14/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(profiles):?= =?UTF-8?q?=20validate=20profiles=20dynamically?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit profile_list discovers profiles by scanning profiles/, but profile_name_is_valid hard-coded "full|minimal", so any newly added profile directory was listed yet rejected by validation. Validate names against a safe charset (which also prevents path traversal) and let profile_exists decide availability. Usage strings now use a generic placeholder alongside the dynamic profile list. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/commands/setup.sh | 2 +- scripts/lib/profiles.sh | 7 +++++-- scripts/setup.sh | 2 +- scripts/test-cli.sh | 12 ++++++++++++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/scripts/commands/setup.sh b/scripts/commands/setup.sh index 411fa6f..e663d57 100755 --- a/scripts/commands/setup.sh +++ b/scripts/commands/setup.sh @@ -12,7 +12,7 @@ source "$REPO_DIR/scripts/lib/logging.sh" source "$REPO_DIR/scripts/lib/profiles.sh" print_usage() { - log_line "Usage: mac setup [--profile full|minimal] [--dry-run]" + log_line "Usage: mac setup [--profile ] [--dry-run]" log_line "Profiles: $(profile_list "$REPO_DIR")" } diff --git a/scripts/lib/profiles.sh b/scripts/lib/profiles.sh index 036fc4d..2c4a720 100644 --- a/scripts/lib/profiles.sh +++ b/scripts/lib/profiles.sh @@ -27,9 +27,12 @@ profile_exists() { profile_name_is_valid() { profile="$1" + # Accept any profile that ships a directory under profiles/, but keep the + # name to a safe charset so it can never escape the profiles directory. case "$profile" in - full|minimal) return 0 ;; - *) return 1 ;; + "") return 1 ;; + *[!a-zA-Z0-9_-]*) return 1 ;; + *) return 0 ;; esac } diff --git a/scripts/setup.sh b/scripts/setup.sh index 27c68de..997a5d7 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -21,7 +21,7 @@ source "$SCRIPT_DIR/lib/profiles.sh" REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" print_usage() { - log_line "Usage: scripts/setup.sh [--profile full|minimal] [--dry-run]" + log_line "Usage: scripts/setup.sh [--profile ] [--dry-run]" log_line "Profiles: $(profile_list "$REPO_DIR")" } diff --git a/scripts/test-cli.sh b/scripts/test-cli.sh index add06c4..dddb8b4 100755 --- a/scripts/test-cli.sh +++ b/scripts/test-cli.sh @@ -57,6 +57,18 @@ if grep -Eq 'mkdir -p logs|"logs/setup\.log"' "$REPO_DIR/scripts/setup.sh"; then exit 1 fi +( + # shellcheck source=scripts/lib/profiles.sh + source "$REPO_DIR/scripts/lib/profiles.sh" + if ! profile_name_is_valid full; then exit 1; fi + if profile_name_is_valid "../etc"; then exit 1; fi + if ! profile_validate "$REPO_DIR" minimal; then exit 1; fi + if profile_validate "$REPO_DIR" no-such-profile; then exit 1; fi +) || { + printf 'Profile validation contract failed.\n' >&2 + exit 1 +} + uninstall_home="$(mktemp -d)" cp "$REPO_DIR/configs/zsh/.zprofile" "$uninstall_home/.zprofile" printf 'unmanaged content\n' >"$uninstall_home/.zshrc" From 40273c131dc91e84ebb40ded836d4eb462eeee41 Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:48:10 +0200 Subject: [PATCH 15/23] =?UTF-8?q?=F0=9F=91=B7=20ci:=20lint=20commits=20wit?= =?UTF-8?q?h=20commitlint=20instead=20of=20check-commit.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/check-commit.sh hard-coded only five gitmoji and checked just the last commit, so it diverged from the commitlint config used by the local commit-msg hook and the Makefile, and would have rejected valid history (emoji such as the refactor/bump gitmoji, including Dependabot's prefix). Replace it with commitlint over the full PR/push range, reusing the same shared config as the local hook, and drop the redundant script. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 31 +++++++++++++++++++++++++++++-- scripts/check-commit.sh | 12 ------------ 2 files changed, 29 insertions(+), 14 deletions(-) delete mode 100755 scripts/check-commit.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 149e6e3..958f918 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,8 @@ jobs: steps: - name: Check out repository uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 @@ -64,8 +66,33 @@ jobs: echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" - - name: Check commit format - run: bash scripts/check-commit.sh + - name: Install Node dependencies + env: + HUSKY: "0" + run: npm ci + + - name: Lint commit messages + env: + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + PUSH_BEFORE_SHA: ${{ github.event.before }} + PUSH_AFTER_SHA: ${{ github.sha }} + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + from="$PR_BASE_SHA" + to="$PR_HEAD_SHA" + else + from="$PUSH_BEFORE_SHA" + to="$PUSH_AFTER_SHA" + fi + + # First push of a branch reports an all-zero "before" SHA; fall back + # to the repository root so the range stays valid. + if printf '%s' "$from" | grep -Eq '^0+$'; then + from="$(git rev-list --max-parents=0 "$to" | tail -1)" + fi + + npx --no -- commitlint --from "$from" --to "$to" --verbose - name: Cache pre-commit environments uses: actions/cache@v4 diff --git a/scripts/check-commit.sh b/scripts/check-commit.sh deleted file mode 100755 index 7c79617..0000000 --- a/scripts/check-commit.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -MSG=$(git log -1 --pretty=%B) - -echo "Checking commit format..." - -if [[ ! "$MSG" =~ ^(โœจ|๐Ÿ›|๐Ÿ‘ท|๐Ÿ“|๐Ÿ”ง) ]]; then - echo "โŒ Commit must start with gitmoji (โœจ๐Ÿ›๐Ÿ‘ท๐Ÿ“๐Ÿ”ง)" - exit 1 -fi - -echo "โœ” Commit format OK" From 84e79497ed5a8eea694b0a44ad6a2cb5e8997054 Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:49:24 +0200 Subject: [PATCH 16/23] =?UTF-8?q?=F0=9F=91=B7=20ci:=20consolidate=20macOS?= =?UTF-8?q?=20workflows=20into=20a=20single=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three macOS workflows overlapped: brewfile.yml installed the full profile and ran hardening twice (once via verify.sh, once directly), hardening.yml ran hardening a third time, and ci-macos.yml installed the full profile again. All three also triggered on every branch push. Collapse them into one macOS workflow that caches Homebrew, installs the full profile, applies setup once, and runs verify.sh (which already runs the hardening layer). Trigger only on pull_request and pushes to main to stop burning macOS minutes on every feature-branch push. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/brewfile.yml | 59 --------------------------------- .github/workflows/ci-macos.yml | 17 +++++++--- .github/workflows/hardening.yml | 23 ------------- 3 files changed, 12 insertions(+), 87 deletions(-) delete mode 100644 .github/workflows/brewfile.yml delete mode 100644 .github/workflows/hardening.yml diff --git a/.github/workflows/brewfile.yml b/.github/workflows/brewfile.yml deleted file mode 100644 index b9d2f61..0000000 --- a/.github/workflows/brewfile.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: MacDevSetup CI (fast) - -on: - push: - pull_request: - -jobs: - ci: - runs-on: macos-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - # ---------------------------- - # Cache Homebrew - # ---------------------------- - - name: Cache Homebrew - uses: actions/cache@v4 - with: - path: | - ~/Library/Caches/Homebrew - key: brew-${{ runner.os }}-${{ hashFiles('profiles/**/Brewfile') }} - - # ---------------------------- - # Ensure Homebrew - # ---------------------------- - - name: Ensure Homebrew - shell: bash - run: | - if ! command -v brew >/dev/null 2>&1; then - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - fi - - # ---------------------------- - # Optimize brew - # ---------------------------- - - name: Optimize brew - run: | - brew update - brew analytics off - - # ---------------------------- - # Install profile Brewfile - # ---------------------------- - - name: Install full profile - run: brew bundle --file=profiles/full/Brewfile - - # ---------------------------- - # Verify (always after install) - # ---------------------------- - - name: Verify setup - run: bash scripts/verify.sh - - # ---------------------------- - # Hardening (after verify) - # ---------------------------- - - name: Run hardening checks - run: bash scripts/hardening.sh diff --git a/.github/workflows/ci-macos.yml b/.github/workflows/ci-macos.yml index 42bc72e..e091102 100644 --- a/.github/workflows/ci-macos.yml +++ b/.github/workflows/ci-macos.yml @@ -7,19 +7,27 @@ on: - main jobs: - macos-bootstrap: + macos: runs-on: macos-latest steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Check Homebrew + - name: Cache Homebrew + uses: actions/cache@v4 + with: + path: ~/Library/Caches/Homebrew + key: brew-${{ runner.os }}-${{ hashFiles('profiles/**/Brewfile') }} + + - name: Ensure Homebrew run: | if ! command -v brew >/dev/null 2>&1; then echo "Homebrew not found (unexpected on macOS runner)" exit 1 fi + brew update + brew analytics off - name: Prepare scripts run: chmod +x scripts/*.sh @@ -33,6 +41,5 @@ jobs: - name: Apply setup once run: CI=true bash scripts/setup.sh --profile full - - name: Validate Brew state - run: | - brew bundle check --file=profiles/full/Brewfile + - name: Verify setup and hardening + run: bash scripts/verify.sh diff --git a/.github/workflows/hardening.yml b/.github/workflows/hardening.yml deleted file mode 100644 index 5f4288e..0000000 --- a/.github/workflows/hardening.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Hardening Gate - -on: - push: - pull_request: - -jobs: - hardening: - runs-on: macos-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Check Homebrew - run: | - if ! command -v brew >/dev/null 2>&1; then - echo "Homebrew not found on macOS runner" - exit 1 - fi - - - name: Run hardening checks - run: bash scripts/hardening.sh From 5be3de95d9dfd403561598562c4ce5b111a5b669 Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:50:29 +0200 Subject: [PATCH 17/23] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(hardening):?= =?UTF-8?q?=20scope=20the=20forbidden-tool=20denylist=20to=20profiles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hardening.sh grepped the machine's global `brew list`, so any developer machine that legitimately had one of the denied tools (e.g. direnv) would fail the gate. Scan the declared profile Brewfiles instead โ€” the contract this repository actually owns โ€” document the rationale, and fold the orbctl check (previously duplicated in verify.sh) into the single denylist. Brew doctor/outdated stay informational and only run when brew is available. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/hardening.sh | 31 ++++++++++++++++++++++--------- scripts/verify.sh | 11 +---------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/scripts/hardening.sh b/scripts/hardening.sh index 7a83000..d7f6a9c 100755 --- a/scripts/hardening.sh +++ b/scripts/hardening.sh @@ -2,18 +2,31 @@ set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + echo "๐Ÿ” MacDevSetup hardening checks..." -echo "โœ” Checking forbidden tools..." -if brew list | grep -E "lazygit|watchexec|hyperfine|direnv|mas" >/dev/null 2>&1; then - echo "โŒ Forbidden tool detected" - exit 1 -fi +# Tools intentionally kept out of the curated setup. The check scans the +# declared profile Brewfiles (the contract this repository owns) instead of +# the machine's global `brew list`, which may legitimately contain these for +# unrelated reasons and would otherwise produce false positives. +FORBIDDEN_TOOLS="lazygit watchexec hyperfine direnv mas orbctl" -echo "โœ” Checking brew doctor..." -brew doctor || true +echo "โœ” Checking forbidden tools in profiles..." +for tool in $FORBIDDEN_TOOLS; do + if grep -REq "\"$tool\"" "$REPO_DIR/profiles"; then + echo "โŒ Forbidden tool declared in a profile Brewfile: $tool" + exit 1 + fi +done -echo "โœ” Checking outdated packages..." -brew outdated || true +if command -v brew >/dev/null 2>&1; then + echo "โœ” Checking brew doctor..." + brew doctor || true + + echo "โœ” Checking outdated packages..." + brew outdated || true +fi echo "โœ” Hardening complete" diff --git a/scripts/verify.sh b/scripts/verify.sh index 028380b..7862967 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -40,16 +40,7 @@ done echo "โœ” Profile Brewfiles satisfied" -# ---------------------------- -# Check forbidden tools -# ---------------------------- -if grep -Rq "orbctl" "$REPO_DIR/profiles"; then - echo "โŒ Forbidden tool detected: orbctl" - exit 1 -fi - -echo "โœ” No forbidden tools detected" - +# Forbidden-tool policy (including orbctl) is owned by the hardening layer. echo "๐Ÿ” Running hardening layer..." bash "$SCRIPT_DIR/hardening.sh" From 23b3b9f3394abd376c0e6616c5c777b5ec11b221 Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:50:57 +0200 Subject: [PATCH 18/23] =?UTF-8?q?=F0=9F=90=9B=20fix(zsh):=20do=20not=20fai?= =?UTF-8?q?l=20setup=20when=20completion=20is=20not=20active=20yet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The post-install check ran `exit 1` if the mac completion was not already registered in a freshly spawned shell. On customised shells, or before compinit has picked up ~/.zsh/completions, this made an otherwise successful "mac setup" report failure. Downgrade it to a warning; the completion loads in a new shell session. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/zsh.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/zsh.sh b/scripts/zsh.sh index c3e56ad..dfc3981 100755 --- a/scripts/zsh.sh +++ b/scripts/zsh.sh @@ -67,8 +67,10 @@ if command -v zsh >/dev/null; then if ZDOTDIR="$HOME" zsh -ic '[[ "${_comps[mac]:-}" == "_mac" ]]' >/dev/null 2>&1; then success "mac zsh completion registered" else - error "mac zsh completion could not be registered" - exit 1 + # Non-fatal: a successful install must not fail just because completions + # have not been compiled in this shell yet. They load in a new session + # once compinit picks up ~/.zsh/completions. + warn "mac zsh completion not active yet; it should load in a new shell session" fi fi From 80862bcfd11c3d78c45ff77646ed6dbd03966aaa Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:52:20 +0200 Subject: [PATCH 19/23] =?UTF-8?q?=F0=9F=93=9D=20docs:=20point=20Brewfile?= =?UTF-8?q?=20edit=20instructions=20at=20profiles/full/Brewfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tool docs told readers to add/remove entries "in/from the root Brewfile", but the root Brewfile is only a compatibility symlink; the editable source is profiles/full/Brewfile (as homebrew.md and the architecture docs already state). Update the boilerplate across the tool docs to reference the real source, leaving the legitimate descriptions of the compatibility symlink untouched. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/cli/bat.md | 2 +- docs/cli/duf.md | 2 +- docs/cli/dust.md | 2 +- docs/cli/tokei.md | 2 +- docs/cli/tree.md | 2 +- docs/containers/ctop.md | 2 +- docs/containers/orbstack.md | 4 ++-- docs/database/beekeeper-studio.md | 4 ++-- docs/docker/hadolint.md | 4 ++-- docs/git/git-delta.md | 2 +- docs/github-actions/act.md | 4 ++-- docs/github-actions/actionlint.md | 4 ++-- docs/macos/pearcleaner.md | 4 ++-- docs/quality/editorconfig-checker.md | 2 +- docs/quality/lychee.md | 2 +- docs/quality/markdownlint-cli2.md | 2 +- docs/security/gitleaks.md | 4 ++-- docs/shell/shellcheck.md | 4 ++-- 18 files changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/cli/bat.md b/docs/cli/bat.md index d3ecac1..edafc65 100644 --- a/docs/cli/bat.md +++ b/docs/cli/bat.md @@ -152,4 +152,4 @@ Remove bat with Homebrew: brew uninstall bat ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. diff --git a/docs/cli/duf.md b/docs/cli/duf.md index d4ee355..f22872e 100644 --- a/docs/cli/duf.md +++ b/docs/cli/duf.md @@ -124,4 +124,4 @@ Remove duf with Homebrew: brew uninstall duf ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. diff --git a/docs/cli/dust.md b/docs/cli/dust.md index fbedd30..36ac204 100644 --- a/docs/cli/dust.md +++ b/docs/cli/dust.md @@ -128,4 +128,4 @@ Remove dust with Homebrew: brew uninstall dust ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. diff --git a/docs/cli/tokei.md b/docs/cli/tokei.md index 7a3d52e..8035081 100644 --- a/docs/cli/tokei.md +++ b/docs/cli/tokei.md @@ -149,4 +149,4 @@ Remove tokei with Homebrew: brew uninstall tokei ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. diff --git a/docs/cli/tree.md b/docs/cli/tree.md index 6e41592..6bb8507 100644 --- a/docs/cli/tree.md +++ b/docs/cli/tree.md @@ -155,4 +155,4 @@ Remove tree with Homebrew: brew uninstall tree ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. diff --git a/docs/containers/ctop.md b/docs/containers/ctop.md index da9a79e..091b1cb 100644 --- a/docs/containers/ctop.md +++ b/docs/containers/ctop.md @@ -168,5 +168,5 @@ Remove ctop with Homebrew: brew uninstall ctop ``` -Then remove its entry from the root `Brewfile` and remove any related shell +Then remove its entry from `profiles/full/Brewfile` and remove any related shell alias. diff --git a/docs/containers/orbstack.md b/docs/containers/orbstack.md index d21477a..095dd29 100644 --- a/docs/containers/orbstack.md +++ b/docs/containers/orbstack.md @@ -12,7 +12,7 @@ OrbStack is installed through Homebrew Cask: brew install --cask orbstack ``` -It is also declared in the root `Brewfile`: +It is also declared in `profiles/full/Brewfile`: ```bash brew bundle install --file=Brewfile @@ -165,6 +165,6 @@ Remove OrbStack with: brew uninstall --cask orbstack ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. Uninstalling the application and deleting its local data are separate operations. Local containers, images, volumes, or virtual machines should only be removed after confirming that they are no longer needed. diff --git a/docs/database/beekeeper-studio.md b/docs/database/beekeeper-studio.md index ad40d8e..0fa4c8c 100644 --- a/docs/database/beekeeper-studio.md +++ b/docs/database/beekeeper-studio.md @@ -12,7 +12,7 @@ Beekeeper Studio is installed through Homebrew Cask: brew install --cask beekeeper-studio ``` -It is also declared in the root `Brewfile`: +It is also declared in `profiles/full/Brewfile`: ```bash brew bundle install --file=Brewfile @@ -130,6 +130,6 @@ Remove the application with: brew uninstall --cask beekeeper-studio ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. Homebrew may leave user-specific application data in the macOS Library. Delete that data only after confirming that saved connections, preferences, and local information are no longer needed. diff --git a/docs/docker/hadolint.md b/docs/docker/hadolint.md index 753ed3e..137e1a6 100644 --- a/docs/docker/hadolint.md +++ b/docs/docker/hadolint.md @@ -12,7 +12,7 @@ Hadolint is installed through Homebrew: brew install hadolint ``` -It is also declared in the root `Brewfile`: +It is also declared in `profiles/full/Brewfile`: ```bash brew bundle install --file=Brewfile @@ -99,6 +99,6 @@ Remove Hadolint with: brew uninstall hadolint ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. Any related pre-commit hook or Hadolint configuration must also be removed separately. diff --git a/docs/git/git-delta.md b/docs/git/git-delta.md index edcce8b..8191ba6 100644 --- a/docs/git/git-delta.md +++ b/docs/git/git-delta.md @@ -228,4 +228,4 @@ Remove delta with Homebrew: brew uninstall git-delta ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. diff --git a/docs/github-actions/act.md b/docs/github-actions/act.md index 9aad329..dfba12b 100644 --- a/docs/github-actions/act.md +++ b/docs/github-actions/act.md @@ -12,7 +12,7 @@ Act is installed through Homebrew: brew install act ``` -It is also declared in the root `Brewfile`: +It is also declared in `profiles/full/Brewfile`: ```bash brew bundle install --file=Brewfile @@ -214,6 +214,6 @@ Remove Act with: brew uninstall act ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. Any project-specific Act configuration must also be reviewed and removed separately. diff --git a/docs/github-actions/actionlint.md b/docs/github-actions/actionlint.md index 6fbdf0c..68f53e3 100644 --- a/docs/github-actions/actionlint.md +++ b/docs/github-actions/actionlint.md @@ -12,7 +12,7 @@ Actionlint is installed through Homebrew: brew install actionlint ``` -It is also declared in the root `Brewfile`: +It is also declared in `profiles/full/Brewfile`: ```bash brew bundle install --file=Brewfile @@ -116,6 +116,6 @@ Remove Actionlint with: brew uninstall actionlint ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. Any related pre-commit hook must also be removed separately. diff --git a/docs/macos/pearcleaner.md b/docs/macos/pearcleaner.md index 45c7aca..e2cfa42 100644 --- a/docs/macos/pearcleaner.md +++ b/docs/macos/pearcleaner.md @@ -12,7 +12,7 @@ Pearcleaner is installed through Homebrew Cask: brew install --cask pearcleaner ``` -It is also declared in the root `Brewfile`: +It is also declared in `profiles/full/Brewfile`: ```bash brew bundle install --file=Brewfile @@ -137,6 +137,6 @@ Remove Pearcleaner with: brew uninstall --cask pearcleaner ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. Removing Pearcleaner does not restore applications or files that were previously deleted with it. diff --git a/docs/quality/editorconfig-checker.md b/docs/quality/editorconfig-checker.md index 5ac7639..4f05c81 100644 --- a/docs/quality/editorconfig-checker.md +++ b/docs/quality/editorconfig-checker.md @@ -188,7 +188,7 @@ brew uninstall editorconfig-checker Then remove: -- its entry from the root `Brewfile`; +- its entry from `profiles/full/Brewfile`; - its hook from `.pre-commit-config.yaml`; - `.editorconfig` only if the repository no longer needs shared formatting rules. diff --git a/docs/quality/lychee.md b/docs/quality/lychee.md index 53aa6a2..461c34c 100644 --- a/docs/quality/lychee.md +++ b/docs/quality/lychee.md @@ -214,6 +214,6 @@ brew uninstall lychee Then remove: -- its entry from the root `Brewfile`; +- its entry from `profiles/full/Brewfile`; - its hook from `.pre-commit-config.yaml`; - any repository-specific lychee configuration no longer in use. diff --git a/docs/quality/markdownlint-cli2.md b/docs/quality/markdownlint-cli2.md index 915ec2f..d8f8585 100644 --- a/docs/quality/markdownlint-cli2.md +++ b/docs/quality/markdownlint-cli2.md @@ -198,6 +198,6 @@ brew uninstall markdownlint-cli2 Then remove: -- its entry from the root `Brewfile`; +- its entry from `profiles/full/Brewfile`; - its hook from `.pre-commit-config.yaml`; - `.markdownlint-cli2.yaml` if no other Markdown tooling uses it. diff --git a/docs/security/gitleaks.md b/docs/security/gitleaks.md index 92bb473..72cd931 100644 --- a/docs/security/gitleaks.md +++ b/docs/security/gitleaks.md @@ -12,7 +12,7 @@ Gitleaks is installed through Homebrew: brew install gitleaks ``` -It is also declared in the root `Brewfile`, so it can be installed with the rest of the curated environment: +It is also declared in `profiles/full/Brewfile`, so it can be installed with the rest of the curated environment: ```bash brew bundle install --file=Brewfile @@ -82,6 +82,6 @@ Remove Gitleaks with: brew uninstall gitleaks ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. Any related pre-commit hook must also be removed separately. diff --git a/docs/shell/shellcheck.md b/docs/shell/shellcheck.md index ba7c097..3e3ed9c 100644 --- a/docs/shell/shellcheck.md +++ b/docs/shell/shellcheck.md @@ -12,7 +12,7 @@ ShellCheck is installed through Homebrew: brew install shellcheck ``` -It is also declared in the root `Brewfile`: +It is also declared in `profiles/full/Brewfile`: ```bash brew bundle install --file=Brewfile @@ -109,6 +109,6 @@ Remove ShellCheck with: brew uninstall shellcheck ``` -Then remove its entry from the root `Brewfile`. +Then remove its entry from `profiles/full/Brewfile`. Any related pre-commit hook must also be removed separately. From 62e1f92bcbe1b08efe1ed48e01025777b0c3ae3d Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:52:47 +0200 Subject: [PATCH 20/23] =?UTF-8?q?=F0=9F=91=B7=20ci:=20reject=20committed?= =?UTF-8?q?=20local=20artifacts=20in=20the=20repository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a guard that fails the quality job if known local artifacts (.DS_Store, a logs/ directory, or *.log files) are ever tracked. These are gitignored today, but the check makes the boundary explicit so a forced add cannot slip generated state into the repository. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 958f918..a6b183d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,15 @@ jobs: with: fetch-depth: 0 + - name: Reject committed local artifacts + run: | + if git ls-files \ + | grep -E '(^|/)\.DS_Store$|(^|/)logs/|\.log$'; then + echo "Local artifacts (.DS_Store, logs/, *.log) must not be committed." + exit 1 + fi + echo "No committed local artifacts." + - name: Set up Python uses: actions/setup-python@v5 with: From 87d582b675bf52489a2486b3cd1ac34dffb18491 Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:53:12 +0200 Subject: [PATCH 21/23] =?UTF-8?q?=F0=9F=94=A7=20chore(deps):=20declare=20@?= =?UTF-8?q?gitmoji/gitmoji-regex=20as=20a=20direct=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commitlint.config.cjs requires @gitmoji/gitmoji-regex directly, but it was only present as a transitive dependency of commitlint-config-gitmoji, so a dedupe or upstream change could break commit linting. Declare it explicitly in devDependencies. Co-Authored-By: Claude Opus 4.8 (1M context) --- package-lock.json | 1 + package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index cab1d9b..670860d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "devDependencies": { "@commitlint/cli": "^21.0.2", "@commitlint/config-conventional": "^21.0.2", + "@gitmoji/gitmoji-regex": "^1.0.0", "commitlint-config-gitmoji": "^2.3.1", "husky": "^9.1.7" } diff --git a/package.json b/package.json index 1bcc122..3a1bce9 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "devDependencies": { "@commitlint/cli": "^21.0.2", "@commitlint/config-conventional": "^21.0.2", + "@gitmoji/gitmoji-regex": "^1.0.0", "commitlint-config-gitmoji": "^2.3.1", "husky": "^9.1.7" } From ea16d7e90cbfcfaf2c18838e1bba0b8026fbf11a Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 18:56:08 +0200 Subject: [PATCH 22/23] =?UTF-8?q?=F0=9F=93=9D=20docs(release):=20reconcile?= =?UTF-8?q?=20version=20metadata=20to=20a=20pre-1.0=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The package declared 1.0.0 and the changelog had a dated 1.0.0 section, but no v1.0.0 tag exists (tags stop at v0.4.1) and the entire modular mac CLI is in fact unreleased; the old "1.0.0" entry actually described the 0.2.0 work. Move the package to 0.5.0, rebuild the changelog honestly (Unreleased holds the CLI and audit fixes; fill in the missing 0.2.0 and 0.4.1 sections from history), and uncheck the 1.0.0 version criteria that are no longer true so they read as targets again. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 54 ++++++++++++++++++++++++++++++++++++++--- docs/releases/v1.0.0.md | 6 ++--- package-lock.json | 4 +-- package.json | 2 +- 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37c0632..4918c82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,57 @@ The format is inspired by Keep a Changelog, and the project follows semantic ver ## Unreleased -## 1.0.0 - 2026-06-17 +### Added + +- `mac` command-line interface with a modular, auto-discovered command + architecture (setup, doctor, update, uninstall) and dynamic help built from a + command registry. +- Official `install.sh` bootstrap that clones the repository, manages the `mac` + symlink, and safely manages the shell PATH block. +- Self-update mechanism (`mac update`) using a temporary worktree and + fast-forward-only updates with rollback. +- Full uninstall command (`mac uninstall`) with `--remove-config` and + `--remove-install-dir`, removing only managed files that match the versioned + copies (now covering all managed Zsh dotfiles). +- Setup profiles system (`full` and `minimal`) with profile-aware Brewfiles and + dynamic profile validation. +- Generated Zsh completion for `mac`, built from the command registry and + verified in CI. +- Structured logging helpers and unified error handling across the CLI. +- CLI smoke tests (`npm test`) and commit linting through commitlint. + +### Changed + +- Homebrew setup split into profile Brewfiles; the root `Brewfile` is now a + compatibility symlink to `profiles/full/Brewfile`. +- Git configuration relies solely on the managed `include.path`, with no direct + writes to the global config. +- macOS CI consolidated into a single gate, and commits are linted with + commitlint across the pull request range. +- `mac setup` writes its log to a user-level location instead of the current + working directory. + +### Fixed + +- `mac setup --help` and `mac doctor --help` print usage instead of failing or + silently running diagnostics. +- `mac doctor` exits non-zero when a required tool is missing. +- Setup no longer regenerates the versioned completion file and no longer fails + when completion is not yet active in the current shell. + +## 0.4.1 - 2026-06-17 + +### Added + +- macOS bootstrap system with split Brewfiles and validation scripts. +- Fast, optimized CI pipeline. +- Standardized gitmoji commit conventions with commitlint and husky. + +### Fixed + +- Stabilized the Homebrew install flow and hardening order in CI. + +## 0.2.0 - 2026-06-17 ### Added @@ -44,8 +94,6 @@ The format is inspired by Keep a Changelog, and the project follows semantic ver - Added private vulnerability reporting guidance. - Added automated secret detection and repository security checks to CI. -For the complete release criteria, see [version 1.0.0 release criteria](docs/releases/v1.0.0.md). - ## 0.1.0 - 2026-06-16 ### Added diff --git a/docs/releases/v1.0.0.md b/docs/releases/v1.0.0.md index 8b8d84a..ef24cbd 100644 --- a/docs/releases/v1.0.0.md +++ b/docs/releases/v1.0.0.md @@ -18,11 +18,11 @@ uninstall flows, and quality checks suitable for ongoing maintenance. ## Versioning and metadata -- [x] Package metadata declares version `1.0.0`. -- [x] Package lock metadata declares version `1.0.0`. +- [ ] Package metadata declares version `1.0.0`. +- [ ] Package lock metadata declares version `1.0.0`. - [x] The bundled French OSS keyboard layout metadata declares version `1.0.0`. - [x] Package metadata uses the repository MIT license. -- [x] The changelog contains a dated `1.0.0` section. +- [ ] The changelog contains a dated `1.0.0` section. ## Installer and CLI diff --git a/package-lock.json b/package-lock.json index 670860d..046fbd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mac-dev-setup", - "version": "1.0.0", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mac-dev-setup", - "version": "1.0.0", + "version": "0.5.0", "license": "MIT", "devDependencies": { "@commitlint/cli": "^21.0.2", diff --git a/package.json b/package.json index 3a1bce9..53a958e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mac-dev-setup", - "version": "1.0.0", + "version": "0.5.0", "description": "Curated macOS development setup managed through a small mac CLI.", "main": "index.js", "directories": { From fbe2c115783a6a73ef1c3a747a31feb4671a2a2a Mon Sep 17 00:00:00 2001 From: Labault Date: Wed, 17 Jun 2026 19:00:18 +0200 Subject: [PATCH 23/23] =?UTF-8?q?=F0=9F=93=9D=20docs:=20deduplicate=20Home?= =?UTF-8?q?brew=20install/update=20boilerplate=20in=20tool=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every tool page repeated the same generic "brew bundle --file=Brewfile" install block and a per-tool "Updates" section that only ran "brew upgrade ", both already documented canonically in docs/homebrew/homebrew.md. Replace the generic install block with a pointer to the Homebrew setup doc and drop the boilerplate Updates sections, while preserving tool-specific content (direct install, verify commands, and data-safety notes such as OrbStack's runtime checks). Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/cli/bat.md | 14 +------------- docs/cli/duf.md | 14 +------------- docs/cli/dust.md | 14 +------------- docs/cli/tokei.md | 14 +------------- docs/cli/tree.md | 14 +------------- docs/containers/ctop.md | 14 +------------- docs/containers/orbstack.md | 6 +----- docs/database/beekeeper-studio.md | 6 +----- docs/docker/hadolint.md | 6 +----- docs/git/git-delta.md | 14 +------------- docs/github-actions/act.md | 6 +----- docs/github-actions/actionlint.md | 6 +----- docs/macos/pearcleaner.md | 6 +----- docs/pre-commit/pre-commit.md | 6 +----- docs/quality/editorconfig-checker.md | 14 +------------- docs/quality/lychee.md | 14 +------------- docs/quality/markdownlint-cli2.md | 14 +------------- docs/security/gitleaks.md | 6 +----- docs/shell/shellcheck.md | 6 +----- 19 files changed, 19 insertions(+), 175 deletions(-) diff --git a/docs/cli/bat.md b/docs/cli/bat.md index edafc65..3dc43f6 100644 --- a/docs/cli/bat.md +++ b/docs/cli/bat.md @@ -11,11 +11,7 @@ The tool is installed through Homebrew and declared in the project `Brewfile`. ## Installation -Install all tools declared in the `Brewfile`: - -```bash -brew bundle --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. Install bat directly: @@ -136,14 +132,6 @@ command -v bat On some Linux distributions, the executable may be named `batcat`, but the Homebrew installation uses `bat`. -## Updates - -Update bat through Homebrew: - -```bash -brew upgrade bat -``` - ## Rollback Remove bat with Homebrew: diff --git a/docs/cli/duf.md b/docs/cli/duf.md index f22872e..b305e8d 100644 --- a/docs/cli/duf.md +++ b/docs/cli/duf.md @@ -10,11 +10,7 @@ The tool is installed through Homebrew and declared in the project `Brewfile`. ## Installation -Install all tools declared in the `Brewfile`: - -```bash -brew bundle --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. Install duf directly: @@ -108,14 +104,6 @@ If a volume is missing, verify that it is currently mounted in Finder or with: mount ``` -## Updates - -Update duf through Homebrew: - -```bash -brew upgrade duf -``` - ## Rollback Remove duf with Homebrew: diff --git a/docs/cli/dust.md b/docs/cli/dust.md index 36ac204..be55065 100644 --- a/docs/cli/dust.md +++ b/docs/cli/dust.md @@ -10,11 +10,7 @@ The tool is installed through Homebrew and declared in the project `Brewfile`. ## Installation -Install all tools declared in the `Brewfile`: - -```bash -brew bundle --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. Install dust directly: @@ -112,14 +108,6 @@ Refresh the shell command cache if necessary: rehash ``` -## Updates - -Update dust through Homebrew: - -```bash -brew upgrade dust -``` - ## Rollback Remove dust with Homebrew: diff --git a/docs/cli/tokei.md b/docs/cli/tokei.md index 8035081..7df4484 100644 --- a/docs/cli/tokei.md +++ b/docs/cli/tokei.md @@ -10,11 +10,7 @@ The tool is installed through Homebrew and declared in the project `Brewfile`. ## Installation -Install all tools declared in the `Brewfile`: - -```bash -brew bundle --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. Install tokei directly: @@ -133,14 +129,6 @@ If the result includes unwanted generated files, add explicit exclusions: tokei --exclude 'path-to-ignore' . ``` -## Updates - -Update tokei through Homebrew: - -```bash -brew upgrade tokei -``` - ## Rollback Remove tokei with Homebrew: diff --git a/docs/cli/tree.md b/docs/cli/tree.md index 6bb8507..38e0311 100644 --- a/docs/cli/tree.md +++ b/docs/cli/tree.md @@ -10,11 +10,7 @@ The tool is installed through Homebrew and declared in the project `Brewfile`. ## Installation -Install all tools declared in the `Brewfile`: - -```bash -brew bundle --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. Install tree directly: @@ -139,14 +135,6 @@ If the output is too large, reduce the depth or exclude additional directories: tree -L 2 -I '.git|vendor|node_modules|var|cache' ``` -## Updates - -Update tree through Homebrew: - -```bash -brew upgrade tree -``` - ## Rollback Remove tree with Homebrew: diff --git a/docs/containers/ctop.md b/docs/containers/ctop.md index 091b1cb..de4ee32 100644 --- a/docs/containers/ctop.md +++ b/docs/containers/ctop.md @@ -12,11 +12,7 @@ The tool is installed through Homebrew and declared in the project `Brewfile`. ## Installation -Install all tools declared in the `Brewfile`: - -```bash -brew bundle --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. Install ctop directly: @@ -152,14 +148,6 @@ ctop can expose container management actions from its interface. Before stopping, restarting, or removing a container, verify that it does not contain important transient work or production data. -## Updates - -Update ctop through Homebrew: - -```bash -brew upgrade ctop -``` - ## Rollback Remove ctop with Homebrew: diff --git a/docs/containers/orbstack.md b/docs/containers/orbstack.md index 095dd29..1f754d0 100644 --- a/docs/containers/orbstack.md +++ b/docs/containers/orbstack.md @@ -12,11 +12,7 @@ OrbStack is installed through Homebrew Cask: brew install --cask orbstack ``` -It is also declared in `profiles/full/Brewfile`: - -```bash -brew bundle install --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. ## Launch OrbStack diff --git a/docs/database/beekeeper-studio.md b/docs/database/beekeeper-studio.md index 0fa4c8c..aa6412e 100644 --- a/docs/database/beekeeper-studio.md +++ b/docs/database/beekeeper-studio.md @@ -12,11 +12,7 @@ Beekeeper Studio is installed through Homebrew Cask: brew install --cask beekeeper-studio ``` -It is also declared in `profiles/full/Brewfile`: - -```bash -brew bundle install --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. ## Launch the application diff --git a/docs/docker/hadolint.md b/docs/docker/hadolint.md index 137e1a6..dd8e39f 100644 --- a/docs/docker/hadolint.md +++ b/docs/docker/hadolint.md @@ -12,11 +12,7 @@ Hadolint is installed through Homebrew: brew install hadolint ``` -It is also declared in `profiles/full/Brewfile`: - -```bash -brew bundle install --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. ## Verify the installation diff --git a/docs/git/git-delta.md b/docs/git/git-delta.md index 8191ba6..4a4a367 100644 --- a/docs/git/git-delta.md +++ b/docs/git/git-delta.md @@ -12,11 +12,7 @@ The tool is installed through Homebrew and declared in the project `Brewfile`. ## Installation -Install all tools declared in the `Brewfile`: - -```bash -brew bundle --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. Install delta directly: @@ -204,14 +200,6 @@ Disable the configured pager temporarily: GIT_PAGER=cat git diff ``` -## Updates - -Update delta through Homebrew: - -```bash -brew upgrade git-delta -``` - ## Rollback Remove the Git configuration: diff --git a/docs/github-actions/act.md b/docs/github-actions/act.md index dfba12b..7eb6cf8 100644 --- a/docs/github-actions/act.md +++ b/docs/github-actions/act.md @@ -12,11 +12,7 @@ Act is installed through Homebrew: brew install act ``` -It is also declared in `profiles/full/Brewfile`: - -```bash -brew bundle install --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. ## Container runtime diff --git a/docs/github-actions/actionlint.md b/docs/github-actions/actionlint.md index 68f53e3..9310a8a 100644 --- a/docs/github-actions/actionlint.md +++ b/docs/github-actions/actionlint.md @@ -12,11 +12,7 @@ Actionlint is installed through Homebrew: brew install actionlint ``` -It is also declared in `profiles/full/Brewfile`: - -```bash -brew bundle install --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. ## Verify the installation diff --git a/docs/macos/pearcleaner.md b/docs/macos/pearcleaner.md index e2cfa42..2932cb4 100644 --- a/docs/macos/pearcleaner.md +++ b/docs/macos/pearcleaner.md @@ -12,11 +12,7 @@ Pearcleaner is installed through Homebrew Cask: brew install --cask pearcleaner ``` -It is also declared in `profiles/full/Brewfile`: - -```bash -brew bundle install --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. ## Launch the application diff --git a/docs/pre-commit/pre-commit.md b/docs/pre-commit/pre-commit.md index 0c97638..fa77604 100644 --- a/docs/pre-commit/pre-commit.md +++ b/docs/pre-commit/pre-commit.md @@ -10,11 +10,7 @@ The tool is installed through Homebrew and declared in the project `Brewfile`. ## Installation -Install all tools declared in the `Brewfile`: - -```bash -brew bundle --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. Verify the installation: diff --git a/docs/quality/editorconfig-checker.md b/docs/quality/editorconfig-checker.md index 4f05c81..dd8cb33 100644 --- a/docs/quality/editorconfig-checker.md +++ b/docs/quality/editorconfig-checker.md @@ -13,11 +13,7 @@ The tool is installed through Homebrew and declared in the project `Brewfile`. ## Installation -Install all tools declared in the `Brewfile`: - -```bash -brew bundle --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. Install editorconfig-checker directly: @@ -170,14 +166,6 @@ git diff If a file is intentionally exceptional, prefer adding a precise `.editorconfig` section instead of disabling checks globally. -## Updates - -Update editorconfig-checker through Homebrew: - -```bash -brew upgrade editorconfig-checker -``` - ## Rollback Remove editorconfig-checker with Homebrew: diff --git a/docs/quality/lychee.md b/docs/quality/lychee.md index 461c34c..e6acf86 100644 --- a/docs/quality/lychee.md +++ b/docs/quality/lychee.md @@ -12,11 +12,7 @@ The tool is installed through Homebrew and declared in the project `Brewfile`. ## Installation -Install all tools declared in the `Brewfile`: - -```bash -brew bundle --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. Install lychee directly: @@ -193,14 +189,6 @@ command -v lychee When a remote site fails temporarily, rerun the check before changing the documentation. -## Updates - -Update lychee through Homebrew: - -```bash -brew upgrade lychee -``` - After an update, rerun the complete link check because HTTP handling or default behavior may change. diff --git a/docs/quality/markdownlint-cli2.md b/docs/quality/markdownlint-cli2.md index d8f8585..366f251 100644 --- a/docs/quality/markdownlint-cli2.md +++ b/docs/quality/markdownlint-cli2.md @@ -12,11 +12,7 @@ The tool is installed through Homebrew and declared in the project `Brewfile`. ## Installation -Install all tools declared in the `Brewfile`: - -```bash -brew bundle --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. Install markdownlint-cli2 directly: @@ -177,14 +173,6 @@ markdownlint-cli2 README.md If a configuration change is not applied, confirm that `.markdownlint-cli2.yaml` exists at the repository root. -## Updates - -Update markdownlint-cli2 through Homebrew: - -```bash -brew upgrade markdownlint-cli2 -``` - After an update, rerun the complete documentation check because new rules or rule behavior may be introduced. diff --git a/docs/security/gitleaks.md b/docs/security/gitleaks.md index 72cd931..6c9e1d3 100644 --- a/docs/security/gitleaks.md +++ b/docs/security/gitleaks.md @@ -12,11 +12,7 @@ Gitleaks is installed through Homebrew: brew install gitleaks ``` -It is also declared in `profiles/full/Brewfile`, so it can be installed with the rest of the curated environment: - -```bash -brew bundle install --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. ## Verify the installation diff --git a/docs/shell/shellcheck.md b/docs/shell/shellcheck.md index 3e3ed9c..dc3a240 100644 --- a/docs/shell/shellcheck.md +++ b/docs/shell/shellcheck.md @@ -12,11 +12,7 @@ ShellCheck is installed through Homebrew: brew install shellcheck ``` -It is also declared in `profiles/full/Brewfile`: - -```bash -brew bundle install --file=Brewfile -``` +It is part of the curated Homebrew environment; see [`Homebrew setup`](../homebrew/homebrew.md) to install everything at once. ## Verify the installation