From 4cfe6b59bae374e7588dc3f3e0e7368d04f28c64 Mon Sep 17 00:00:00 2001 From: Maurice Date: Thu, 8 Oct 2026 11:39:24 +0200 Subject: [PATCH] ci: move every workflow off Node 20 actions, pin the third-party ones, add Dependabot security.yml, lint-prettier.yml and the release workflows still ran actions built for Node 20 (checkout and setup-node v4, github-script v7, the docker actions v3/v5, create-github-app-token v1), which GitHub now forces onto Node 24 with a deprecation warning on every run. They move to the Node 24 majors test.yml already uses (download-artifact to v7, since v6 is still Node 20). Third-party actions, several of which hold the Docker Hub and GitHub App secrets, are pinned by commit with the tag in a comment. ubuntu-latest moves to a new Ubuntu release on 10-19, so every job names ubuntu-24.04 and the switch happens in a PR. Every setup-node reads .nvmrc; publish-plugin-sdk leaves 22, which it ran under a comment claiming it matched the server. security.yml gains an npm audit job over the lockfile, on PRs and pushes into dev as well as main. It covers what the image installs and the plugin SDK, takes seconds, and would have flagged the brace-expansion regression the ORM squash brought in the day it landed. Scout keeps its image scan on main. Neither becomes a required check: a CVE published overnight would turn every open PR red, including the ones that cannot fix it. The scout job now also skips PRs into dev, which the trigger change would otherwise hand it. .github/dependabot.yml adds weekly version updates into dev for the root workspace, plugin-sdk, the actions and the Docker base images, minor and patch grouped per ecosystem, majors one PR each. The Node major in the Dockerfile is left to a hand-made change together with .nvmrc. --- .github/dependabot.yml | 70 +++++++++++++++++++ .../workflows/close-stale-invalid-titles.yml | 4 +- .../workflows/close-stale-wrong-branch.yml | 4 +- .github/workflows/close-untitled-issues.yml | 4 +- .github/workflows/docker-dev.yml | 26 +++---- .github/workflows/docker.yml | 34 ++++----- .github/workflows/enforce-target-branch.yml | 16 ++--- .github/workflows/lint-prettier.yml | 10 +-- .github/workflows/publish-plugin-sdk.yml | 12 ++-- .github/workflows/security.yml | 60 +++++++++++++--- .github/workflows/wiki.yml | 6 +- CLAUDE.md | 2 + 12 files changed, 181 insertions(+), 67 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..05787405d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,70 @@ +# Version updates, weekly, into dev. Minor and patch bumps of one ecosystem +# arrive as a single grouped PR; a major bump gets a PR of its own so its +# breaking notes are read on their own. +# +# Security updates are a repository setting, not part of this file, and +# GitHub opens them against the default branch whatever target-branch says. +# enforce-target-branch.yml moves those onto dev. +# +# Every PR runs test.yml like any other, so ci-ok decides whether it can +# merge. +version: 2 +updates: + # The root lockfile covers the client, server and shared workspaces. + - package-ecosystem: npm + directory: / + target-branch: dev + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 5 + commit-message: + prefix: chore(deps) + prefix-development: chore(deps-dev) + groups: + npm-minor-and-patch: + update-types: [minor, patch] + + # plugin-sdk is standalone with a lockfile of its own and ships to npm. + - package-ecosystem: npm + directory: /plugin-sdk + target-branch: dev + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 3 + commit-message: + prefix: chore(plugin-sdk) + prefix-development: chore(plugin-sdk) + groups: + plugin-sdk-minor-and-patch: + update-types: [minor, patch] + + # Keeps the SHA pins of third-party actions current, comment included. + - package-ecosystem: github-actions + directory: / + target-branch: dev + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 3 + commit-message: + prefix: ci + groups: + actions-minor-and-patch: + update-types: [minor, patch] + + # The Dockerfile's base images. The Node major follows .nvmrc and the + # engines ranges, so it moves by hand, together with them. + - package-ecosystem: docker + directory: / + target-branch: dev + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 3 + commit-message: + prefix: chore(docker) + ignore: + - dependency-name: node + update-types: [version-update:semver-major] diff --git a/.github/workflows/close-stale-invalid-titles.yml b/.github/workflows/close-stale-invalid-titles.yml index 1c56d4f77..b39a46a11 100644 --- a/.github/workflows/close-stale-invalid-titles.yml +++ b/.github/workflows/close-stale-invalid-titles.yml @@ -10,11 +10,11 @@ permissions: jobs: close-stale: if: github.repository == 'liketrek/TREK' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 steps: - name: Close stale invalid-title issues - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const badTitles = [ diff --git a/.github/workflows/close-stale-wrong-branch.yml b/.github/workflows/close-stale-wrong-branch.yml index 55ede86b4..ef16e607b 100644 --- a/.github/workflows/close-stale-wrong-branch.yml +++ b/.github/workflows/close-stale-wrong-branch.yml @@ -11,11 +11,11 @@ permissions: jobs: close-stale: if: github.repository == 'liketrek/TREK' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 steps: - name: Close stale wrong-base-branch PRs - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const { data: pulls } = await github.rest.pulls.list({ diff --git a/.github/workflows/close-untitled-issues.yml b/.github/workflows/close-untitled-issues.yml index c84ce4cce..08b7de970 100644 --- a/.github/workflows/close-untitled-issues.yml +++ b/.github/workflows/close-untitled-issues.yml @@ -10,11 +10,11 @@ permissions: jobs: check-title: if: github.repository == 'liketrek/TREK' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 steps: - name: Flag or redirect issue - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const title = context.payload.issue.title.trim(); diff --git a/.github/workflows/docker-dev.yml b/.github/workflows/docker-dev.yml index ae98b83dc..b6d54262b 100644 --- a/.github/workflows/docker-dev.yml +++ b/.github/workflows/docker-dev.yml @@ -19,13 +19,13 @@ concurrency: jobs: version-bump: if: github.repository == 'liketrek/TREK' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 120 outputs: version: ${{ steps.bump.outputs.VERSION }} sha: ${{ steps.bump.outputs.SHA }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} @@ -76,20 +76,20 @@ jobs: matrix: include: - platform: linux/amd64 - runner: ubuntu-latest + runner: ubuntu-24.04 - platform: linux/arm64 runner: ubuntu-24.04-arm steps: - name: Prepare platform tag-safe name run: echo "PLATFORM_PAIR=$(echo ${{ matrix.platform }} | sed 's|/|-|g')" >> $GITHUB_ENV - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ needs.version-bump.outputs.sha }} - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 - - uses: docker/login-action@v3 + - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -102,7 +102,7 @@ jobs: - name: Build and push by digest id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . platforms: ${{ matrix.platform }} @@ -121,7 +121,7 @@ jobs: touch "/tmp/digests/${digest#sha256:}" - name: Upload digest artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: digests-${{ env.PLATFORM_PAIR }} path: /tmp/digests/* @@ -129,27 +129,27 @@ jobs: retention-days: 1 merge: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 120 needs: [version-bump, build] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ needs.version-bump.outputs.sha }} fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Download build digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: path: /tmp/digests pattern: digests-* # Deliberately NOT merged: the per-platform folder names are what lets # the manifest below be assembled in a fixed order. - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 - - uses: docker/login-action@v3 + - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fa27a782f..436accbf1 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -23,18 +23,18 @@ concurrency: jobs: version-bump: if: github.repository == 'liketrek/TREK' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 120 outputs: version: ${{ steps.bump.outputs.VERSION }} steps: - - uses: actions/create-github-app-token@v1 + - uses: actions/create-github-app-token@v3 id: app-token with: app-id: ${{ secrets.RELEASE_APP_ID }} private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 fetch-tags: true @@ -122,20 +122,20 @@ jobs: matrix: include: - platform: linux/amd64 - runner: ubuntu-latest + runner: ubuntu-24.04 - platform: linux/arm64 runner: ubuntu-24.04-arm steps: - name: Prepare platform tag-safe name run: echo "PLATFORM_PAIR=$(echo ${{ matrix.platform }} | sed 's|/|-|g')" >> $GITHUB_ENV - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: main - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 - - uses: docker/login-action@v3 + - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -148,7 +148,7 @@ jobs: - name: Build and push by digest id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . platforms: ${{ matrix.platform }} @@ -167,7 +167,7 @@ jobs: touch "/tmp/digests/${digest#sha256:}" - name: Upload digest artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: digests-${{ env.PLATFORM_PAIR }} path: /tmp/digests/* @@ -175,25 +175,25 @@ jobs: retention-days: 1 merge: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 120 needs: [version-bump, build] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: main - name: Download build digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: path: /tmp/digests pattern: digests-* # Deliberately NOT merged: the per-platform folder names are what lets # the manifest below be assembled in a fixed order. - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 - - uses: docker/login-action@v3 + - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -234,17 +234,17 @@ jobs: run: docker buildx imagetools inspect mauriceboe/trek:latest release-helm: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 120 needs: version-bump steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: main - name: Publish Helm chart - uses: stefanprodan/helm-gh-pages@v1.7.0 + uses: stefanprodan/helm-gh-pages@0ad2bb377311d61ac04ad9eb6f252fb68e207260 # v1.7.0 with: token: ${{ secrets.GITHUB_TOKEN }} charts_dir: charts diff --git a/.github/workflows/enforce-target-branch.yml b/.github/workflows/enforce-target-branch.yml index bcb9d3a97..e9c1a4600 100644 --- a/.github/workflows/enforce-target-branch.yml +++ b/.github/workflows/enforce-target-branch.yml @@ -7,7 +7,7 @@ on: jobs: check-target: if: github.repository == 'liketrek/TREK' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 permissions: pull-requests: write @@ -16,7 +16,7 @@ jobs: steps: - name: Flag or clear wrong base branch - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const base = context.payload.pull_request.base.ref; @@ -69,12 +69,12 @@ jobs: return; } - // Dependabot cannot be pointed at dev. `target-branch` in dependabot.yml - // applies to version updates only; security updates always open against - // the default branch, and security updates are all this repository gets - // (there is no dependabot.yml). Labelling them meant close-stale-wrong-branch - // shut them 24 hours later, which is how 13 of them died. Retarget instead: - // a bot cannot click "Edit" the way the comment below asks a human to. + // Dependabot's security updates cannot be pointed at dev. `target-branch` + // in .github/dependabot.yml applies to version updates only; security + // updates always open against the default branch. Labelling them meant + // close-stale-wrong-branch shut them 24 hours later, which is how 13 of + // them died. Retarget instead: a bot cannot click "Edit" the way the + // comment below asks a human to. if (context.payload.pull_request.user.login === 'dependabot[bot]') { try { await github.rest.pulls.update({ diff --git a/.github/workflows/lint-prettier.yml b/.github/workflows/lint-prettier.yml index ba0d12474..326450167 100644 --- a/.github/workflows/lint-prettier.yml +++ b/.github/workflows/lint-prettier.yml @@ -9,15 +9,15 @@ concurrency: jobs: lint: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 permissions: pull-requests: write steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: - node-version: '24' + node-version-file: .nvmrc - name: Install dependencies run: npm install @@ -32,7 +32,7 @@ jobs: - name: Comment on PR if checks failed if: steps.checks.outcome == 'failure' - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | await github.rest.issues.createComment({ diff --git a/.github/workflows/publish-plugin-sdk.yml b/.github/workflows/publish-plugin-sdk.yml index fa3610fa8..d7a3a9e75 100644 --- a/.github/workflows/publish-plugin-sdk.yml +++ b/.github/workflows/publish-plugin-sdk.yml @@ -15,18 +15,18 @@ permissions: jobs: publish: if: github.repository == 'liketrek/TREK' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 defaults: run: working-directory: plugin-sdk steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: - # 22 matches the TREK server runtime and has node:sqlite, which the - # dev-server tests exercise. - node-version: 22 + # The Node the TREK image runs the plugins on. It has the node:sqlite + # the dev-server tests exercise. + node-version-file: .nvmrc registry-url: 'https://registry.npmjs.org' - run: npm ci - run: npm publish diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 3ccfecf18..7d7b6a8f0 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -1,12 +1,27 @@ name: Security Scan +# Neither job is a required check. What they report depends on the advisory +# databases as much as on the change: a CVE published overnight would turn +# every open PR red at once, including the ones that cannot fix it. The +# security floors that hold the fixes live in the root package.json's +# overrides, which a regenerated lockfile keeps. +# +# - npm-audit reads the lockfile only, so it runs in seconds on every PR +# and push into dev and main. It audits what the image installs (the +# server and shared workspaces without dev dependencies) and the +# standalone plugin-sdk. +# - scout scans the built image, base image packages included. It needs +# the Docker Hub login, so it runs on PRs and pushes into main only and +# skips fork PRs. Whether the image builds at all is test.yml's +# docker-image job, which a red scan cannot hide. on: pull_request: - branches: [main] + branches: [main, dev] push: - branches: [main] + branches: [main, dev] permissions: + contents: read pull-requests: write concurrency: @@ -14,30 +29,57 @@ concurrency: cancel-in-progress: true jobs: + npm-audit: + name: npm audit + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + - uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + + # The client's dependencies end up in a browser bundle, not in the + # image, and are audited by hand until maplibre-gl's fix (a major) lands. + - name: Audit the image's runtime dependencies + run: npm audit --package-lock-only --omit=dev --audit-level=high --workspace=server --workspace=shared + + - name: Audit trek-plugin-sdk + working-directory: plugin-sdk + run: npm audit --package-lock-only --omit=dev --audit-level=high + scout: # Docker Hub secrets are not exposed to pull requests from forks, so the # Scout login can never succeed there. - if: github.repository == 'liketrek/TREK' && github.event.pull_request.head.repo.fork != true - runs-on: ubuntu-latest + if: >- + github.repository == 'liketrek/TREK' + && github.event.pull_request.head.repo.fork != true + && (github.base_ref == 'main' || github.ref == 'refs/heads/main') + runs-on: ubuntu-24.04 timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + with: + persist-credentials: false - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 - - uses: docker/build-push-action@v5 + - uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . push: false load: true tags: trek:scan - - uses: docker/login-action@v3 + - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - uses: docker/scout-action@v1 + - uses: docker/scout-action@221e7f4860634eeb1579e3bd7ca232e577bd1864 # v1.26.0 with: command: cves image: trek:scan diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml index 5f3ae4fd0..39d2b783d 100644 --- a/.github/workflows/wiki.yml +++ b/.github/workflows/wiki.yml @@ -18,11 +18,11 @@ concurrency: jobs: deploy: if: github.repository == 'liketrek/TREK' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Publish to GitHub wiki - uses: Andrew-Chen-Wang/github-wiki-action@v5 + uses: Andrew-Chen-Wang/github-wiki-action@1bbb4280446f9630e8e21a18012cbacf3b0f992e # v5.0.6 with: strategy: clone diff --git a/CLAUDE.md b/CLAUDE.md index 15b09232e..77b697518 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -114,6 +114,8 @@ These principles come out of a full-repo audit and shape all new code: - **`ci-ok` is the required check.** The branch ruleset requires `ci-ok` (plus `lint` and `check-target` from their own workflows), and `ci-ok` needs every job above except the scan. It fails if any of them failed or was cancelled and passes if they passed or were skipped. A new gate job counts only once it is listed in `ci-ok`'s `needs:`. - **What runs is decided by `changes`, not by `on.paths`**: `scripts/ci/changed-areas.mjs` maps the changed files to the `code`, `image` and `deploy` areas (tests: `npm run test:ci-scripts`). A path filter on the workflow would leave `ci-ok` unreported on a docs-only PR and block it. Add a new top-level file the build reads to the right area there. +- **Runners and Node**: jobs run on `ubuntu-24.04`, not `ubuntu-latest`, and every `setup-node` reads `.nvmrc`. Third-party actions (anything outside `actions/*`) are pinned by commit SHA with the tag in a comment; Dependabot keeps both current. +- **Security scans are advisory.** `security.yml` runs `npm audit` (what the image installs, plus `plugin-sdk`) on every PR and push into dev and main, and Docker Scout on the built image for main. Neither is required: a newly published advisory would turn every open PR red at once. Fixes are held as floors in the root `package.json` `overrides`; npm ignores an `overrides` block in a workspace `package.json`, so never put one there. **SonarCloud** (project `liketrek_TREK`, the built-in Sonar way gate; the run takes 20–25 min, so get it right before pushing) measures **new code only** on a PR; any failing condition blocks it: