diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 3deee26..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff69b3f..c1cb950 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,10 +13,23 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Every job below repeats this, because Actions does not expand YAML anchors: +# +# runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'self-hosted' }} +# +# A fork PR runs on GitHub's hardware; everything else runs on ours. The line +# that matters is `pnpm install` plus `pnpm test` plus `docker build` — that is +# arbitrary code from the pull request, and on a self-hosted runner it would be +# arbitrary code on our LAN, beside a beans-web instance that has no +# authentication. Opening a fork PR requires nothing; pushing a branch to this +# repo requires write access, and that is the whole of the trust boundary. +# +# On a push to main the pull_request context is absent, so the expression falls +# through to self-hosted, which is correct: that ref is already merged. jobs: lint: name: Lint - runs-on: ubuntu-latest + runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'self-hosted' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -37,7 +50,7 @@ jobs: audit: name: Audit - runs-on: ubuntu-latest + runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'self-hosted' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -55,7 +68,7 @@ jobs: typecheck: name: Typecheck - runs-on: ubuntu-latest + runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'self-hosted' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -73,7 +86,7 @@ jobs: test: name: Test (coverage) - runs-on: ubuntu-latest + runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'self-hosted' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -182,7 +195,7 @@ jobs: e2e: name: E2E - runs-on: ubuntu-latest + runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'self-hosted' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -241,7 +254,7 @@ jobs: build: name: Build needs: [lint, typecheck] - runs-on: ubuntu-latest + runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'self-hosted' }} env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} steps: @@ -261,7 +274,7 @@ jobs: quality: name: Quality - runs-on: ubuntu-latest + runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'self-hosted' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -292,7 +305,7 @@ jobs: image-scan: name: Image Scan - runs-on: ubuntu-latest + runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'self-hosted' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/cspell.config.yaml b/cspell.config.yaml index 3672051..c0f1b7d 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -17,7 +17,6 @@ words: - typeof - devcontainer - codecov - - dependabot - typecheck - codegen - browsable diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..f7f8c0c --- /dev/null +++ b/renovate.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"], + "schedule": ["before 9am on monday"], + "prConcurrentLimit": 10, + "dependencyDashboard": true, + "vulnerabilityAlerts": { + "enabled": true, + "schedule": ["at any time"], + "automerge": false, + "labels": ["security"] + }, + "packageRules": [ + { + "description": "Runtime dependencies ship to users; every bump gets read by a human.", + "matchDepTypes": ["dependencies", "engines", "packageManager"], + "automerge": false + }, + { + "description": "Non-major dev dependencies: one PR a week, merged by the gate rather than by hand.", + "matchDepTypes": ["devDependencies"], + "matchUpdateTypes": ["patch", "minor"], + "groupName": "dev dependencies (non-major)", + "automerge": true, + "minimumReleaseAge": "3 days" + }, + { + "description": "Major dev dependencies still automerge, but one at a time — a group is only as mergeable as its worst member.", + "matchDepTypes": ["devDependencies"], + "matchUpdateTypes": ["major"], + "automerge": true, + "minimumReleaseAge": "3 days" + }, + { + "description": "Actions run with a token and are not dev dependencies. Digest-pinned, and merged deliberately.", + "matchManagers": ["github-actions"], + "groupName": "github actions", + "pinDigests": true, + "automerge": false + }, + { + "description": "The Node floor is stated in four places and they have drifted before, so they move in one PR: .nvmrc, engines.node, and both Dockerfile stages.", + "matchPackageNames": ["node"], + "groupName": "node" + } + ], + "customManagers": [ + { + "customType": "regex", + "description": "ARG BEANS_VERSION is the only pin for the beans binary — the Dockerfile builds from it and CI reads it back out with sed, so nothing else may state a version. No manager covers an ARG, so this is the one place a bot can see it.", + "managerFilePatterns": ["/^Dockerfile$/"], + "matchStrings": ["ARG BEANS_VERSION=(?v[0-9][^\\s]*)"], + "depNameTemplate": "github.com/hmans/beans", + "datasourceTemplate": "go" + } + ] +}