From cc83d94bc92c19d7ea15e03a6ce4bf65537d6426 Mon Sep 17 00:00:00 2001 From: adamXbot <111877622+adamXbot@users.noreply.github.com> Date: Tue, 7 Jul 2026 11:59:44 +1000 Subject: [PATCH] ci: replace Dependabot with Renovate for single-PR dependency updates Dependabot's pnpm support left pnpm-lock.yaml stale (manual regen needed) and fanned each ecosystem out into separate, mutually-conflicting PRs. Renovate regenerates the lockfile natively and bundles every non-major update across all four ecosystems (npm, cargo, docker, github-actions) into a single PR on a stable branch. Major upgrades are held on the Dependency Dashboard for one-at-a-time review. - Add renovate.json: semver-safe grouping, majors gated by dashboard approval, weekly Monday schedule, native pnpm-lock regen, self-image in deploy compose ignored. - Add .github/workflows/renovate.yml: self-hosted runner with a workflow_dispatch dry-run preview + weekly cron. Header documents the RENOVATE_TOKEN requirement (GITHUB_TOKEN PRs don't trigger CI) and the Mend-app alternative. - Remove .github/dependabot.yml (superseded; keeping it would duplicate Renovate's PRs). - codeql.yml: add a paths-filtered pull_request trigger so dependency PRs are security-scanned pre-merge; broad PR scanning stays off to respect the free-tier cost tradeoff. - Document the Renovate setup in AGENTS.md. Co-Authored-By: Claude Opus 4.8 --- .github/dependabot.yml | 61 ----------------------- .github/workflows/codeql.yml | 31 ++++++++---- .github/workflows/renovate.yml | 88 ++++++++++++++++++++++++++++++++++ AGENTS.md | 6 +++ renovate.json | 45 +++++++++++++++++ 5 files changed, 162 insertions(+), 69 deletions(-) delete mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/renovate.yml create mode 100644 renovate.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index b652219..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,61 +0,0 @@ -version: 2 -updates: - - package-ecosystem: npm - directory: / - schedule: - interval: weekly - day: monday - time: "09:00" - timezone: Australia/Melbourne - groups: - next-react: - patterns: - - next - - react - - react-dom - biome-lint: - patterns: - - "@biomejs/*" - - ultracite - test-tooling: - patterns: - - "@playwright/*" - - tsx - - typescript - - "@types/*" - - - package-ecosystem: cargo - directory: /src-tauri - schedule: - interval: weekly - day: monday - time: "09:30" - timezone: Australia/Melbourne - groups: - tauri: - patterns: - - tauri - - tauri-* - rust-support: - patterns: - - serde* - - ureq - - dirs - - log - - tar - - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - day: monday - time: "10:00" - timezone: Australia/Melbourne - - - package-ecosystem: docker - directory: / - schedule: - interval: weekly - day: monday - time: "10:30" - timezone: Australia/Melbourne diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 35ecf54..5cb85ac 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,24 +1,39 @@ name: CodeQL -# Run CodeQL only against `main` + on a weekly schedule. PR triggers -# are intentionally OFF — we're on the free GitHub Actions tier and a -# 2-job matrix (JS/TS + Rust) on every PR push was eating the bulk of -# our Action minutes for marginal additional coverage. Trade-off: -# CodeQL findings now surface post-merge rather than pre-merge. +# Run CodeQL against `main`, on a weekly schedule, and on PRs that touch +# dependency manifests. Broad PR scanning stays OFF — we're on the free +# GitHub Actions tier and a 2-job matrix (JS/TS + Rust) on *every* PR push +# was eating the bulk of our Action minutes for marginal coverage. The +# `pull_request` + `paths` filter below is the deliberate exception: it +# scans Renovate's single dependency-update PR (and any hand-made dep +# change) pre-merge, since a lockfile/manifest bump is exactly where a +# newly-pulled transitive vuln would land. Feature PRs that don't touch +# these paths still skip CodeQL and surface findings post-merge. # # - main: every merged push runs CodeQL so the latest tip is always # scanned and the weekly cron has a fresh baseline. # - schedule: weekly (Sun 18:24 UTC). CodeQL's upstream rule database # updates regularly; a re-scan catches new findings even when the # code hasn't changed. +# - pull_request (paths-filtered): dependency-manifest changes only — +# the paths mirror what Renovate edits across all four ecosystems. # -# To re-enable PR scanning later, add a `pull_request:` block with the -# `paths:` filter that was here previously (see git blame on this -# file). Or drop the filter for full coverage at higher cost. +# To scan ALL PRs (full pre-merge coverage at higher cost), drop the +# `paths:` filter below. on: push: branches: - main + pull_request: + paths: + - 'package.json' + - 'pnpm-lock.yaml' + - 'src-tauri/Cargo.toml' + - 'src-tauri/Cargo.lock' + - 'Dockerfile' + - 'docker-compose*.yml' + - 'deploy/**/compose.yaml' + - '.github/workflows/**' schedule: - cron: '24 18 * * 0' diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml new file mode 100644 index 0000000..0bd78d4 --- /dev/null +++ b/.github/workflows/renovate.yml @@ -0,0 +1,88 @@ +# +# Renovate — single-PR dependency updates. +# +# Replaces Dependabot (see the deleted .github/dependabot.yml). Renovate +# regenerates pnpm-lock.yaml *natively* — the whole reason for the switch — +# and, per renovate.json, bundles every non-major update across all four +# ecosystems (npm, cargo, docker, github-actions) into ONE PR on a stable +# branch. Major upgrades are parked on the Dependency Dashboard issue for +# manual, one-at-a-time review. +# +# Two ways to run: +# 1. Scheduled — Mondays; renovate.json's `schedule` is the effective +# time gate, so the job no-ops fast outside the window. +# 2. Manual — Actions ▸ Renovate ▸ "Run workflow". Defaults to a +# DRY RUN: it logs the exact PR it *would* open and +# changes nothing. Flip `dryRun` to `null` to go live. +# Manual runs ignore the Monday time-gate so you can +# test any day. +# +# Token (Settings ▸ Secrets and variables ▸ Actions ▸ RENOVATE_TOKEN): +# Optional for a DRY RUN — the built-in GITHUB_TOKEN is enough to preview. +# REQUIRED for live runs whose PR must trigger CI: PRs opened with the +# default GITHUB_TOKEN do NOT trigger `on: pull_request` workflows (GitHub's +# recursion guard), so the ci.yml checks would never run and the branch- +# protection gate would stay pending. Use a fine-grained PAT or a GitHub +# App installation token (contents:write + pull-requests:write + workflows) +# so the update PR is authored by a distinct identity and CI fires normally. +# +# Alternative to this workflow: install the hosted Mend Renovate GitHub App +# on the repo instead. It reads the same renovate.json, its PRs trigger CI +# automatically (no PAT to manage), and its onboarding PR doubles as the +# dry-run preview. If you go that route, delete this workflow so the two +# don't both run. Pick exactly one activation path. +# +name: Renovate + +on: + workflow_dispatch: + inputs: + dryRun: + description: 'full = preview only, open no PR (default) · null = go live and open the PR' + type: choice + options: + - 'full' + - 'null' + default: 'full' + logLevel: + description: 'Renovate log level' + type: choice + options: + - 'info' + - 'debug' + default: 'info' + schedule: + # Sunday 21:00 UTC ≈ Monday 07:00–08:00 Australia/Melbourne, inside + # renovate.json's "before 9am on monday" window (robust across DST). + - cron: '0 21 * * 0' + +# Never let two Renovate runs race on the same branch/lockfile. +concurrency: + group: renovate + cancel-in-progress: false + +permissions: + contents: write + pull-requests: write + +jobs: + renovate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Run Renovate + uses: renovatebot/github-action@b50d2ba2bd928235abdcc14d06dfafc217f1c565 # v46.1.18 + with: + configurationFile: renovate.json + token: ${{ secrets.RENOVATE_TOKEN || github.token }} + env: + RENOVATE_REPOSITORIES: ${{ github.repository }} + LOG_LEVEL: ${{ inputs.logLevel || 'info' }} + # Scheduled runs go live ('null' = dry-run disabled). Manual runs + # honour the dropdown, which defaults to 'full' (preview only). + RENOVATE_DRY_RUN: ${{ github.event_name == 'schedule' && 'null' || inputs.dryRun }} + # Manual runs bypass renovate.json's Monday time-gate so testing + # works any day; scheduled runs leave it in force. + RENOVATE_FORCE: ${{ github.event_name == 'workflow_dispatch' && '{"schedule":null}' || '' }} diff --git a/AGENTS.md b/AGENTS.md index 343e33c..997a4fa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,6 +28,12 @@ The test suite is intentionally small and focused (`pnpm test`). Container healt Separate Python companion script in `scripts/ios-app-import/` (stdlib-only, Python 3.9+): `python3 scripts/ios-app-import/export_ios_apps.py --mode backup|device`. It is *not* wired into the Node app — it produces a `.txt`/`.csv` that the user feeds back into the web onboarding flow. +## Dependency updates (Renovate, not Dependabot) + +Dependency bumps are driven by **Renovate**, not Dependabot — `.github/dependabot.yml` was removed because its pnpm support left `pnpm-lock.yaml` stale (needing a manual regen) and it fanned each ecosystem out into separate, mutually-conflicting PRs. Renovate regenerates the lockfile natively and, per `renovate.json`, bundles every **non-major** update across all four ecosystems (npm, cargo, docker, github-actions) into a **single** PR on a stable branch. **Major** upgrades are held on the Dependency Dashboard issue (`dependencyDashboardApproval`) for one-at-a-time review — tick one there to let Renovate raise its PR. Do NOT reintroduce a `dependabot.yml`; that would duplicate Renovate's PRs. + +Activation is one of two mutually-exclusive paths (pick one): the self-hosted `.github/workflows/renovate.yml` (weekly cron + a `workflow_dispatch` **dry-run** button that previews the PR without opening it — needs a `RENOVATE_TOKEN` secret for live-run PRs to trigger CI, since GITHUB_TOKEN-authored PRs don't), **or** the hosted Mend Renovate GitHub App (its PRs trigger CI automatically; delete the workflow if you install the app). Both read the same `renovate.json`. See the header comment in the workflow for the token rationale. + ## Architecture This is a Next.js 16 App Router app (TypeScript, React 19) backed by a single local SQLite file. All scraping, parsing, diffing, and AI calls happen server-side inside API routes that import helpers from `lib/`. diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..6f455b9 --- /dev/null +++ b/renovate.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":dependencyDashboard", + ":semanticCommits", + "helpers:pinGitHubActionDigests" + ], + "timezone": "Australia/Melbourne", + "schedule": ["before 9am on monday"], + "labels": ["dependencies"], + "postUpdateOptions": ["pnpmDedupe"], + "lockFileMaintenance": { + "enabled": true, + "schedule": ["before 9am on monday"] + }, + "vulnerabilityAlerts": { + "labels": ["dependencies", "security"] + }, + "packageRules": [ + { + "description": "Bundle every non-major update (all four ecosystems: npm, cargo, docker, github-actions), plus digest bumps and weekly lockfile maintenance, into ONE reviewable PR on a stable branch. pnpm-lock.yaml is regenerated natively by Renovate as part of the same PR.", + "matchUpdateTypes": [ + "minor", + "patch", + "pin", + "digest", + "lockFileMaintenance" + ], + "groupName": "all non-major dependencies", + "groupSlug": "all-non-major" + }, + { + "description": "Hold major upgrades (e.g. Next 16 to 17, Tauri 2 to 3, caddy:2 to :3) back as individually reviewable entries on the Dependency Dashboard rather than auto-opening PRs. Tick one there to let Renovate raise its own PR when you're ready to take the breaking change.", + "matchUpdateTypes": ["major"], + "dependencyDashboardApproval": true + }, + { + "description": "Never try to bump privacytracker's own published image referenced in the deploy compose files.", + "matchDatasources": ["docker"], + "matchPackageNames": ["ghcr.io/privacykey/privacytracker"], + "enabled": false + } + ] +}