Skip to content

Build(deps): Bump the minor-and-patch group with 8 updates #283

Build(deps): Bump the minor-and-patch group with 8 updates

Build(deps): Bump the minor-and-patch group with 8 updates #283

Workflow file for this run

name: CI
on:
# `ready_for_review` is not one of the default activity types, and naming any type
# replaces the defaults rather than adding to them — so the three defaults have to
# be written out again alongside it. Without it, a PR opened as a draft and later
# marked ready would sit with no CI run at all until its next push.
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
push:
branches:
- main
permissions:
contents: read
jobs:
quality:
# Named for what the job *is*, not for the steps it currently runs. This string is a
# required status check on `main`, so every rename is a branch protection change made
# in the GitHub settings UI — and a name that enumerates its steps earns one of those
# every time a step is added. `Lint, test and build` lasted exactly as long as it took
# to add a test step. Not `CI`, which the workflow is already called and which would
# render the check as `CI / CI`.
name: Quality
runs-on: ubuntu-latest
# Draft PRs skip CI; marking one ready fires `ready_for_review` and runs it then.
#
# This must stay a job-level condition, not a filter in the `on:` block — and not
# only because draft state is not expressible there. A job skipped by `if:` reports
# Success, whereas a whole workflow skipped by filtering leaves its checks pending,
# blocking the merge. Once this check is required on `main` (#18), the first blocks
# nothing and the second would make every draft unmergeable short of an admin
# override or dropping the requirement.
#
# `!= true` rather than `!github.event.pull_request.draft`: on `push` there is no
# `pull_request` object, so both forms pass, but only this one says why.
if: github.event.pull_request.draft != true
steps:
# Actions are pinned to commit SHAs, not tags: a major-version tag can be
# retargeted. Comments record the version each SHA corresponds to; bump both
# together.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# The step below diffs against the base branch, and the default
# `fetch-depth: 1` clones no commit to diff against.
fetch-depth: 0
# A pull request that changes nothing but Markdown cannot break any of the steps at
# the bottom of this job: no `.md` file is an input to `pnpm lint`, `pnpm test` or
# `next build`, so all three would spend several minutes proving it. Skip them for it.
#
# This is a step condition rather than `paths-ignore` on the `pull_request`
# trigger above, for the same reason the draft skip is a job-level `if:` — see the
# comment on it. `paths-ignore` skips the *workflow*, which leaves its checks
# pending rather than green, and once this check is required on `main` (#18) that
# would make every documentation pull request unmergeable.
#
# Two things this has to get right, both of which fail silently:
#
# - The test is "every changed file is documentation", not "some documentation
# changed". A pull request touching `AGENTS.md` *and* `src/` must run.
# - It is an allowlist, so anything the pattern does not recognise falls through
# to a full build — including a diff that could not be computed at all. The
# cost of a needless build is a few minutes; the cost of a wrongly skipped one
# is a broken `main`.
#
# "Documentation" is `**/*.md` and nothing else. Not because Markdown is prose —
# `AGENTS.md` is instructions to agents, and `.claude/skills/` holds working
# skills — but because the question this job asks is narrower than that: whether
# lint, the tests or the build read the file. Nothing here does. The `scripts/*.sh` sitting
# beside a skill's `SKILL.md` is not matched and so is not skipped, which is the
# right way round.
- name: Look for changes outside documentation
id: scope
if: github.event_name == 'pull_request'
env:
BASE_REF: ${{ github.base_ref }}
run: |
set -euo pipefail
# `--no-renames` is load-bearing, not tidiness. Rename detection is on by
# default, and `--name-only` then prints only the *destination* path — so
# `git mv src/thing.ts docs/thing.md` reports one `.md` file and nothing else,
# and a module leaves `src/` classified as documentation. With the flag, both
# paths are listed and the deleted one forces a build.
if ! changed=$(git diff --name-only --no-renames "origin/$BASE_REF...HEAD"); then
echo "Could not diff against origin/$BASE_REF — building rather than guessing."
echo "docs-only=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Changed files:"
printf '%s\n' "$changed" | sed 's/^/ /'
# A here-string rather than `printf | grep`, and for the same fail-closed
# reason. `grep -q` exits on its first match; with a list bigger than the pipe
# buffer the writer is still going, takes SIGPIPE and exits 141, and
# `pipefail` reports that 141 instead of grep's 0 — which `!` then reads as
# "no non-Markdown file found". A here-string is not a pipeline, so none of
# that applies.
if [ -n "$changed" ] && ! grep -qv '\.md$' <<<"$changed"; then
echo "Documentation only — skipping install, lint and build."
echo "docs-only=true" >> "$GITHUB_OUTPUT"
else
echo "docs-only=false" >> "$GITHUB_OUTPUT"
fi
# Every step from here down carries the same condition. An unset output — a `push`
# build, where the step above does not run, or a diff that failed — is not `true`,
# so the default in both cases is to build.
#
# pnpm/action-setup must come before setup-node, or the cache step finds no
# lockfile it understands. Omitting `version` makes the action read
# `packageManager` from package.json, so the pin cannot drift from local dev.
- name: Install pnpm
if: steps.scope.outputs.docs-only != 'true'
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
# Read from `.nvmrc` rather than a literal, so this workflow cannot drift from
# local development or from any other workflow.
- name: Install Node
if: steps.scope.outputs.docs-only != 'true'
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .nvmrc
cache: pnpm
- name: Install dependencies
if: steps.scope.outputs.docs-only != 'true'
run: pnpm install --frozen-lockfile
# `next build` no longer runs ESLint, so lint has to be its own CI step.
- name: Lint
if: steps.scope.outputs.docs-only != 'true'
run: pnpm lint
# Vitest's `src` project only — `pnpm test` is `vitest run --project src`.
#
# The `db` project (`tests/db`, run by `pnpm test:db`) is deliberately not here.
# It asserts the schema's invariants against a running Supabase stack — Postgres,
# PostgREST, GoTrue and Kong in containers — and booting those would turn a fast
# required check into a slow one. Note what the reason is not: this runner does
# have Docker, and an earlier version of this comment said otherwise. The cost is
# minutes, not capability.
#
# It runs in `schema.yml` instead, which is a separate workflow for that reason
# and is not a required check. That is the house pattern rather than a new
# exception — the end-to-end suite is a separate workflow too.
#
# Playwright's end-to-end suite is a separate workflow, and one that is currently
# disabled by hand — so nothing runs Playwright on a pull request at all, and this
# step is not the reason why. Keeping e2e out of this job is still right whatever
# that workflow's state: it builds and serves the app, and this job should stay
# fast enough to be a required check.
- name: Test
if: steps.scope.outputs.docs-only != 'true'
run: pnpm test
- name: Build
if: steps.scope.outputs.docs-only != 'true'
run: pnpm build