Skip to content

windows

windows #26

name: Install Dotfiles
on:
# No paths filter: bootstrap.sh touches most of the repo (.gitconfig,
# .tmux.conf, .gitattributes, .claude/**, etc.). Listing every relevant
# file is fragile and silently misses regressions when the list drifts.
push:
branches: [main]
pull_request:
schedule:
- cron: '0 3 * * 6'
workflow_dispatch:
permissions:
contents: read
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
install-ubuntu:
name: Install (Ubuntu container)
runs-on: ubuntu-latest
timeout-minutes: 30
container:
image: ubuntu:24.04
env:
DEBIAN_FRONTEND: noninteractive
steps:
- name: Install prerequisites
run: |
apt-get update
apt-get install -y git curl sudo tree xz-utils fontconfig gcc make python3
- name: Create test user
run: |
useradd -m -s /bin/bash clay
echo "clay ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
su - clay -c "git config --global --add safe.directory '*'"
- name: Checkout PR
uses: actions/checkout@v4
with:
path: dotfiles-pr
- name: Stage PR repo at ~/dotfiles
run: |
cp -a dotfiles-pr /home/clay/dotfiles
chown -R clay:clay /home/clay/dotfiles
# restore-keys intentionally omitted: a loose fallback would let a
# half-broken install from a prior run propagate. Cold rebuild on key
# miss is slower but always reflects current bootstrap.sh state.
- name: Cache pixi globals
uses: actions/cache@v4
with:
path: /home/clay/.pixi
key: pixi-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('dotfiles-pr/scripts/bootstrap.sh') }}
- name: Cache lazy.nvim plugins
uses: actions/cache@v4
with:
path: /home/clay/.local/share/nvim/lazy
key: lazy-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('dotfiles-pr/.config/nvim/lazy-lock.json') }}
- name: Reset cached path ownership
run: chown -R clay:clay /home/clay
- name: Authenticated git for plugin clones (lift GH rate limit)
if: github.actor == github.repository_owner
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
su - clay -c "git config --global url.\"https://x-access-token:${GH_TOKEN}@github.com/\".insteadOf \"https://github.com/\""
- name: Run bootstrap (first run)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: su - clay -c "export GITHUB_TOKEN='${GH_TOKEN}'; cd ~/dotfiles && ./scripts/bootstrap.sh"
- name: Assert first bootstrap produced no tracked-file drift
run: |
su - clay -c '
set -e
cd ~/dotfiles
if [ -n "$(git status --porcelain)" ]; then
echo "::error::first bootstrap dirtied tracked files (would be invisible to a same-output two-run check)"
git status --porcelain
git diff
exit 1
fi
echo "✓ first run clean"
'
- name: Run bootstrap (idempotency check)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: su - clay -c "export GITHUB_TOKEN='${GH_TOKEN}'; cd ~/dotfiles && ./scripts/bootstrap.sh"
- name: Assert second bootstrap did not modify tracked files
run: |
su - clay -c '
set -e
cd ~/dotfiles
if [ -n "$(git status --porcelain)" ]; then
echo "::error::second bootstrap modified tracked files"
git status --porcelain
git diff
exit 1
fi
echo "✓ no tracked-file changes after two bootstrap runs"
'
- name: Verify symlinks
run: |
su - clay -c '
set -eo pipefail
echo "Checking symlinks..."
test_symlink() {
local symlink=$1
local expected=$2
if [ -L "$symlink" ]; then
local target=$(readlink -f "$symlink")
if [[ "$target" == *"$expected"* ]]; then
echo "✓ $symlink -> $expected"
else
echo "✗ $symlink points to wrong target: $target"
return 1
fi
else
echo "✗ $symlink is not a symlink"
return 1
fi
}
test_symlink ~/.bashrc ".bashrc"
test_symlink ~/.gitignore ".gitignore"
test_symlink ~/.gitlab_ci_skip ".gitlab_ci_skip"
test_symlink ~/.claude ".claude"
test_symlink ~/.config/nvim "nvim"
test_symlink ~/.config/kitty "kitty"
test_symlink ~/.config/starship.toml "starship.toml"
if bash -n ~/.bashrc 2>/dev/null; then
echo "✓ .bashrc syntax valid"
else
echo "✗ .bashrc has syntax errors"
exit 1
fi
'
- name: Verify pixi packages
run: |
su - clay -c '
export PATH="$HOME/.pixi/bin:$HOME/.local/bin:$PATH"
set -eo pipefail
echo "Checking pixi packages functionality..."
pixi --version && echo "✓ pixi"
nvim --version | head -1 && echo "✓ neovim"
tmux -V && echo "✓ tmux"
starship --version && echo "✓ starship"
eza --version | head -1 && echo "✓ eza"
bat --version && echo "✓ bat"
fzf --version && echo "✓ fzf"
rg --version | head -1 && echo "✓ ripgrep"
fd --version && echo "✓ fd-find"
fastfetch --version && echo "✓ fastfetch"
stylua --version && echo "✓ stylua"
selene --version && echo "✓ selene"
jj --version && echo "✓ jujutsu"
hyperfine --version && echo "✓ hyperfine"
claude --version && echo "✓ claude code"
'
- name: Verify NVM + Node
run: |
su - clay -c '
export NVM_DIR="$HOME/.local/share/nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
set -eo pipefail
nvm --version && echo "✓ nvm"
node_version=$(node --version)
if [[ "$node_version" =~ ^v[0-9]+\.[0-9]+\.[0-9]+ ]]; then
echo "✓ node $node_version"
else
echo "✗ node returned invalid version: $node_version"
exit 1
fi
npm --version && echo "✓ npm"
'
- name: Verify kitty
run: |
su - clay -c '
if [ -x ~/.local/kitty.app/bin/kitty ]; then
kitty_version=$(~/.local/kitty.app/bin/kitty --version 2>&1 || true)
echo "✓ kitty binary: $kitty_version"
else
echo "✗ kitty binary not found or not executable"
exit 1
fi
if [ -L ~/.local/bin/kitty ]; then
echo "✓ kitty symlink exists"
else
echo "⚠ kitty symlink missing (non-fatal)"
fi
'
- name: Verify nvim setup
run: |
su - clay -c '
set -eo pipefail
export PATH="$HOME/.pixi/bin:$PATH"
nvim_version=$(nvim --version | head -1)
echo "✓ $nvim_version"
if [ -d ~/.local/share/nvim/lazy ]; then
plugin_count=$(find ~/.local/share/nvim/lazy -maxdepth 1 -type d | wc -l)
if [ "$plugin_count" -gt 10 ]; then
echo "✓ Lazy plugins installed ($plugin_count plugins)"
else
echo "⚠ Fewer than expected plugins ($plugin_count)"
fi
else
echo "✗ Lazy plugin directory not found"
exit 1
fi
PARSER_DIR="$HOME/.local/share/nvim/site/parser"
if [ ! -d "$PARSER_DIR" ]; then
echo "✗ Treesitter parser directory not found at $PARSER_DIR"
exit 1
fi
parser_count=$(ls "$PARSER_DIR"/*.so 2>/dev/null | wc -l)
for p in lua c bash markdown markdown_inline json yaml; do
[ -f "$PARSER_DIR/$p.so" ] || { echo "✗ missing required parser: $p.so"; exit 1; }
done
if [ "$parser_count" -lt 12 ]; then
echo "✗ only $parser_count parsers installed (expected >=12)"
exit 1
fi
echo "✓ Treesitter parsers ($parser_count parsers; lua/c/bash/markdown/json/yaml present)"
if [ -d ~/.local/share/nvim/mason/packages ]; then
mason_count=$(ls -d ~/.local/share/nvim/mason/packages/* 2>/dev/null | wc -l)
echo "✓ Mason packages ($mason_count packages)"
else
echo "✗ Mason directory not found"
exit 1
fi
FZF_LIB=~/.local/share/nvim/lazy/telescope-fzf-native.nvim/build/libfzf.so
if [ -f "$FZF_LIB" ]; then
echo "✓ telescope-fzf-native built"
else
echo "✗ telescope-fzf-native build failed (missing $FZF_LIB)"
exit 1
fi
# Capture stdout+stderr and exit status independently — a non-zero
# exit with no "error" string in output would otherwise pass.
set +e
output=$(timeout 30 nvim --headless -c "qa" 2>&1)
status=$?
set -e
if [ "$status" -ne 0 ]; then
echo "✗ nvim exited with status $status"
echo "$output"
exit 1
fi
if echo "$output" | grep -qE "^E[0-9]+:|Error detected while processing|stack traceback"; then
echo "✗ nvim startup output contains errors:"
echo "$output"
exit 1
fi
echo "✓ nvim starts cleanly"
'
- name: Measure nvim startup
run: |
su - clay -c '
export PATH="$HOME/.pixi/bin:$PATH"
cd ~/dotfiles
bash .github/scripts/benchmark-nvim.sh /tmp/result.json
cat /tmp/result.json
'
- name: Measure bash startup
run: |
su - clay -c '
export PATH="$HOME/.pixi/bin:$PATH"
cd ~/dotfiles
bash .github/scripts/benchmark-bash.sh /tmp/bash-result.json
cat /tmp/bash-result.json
'
- name: Restore benchmark history
uses: actions/cache/restore@v4
with:
path: /tmp/cache
key: ${{ runner.os }}-benchmark-history-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-benchmark-history-
- name: Compare against rolling baseline (nvim + bash)
env:
RECORD: ${{ github.event_name == 'push' && '--record' || '' }}
GITHUB_SHA: ${{ github.sha }}
run: |
chown -R clay:clay /tmp/cache 2>/dev/null || true
su - clay -c "
export GITHUB_SHA='${GITHUB_SHA}'
cd ~/dotfiles
python3 .github/scripts/compare-benchmark.py /tmp/result.json /tmp/cache 'nvim startup (Ubuntu)' ${RECORD}
python3 .github/scripts/compare-benchmark.py /tmp/bash-result.json /tmp/cache 'bash startup (Ubuntu)' ${RECORD}
"
- name: Save benchmark history
if: github.event_name == 'push'
uses: actions/cache/save@v4
with:
path: /tmp/cache
key: ${{ runner.os }}-benchmark-history-${{ github.run_id }}
- name: Collect failure diagnostics
if: failure()
run: |
mkdir -p /tmp/failure-bundle
su - clay -c '
cd ~/dotfiles
git status --porcelain > /tmp/failure-bundle/git-status.txt 2>&1 || true
git diff > /tmp/failure-bundle/git-diff.txt 2>&1 || true
cp -a ~/.local/state/nvim /tmp/failure-bundle/nvim-state 2>/dev/null || true
pixi global list > /tmp/failure-bundle/pixi-globals.txt 2>&1 || true
ls -la ~ > /tmp/failure-bundle/home-listing.txt 2>&1 || true
' || true
- name: Upload failure diagnostics
if: failure()
uses: actions/upload-artifact@v4
with:
name: failure-ubuntu-${{ github.run_id }}-${{ github.run_attempt }}
path: /tmp/failure-bundle
if-no-files-found: ignore
- name: Test summary
if: always()
run: |
{
echo "## Install Dotfiles — Ubuntu"
echo ""
echo "| Component | Version |"
echo "|-----------|---------|"
} >> "$GITHUB_STEP_SUMMARY"
su - clay -c '
export PATH="$HOME/.pixi/bin:$PATH"
export NVM_DIR="$HOME/.local/share/nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
echo "| pixi | $(pixi --version 2>/dev/null || echo "N/A") |"
echo "| nvim | $(nvim --version 2>/dev/null | head -1 || echo "N/A") |"
echo "| node | $(node --version 2>/dev/null || echo "N/A") |"
echo "| kitty | $(~/.local/kitty.app/bin/kitty --version 2>/dev/null || echo "N/A") |"
' >> "$GITHUB_STEP_SUMMARY"
install-macos:
name: Install (macOS)
runs-on: macos-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
with:
path: dotfiles-pr
- name: Stage dotfiles at $HOME
run: |
mkdir -p "$HOME/dotfiles"
rsync -a --delete dotfiles-pr/ "$HOME/dotfiles/"
cd "$HOME/dotfiles"
git config --global --add safe.directory "$HOME/dotfiles"
- name: Cache pixi globals
uses: actions/cache@v4
with:
path: ~/.pixi
key: pixi-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('dotfiles-pr/scripts/bootstrap.sh') }}
- name: Cache lazy.nvim plugins
uses: actions/cache@v4
with:
path: ~/.local/share/nvim/lazy
key: lazy-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('dotfiles-pr/.config/nvim/lazy-lock.json') }}
- name: Authenticated git for plugin clones
if: github.actor == github.repository_owner
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/"
- name: Run bootstrap (first run)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: cd "$HOME/dotfiles" && ./scripts/bootstrap.sh
- name: Assert first bootstrap produced no tracked-file drift
run: |
set -e
cd "$HOME/dotfiles"
if [ -n "$(git status --porcelain)" ]; then
echo "::error::first bootstrap dirtied tracked files"
git status --porcelain
git diff
exit 1
fi
echo "✓ first run clean"
- name: Run bootstrap (idempotency check)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: cd "$HOME/dotfiles" && ./scripts/bootstrap.sh
- name: Assert second bootstrap did not modify tracked files
run: |
set -e
cd "$HOME/dotfiles"
if [ -n "$(git status --porcelain)" ]; then
echo "::error::second bootstrap modified tracked files"
git status --porcelain
git diff
exit 1
fi
echo "✓ no tracked-file changes after two bootstrap runs"
- name: Verify symlinks (macOS)
run: |
set -eo pipefail
test_symlink() {
local s=$1 expected=$2
[ -L "$s" ] || { echo "✗ $s not a symlink"; return 1; }
local target=$(readlink "$s")
[[ "$target" == *"$expected"* ]] || { echo "✗ $s -> $target (expected *$expected*)"; return 1; }
echo "✓ $s -> $expected"
}
test_symlink ~/.bashrc ".bashrc"
test_symlink ~/.gitignore ".gitignore"
test_symlink ~/.gitlab_ci_skip ".gitlab_ci_skip"
test_symlink ~/.claude ".claude"
test_symlink ~/.config/nvim "nvim"
test_symlink ~/.config/kitty "kitty"
test_symlink ~/.config/starship.toml "starship.toml"
test_symlink ~/.config/git/config "gitconfig"
[ -f ~/.bash_profile ] && echo "✓ .bash_profile present (Darwin)"
bash -n ~/.bashrc && echo "✓ .bashrc syntax valid"
- name: Verify pixi tools (macOS)
run: |
export PATH="$HOME/.pixi/bin:$HOME/.local/bin:$PATH"
set -eo pipefail
pixi --version && echo "✓ pixi"
nvim --version | head -1 && echo "✓ neovim"
tmux -V && echo "✓ tmux"
starship --version && echo "✓ starship"
eza --version | head -1 && echo "✓ eza"
bat --version && echo "✓ bat"
fzf --version && echo "✓ fzf"
rg --version | head -1 && echo "✓ ripgrep"
fd --version && echo "✓ fd-find"
fastfetch --version && echo "✓ fastfetch"
stylua --version && echo "✓ stylua"
selene --version && echo "✓ selene"
jj --version && echo "✓ jujutsu"
hyperfine --version && echo "✓ hyperfine"
claude --version && echo "✓ claude code"
- name: Verify modern bash is default shell (macOS)
run: |
set -eo pipefail
login_shell=$(dscl . -read /Users/"$USER" UserShell | awk '{print $2}')
want="$HOME/.pixi/bin/bash"
if [ "$login_shell" = "$want" ]; then
echo "✓ login shell is pixi bash ($login_shell)"
else
echo "✗ login shell is $login_shell, expected $want"
exit 1
fi
- name: Verify nvim starts (macOS)
run: |
export PATH="$HOME/.pixi/bin:$PATH"
# perl's alarm + exec gives a portable 30s timeout (no coreutils on
# stock macOS; SIGALRM kills the child, status 142 surfaces below).
set +e
output=$(perl -e 'alarm 30; exec @ARGV' nvim --headless -c "qa" 2>&1)
status=$?
set -e
if [ "$status" -ne 0 ]; then
echo "✗ nvim exited with status $status"
echo "$output"
exit 1
fi
if echo "$output" | grep -qE "^E[0-9]+:|Error detected while processing|stack traceback"; then
echo "✗ nvim startup output contains errors:"
echo "$output"
exit 1
fi
echo "✓ nvim starts cleanly"
- name: Measure nvim startup
run: |
export PATH="$HOME/.pixi/bin:$PATH"
cd "$HOME/dotfiles"
bash .github/scripts/benchmark-nvim.sh /tmp/result.json
cat /tmp/result.json
- name: Measure bash startup
run: |
export PATH="$HOME/.pixi/bin:$PATH"
cd "$HOME/dotfiles"
bash .github/scripts/benchmark-bash.sh /tmp/bash-result.json
cat /tmp/bash-result.json
- name: Restore benchmark history
uses: actions/cache/restore@v4
with:
path: /tmp/cache
key: ${{ runner.os }}-benchmark-history-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-benchmark-history-
- name: Compare against rolling baseline (nvim + bash)
env:
RECORD: ${{ github.event_name == 'push' && '--record' || '' }}
run: |
cd "$HOME/dotfiles"
python3 .github/scripts/compare-benchmark.py /tmp/result.json /tmp/cache 'nvim startup (macOS)' ${RECORD}
python3 .github/scripts/compare-benchmark.py /tmp/bash-result.json /tmp/cache 'bash startup (macOS)' ${RECORD}
- name: Save benchmark history
if: github.event_name == 'push'
uses: actions/cache/save@v4
with:
path: /tmp/cache
key: ${{ runner.os }}-benchmark-history-${{ github.run_id }}
- name: Collect failure diagnostics (macOS)
if: failure()
run: |
mkdir -p /tmp/failure-bundle
cd "$HOME/dotfiles"
git status --porcelain > /tmp/failure-bundle/git-status.txt 2>&1 || true
git diff > /tmp/failure-bundle/git-diff.txt 2>&1 || true
cp -a ~/.local/state/nvim /tmp/failure-bundle/nvim-state 2>/dev/null || true
"$HOME/.pixi/bin/pixi" global list > /tmp/failure-bundle/pixi-globals.txt 2>&1 || true
ls -la ~ > /tmp/failure-bundle/home-listing.txt 2>&1 || true
- name: Upload failure diagnostics (macOS)
if: failure()
uses: actions/upload-artifact@v4
with:
name: failure-macos-${{ github.run_id }}-${{ github.run_attempt }}
path: /tmp/failure-bundle
if-no-files-found: ignore
install-windows:
name: Install (Windows)
runs-on: windows-latest
timeout-minutes: 60
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
with:
path: dotfiles-pr
- name: Enable Developer Mode (native symlinks)
shell: pwsh
run: |
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" `
-Name AllowDevelopmentWithoutDevLicense -PropertyType DWord -Value 1 -Force | Out-Null
Write-Host "Developer Mode enabled"
- name: Stage PR repo at ~/dotfiles
run: |
cp -a dotfiles-pr "$HOME/dotfiles"
git config --global --add safe.directory '*'
git config --global core.symlinks true
- name: Cache pixi globals
uses: actions/cache@v4
with:
path: ~/.pixi
key: pixi-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('dotfiles-pr/scripts/bootstrap.sh') }}
- name: Cache lazy.nvim plugins
uses: actions/cache@v4
with:
path: ~/.local/share/nvim-data/lazy
key: lazy-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('dotfiles-pr/.config/nvim/lazy-lock.json') }}
- name: Authenticated git for plugin clones (lift GH rate limit)
if: github.actor == github.repository_owner
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config --global url."https://x-access-token:${GH_TOKEN}@github.com/".insteadOf "https://github.com/"
- name: Run bootstrap (first run)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: cd "$HOME/dotfiles" && ./scripts/bootstrap.sh
- name: Assert first bootstrap produced no tracked-file drift
run: |
set -e
cd "$HOME/dotfiles"
if [ -n "$(git status --porcelain)" ]; then
echo "::error::first bootstrap dirtied tracked files"
git status --porcelain
git diff
exit 1
fi
echo "✓ first run clean"
- name: Run bootstrap (idempotency check)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: cd "$HOME/dotfiles" && ./scripts/bootstrap.sh
- name: Assert second bootstrap did not modify tracked files
run: |
set -e
cd "$HOME/dotfiles"
if [ -n "$(git status --porcelain)" ]; then
echo "::error::second bootstrap modified tracked files"
git status --porcelain
git diff
exit 1
fi
echo "✓ no tracked-file changes after two bootstrap runs"
- name: Verify symlinks
run: |
set -eo pipefail
test_link() {
local s=$1 expected=$2
if [ -e "$s" ] && { [ -L "$s" ] || [ -d "$s" ]; }; then
echo "✓ $s ($expected)"
else
echo "✗ $s missing or not a link"
exit 1
fi
}
test_link ~/.bashrc ".bashrc"
test_link ~/.gitignore ".gitignore"
test_link ~/.config/nvim "nvim"
test_link ~/.config/wezterm "wezterm"
test_link ~/.config/git/config "gitconfig"
test_link ~/.claude ".claude"
bash -n ~/.bashrc && echo "✓ .bashrc syntax valid"
- name: Verify pixi tools
run: |
export PATH="$HOME/.pixi/bin:$HOME/.local/bin:$PATH"
set -eo pipefail
nvim --version | head -1 && echo "✓ neovim"
starship --version && echo "✓ starship"
eza --version | head -1 && echo "✓ eza"
bat --version && echo "✓ bat"
fzf --version && echo "✓ fzf"
rg --version | head -1 && echo "✓ ripgrep"
fd --version && echo "✓ fd-find"
stylua --version && echo "✓ stylua"
selene --version && echo "✓ selene"
jj --version && echo "✓ jujutsu"
hyperfine --version && echo "✓ hyperfine"
zig version && echo "✓ zig"
fnm --version && echo "✓ fnm"
claude --version && echo "✓ claude code"
- name: Verify nvim setup
run: |
set -eo pipefail
export PATH="$HOME/.pixi/bin:$PATH"
export XDG_CONFIG_HOME="$HOME/.config"
export XDG_DATA_HOME="$HOME/.local/share"
if [ -d ~/.local/share/nvim-data/lazy ]; then
plugin_count=$(find ~/.local/share/nvim-data/lazy -maxdepth 1 -type d | wc -l)
echo "✓ Lazy plugins installed ($plugin_count)"
else
echo "✗ Lazy plugin directory not found"
exit 1
fi
PARSER_DIR="$HOME/.local/share/nvim-data/site/parser"
if [ ! -d "$PARSER_DIR" ]; then
echo "✗ Treesitter parser directory not found at $PARSER_DIR (zig-cc pipeline likely broken)"
exit 1
fi
# Parsers compile to *.so even on Windows (loaded by libuv).
parser_count=$(find "$PARSER_DIR" -name '*.so' | wc -l)
for p in lua c bash markdown markdown_inline json yaml; do
[ -f "$PARSER_DIR/$p.so" ] || { echo "✗ missing required parser: $p.so"; exit 1; }
done
if [ "$parser_count" -lt 12 ]; then
echo "✗ only $parser_count parsers installed (expected >=12)"
exit 1
fi
echo "✓ Treesitter parsers ($parser_count; lua/c/bash/markdown/json/yaml present)"
if [ -d ~/.local/share/nvim-data/mason/packages ]; then
mason_count=$(find ~/.local/share/nvim-data/mason/packages -maxdepth 1 -mindepth 1 -type d | wc -l)
echo "✓ Mason packages ($mason_count)"
else
echo "⚠ Mason directory not found"
fi
FZF_DLL=~/.local/share/nvim-data/lazy/telescope-fzf-native.nvim/build/libfzf.dll
if [ -f "$FZF_DLL" ]; then
echo "✓ telescope-fzf-native built"
else
echo "⚠ telescope-fzf-native not built (telescope falls back to its Lua sorter; needs MSVC)"
fi
# Git Bash ships /usr/bin/timeout (MSYS coreutils) — use it directly.
set +e
output=$(timeout 30 nvim --headless -c "qa" 2>&1)
status=$?
set -e
if [ "$status" -ne 0 ]; then
echo "✗ nvim exited with status $status (124 = timeout)"
echo "$output"
exit 1
fi
if echo "$output" | grep -qE "^E[0-9]+:|Error detected while processing|stack traceback"; then
echo "✗ nvim startup output contains errors:"
echo "$output"
exit 1
fi
echo "✓ nvim starts cleanly"
- name: Collect failure diagnostics (Windows)
if: failure()
run: |
mkdir -p /tmp/failure-bundle
cd "$HOME/dotfiles"
git status --porcelain > /tmp/failure-bundle/git-status.txt 2>&1 || true
git diff > /tmp/failure-bundle/git-diff.txt 2>&1 || true
cp -a "$HOME/.local/state/nvim-data" /tmp/failure-bundle/nvim-state 2>/dev/null || true
"$HOME/.pixi/bin/pixi.exe" global list > /tmp/failure-bundle/pixi-globals.txt 2>&1 || true
ls -la "$HOME" > /tmp/failure-bundle/home-listing.txt 2>&1 || true
- name: Upload failure diagnostics (Windows)
if: failure()
uses: actions/upload-artifact@v4
with:
name: failure-windows-${{ github.run_id }}-${{ github.run_attempt }}
path: /tmp/failure-bundle
if-no-files-found: ignore
- name: Test summary
if: always()
shell: bash
run: |
export PATH="$HOME/.pixi/bin:$PATH"
{
echo "## Install Dotfiles — Windows"
echo ""
echo "| Component | Version |"
echo "|-----------|---------|"
echo "| pixi | $(pixi --version 2>/dev/null || echo N/A) |"
echo "| nvim | $(nvim --version 2>/dev/null | head -1 || echo N/A) |"
echo "| zig | $(zig version 2>/dev/null || echo N/A) |"
echo "| pwsh | $(pwsh --version 2>/dev/null || echo N/A) |"
} >> "$GITHUB_STEP_SUMMARY"
plugin-freshness:
name: Lazy.nvim plugin archive check
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@v4
# Pinned to commit SHA — third-party action, v1 tag is mutable.
- uses: yutkat/lazy-nvim-archived-checker@37335f7b997e03ecd574641c0ee400b76932ba0e # v1
with:
nvim-config-path: .config/nvim
github-token: ${{ secrets.GITHUB_TOKEN }}