diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..2301a70 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,127 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":semanticCommits", + "group:monorepos", + "group:recommended", + ":maintainLockFilesWeekly" + ], + "timezone": "Europe/Berlin", + "schedule": ["before 6am on monday"], + "labels": ["dependencies"], + "prConcurrentLimit": 10, + "prHourlyLimit": 4, + "dependencyDashboard": true, + "configMigration": true, + + "major": { + "dependencyDashboardApproval": true + }, + + "vulnerabilityAlerts": { + "enabled": true, + "labels": ["dependencies", "security"], + "schedule": ["at any time"] + }, + "osvVulnerabilityAlerts": true, + + "customManagers": [ + { + "customType": "regex", + "description": "Rust channel in rust-toolchain.toml", + "managerFilePatterns": ["/^rust-toolchain\\.toml$/"], + "matchStrings": ["channel\\s*=\\s*\"(?\\d+\\.\\d+(\\.\\d+)?)\""], + "depNameTemplate": "rust", + "packageNameTemplate": "rust-lang/rust", + "datasourceTemplate": "github-releases" + }, + { + "customType": "regex", + "description": "node-version input of actions/setup-node", + "managerFilePatterns": ["/^\\.github/workflows/[^/]+\\.ya?ml$/"], + "matchStrings": ["node-version:\\s*(?\\d+(\\.\\d+)*)"], + "depNameTemplate": "node", + "datasourceTemplate": "node-version", + "versioningTemplate": "node" + }, + { + "customType": "regex", + "description": "version input of pnpm/action-setup", + "managerFilePatterns": ["/^\\.github/workflows/[^/]+\\.ya?ml$/"], + "matchStrings": [ + "uses:\\s*pnpm/action-setup@[^\\n]*\\n\\s*with:\\n\\s*version:\\s*(?\\d+(\\.\\d+)*)" + ], + "depNameTemplate": "pnpm", + "datasourceTemplate": "npm", + "versioningTemplate": "npm" + }, + { + "customType": "regex", + "description": "ZIG_VERSION build arg in the Dockerfile", + "managerFilePatterns": ["/^Dockerfile$/"], + "matchStrings": ["ARG ZIG_VERSION=(?\\d+\\.\\d+\\.\\d+)"], + "depNameTemplate": "zig", + "packageNameTemplate": "ziglang/zig", + "datasourceTemplate": "github-releases" + } + ], + + "packageRules": [ + { + "description": "CI gates every PR (nextest, vitest, playwright, clippy, cargo-deny), so anything below a major merges itself once green.", + "matchUpdateTypes": ["patch", "minor", "pin", "digest"], + "automerge": true, + "automergeType": "pr", + "platformAutomerge": true + }, + { + "description": "Refreshing the lockfiles is how transitive advisories reach us — a direct dependency bump cannot pull a patched sub-dependency on its own.", + "matchUpdateTypes": ["lockFileMaintenance"], + "automerge": true, + "automergeType": "pr", + "platformAutomerge": true + }, + { + "description": "The Rust version is pinned in three places (rust-toolchain.toml, the dtolnay/rust-toolchain action, and the Dockerfile's rust image) and they must move together. Never automerged: a new toolchain relints and reformats the workspace, which is a deliberate change.", + "groupName": "rust toolchain", + "matchDepNames": ["rust", "dtolnay/rust-toolchain"], + "automerge": false, + "dependencyDashboardApproval": true + }, + { + "description": "pnpm is pinned in web/package.json, e2e/package.json and the pnpm/action-setup input. web/pnpm-workspace.yaml documents that these are one atomic change — pnpm 9 hard-fails on the workspace file this repo now ships.", + "groupName": "pnpm", + "matchDepNames": ["pnpm"], + "automerge": false, + "dependencyDashboardApproval": true + }, + { + "description": "Node is pinned in the Dockerfile's web-builder stage and in actions/setup-node; keep the build and CI on the same major.", + "groupName": "node", + "matchDepNames": ["node"], + "automerge": false, + "dependencyDashboardApproval": true + }, + { + "description": "web/pnpm-workspace.yaml pins these EXACTLY, to deduplicate the ProseMirror core that @tiptap/pm and y-prosemirror both depend on. They are not ordinary patches: taking prosemirror-view 1.42 changed how a remote replace-the-document update applies and broke history restore, which only CI caught. Surfaced on the dashboard so they stay visible, never opened or merged automatically.", + "matchPackageNames": [ + "prosemirror-model", + "prosemirror-state", + "prosemirror-view" + ], + "automerge": false, + "dependencyDashboardApproval": true + }, + { + "description": "Security floors in web/pnpm-workspace.yaml overrides. Raising them is welcome, but they exist because a parent hard-pins a vulnerable version, so a bump here needs a human to confirm the parent still resolves.", + "matchFileNames": ["web/pnpm-workspace.yaml"], + "automerge": false + }, + { + "description": "Playwright ships its browser binaries per version; a bump changes what the e2e suite actually runs against.", + "matchPackageNames": ["@playwright/test"], + "automerge": false + } + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 110da29..a532a76 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,6 +81,41 @@ This regenerates both `crates/knot-markdown/src/schema.rs` and `web/src/features - `build:` — build system (Dockerfile, Makefile, Cargo.toml) - `ci:` — GitHub Actions +## Dependencies + +[Renovate](https://docs.renovatebot.com/) (`.github/renovate.json`) keeps the +Cargo workspace, both pnpm projects, the GitHub Actions, and the Dockerfile base +images current. Patch and minor updates merge themselves once CI is green; +everything else waits for a human. + +Majors do not open a PR on their own — they are listed on the **Dependency +Dashboard** issue, and ticking a box there tells Renovate to raise that one. +That keeps a major migration (tiptap 2 → 3, tailwind 3 → 4) a deliberate choice +rather than eight red PRs. + +Weekly `lockFileMaintenance` runs `cargo update` / `pnpm update` against the +lockfiles. This is the only route by which a patched *transitive* dependency +reaches us — bumping a direct dependency cannot pull one in on its own — so it +is how most RUSTSEC and GHSA advisories actually get closed here. + +Three versions are pinned in more than one file and must move together. +Renovate groups each into a single PR; do the same by hand: + +| what | pinned in | +|---|---| +| Rust | `rust-toolchain.toml`, `dtolnay/rust-toolchain@…` in `ci.yml`, `rust:…-alpine` in `Dockerfile` | +| pnpm | `packageManager` in `web/` and `e2e/package.json`, `pnpm/action-setup` `version:` | +| Node | `node:…-alpine` in `Dockerfile`, `node-version:` in `ci.yml` | + +Left out of automation on purpose: the exact `prosemirror-*` pins in +`web/pnpm-workspace.yaml` (they deduplicate the editor core — see the comment +there for what breaks), the security floors in the same file's `overrides`, and +`@playwright/test`, which ships the browser the e2e suite runs against. + +`cargo deny check` gates every PR. When an advisory has no reachable fix, add it +to `deny.toml` with the dependency path and the upstream event that would let us +drop it again. + ## Pull requests Before opening a PR: