diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml new file mode 100644 index 0000000..78f653b --- /dev/null +++ b/.github/actions/build/action.yml @@ -0,0 +1,28 @@ +name: Build +description: > + The static site, into ./dist. + + A composite action rather than a reusable workflow so it runs in the caller's + job, under the caller's name -- CI / Build -- rather than as a nested + "caller / callee" check. + + It has exactly one caller: there is no CD here, so nothing can drift from + anything. It is split out so this repository has the same shape as the rest of + the organisation, and for no stronger reason than that. + + NOTHING LEAVES THE RUNNER. The site is left in ./dist for ../test to assert + against. + +runs: + using: composite + steps: + # --frozen-lockfile so CI fails on a lockfile that does not match + # package.json, rather than quietly resolving something newer than what + # anyone has run locally. + - name: Install JS dependencies + shell: bash + run: bun install --frozen-lockfile + + - name: Build + shell: bash + run: bun run build diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml new file mode 100644 index 0000000..15e4b50 --- /dev/null +++ b/.github/actions/test/action.yml @@ -0,0 +1,37 @@ +name: Test +description: > + Is this commit good. Nothing is published here; a failure means the site is + wrong, not that the pipeline is. + + Runs AFTER ../build, because everything it asserts is a property of ./dist. A + static site has two ways to be broken that matter -- it did not build, and it + built the wrong thing -- and only the second one needs a test. + + The assertions are this site's own and differ from its siblings': how many + pages it emits, and what else has to be on them. That is the part worth + keeping per-repo. + +runs: + using: composite + steps: + # A build that emits nothing still "succeeds", so the artefacts are + # checked rather than the exit code. All three pages, because a broken + # route is invisible from the index alone. + - name: Every page was actually written + shell: bash + run: | + for page in index devtools/index games/index; do + test -s "dist/$page.html" || { echo "dist/$page.html is missing or empty"; exit 1; } + done + # The point of this site is that it ships no JavaScript. Nothing else + # would notice an integration being added back, and by then the pages + # have a runtime they did not need. + - name: No JavaScript was shipped + shell: bash + run: | + if find dist -name '*.js' | grep -q .; then + echo "::error::dist contains JavaScript -- this site is meant to ship none" + find dist -name '*.js' + exit 1 + fi + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index db9e835..201d1d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,17 @@ name: CI -# A static site has one way to be broken that matters: it does not build. That -# is the whole check. +# Every pull request and every merge to main: build, then test, on one runner. +# +# Nothing is published and nothing is deployed, so a green tick means one thing +# -- this commit is good. +# +# The push trigger is not redundant: this repository has no CD, so nothing else +# covers a merge to main. + on: + pull_request: push: branches: [main] - pull_request: workflow_dispatch: permissions: @@ -16,29 +22,24 @@ concurrency: cancel-in-progress: true jobs: - build: + ci: runs-on: ubuntu-latest + # Bounded, so a step that hangs fails here rather than sitting until the + # runner's own timeout hours later. + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - with: - bun-version: "1.3" - - run: bun install --frozen-lockfile - - run: bun run build - # A build that emits nothing still "succeeds", so the artefacts are - # checked rather than the exit code. All three pages, because a broken - # route is invisible from the index alone. - - name: Every page was actually written - run: | - for page in index devtools/index games/index; do - test -s "dist/$page.html" || { echo "dist/$page.html is missing or empty"; exit 1; } - done - # The point of this site is that it ships no JavaScript. Nothing else - # would notice an integration being added back, and by then the pages - # have a runtime they did not need. - - name: No JavaScript was shipped - run: | - if grep -rq ']*src=' dist/*.html dist/*/*.html; then - echo "a page loads a script — this site is meant to ship none" - exit 1 - fi + # Third-party actions are pinned by SHA, with the tag in a trailing + # comment so the version is still readable. + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + + # Reads .mise.toml, which is where bun's version is already pinned for + # everyone working on this repo. This used to be oven-sh/setup-bun with + # `bun-version: "1.3"` written out here -- a second place to change one + # version, and two places drift. + - uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2 + + - name: Build + uses: ./.github/actions/build + + - name: Test + uses: ./.github/actions/test