From dc4ffaa94f95e9ca06a771cbfd5156eaeb1c5cf9 Mon Sep 17 00:00:00 2001 From: SimonKimDev Date: Fri, 16 Oct 2026 15:22:11 -0700 Subject: [PATCH] Add Dependabot for actions and a weekly plugin update workflow Dependabot can't update lazy.nvim plugins, so it only covers GitHub Actions: monthly, grouped, skipping gh-aw's generated *.lock.yml files. plugin-update.yml runs :Lazy update every Monday, commits lazy-lock.json to simonk/dev/plugin-update, opens or refreshes a PR with compare links, and dispatches CI on it so the smoke test covers the new commits. On PRs that change the updater it only does a dry run. The Neovim install steps move into .github/actions/setup-nvim so CI and the updater share them. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 90315f97-7c82-4bc1-9bf1-2f8460782554 --- .github/actions/setup-nvim/action.yml | 47 ++++++++++++++ .github/copilot-instructions.md | 1 + .github/dependabot.yml | 16 +++++ .github/scripts/plugin-update.lua | 81 ++++++++++++++++++++++++ .github/workflows/ci.yml | 32 +--------- .github/workflows/plugin-update.yml | 89 +++++++++++++++++++++++++++ README.md | 5 ++ 7 files changed, 240 insertions(+), 31 deletions(-) create mode 100644 .github/actions/setup-nvim/action.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/scripts/plugin-update.lua create mode 100644 .github/workflows/plugin-update.yml diff --git a/.github/actions/setup-nvim/action.yml b/.github/actions/setup-nvim/action.yml new file mode 100644 index 0000000..a7d8f95 --- /dev/null +++ b/.github/actions/setup-nvim/action.yml @@ -0,0 +1,47 @@ +name: Set up Neovim +description: >- + Install Neovim on a Windows runner and point it at this config with empty data, state and cache directories. + Check the repo out with `path: nvim` first, since Neovim reads $XDG_CONFIG_HOME/nvim. + +inputs: + version: + description: Neovim release to install + default: 0.11.4 # keep in sync with the Neovim you run locally + +runs: + using: composite + steps: + - name: Isolate Neovim directories + shell: pwsh + run: | + "XDG_CONFIG_HOME=$env:GITHUB_WORKSPACE" >> $env:GITHUB_ENV + foreach ($kind in 'DATA', 'STATE', 'CACHE') { + "XDG_${kind}_HOME=$(Join-Path $env:RUNNER_TEMP "xdg\$($kind.ToLower())")" >> $env:GITHUB_ENV + } + + - name: Install Neovim ${{ inputs.version }} + shell: pwsh + env: + NVIM_VERSION: ${{ inputs.version }} + run: | + $zip = Join-Path $env:RUNNER_TEMP nvim-win64.zip + Invoke-WebRequest "https://github.com/neovim/neovim/releases/download/v$env:NVIM_VERSION/nvim-win64.zip" -OutFile $zip + Expand-Archive $zip -DestinationPath $env:RUNNER_TEMP + $bin = Join-Path $env:RUNNER_TEMP nvim-win64\bin + & "$bin\nvim.exe" --version + $bin >> $env:GITHUB_PATH + + - name: Provide make + # telescope-fzf-native and LuaSnip build with `make`; the runner image only ships mingw32-make. + shell: pwsh + run: | + if (Get-Command make -ErrorAction SilentlyContinue) { exit 0 } + $mingw = Get-Command mingw32-make -ErrorAction SilentlyContinue + if ($mingw) { + $bin = Join-Path $env:RUNNER_TEMP make-bin + New-Item -ItemType Directory $bin -Force | Out-Null + Copy-Item $mingw.Source (Join-Path $bin make.exe) + $bin >> $env:GITHUB_PATH + } else { + choco install make --no-progress -y + } diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 9fb83d3..542f403 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -7,6 +7,7 @@ Personal Neovim config (Lua, lazy.nvim), used on Windows with pwsh. CI checks ar - Name branches `simonk/dev/` in kebab-case, e.g. `simonk/dev/add-smoke-assertions`. If a tool manages the local branch name, push with `git push -u origin HEAD:simonk/dev/` and open the PR from that branch. - Commit and push only as the personal `SimonKimDev` GitHub account, never a work account. - Merge PRs with a merge commit, not squash or rebase. `.git-blame-ignore-revs` lists commit SHAs that must stay on `main`. +- Don't reuse `simonk/dev/plugin-update`; the plugin update workflow owns it. ## Checks diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..418b3d1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: + # lazy.nvim plugins aren't a Dependabot ecosystem; .github/workflows/plugin-update.yml updates lazy-lock.json. + - package-ecosystem: github-actions + directories: + - / + - /.github/actions/* + exclude-paths: + - "*.lock.yml" # generated by `gh aw compile`; refresh those with `gh aw upgrade` instead + schedule: + interval: monthly + groups: + actions: + patterns: ["*"] + commit-message: + prefix: ci diff --git a/.github/scripts/plugin-update.lua b/.github/scripts/plugin-update.lua new file mode 100644 index 0000000..56ccb0a --- /dev/null +++ b/.github/scripts/plugin-update.lua @@ -0,0 +1,81 @@ +-- Update every plugin, rewrite lazy-lock.json and write a Markdown summary for the update PR. +-- Run by .github/workflows/plugin-update.yml, after the setup-nvim action isolated Neovim's directories: +-- nvim --headless "+luafile .github/scripts/plugin-update.lua" +-- Env: OLD_LOCKFILE is a copy of lazy-lock.json from before the update; the summary goes to SUMMARY_FILE. + +local function read_json(path) + return vim.json.decode(table.concat(vim.fn.readfile(path), "\n")) +end + +local function short(sha) + return "`" .. sha:sub(1, 7) .. "`" +end + +local function update() + local Config = require("lazy.core.config") + local Plugin = require("lazy.core.plugin") + + require("lazy").update({ wait = true, show = false }) + + local old, new = read_json(vim.env.OLD_LOCKFILE), read_json(Config.options.lockfile) + local names = vim.tbl_keys(vim.tbl_extend("force", {}, old, new)) + table.sort(names) + + local rows = {} + for _, name in ipairs(names) do + local from, to = old[name] and old[name].commit, new[name] and new[name].commit + if from ~= to then + local plugin = Config.plugins[name] + local repo = plugin and plugin.url and (plugin.url:gsub("%.git$", "")) + local change + if not from then + change = "added at " .. short(to) + elseif not to then + change = "removed" + elseif repo and repo:match("^https://github%.com/") then + change = ("[%s...%s](%s/compare/%s...%s)"):format(short(from), short(to), repo, from, to) + else + change = short(from) .. "..." .. short(to) + end + table.insert(rows, ("| %s | %s |"):format(name, change)) + end + end + + local failed = {} + for name, plugin in pairs(Config.plugins) do + if Plugin.has_errors(plugin) then + table.insert(failed, name) + end + end + table.sort(failed) + + local run = ("%s/%s/actions/runs/%s"):format( + vim.env.GITHUB_SERVER_URL or "https://github.com", + vim.env.GITHUB_REPOSITORY or "", + vim.env.GITHUB_RUN_ID or "" + ) + local lines = { + ("Weekly `:Lazy update` from the [plugin update workflow](%s)."):format(run), + "CI installs these exact commits into empty directories and runs the smoke test; merge if it's green.", + "", + "| Plugin | Change |", + "| --- | --- |", + } + vim.list_extend(lines, rows) + if #failed > 0 then + vim.list_extend(lines, { + "", + "> [!WARNING]", + "> These plugins reported errors while updating (see the workflow log): " .. table.concat(failed, ", "), + }) + end + vim.fn.writefile(lines, vim.env.SUMMARY_FILE) + + io.stdout:write(("\n%d plugin(s) changed, %d with errors\n"):format(#rows, #failed)) +end + +local ok, err = xpcall(update, debug.traceback) +if not ok then + io.stdout:write("\nplugin update failed:\n" .. err .. "\n") +end +vim.cmd(ok and "qa!" or "cquit 1") diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d94a117..d249f40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,8 +29,6 @@ jobs: name: Startup smoke test (Windows) runs-on: windows-latest timeout-minutes: 20 - env: - NVIM_VERSION: 0.11.4 # keep in sync with the Neovim you run locally defaults: run: shell: pwsh @@ -39,35 +37,7 @@ jobs: with: path: nvim # Neovim reads $XDG_CONFIG_HOME/nvim - - name: Isolate Neovim directories - run: | - "XDG_CONFIG_HOME=$env:GITHUB_WORKSPACE" >> $env:GITHUB_ENV - foreach ($kind in 'DATA', 'STATE', 'CACHE') { - "XDG_${kind}_HOME=$(Join-Path $env:RUNNER_TEMP "xdg\$($kind.ToLower())")" >> $env:GITHUB_ENV - } - - - name: Install Neovim ${{ env.NVIM_VERSION }} - run: | - $zip = Join-Path $env:RUNNER_TEMP nvim-win64.zip - Invoke-WebRequest "https://github.com/neovim/neovim/releases/download/v$env:NVIM_VERSION/nvim-win64.zip" -OutFile $zip - Expand-Archive $zip -DestinationPath $env:RUNNER_TEMP - $bin = Join-Path $env:RUNNER_TEMP nvim-win64\bin - & "$bin\nvim.exe" --version - $bin >> $env:GITHUB_PATH - - - name: Provide make - # telescope-fzf-native and LuaSnip build with `make`; the runner image only ships mingw32-make. - run: | - if (Get-Command make -ErrorAction SilentlyContinue) { exit 0 } - $mingw = Get-Command mingw32-make -ErrorAction SilentlyContinue - if ($mingw) { - $bin = Join-Path $env:RUNNER_TEMP make-bin - New-Item -ItemType Directory $bin -Force | Out-Null - Copy-Item $mingw.Source (Join-Path $bin make.exe) - $bin >> $env:GITHUB_PATH - } else { - choco install make --no-progress -y - } + - uses: ./nvim/.github/actions/setup-nvim - name: Smoke test run: | diff --git a/.github/workflows/plugin-update.yml b/.github/workflows/plugin-update.yml new file mode 100644 index 0000000..7a81fea --- /dev/null +++ b/.github/workflows/plugin-update.yml @@ -0,0 +1,89 @@ +name: Plugin update + +# Weekly `:Lazy update`. Commits the new lazy-lock.json to simonk/dev/plugin-update, opens or refreshes a PR, +# and dispatches CI on it, which runs the smoke test on the new commits. On a PR that changes the updater it only +# does a dry run and shows the would-be PR body in the job summary. + +on: + schedule: + - cron: "0 15 * * 1" # Mondays 15:00 UTC + workflow_dispatch: + pull_request: + paths: + - .github/workflows/plugin-update.yml + - .github/scripts/plugin-update.lua + - .github/actions/setup-nvim/** + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + update: + name: Update plugins + runs-on: windows-latest + timeout-minutes: 30 + permissions: + contents: write # push the update branch + pull-requests: write # open or refresh the PR + actions: write # dispatch CI; pushes made with GITHUB_TOKEN don't trigger workflows + env: + BRANCH: simonk/dev/plugin-update + BOT_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com + GH_TOKEN: ${{ github.token }} + defaults: + run: + shell: pwsh + working-directory: nvim + steps: + - uses: actions/checkout@v7 + with: + path: nvim # Neovim reads $XDG_CONFIG_HOME/nvim + + - uses: ./nvim/.github/actions/setup-nvim + + - name: Update plugins + id: update + env: + OLD_LOCKFILE: ${{ runner.temp }}/old-lock.json + SUMMARY_FILE: ${{ runner.temp }}/pr-body.md + run: | + Copy-Item lazy-lock.json $env:OLD_LOCKFILE + nvim --headless "+luafile .github/scripts/plugin-update.lua" + if ($LASTEXITCODE) { exit $LASTEXITCODE } + if (-not (git status --porcelain -- lazy-lock.json)) { + 'Every plugin is already at its latest commit.' >> $env:GITHUB_STEP_SUMMARY + exit 0 + } + Get-Content $env:SUMMARY_FILE >> $env:GITHUB_STEP_SUMMARY + "changed=true" >> $env:GITHUB_OUTPUT + + - name: Open or refresh the PR + if: steps.update.outputs.changed == 'true' && github.event_name != 'pull_request' + run: | + $PSNativeCommandUseErrorActionPreference = $true + $pr = gh pr list --head $env:BRANCH --state open --json number --jq '.[].number' + if ($pr) { + $authors = gh pr view $pr --json commits --jq '.commits[].authors[].email' + if ($authors | Where-Object { $_ -ne $env:BOT_EMAIL }) { + "::warning::#$pr has commits from someone else, so it wasn't overwritten. Merge or close it, then rerun this workflow." + exit 0 + } + } + + git config user.name 'github-actions[bot]' + git config user.email $env:BOT_EMAIL + git switch --quiet -C $env:BRANCH + git commit --quiet -m 'Update plugins' -- lazy-lock.json + git push --quiet --force origin "HEAD:refs/heads/$env:BRANCH" + + $body = Join-Path $env:RUNNER_TEMP pr-body.md + if ($pr) { + gh pr edit $pr --body-file $body + } else { + gh pr create --base main --head $env:BRANCH --title 'Update plugins' --body-file $body + } + gh workflow run ci.yml --ref $env:BRANCH diff --git a/README.md b/README.md index a109eb8..3ffd2f8 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,11 @@ This is my Neovim setup largely based and expanded on Josean Martinez's [blog po Other than small retouch the setup is virtually identitcal to Josean's setup, checkout his video for more detailed explanation on the setup. +## Updates + +- **Plugins**: `.github/workflows/plugin-update.yml` runs `:Lazy update` every Monday (or on demand from the Actions tab), commits the new `lazy-lock.json` to `simonk/dev/plugin-update`, and opens or refreshes an *Update plugins* PR that links each plugin's changes. CI runs on that PR, so the smoke test covers the new commits; merge it if it's green. The workflow force-pushes the branch each week, but leaves it alone once it has a commit from anyone else, so you can push a fix to it. It needs **Allow GitHub Actions to create and approve pull requests** turned on in Settings → Actions → General. +- **GitHub Actions**: Dependabot (`.github/dependabot.yml`) opens one grouped PR a month for the actions used in workflows and `.github/actions/`. It skips generated `*.lock.yml` workflows, because `gh aw compile` would overwrite its edits; refresh those with `gh aw upgrade`. Dependabot can't update lazy.nvim plugins, hence the workflow above. + ## CI checks Every pull request (and push to `main`) runs `.github/workflows/ci.yml`: