docs: scaffold the Integration Kit — front page, outline, and the checks
Phase 5 slice 0 (MODULE_SYSTEM.md §2.11.1). The repo's governance, the front page, the book's outline, and the CI that keeps the whole thing from rotting. README.md What the reader is building, all three parts, and the draft banner: the kit is finished when someone outside this project builds a working module by following it alone, and that has not happened. Says the sidecar rule plainly (MODULE_API.md §2.7) rather than leaving it to chapter 3, because a reader who skims the front page and starts coding should still get that one right. book/README.md The outline of four chapters, landed before the prose so the shape can be argued with. Chapters are named but NOT linked — a link to a file that does not exist is what the link check is for, and an outline should not be the first thing to fail it. CONTRIBUTING.md The rule that governs every change here: the kit never re-specifies a contract. Also the prose conventions, and why the pinned ref points at core's `edge` rather than `main`. SECURITY.md Scoped for a repo that runs nothing: the two things that ARE reportable are a template that teaches an insecure pattern (it is meant to be copied) and a chapter that teaches something dangerous. scripts/checkLinks.js Relative links resolve; anchors match a real heading; no link pins a reader to a commit snapshot of a moving document. Nothing is fetched — a self-hosted Gitea would fail on a credential-less runner and teach us to ignore red. Fences and code spans are stripped by a line walk, not a regexp. Its first run found a real one: a PR template's relative links resolve from the REPO ROOT, because that is where their text ends up when Gitea inlines them into a pull request body. Encoded, with the reason. scripts/checkCoreApi.js The anti-rot check. Asserts template/module.json's `coreApi` EQUALS the pinned core's MODULE_API_VERSION — equality, not "satisfies", because a range check stays green across a contract bump and green would then mean "the template still loads" instead of "someone has re-read the book". Both failure branches and the pass were exercised against a real core checkout. ci/core-ref.json The pin, same convention as Module-uo's. Points at `edge`: core's `main` has no server/src/modules/ until the cutover, and that pin is one of the things the cutover has to revisit. .gitea/workflows/pr-checks.yml Two jobs. `links` always runs; `template` is conditional on template/module.json existing, so the repo is gated now and the job arms itself when slice 1 lands, with no edit to the workflow. Same guard Module-uo used through its planning phase. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
138
.gitea/workflows/pr-checks.yml
Normal file
138
.gitea/workflows/pr-checks.yml
Normal file
@@ -0,0 +1,138 @@
|
||||
# Gate every pull request into `main`. This repo is documentation plus a template
|
||||
# module, so the checks are about whether the documentation is still TRUE rather
|
||||
# than whether software works.
|
||||
#
|
||||
# ── What each job is really asking ───────────────────────────────────────────
|
||||
#
|
||||
# • `links` — every relative link resolves, and no link pins a reader to a
|
||||
# commit snapshot of a document that moves. Nothing is fetched: this project's
|
||||
# Gitea is self-hosted, so an HTTP check would fail on a runner without
|
||||
# credentials and teach everyone to ignore red. What breaks in practice is a
|
||||
# relative path after a file moves, and that is answerable offline.
|
||||
#
|
||||
# • `template` — the interesting one, and the anti-rot mechanism of the whole
|
||||
# repo (MODULE_SYSTEM.md §2.11.1 d2). It clones CORE at the ref pinned in
|
||||
# `ci/core-ref.json` and asks three things:
|
||||
#
|
||||
# 1. does `template/module.json`'s `coreApi` still EQUAL that core's
|
||||
# `MODULE_API_VERSION`? Equality, not "satisfies" — a range check would
|
||||
# stay green across a contract bump, and green would then mean "the
|
||||
# template still loads" when we need it to mean "someone has re-read the
|
||||
# book since the contract changed". This failing is the system working.
|
||||
# 2. does the template still build? A kit whose examples do not compile is
|
||||
# worse than no kit, because the reader trusts it first.
|
||||
# 3. do the template's own boundary guards still pass? They are the same
|
||||
# checks a real module ships (MODULE_API.md §5.1, §3.6), and the template
|
||||
# is what teaches a newcomer that they exist.
|
||||
#
|
||||
# ── The guard, and why the template job can report green with no template ────
|
||||
#
|
||||
# Slice 0 is this scaffold; the template lands in slice 1. Rather than leave the
|
||||
# repo ungated in between, or land a workflow that red-Xes every docs PR until
|
||||
# there is something to build, the template steps are conditional on
|
||||
# `template/module.json` existing. Before it lands the job prints why it did
|
||||
# nothing; the moment the file appears the job arms itself with no edit here.
|
||||
# Same guard Module-uo#1 used through its own planning phase.
|
||||
#
|
||||
# Enforcement (one-time, in the Gitea UI):
|
||||
# Repository Settings → Branches → Branch Protection (rule for `main`)
|
||||
# • Enable Status Check
|
||||
# • Status check patterns: PR Checks / *
|
||||
# Gitea only lists a context in its dropdown after it has reported once, so let
|
||||
# this run on one PR first. The glob keeps matching as jobs are added.
|
||||
#
|
||||
# Runner: the shared self-hosted `ubuntu-latest` runner. Node only — no database,
|
||||
# no Docker socket.
|
||||
|
||||
name: PR Checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: pr-checks-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
NPM_CONFIG_FETCH_RETRIES: 5
|
||||
NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: 20000
|
||||
NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: 120000
|
||||
|
||||
jobs:
|
||||
links:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
# No dependencies on purpose — this has to run on a clone with nothing
|
||||
# installed, which is also how a reader will run it.
|
||||
- name: Check every link in the book
|
||||
run: node scripts/checkLinks.js
|
||||
|
||||
template:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Is there a template yet?
|
||||
id: guard
|
||||
run: |
|
||||
if [ -f template/module.json ]; then
|
||||
echo "present=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "present=false" >> "$GITHUB_OUTPUT"
|
||||
echo "No template/module.json — the template lands in Phase 5 slice 1."
|
||||
echo "The steps below are skipped until it does; see this file's header."
|
||||
fi
|
||||
|
||||
# Anonymous HTTPS, and a full clone rather than a shallow one: the pin is a
|
||||
# commit sha, and `--depth 1` can only fetch a branch tip.
|
||||
- name: Clone core at the pinned ref
|
||||
if: steps.guard.outputs.present == 'true'
|
||||
run: |
|
||||
REPO=$(node -p "require('./ci/core-ref.json').repo")
|
||||
REF=$(node -p "require('./ci/core-ref.json').ref")
|
||||
echo "core: $REPO @ $REF"
|
||||
git clone --quiet "$REPO" .core
|
||||
git -C .core checkout --quiet "$REF"
|
||||
|
||||
- name: Is the kit still written against this core? (MODULE_SYSTEM.md §2.11.1 d2)
|
||||
if: steps.guard.outputs.present == 'true'
|
||||
run: node scripts/checkCoreApi.js --core .core
|
||||
|
||||
- name: Install the template's deps
|
||||
if: steps.guard.outputs.present == 'true'
|
||||
run: |
|
||||
npm ci --prefix template/server
|
||||
npm ci --prefix template/client
|
||||
|
||||
- name: Check the template's module boundary (MODULE_API.md §5.1)
|
||||
if: steps.guard.outputs.present == 'true'
|
||||
run: npm run check:imports --prefix template/server
|
||||
|
||||
# The build comes before the externals check because that check reads the
|
||||
# BUILT chunk: whether `import { useState } from 'react'` became core's React
|
||||
# or a bare specifier no browser can resolve is decided by vite.config.js, and
|
||||
# is invisible in source.
|
||||
- name: Build the template's client chunk
|
||||
if: steps.guard.outputs.present == 'true'
|
||||
run: npm run build --prefix template/client
|
||||
|
||||
- name: Check the built chunk's externals (MODULE_API.md §3.6)
|
||||
if: steps.guard.outputs.present == 'true'
|
||||
run: npm run check:externals --prefix template/client
|
||||
|
||||
- name: Run the template's tests
|
||||
if: steps.guard.outputs.present == 'true'
|
||||
run: npm test --prefix template/server
|
||||
Reference in New Issue
Block a user