From d67739aae5f29ffc3ef8f5e27558d640dfde69a8 Mon Sep 17 00:00:00 2001 From: Maxim Kalina Date: Sat, 9 May 2026 12:49:02 +0200 Subject: [PATCH] feat(workflow): reusable Renovate workflow Adds .github/workflows/renovate.yml as a reusable workflow (on: workflow_call) so every plugwerk repo can run self-hosted Renovate through a single place instead of duplicating the 70-line trigger. Inputs: - logLevel (default 'info'): pass-through to RENOVATE_LOG_LEVEL - configurationFile (default '.github/renovate.json'): per-caller override if a repo keeps its config elsewhere Token model is single-repo: each caller's GITHUB_TOKEN is scoped to its own repo. The reusable workflow does not add cross-repo write access, so adopting it is no different in security posture from the original per-repo trigger. Concurrency keys on github.repository so two runs against the same repo serialize but different repos run in parallel. README updated with the consumer-stub example and override behavior note. --- .github/workflows/renovate.yml | 60 ++++++++++++++++++++++++++++++++++ README.md | 37 +++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 .github/workflows/renovate.yml diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml new file mode 100644 index 0000000..f3a21d3 --- /dev/null +++ b/.github/workflows/renovate.yml @@ -0,0 +1,60 @@ +# Reusable Renovate workflow for the plugwerk org. +# +# Why reusable: every plugwerk repo runs the same Renovate binary +# against its own renovate.json with the same SHA-pinned action and +# the same logging conventions. Duplicating the 70-line trigger in +# every consumer repo means three places to touch on every action +# bump. This workflow holds the real logic; consumers add a 15-line +# stub that wires their schedule + permissions in. +# +# Token model: this is single-repo. Each caller's GITHUB_TOKEN is +# scoped to its own repo, so when the renovate binary opens branches +# and PRs, it does so against the calling repo only. No PAT, no +# GitHub App, no cross-repo write access. +# +# Trigger: callers decide when to fire. Recommended pattern is +# `cron: "0 4 * * 1-5"` (Mon-Fri 04:00 UTC) plus workflow_dispatch +# for on-demand runs from the Actions tab. + +name: Renovate (reusable) + +on: + workflow_call: + inputs: + logLevel: + description: "Renovate log level (info or debug)." + required: false + type: string + default: "info" + configurationFile: + description: "Path to the calling repo's renovate config." + required: false + type: string + default: ".github/renovate.json" + +jobs: + renovate: + runs-on: ubuntu-latest + # Concurrency keyed by the calling repo so two Renovate runs + # against the same repo cannot race on branch creation. Different + # repos are independent and run in parallel without contention. + concurrency: + group: renovate-${{ github.repository }}-${{ github.ref }} + cancel-in-progress: false + # Lift the input into an env var so the run-step never interpolates + # workflow input straight into a shell field — defensive practice + # that satisfies hardened-runner injection-prevention guidance even + # though the input has limited shape (string from a workflow_call). + env: + RENOVATE_LOG_LEVEL: ${{ inputs.logLevel }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Run Renovate + uses: renovatebot/github-action@79dc0ba74dc3de28db0a7aeb1d0b95d5bf5fde2a # v46.1.13 + with: + configurationFile: ${{ inputs.configurationFile }} + token: ${{ secrets.GITHUB_TOKEN }} + env: + LOG_LEVEL: ${{ env.RENOVATE_LOG_LEVEL }} diff --git a/README.md b/README.md index 906fe12..bf76b32 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ every repository under `plugwerk/*` that does not provide its own override. | `.github/ISSUE_TEMPLATE/` | YAML issue forms (bug, feature, config). | | `.github/PULL_REQUEST_TEMPLATE.md` | Default PR template. | | `.github/workflows/renovate-config-validator.yml` | Validates `default.json` on every PR. | +| `.github/workflows/renovate.yml` | Reusable Renovate trigger; consumer repos call it via `uses:` with their own schedule. | ## Override Behavior @@ -35,6 +36,42 @@ For Renovate, per-repo `renovate.json` files extend `github>plugwerk/.github` and add their own `packageRules` for project-specific stacks (e.g. Java/Kotlin groupings live in `plugwerk/plugwerk/.github/renovate.json`, not here). +## Reusable Renovate Workflow + +Consumer repos call the reusable trigger from this repo so the +`renovatebot/github-action` SHA pin and run conventions live in one +place. Minimal stub for a consumer repo's +`.github/workflows/renovate.yml`: + +```yaml +name: Renovate + +on: + schedule: + - cron: "0 4 * * 1-5" + workflow_dispatch: + inputs: + logLevel: + description: "Log level" + type: choice + default: info + options: [info, debug] + +jobs: + renovate: + uses: plugwerk/.github/.github/workflows/renovate.yml@main + permissions: + contents: write + pull-requests: write + issues: write + with: + logLevel: ${{ inputs.logLevel || 'info' }} +``` + +Each caller's `GITHUB_TOKEN` is scoped to its own repo, so the call is +single-repo by design — the reusable workflow does not add cross-repo +write access. + ## Maintenance Changes to `default.json` affect every repo in the org. Treat changes here as