diff --git a/.github/workflows/automated_test.yaml b/.github/workflows/automated_test.yaml deleted file mode 100644 index 746327e..0000000 --- a/.github/workflows/automated_test.yaml +++ /dev/null @@ -1,235 +0,0 @@ ---- -name: "Automated Tests" - -on: - workflow_dispatch: - push: - paths: - - ".github/workflows/**" - - ".github/actions/**" - - ".chezmoiroot" - - "chezmoi/**" - - "tests/chezmoi-render-config.sh" - branches: - - master - - main - pull_request: - types: - - opened - - reopened - - synchronize - paths: - - ".github/workflows/**" - - ".github/actions/**" - - ".chezmoiroot" - - "chezmoi/**" - - "tests/chezmoi-render-config.sh" - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - test-dotfiles: - env: - CI: "true" - # runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - os: ["ubuntu-latest", "macos-latest"] - runs-on: ${{ matrix.os }} - steps: - # - name: Install APT dependencies - # if: startsWith(matrix.os, 'ubuntu') - # run: | - # apt-get update - # apt-get install -y git zsh - - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Copy repository to /home/runner/ - run: | - if [ -d /home/runner ]; then homedir="/home/runner"; else homedir="/Users/runner"; fi - - mkdir -p ${homedir}/.local/share/chezmoi - cp -a . ${homedir}/.local/share/chezmoi - - # ---------------------------------------------- - # Create config file - # ---------------------------------------------- - - name: Create config file - run: | - if [ -d /home/runner ]; then homedir="/home/runner"; else homedir="/Users/runner"; fi - - mkdir -p ${homedir}/.config/chezmoi - - cat > ${homedir}/.config/chezmoi/chezmoi.toml<< EOF - [data.identity] - profile = "server-minimal" - git_name = "chianyungcode-server" - git_email = "chianyungcode-server@local.invalid" - github_username = "" - signing_key = "" - github_token = "" - - [data.machine] - role = "server" - - [data.features] - development = true - homelab = false - personal = false - graphical = false - - [data.secrets] - provider = "none" - - [data.encrypted_files] - enabled = false - - [data.xdg] - cache_home = "${homedir}/.cache" - config_home = "${homedir}/.config" - data_home = "${homedir}/.local/share" - state_home = "${homedir}/.local/state" - EOF - - # ---------------------------------------------- - # Install chezmoi - # ---------------------------------------------- - - name: Install chezmoi - run: | - sh -c "$(curl -fsLS get.chezmoi.io)" - - - name: Install render test dependencies - run: | - if [[ "$RUNNER_OS" == "Linux" ]]; then - sudo apt-get update - sudo apt-get install -y jq ripgrep - else - brew install jq ripgrep - fi - - - name: Validate configuration render matrix - run: | - export PATH="$PWD/bin:$PATH" - ./tests/chezmoi-render-config.sh - - # ---------------------------------------------- - # Run chezmoi apply - # ---------------------------------------------- - - name: Run chezmoi apply - run: | - if [ -d /home/runner ]; then homedir="/home/runner"; else homedir="/Users/runner"; fi - source_dir="${homedir}/.local/share/chezmoi/chezmoi" - - ./bin/chezmoi --source "${source_dir}" --config "${homedir}/.config/chezmoi/chezmoi.toml" apply - - # ---------------------------------------------- - # UBUNTU: Confirm dotfiles are installed - # ---------------------------------------------- - - name: Confirm dotfiles are installed - if: startsWith(matrix.os, 'ubuntu') - run: | - if [ -d /home/runner ]; then homedir="/home/runner"; else homedir="/Users/runner"; fi - echo "------- Testing files -------" - cd /home/runner - # echo "pwd: $(pwd)" - # ls -al - - existing_files=( - .config/zsh/.zshrc - .bashrc - .config/git/config - .ssh/config - .config/zsh/conf.d/080-linux.sh - ) - - missing_files=( - .config/zsh/conf.d/080-macos.sh - Library - ) - - installed_packages=( - jq - pygmentize - ) - - # Confirm files exist - for file in ${existing_files[@]}; do - if [ ! -e "${homedir}/$file" ]; then - echo "$file not found" - exit 1 - fi - done - - # Confirm files don't exist - for file in ${missing_files[@]} ; do - if [ -e "${homedir}/$file" ]; then - echo "$file found" - exit 1 - fi - done - - # Confirm packages are installed - for package in ${installed_packages[@]}; do - if [ ! $(command -v $package) ]; then - echo "$package not found" - exit 1 - fi - done - - # ---------------------------------------------- - # MACOS: Confirm dotfiles are installed - # ---------------------------------------------- - - name: Confirm dotfiles are installed - if: startsWith(matrix.os, 'macos') - run: | - if [ -d /home/runner ]; then homedir="/home/runner"; else homedir="/Users/runner"; fi - - echo "------- Testing files -------" - cd ${homedir} - - existing_files=( - .config/zsh/.zshrc - .bashrc - .config/git/config - .config/nano/nanorc - .ssh/config - .config/zsh/conf.d/080-macos.sh - ) - - missing_files=( - .config/zsh/conf.d/080-linux.sh - ) - - installed_packages=( - jq - pygmentize - ) - - # Confirm files exist - for file in ${existing_files[@]}; do - if [ ! -e "${homedir}/$file" ]; then - echo "$file not found" - exit 1 - fi - done - - # Confirm files don't exist - for file in ${missing_files[@]} ; do - if [ -e "${homedir}/$file" ]; then - echo "$file found" - exit 1 - fi - done - - # Confirm packages are installed - for package in ${installed_packages[@]}; do - if [ ! $(command -v $package) ]; then - echo "$package not found" - exit 1 - fi - done diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..c52aeaf --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,78 @@ +--- +# Fast gate for every push/PR: renders all profiles and verifies every +# package name exists upstream. No packages are installed (~1 minute). +name: "CI" + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - ".github/workflows/ci.yaml" + - ".chezmoiroot" + - "chezmoi/**" + - "tests/**" + pull_request: + paths: + - ".github/workflows/ci.yaml" + - ".chezmoiroot" + - "chezmoi/**" + - "tests/**" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + checks: + name: Render and package checks + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install tools + env: + GH_TOKEN: ${{ github.token }} + # Tests expect fish 4 key notation; Ubuntu's apt only has fish 3. + FISH_VERSION: "4.9.3" + JJ_VERSION: "v0.46.0" + run: | + bin="$HOME/.local/bin" + mkdir -p "$bin" + echo "$bin" >> "$GITHUB_PATH" + sh -c "$(curl -fsLS get.chezmoi.io)" -- -b "$bin" + + sudo apt-get update -qq + sudo apt-get install -y -qq ripgrep shellcheck shfmt zsh + + cd "$RUNNER_TEMP" + gh release download "$FISH_VERSION" -R fish-shell/fish-shell \ + -p "fish-$FISH_VERSION-linux-x86_64.tar.xz" + tar -xJf "fish-$FISH_VERSION-linux-x86_64.tar.xz" -C "$bin" fish + gh release download "$JJ_VERSION" -R jj-vcs/jj \ + -p "jj-$JJ_VERSION-x86_64-unknown-linux-musl.tar.gz" + tar -xzf "jj-$JJ_VERSION-x86_64-unknown-linux-musl.tar.gz" -C "$bin" ./jj + + - name: Run test suite + run: | + status=0 + for test in tests/*.sh tests/chezmoi-init-prompts.py; do + # Needs network and a token; runs as its own step below. + [[ "$test" == tests/chezmoi-package-names.sh ]] && continue + echo "::group::$test" + if [[ "$test" == *.py ]]; then python3 "$test"; else bash "$test"; fi || + { echo "::error::$test failed"; status=1; } + echo "::endgroup::" + done + exit "$status" + + - name: Validate package names + env: + GH_TOKEN: ${{ github.token }} + run: ./tests/chezmoi-package-names.sh diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml new file mode 100644 index 0000000..dd6f58c --- /dev/null +++ b/.github/workflows/e2e.yaml @@ -0,0 +1,117 @@ +--- +# Slow, real `chezmoi apply` on fresh runners (macOS installs ~140 formulae). +# Runs only when install logic changes on main, weekly to catch upstream +# drift (renamed/removed packages), or on demand: +# gh workflow run e2e.yaml --ref +name: "E2E Apply" + +on: + workflow_dispatch: + schedule: + - cron: "17 2 * * 1" + push: + branches: + - main + paths: + - ".github/workflows/e2e.yaml" + - ".chezmoiroot" + - "chezmoi/.chezmoi.toml.tmpl" + - "chezmoi/.chezmoidata/packages/**" + - "chezmoi/.chezmoidata/cli-tool-setup.toml" + - "chezmoi/.chezmoiscripts/**" + - "chezmoi/.chezmoitemplates/scripts/**" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + apply: + name: chezmoi apply (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest", "macos-latest"] + runs-on: ${{ matrix.os }} + timeout-minutes: 40 + env: + CI: "true" + HOMEBREW_NO_AUTO_UPDATE: "1" + HOMEBREW_NO_ANALYTICS: "1" + HOMEBREW_NO_ENV_HINTS: "1" + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Stage source and config + run: | + mkdir -p "$HOME/.local/share/chezmoi" "$HOME/.config/chezmoi" + cp -a . "$HOME/.local/share/chezmoi" + + cat > "$HOME/.config/chezmoi/chezmoi.toml" << EOF + [data.identity] + profile = "server-minimal" + git_name = "chianyungcode-server" + git_email = "chianyungcode-server@local.invalid" + github_username = "" + signing_key = "" + github_token = "" + + [data.machine] + role = "server" + + [data.features] + development = true + homelab = false + personal = false + graphical = false + + [data.secrets] + provider = "none" + + [data.encrypted_files] + enabled = false + + [data.xdg] + cache_home = "$HOME/.cache" + config_home = "$HOME/.config" + data_home = "$HOME/.local/share" + state_home = "$HOME/.local/state" + EOF + + - name: Install chezmoi + run: | + sh -c "$(curl -fsLS get.chezmoi.io)" -- -b "$HOME/.local/bin" + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Run chezmoi apply + run: | + chezmoi --source "$HOME/.local/share/chezmoi/chezmoi" \ + --config "$HOME/.config/chezmoi/chezmoi.toml" apply + + - name: Confirm dotfiles are installed + run: | + existing_files=(.config/zsh/.zshrc .bashrc .config/git/config .ssh/config) + missing_files=() + if [[ "$RUNNER_OS" == "macOS" ]]; then + existing_files+=(.config/nano/nanorc .config/zsh/conf.d/080-macos.sh) + missing_files+=(.config/zsh/conf.d/080-linux.sh) + else + existing_files+=(.config/zsh/conf.d/080-linux.sh) + missing_files+=(.config/zsh/conf.d/080-macos.sh Library) + fi + + status=0 + for file in "${existing_files[@]}"; do + [[ -e "$HOME/$file" ]] || { echo "::error::$file not found"; status=1; } + done + for file in "${missing_files[@]}"; do + [[ ! -e "$HOME/$file" ]] || { echo "::error::$file unexpectedly exists"; status=1; } + done + for package in jq pygmentize; do + command -v "$package" >/dev/null || { echo "::error::$package not installed"; status=1; } + done + exit "$status" diff --git a/README.md b/README.md index abf0bd8..2f0fd89 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,13 @@ The matrix renders secretless server and development data, a graphical workstation, CI-derived ignores, and custom XDG paths into temporary destinations. It does not install packages or require `op` credentials. +To catch package typos before a slow apply, verify every Homebrew and APT +entry resolves upstream (needs network and `gh`): + +```bash +./tests/chezmoi-package-names.sh +``` + ## Usage - **Edit Files**: Modify files in `~/.local/share/chezmoi/` and run diff --git a/chezmoi/.chezmoidata/packages/system/homebrew.toml b/chezmoi/.chezmoidata/packages/system/homebrew.toml index 9258064..5de5414 100644 --- a/chezmoi/.chezmoidata/packages/system/homebrew.toml +++ b/chezmoi/.chezmoidata/packages/system/homebrew.toml @@ -204,7 +204,6 @@ # Terminal Multiplexer # "gitmux", # Git status bar in tmux "herdr", # Tmux alternative with AI native integration - "herdr-plus", # "lazy-tmux", "raine/workmux/workmux", "sesh", # Tmux session manager diff --git a/chezmoi/dot_config/zsh/conf.d/third-party/sk-git.sh b/chezmoi/dot_config/zsh/conf.d/third-party/sk-git.sh index ea239d5..6ab0bf5 100644 --- a/chezmoi/dot_config/zsh/conf.d/third-party/sk-git.sh +++ b/chezmoi/dot_config/zsh/conf.d/third-party/sk-git.sh @@ -310,24 +310,34 @@ if [[ -o interactive ]] && (( $+commands[git] && $+commands[sk] )); then zle -N sk-git-each-ref-widget zle -N sk-git-worktrees-widget - for keymap in emacs viins vicmd; do - bindkey -M "$keymap" '^g^h' sk-git-hashes-widget - bindkey -M "$keymap" '^gh' sk-git-hashes-widget - bindkey -M "$keymap" '^g^f' sk-git-files-widget - bindkey -M "$keymap" '^gf' sk-git-files-widget - bindkey -M "$keymap" '^g^b' sk-git-branches-widget - bindkey -M "$keymap" '^gb' sk-git-branches-widget - bindkey -M "$keymap" '^g^t' sk-git-tags-widget - bindkey -M "$keymap" '^gt' sk-git-tags-widget - bindkey -M "$keymap" '^g^r' sk-git-remotes-widget - bindkey -M "$keymap" '^gr' sk-git-remotes-widget - bindkey -M "$keymap" '^g^s' sk-git-stashes-widget - bindkey -M "$keymap" '^gs' sk-git-stashes-widget - bindkey -M "$keymap" '^g^l' sk-git-reflogs-widget - bindkey -M "$keymap" '^gl' sk-git-reflogs-widget - bindkey -M "$keymap" '^g^e' sk-git-each-ref-widget - bindkey -M "$keymap" '^ge' sk-git-each-ref-widget - bindkey -M "$keymap" '^g^w' sk-git-worktrees-widget - bindkey -M "$keymap" '^gw' sk-git-worktrees-widget - done + __sk_git_bind_keys() { + local keymap + for keymap in emacs viins vicmd; do + bindkey -M "$keymap" '^g^h' sk-git-hashes-widget + bindkey -M "$keymap" '^gh' sk-git-hashes-widget + bindkey -M "$keymap" '^g^f' sk-git-files-widget + bindkey -M "$keymap" '^gf' sk-git-files-widget + bindkey -M "$keymap" '^g^b' sk-git-branches-widget + bindkey -M "$keymap" '^gb' sk-git-branches-widget + bindkey -M "$keymap" '^g^t' sk-git-tags-widget + bindkey -M "$keymap" '^gt' sk-git-tags-widget + bindkey -M "$keymap" '^g^r' sk-git-remotes-widget + bindkey -M "$keymap" '^gr' sk-git-remotes-widget + bindkey -M "$keymap" '^g^s' sk-git-stashes-widget + bindkey -M "$keymap" '^gs' sk-git-stashes-widget + bindkey -M "$keymap" '^g^l' sk-git-reflogs-widget + bindkey -M "$keymap" '^gl' sk-git-reflogs-widget + bindkey -M "$keymap" '^g^e' sk-git-each-ref-widget + bindkey -M "$keymap" '^ge' sk-git-each-ref-widget + bindkey -M "$keymap" '^g^w' sk-git-worktrees-widget + bindkey -M "$keymap" '^gw' sk-git-worktrees-widget + done + } + + # Bind after deferred plugins so none of them can override these keys. + if (( $+functions[zsh-defer] )); then + zsh-defer __sk_git_bind_keys + else + __sk_git_bind_keys + fi fi diff --git a/docs/06-cli-integrations.md b/docs/06-cli-integrations.md index 81ba7b4..017b190 100644 --- a/docs/06-cli-integrations.md +++ b/docs/06-cli-integrations.md @@ -71,7 +71,7 @@ - `chezmoi/dot_config/fish/conf.d/99_skim.fish`: Uses fd to enumerate hidden files and directories, excluding `.git`, for the Fish `sk` picker. -- `chezmoi/dot_config/zsh/conf.d/third-party/99_skim.sh`: Uses fd to generate +- `chezmoi/dot_config/zsh/conf.d/third-party/skim.sh`: Uses fd to generate candidates for the Zsh `sk` `Ctrl-T` widget. - `chezmoi/dot_config/zsh/conf.d/third-party/fzf.sh.tmpl`: Uses fd for fzf path and directory completion and for the default file candidate list. @@ -100,7 +100,7 @@ - `chezmoi/dot_config/fish/conf.d/90_sk-git.fish`: Provides native Fish `sk-git` widgets that paste selected commit hashes (`Ctrl-G H`) or local branches (`Ctrl-G B`) into the command line with Git previews. -- `chezmoi/dot_config/zsh/conf.d/third-party/90_sk-git.sh`: Provides the +- `chezmoi/dot_config/zsh/conf.d/third-party/sk-git.sh`: Provides the native Zsh `sk` Git integration, preserving pickers for files, branches, tags, remotes, hashes, stashes, reflogs, each-ref, and worktrees. In the hashes picker, `Ctrl-O` opens the selected commit in a browser and `Ctrl-D` @@ -187,7 +187,7 @@ - `chezmoi/dot_config/fish/conf.d/90_sk-git.fish`: Uses `sk` for multi-select Git commit and local-branch pickers with adaptive previews and Fish-native key bindings. -- `chezmoi/dot_config/zsh/conf.d/third-party/90_sk-git.sh`: Provides the +- `chezmoi/dot_config/zsh/conf.d/third-party/sk-git.sh`: Provides the native Zsh `sk` Git picker set for files, branches, tags, remotes, hashes, stashes, reflogs, each-ref, and worktrees; the hashes picker keeps `Ctrl-O` for browser opening and `Ctrl-D` for diffs. @@ -197,7 +197,7 @@ opens all marked files in `$EDITOR`, `Ctrl-C` keeps the default cancellation behavior, and `Ctrl-D` changes into a highlighted directory before reopening the picker. -- `chezmoi/dot_config/zsh/conf.d/third-party/99_skim.sh`: Binds `Ctrl-T` to a +- `chezmoi/dot_config/zsh/conf.d/third-party/skim.sh`: Binds `Ctrl-T` to a Zsh Line Editor widget that uses fd for candidates and bat/lla for previews; `Ctrl-E` opens all marked files in `$EDITOR`, `Ctrl-C` keeps the default cancellation behavior, and `Ctrl-D` changes into a highlighted directory diff --git a/tests/chezmoi-package-names.sh b/tests/chezmoi-package-names.sh new file mode 100755 index 0000000..ba28aa4 --- /dev/null +++ b/tests/chezmoi-package-names.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash + +# Fail fast when a package list names something the package manager cannot +# install. A typo here otherwise surfaces only after a long `chezmoi apply`. +# +# Checks every Homebrew and APT entry regardless of feature flags: +# - Homebrew core formulae and casks against the formulae.brew.sh API. +# - Tap entries (owner/tap/name) against the tap's GitHub repository. +# - APT packages against the local apt cache (skipped without apt-cache). + +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +source_dir="$repo_root/chezmoi" +tmp_dir=$(mktemp -d) +trap 'rm -rf "$tmp_dir"' EXIT + +for command_name in chezmoi curl gh jq rg; do + command -v "$command_name" >/dev/null || { + printf 'missing required command: %s\n' "$command_name" >&2 + exit 1 + } +done + +config_file="$tmp_dir/chezmoi.toml" +data_file="$tmp_dir/data.json" +chezmoi -S "$source_dir" -c "$tmp_dir/missing.toml" execute-template --init \ + --file "$source_dir/.chezmoi.toml.tmpl" >"$config_file" +chezmoi -S "$source_dir" -c "$config_file" data --format json >"$data_file" + +failed=0 + +report_missing() { + local label=$1 + local wanted=$2 + local available=$3 + local missing + missing=$(comm -23 "$wanted" "$available") + [[ -z "$missing" ]] && return 0 + printf '%s not found:\n' "$label" >&2 + printf '%s\n' "$missing" | sed 's/^/ - /' >&2 + failed=1 +} + +brew_entries() { + local kind=$1 + jq -r --arg kind "$kind" \ + '.packages.homebrew | del(.to_remove) | .[] | .[$kind][]?' \ + "$data_file" | sort -u +} + +brew_entries formulae | rg -v / >"$tmp_dir/formulae" || true +brew_entries casks | rg -v / >"$tmp_dir/casks" || true + +curl -fsSL --retry 3 https://formulae.brew.sh/api/formula.json | + jq -r '.[] | .name, .aliases[]?, .oldnames[]?, (.oldname // empty)' | + sort -u >"$tmp_dir/available-formulae" +curl -fsSL --retry 3 https://formulae.brew.sh/api/cask.json | + jq -r '.[] | .token, .old_tokens[]?' | + sort -u >"$tmp_dir/available-casks" + +report_missing 'Homebrew formulae' "$tmp_dir/formulae" "$tmp_dir/available-formulae" +report_missing 'Homebrew casks' "$tmp_dir/casks" "$tmp_dir/available-casks" + +# Tap entries resolve to github.com//homebrew-, containing +# .rb anywhere in the tree (Formula/, Formula/x/, Casks/, root, ...). +{ + brew_entries formulae + brew_entries casks +} | rg / | sort -u | while IFS=/ read -r owner tap name; do + if ! gh api "repos/$owner/homebrew-$tap/git/trees/HEAD?recursive=1" \ + --jq '.tree[].path' 2>/dev/null | + rg -q "(^|/)$name\\.rb\$"; then + printf '%s/%s/%s\n' "$owner" "$tap" "$name" + fi +done >"$tmp_dir/missing-taps" +if [[ -s "$tmp_dir/missing-taps" ]]; then + printf 'Homebrew tap entries not found:\n' >&2 + sed 's/^/ - /' "$tmp_dir/missing-taps" >&2 + failed=1 +fi + +if command -v apt-cache >/dev/null; then + jq -r '.packages.apt | del(.to_remove) | .[] | .packages[]?' "$data_file" | + sort -u >"$tmp_dir/apt" + while read -r package; do + apt-cache show "$package" >/dev/null 2>&1 || printf '%s\n' "$package" + done <"$tmp_dir/apt" >"$tmp_dir/missing-apt" + if [[ -s "$tmp_dir/missing-apt" ]]; then + printf 'APT packages not found:\n' >&2 + sed 's/^/ - /' "$tmp_dir/missing-apt" >&2 + failed=1 + fi +else + printf 'apt-cache not found; skipping APT package check\n' +fi + +[[ "$failed" -eq 0 ]] || exit 1 +printf 'package names resolved\n' diff --git a/tests/chezmoi-render-scripts.sh b/tests/chezmoi-render-scripts.sh index c08869b..878df79 100755 --- a/tests/chezmoi-render-scripts.sh +++ b/tests/chezmoi-render-scripts.sh @@ -2,6 +2,10 @@ set -euo pipefail +# CI runners export CI=true, which makes templates skip CI-gated work. +# Cases that need CI behavior set it explicitly. +unset CI + fail() { printf '%s\n' "$1" >&2 exit 1 @@ -120,8 +124,13 @@ for command_name in bash chezmoi jq rg shellcheck shfmt; do } done +# Generate a config instead of reading the machine's own, so the base data +# (including .identity and .xdg) is the same locally and in CI. +config_file="$tmp_dir/chezmoi.toml" +chezmoi -S "$source_dir" -c "$tmp_dir/missing.toml" execute-template --init \ + --file "$source_dir/.chezmoi.toml.tmpl" >"$config_file" base_data_file="$tmp_dir/base.json" -chezmoi -S "$source_dir" data >"$base_data_file" +chezmoi -S "$source_dir" -c "$config_file" data >"$base_data_file" for data_path in \ directories.toml packages/github-binaries.toml packages/languages.toml \ diff --git a/tests/chezmoi-script-behavior.sh b/tests/chezmoi-script-behavior.sh index e858de0..cc1b19d 100755 --- a/tests/chezmoi-script-behavior.sh +++ b/tests/chezmoi-script-behavior.sh @@ -2,6 +2,10 @@ set -euo pipefail +# CI runners export CI=true, which makes templates skip CI-gated work. +# Cases that need CI behavior set it explicitly. +unset CI + repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) source_dir="$repo_root/chezmoi" tmp_dir=$(mktemp -d) @@ -102,8 +106,13 @@ rg -q '\[core-test\] ERROR line [0-9]+: false \(status [0-9]+\)' \ failure_temp=$(<"$failure_record") [[ ! -d "$failure_temp" ]] +# Generate a config instead of reading the machine's own, so the base data +# is the same locally and in CI. +config_file="$tmp_dir/chezmoi.toml" +chezmoi -S "$source_dir" -c "$tmp_dir/missing.toml" execute-template --init \ + --file "$source_dir/.chezmoi.toml.tmpl" >"$config_file" base_data_file="$tmp_dir/base.json" -chezmoi -S "$source_dir" data >"$base_data_file" +chezmoi -S "$source_dir" -c "$config_file" data >"$base_data_file" imperative_source="$source_dir/.chezmoiscripts/run_after_70-imperative-cli-tools.sh.tmpl" imperative_default_fixture="$tmp_dir/imperative-default.sh" @@ -111,10 +120,19 @@ chezmoi -S "$source_dir" execute-template \ --override-data-file "$base_data_file" \ --file "$imperative_source" >"$imperative_default_fixture" chmod +x "$imperative_default_fixture" -if rg -q 'herdr plugin install|skills add' "$imperative_default_fixture"; then - printf 'disabled imperative commands should not be rendered\n' >&2 - exit 1 -fi +# The real data must render exactly its enabled entries. +while IFS=$'\t' read -r enabled name; do + if [[ "$enabled" == true ]]; then + rg -Fq "$name" "$imperative_default_fixture" || { + printf 'enabled imperative command not rendered: %s\n' "$name" >&2 + exit 1 + } + elif rg -Fq "$name" "$imperative_default_fixture"; then + printf 'disabled imperative command rendered: %s\n' "$name" >&2 + exit 1 + fi +done < <(jq -r '.imperative_cli_tools[] | [(.enabled | tostring), .name] | @tsv' \ + "$base_data_file") imperative_data="$tmp_dir/imperative-behavior.json" jq \ @@ -712,7 +730,7 @@ case "$url" in esac STUB chmod +x "$standalone_fake_bin/curl" -for command_name in awk bash cat chmod cp find grep head jq mkdir mktemp mv rm \ +for command_name in awk bash cat chmod cp find grep gzip head jq mkdir mktemp mv rm \ sha256sum shasum tar touch; do command_path=$(command -v "$command_name") || continue ln -s "$command_path" "$standalone_fake_bin/$command_name" diff --git a/tests/fish-sk-git.sh b/tests/fish-sk-git.sh index c4dd658..d7c8a58 100755 --- a/tests/fish-sk-git.sh +++ b/tests/fish-sk-git.sh @@ -69,6 +69,8 @@ set -euo pipefail printf '%s\n' "$1" >"$SK_GIT_TEST_OPEN" STUB chmod +x "$stub_bin/open" +# Linux resolves the opener as xdg-open; macOS uses open. +ln -s "$stub_bin/open" "$stub_bin/xdg-open" cat >"$stub_bin/ssh" <<'STUB' #!/usr/bin/env bash diff --git a/tests/fish-skim.sh b/tests/fish-skim.sh old mode 100644 new mode 100755 diff --git a/tests/television-git-action-commands.sh b/tests/television-git-action-commands.sh old mode 100644 new mode 100755 index 3f0b0c7..59b644b --- a/tests/television-git-action-commands.sh +++ b/tests/television-git-action-commands.sh @@ -53,6 +53,7 @@ git init -q --bare "$remote" git -C "$repo" config user.name "Television test" git -C "$repo" config user.email "television@example.invalid" git -C "$repo" config commit.gpgsign false +git -C "$repo" config tag.gpgsign false printf 'base\n' >"$repo/file with spaces.txt" git -C "$repo" add "file with spaces.txt" git -C "$repo" commit -qm "base" diff --git a/tests/television-git-actions.py b/tests/television-git-actions.py old mode 100644 new mode 100755 diff --git a/tests/television-git-channels.sh b/tests/television-git-channels.sh index 8e13028..79cff93 100755 --- a/tests/television-git-channels.sh +++ b/tests/television-git-channels.sh @@ -36,14 +36,24 @@ assert_contains() { fail "$file is missing: $pattern" } +# Match a whole `key = value` line, ignoring the indentation and `=` +# alignment that taplo adds. +assert_setting() { + local file=$1 + local setting=$2 + sed -E 's/^[[:space:]]+//; s/[[:space:]]*=[[:space:]]*/ = /' "$file" | + rg -Fxq -- "$setting" || + fail "$file is missing setting: $setting" +} + for channel in "${channels[@]}"; do file="$channel_dir/$channel.toml" [[ -f "$file" ]] || fail "$file is missing" - assert_contains "$file" "name = \"x$channel\"" - assert_contains "$file" 'requirements = ["git"]' - assert_contains "$file" 'no_sort = true' - assert_contains "$file" 'frecency = false' + assert_setting "$file" "name = \"x$channel\"" + assert_setting "$file" 'requirements = ["git"]' + assert_setting "$file" 'no_sort = true' + assert_setting "$file" 'frecency = false' assert_contains "$file" '[source]' assert_contains "$file" '[preview]' @@ -103,6 +113,7 @@ git init -q "$worktree_repo" git -C "$worktree_repo" config user.name "Television test" git -C "$worktree_repo" config user.email "television@example.invalid" git -C "$worktree_repo" config commit.gpgsign false +git -C "$worktree_repo" config tag.gpgsign false printf 'base\n' >"$worktree_repo/base.txt" git -C "$worktree_repo" add base.txt git -C "$worktree_repo" commit -qm "base" diff --git a/tests/television-git-open.sh b/tests/television-git-open.sh old mode 100644 new mode 100755 index e5a6d8d..27e14ba --- a/tests/television-git-open.sh +++ b/tests/television-git-open.sh @@ -22,6 +22,7 @@ git -C "$repo" init -q git -C "$repo" config user.name "Television test" git -C "$repo" config user.email "television@example.invalid" git -C "$repo" config commit.gpgsign false +git -C "$repo" config tag.gpgsign false printf 'tracked\n' >"$repo/tracked.txt" git -C "$repo" add tracked.txt git -C "$repo" commit -qm "initial" diff --git a/tests/zsh-sk-git-arguments.sh b/tests/zsh-sk-git-arguments.sh old mode 100644 new mode 100755 index cc27672..e03f742 --- a/tests/zsh-sk-git-arguments.sh +++ b/tests/zsh-sk-git-arguments.sh @@ -8,7 +8,7 @@ fail() { } repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) -zsh_config="$repo_root/chezmoi/dot_config/zsh/conf.d/third-party/90_sk-git.sh" +zsh_config="$repo_root/chezmoi/dot_config/zsh/conf.d/third-party/sk-git.sh" tmp_dir=$(mktemp -d) trap 'rm -rf "$tmp_dir"' EXIT export GIT_CONFIG_GLOBAL="$tmp_dir/global-gitconfig" @@ -36,6 +36,7 @@ git -C "$git_repo" init -q -b main git -C "$git_repo" config user.name "sk-git argument test" git -C "$git_repo" config user.email "sk-git-arguments@example.invalid" git -C "$git_repo" config commit.gpgsign false +git -C "$git_repo" config tag.gpgsign false printf 'tracked\n' >"$git_repo/tracked.txt" git -C "$git_repo" add tracked.txt git -C "$git_repo" commit -q -m "initial commit" diff --git a/tests/zsh-sk-git.sh b/tests/zsh-sk-git.sh old mode 100644 new mode 100755 index cab82d2..fb63d23 --- a/tests/zsh-sk-git.sh +++ b/tests/zsh-sk-git.sh @@ -8,14 +8,13 @@ fail() { } repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) -zsh_config="$repo_root/chezmoi/dot_config/zsh/conf.d/third-party/90_sk-git.sh" +zsh_config="$repo_root/chezmoi/dot_config/zsh/conf.d/third-party/sk-git.sh" legacy_fzf_git="$repo_root/chezmoi/dot_config/fzf-git/fzf-git.sh" legacy_fzf_git_dir="$repo_root/chezmoi/dot_config/fzf-git" legacy_fzf_git_readme="$legacy_fzf_git_dir/README.md" zother_tools="$repo_root/chezmoi/dot_config/zsh/conf.d/third-party/zother-tools.sh.tmpl" zsh_loader="$repo_root/chezmoi/dot_config/zsh/dot_zshrc.tmpl" third_party_dir="$repo_root/chezmoi/dot_config/zsh/conf.d/third-party" -unrelated_fzf="$third_party_dir/fzf.sh.tmpl" unrelated_fzf_zoxide="$third_party_dir/zoxide.sh" tmp_dir=$(mktemp -d) trap 'rm -rf "$tmp_dir"' EXIT @@ -29,21 +28,18 @@ git_bin=$(command -v git) awk_bin=$(command -v awk) uname_bin=$(command -v uname) -[[ -f "$zsh_config" ]] || fail "90_sk-git.sh is missing" +[[ -f "$zsh_config" ]] || fail "sk-git.sh is missing" [[ ! -e "$legacy_fzf_git" ]] || fail 'obsolete fzf-git payload still exists' [[ ! -e "$legacy_fzf_git_readme" ]] || fail 'obsolete fzf-git README still exists' [[ ! -d "$legacy_fzf_git_dir" ]] || fail 'obsolete fzf-git directory still exists' if rg -n -e '^# FZF Git integration$' -e 'fzf-git/fzf-git\.sh' "$zother_tools"; then fail 'obsolete FZF Git source block still exists' fi -sk_git_module_count=$(rg --files "$third_party_dir" | awk '$0 ~ /\/90_sk-git\.sh$/ { count++ } END { print count + 0 }') -[[ "$sk_git_module_count" == 1 ]] || fail "expected exactly one 90_sk-git.sh module, found $sk_git_module_count" +sk_git_module_count=$(rg --files "$third_party_dir" | awk '$0 ~ /\/sk-git\.sh$/ { count++ } END { print count + 0 }') +[[ "$sk_git_module_count" == 1 ]] || fail "expected exactly one sk-git.sh module, found $sk_git_module_count" [[ $(rg -Fxc ' source "$file"' "$zsh_loader") == 1 ]] || fail 'Zsh conf.d loader does not load modules exactly once' -[[ -f "$unrelated_fzf" ]] || fail 'unrelated Zsh FZF integration was removed' [[ -f "$unrelated_fzf_zoxide" ]] || fail 'unrelated Zsh zoxide FZF widget was removed' -rg -Fq 'FZF_CTRL_R_COMMAND' "$unrelated_fzf" || - fail 'unrelated Zsh FZF integration was changed' rg -Fq 'fzf_zoxide_widget' "$unrelated_fzf_zoxide" || fail 'unrelated zoxide FZF widget was changed' zsh -n "$zsh_config" diff --git a/tests/zsh-skim.sh b/tests/zsh-skim.sh old mode 100644 new mode 100755 index 5d9b8f3..19a40c3 --- a/tests/zsh-skim.sh +++ b/tests/zsh-skim.sh @@ -8,7 +8,7 @@ fail() { } repo_root=$(cd "$(dirname "$0")/.." && pwd) -skim_source="$repo_root/chezmoi/dot_config/zsh/conf.d/third-party/99_skim.sh" +skim_source="$repo_root/chezmoi/dot_config/zsh/conf.d/third-party/skim.sh" tmp_dir=$(mktemp -d) trap 'rm -rf "$tmp_dir"' EXIT