Merge pull request 'ci: gate pull requests into main on server and client checks' (#1) from ci/add-pr-checks into main

Reviewed-on: #1
This commit is contained in:
2026-08-10 08:32:22 +00:00

View File

@@ -0,0 +1,127 @@
# Gate every pull request into `main` on a fast, DB-free check suite, so a broken
# build or a failing test can't reach the branch that gets released.
#
# Mirrors RunicGateway/website's pr-checks.yml — this module is two npm packages
# shaped like that repo's `server/` and `client/`, and it is loaded into that
# repo's process, so it is checked the same way with the same Node version.
#
# ── Package guard ────────────────────────────────────────────────────────────
# This repo is in the planning phase and has no module code yet: the API contract
# is settled in Phase 1 and Phase 3 is what extracts the UO half of `website/`
# into this repo (docs/website/MODULE_SYSTEM.md §2.7). Rather than leave the repo
# ungated until then — or land a workflow that red-Xes every governance/docs PR —
# each half's gates are conditional on its package.json existing. Before the code
# lands, the job reports green with a notice saying so. The moment a package.json
# appears the gates arm themselves; nothing here has to change.
#
# The same trick guards the client build, which is the higher-risk half: it must
# build with Vite in library mode with react/react-dom/react-router-dom EXTERNAL,
# because there is exactly one React instance in the page and core owns it. A
# module that bundles its own React loads and then breaks hooks at runtime, which
# is precisely the kind of failure worth catching before merge.
#
# Not here yet, deliberately, because there is nothing for them to act on until
# Phase 3: a release workflow (the `module-uo-<version>.tar.gz` artifact and its
# sha256 manifest), the zero-internal-imports check, and the module's own frozen
# route manifest. Each lands with the code it checks.
#
# Enforcement (one-time, in the Gitea UI):
# Repository Settings → Branches → Branch Protection (rule for `main`)
# • Enable Status Check
# • Status check patterns: PR Checks / *
# Note: Gitea only lists a context in its dropdown after it has reported once,
# so let this workflow run on one PR first. The `PR Checks / *` glob matches
# without needing the dropdown, and keeps matching as jobs are added.
#
# Runner: the shared self-hosted `ubuntu-latest` runner. These jobs need only
# Node — no Docker socket, no database.
name: PR Checks
on:
pull_request:
branches: [main]
# A newer push to the same PR cancels the in-flight run.
concurrency:
group: pr-checks-${{ github.ref }}
cancel-in-progress: true
jobs:
server-tests:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Detect whether the server half exists yet
id: detect
run: |
set -euo pipefail
if [ -f server/package.json ]; then
echo "pkg=true" >> "$GITHUB_OUTPUT"
echo "==> server/package.json found - running the server gates."
else
echo "pkg=false" >> "$GITHUB_OUTPUT"
echo "==> No server/package.json yet (planning phase)."
echo " Skipping install/test. These gates arm themselves as soon"
echo " as Phase 3 lands the server half - see MODULE_SYSTEM.md."
fi
- uses: actions/setup-node@v4
if: ${{ steps.detect.outputs.pkg == 'true' }}
with:
node-version: 20
cache: npm
cache-dependency-path: server/package-lock.json
# `npm ci` rather than `npm install`: it also proves the lockfile is in
# sync with package.json instead of silently updating it.
- name: Install server deps
if: ${{ steps.detect.outputs.pkg == 'true' }}
run: npm ci --prefix server
- name: Run server tests
if: ${{ steps.detect.outputs.pkg == 'true' }}
run: npm test --prefix server
client-build:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: Detect whether the client half exists yet
id: detect
run: |
set -euo pipefail
if [ -f client/package.json ]; then
echo "pkg=true" >> "$GITHUB_OUTPUT"
echo "==> client/package.json found - running the client gates."
else
echo "pkg=false" >> "$GITHUB_OUTPUT"
echo "==> No client/package.json yet (planning phase)."
echo " Skipping install/test/build. These gates arm themselves as"
echo " soon as Phase 3 lands the client half."
fi
- uses: actions/setup-node@v4
if: ${{ steps.detect.outputs.pkg == 'true' }}
with:
node-version: 20
cache: npm
cache-dependency-path: client/package-lock.json
- name: Install client deps
if: ${{ steps.detect.outputs.pkg == 'true' }}
run: npm ci --prefix client
- name: Run client tests
if: ${{ steps.detect.outputs.pkg == 'true' }}
run: npm test --prefix client
# Building the ESM chunk in CI is not just a check: it is how the chunk
# that ships in the release is produced, since an operator never builds.
- name: Build the client chunk
if: ${{ steps.detect.outputs.pkg == 'true' }}
run: npm run build --prefix client