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: