diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..d3aaaeb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +version: 2 + +# Actions only. The site is hand-written HTML, CSS and one 3 KB script with no +# build step and no third-party requests — there is no package manifest here for +# Dependabot to read, and adding one to give it something to do would undo the +# property `scripts/check_site.py` exists to enforce. +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + day: monday + commit-message: + prefix: "ci" + groups: + actions: + patterns: ["*"] + open-pull-requests-limit: 5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a0e472..40a2bc8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,9 +18,9 @@ jobs: name: Site checks runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: "3.12" @@ -31,3 +31,20 @@ jobs: - name: Check the sitemap is well-formed run: python -c "import xml.dom.minidom, sys; xml.dom.minidom.parse('sitemap.xml'); print('sitemap.xml parses')" + + ci: + name: CI + runs-on: ubuntu-latest + if: always() + needs: [site] + steps: + # One aggregate check to require in branch protection. There is one job + # today, and this is what keeps that from mattering: a second job added + # later is covered without touching the protection rule. + - name: Fail if any job did not succeed + if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: | + echo "One or more jobs failed:" + echo '${{ toJSON(needs) }}' + exit 1 + - run: echo "All checks passed." diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..e299c3a --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,39 @@ +name: CodeQL + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + # Weekly, because advisories land between pushes: a rule added after the + # last commit would otherwise never run against this code. + - cron: "47 5 * * 1" + workflow_dispatch: + +permissions: + contents: read + +jobs: + analyze: + name: Analyze JavaScript + runs-on: ubuntu-latest + permissions: + security-events: write + actions: read + contents: read + steps: + - uses: actions/checkout@v7 + + - uses: github/codeql-action/init@v4 + with: + # `assets/site.js` is the only executable thing on the site: 3 KB, no + # dependencies, no build step. That is exactly the kind of file that + # accumulates an innerHTML assignment nobody reviews, which is what + # the default query pack is good at finding. + languages: javascript-typescript + queries: security-and-quality + + - uses: github/codeql-action/analyze@v4 + with: + category: "/language:javascript-typescript" diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..2c627ba --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,44 @@ +name: Dependabot auto-merge + +# Actions are the only ecosystem Dependabot watches here, and an action bump is +# the update that piles up unread until the queue is too long to review +# honestly. Auto-merge is queued, not immediate: GitHub still waits for the +# required checks, and `scripts/check_site.py` is what those checks run. + +on: pull_request_target + +permissions: + contents: read + +jobs: + auto-merge: + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + # Reads the update type from the PR that Dependabot opened. Nothing from + # the branch is checked out or executed, which is what makes + # pull_request_target safe to use here. + - id: metadata + uses: dependabot/fetch-metadata@v2 + + - name: Approve and queue the merge + if: >- + steps.metadata.outputs.package-ecosystem == 'github_actions' || + steps.metadata.outputs.update-type == 'version-update:semver-patch' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR: ${{ github.event.pull_request.html_url }} + run: | + gh pr review --approve "$PR" + gh pr merge --auto --squash "$PR" + + - name: Explain why this one was left alone + if: >- + steps.metadata.outputs.package-ecosystem != 'github_actions' && + steps.metadata.outputs.update-type != 'version-update:semver-patch' + env: + TYPE: ${{ steps.metadata.outputs.update-type }} + run: echo "$TYPE is not auto-merged; this pull request needs a human." diff --git a/.github/workflows/external-links.yml b/.github/workflows/external-links.yml index 8c70449..04d615f 100644 --- a/.github/workflows/external-links.yml +++ b/.github/workflows/external-links.yml @@ -16,7 +16,7 @@ jobs: name: Check outbound links runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Run lychee id: lychee