PR 0 of the router domain split (docs/website/API_V2_PLAN.md § Phase 2). The
split promises that admin.routes.js can be carved into one router file per
business capability without moving a single URL. That promise has to be proved
by a diff, not asserted in review — this lands the tool that proves it, with no
router file moved.
scripts/routeManifest.js walks the live Express stack (runtime introspection,
not source parsing: route paths in admin.routes.js sit on the line *after*
`adminRouter.get(`, which defeats greps) and writes a sorted { method, path }
list to routes.manifest.json. It reproduces the frozen baseline in
docs/website/api-route-inventory.json byte-for-byte — 199 public routes plus 2
on the internal listener — so the freeze is confirmed accurate, not just
claimed.
Scope is /api/** and /.well-known/** plus the internal app. The SPA catch-all,
/uploads and /brand are filesystem-conditional static mounts, so including them
would make the output depend on whether CI had built the client. Static mounts
are not API contract.
Also emits routes.guards.json — a review aid, not a contract: per route, the
handler count and the *named* middleware on its mount chain. Router-level
`use(noindex, isLoggedIn, staffOnly)` gates never appear in an individual
route's own stack, so an extracted capability router that forgot to re-apply
one would otherwise publish authenticated endpoints silently. Names are a hint
only (requireRole(...) returns an anonymous arrow), but a vanished requireAuth
is unambiguous — and the test suite asserts every /admin/** and /player/**
route still carries it.
The plan's optional unauthenticated-status snapshot was tried and dropped, as
it allowed: against the dead-port mariadb pool the tests use, the sweep sits on
the pool's acquire timeout and had not finished after two minutes. A flaky
two-minute gate is worse than none; the requireAuth assertion covers the same
regression deterministically.
CI runs `npm run routes:manifest -- --check` on every PR, so a URL change can
only merge by deliberately committing the new manifest.
Co-Authored-By: Claude <noreply@anthropic.com>
4.0 KiB
Contributing to Runic Gateway — Website
Thanks for your interest in contributing! This repo is the full-stack website (Node.js + Express API, MariaDB, React + Vite SPA). This guide covers how to get set up, the workflow we follow, and the rules for contributions.
By participating you agree to abide by our Code of Conduct.
Ways to contribute
- Report a bug or request a feature through the issue tracker (issue templates are provided).
- Improve the code or docs by opening a pull request (see below).
- Never report a security vulnerability in a public issue — see SECURITY.md.
Development setup
Prerequisites: Node.js 20+ and npm, plus Docker (for MariaDB).
The README has the full setup guide. The short version for local development with hot reload:
# 1. Start a MariaDB the backend can reach
docker run -d --name rg-db -p 3306:3306 \
-e MARIADB_DATABASE=runic_gateway -e MARIADB_USER=runic \
-e MARIADB_PASSWORD=devpass -e MARIADB_ROOT_PASSWORD=rootpass mariadb:11
# 2. Backend (terminal 1)
cp server/.env.example server/.env # set DB_* , JWT_SECRET, ADMIN_USERNAME/PASSWORD
npm run install-all
npm run server # nodemon -> http://localhost:3000
# 3. Frontend (terminal 2)
npm run client # Vite -> http://localhost:5173
Develop against http://localhost:5173 (the Vite dev server proxies /api).
Tests & checks
Please run the server test suite and make sure the client builds before opening a PR — these are the same checks CI runs on your PR:
npm test # server tests
npm run build # client production build
If you add or change an API route, regenerate the Swagger spec
(cd server && npm run swagger) and commit the updated
server/swagger/swagger-output.json.
The URL surface is also frozen by a generated manifest. If your change adds,
removes or renames a route, regenerate it (cd server && npm run routes:manifest)
and commit server/routes.manifest.json + server/routes.guards.json — CI fails
otherwise. A non-empty diff in routes.manifest.json means you changed the API
contract, so call it out in the PR description; a pure refactor must produce none.
Branch & PR workflow
- Fork or branch from
main. Use a descriptive branch name (feature/…,fix/…,docs/…,chore/…). - Keep changes focused; small PRs are easier to review.
- Push and open a pull request against
main. Fill out the PR template, including the AI-assisted contributions disclosure. - Make sure PR checks (server tests + client build) are green.
- A maintainer will review; address feedback by pushing follow-up commits.
Commit messages
We use Conventional Commits —
type(scope): summary (e.g. feat(auth): add TOTP challenge step,
fix(brand): link footer badge to Gitea org). Common types: feat, fix,
docs, chore, refactor, test, ci.
AI-assisted contributions (disclosure required)
This project is developed openly with AI assistance, and we ask the same transparency of everyone. If you used an AI tool (Claude, Copilot, ChatGPT, Cursor, etc.) to help produce a contribution, you must disclose it:
- Tick the AI-usage box in the pull-request template and name the tool(s).
- Mark AI-authored commits with a trailer, e.g.
Co-Authored-By: Claude <noreply@anthropic.com>orAssisted-By: <tool>. - You remain responsible for every line you submit: review it, understand it, and make sure it is correct and that you have the right to contribute it.
Disclosed AI assistance is welcome. Undisclosed AI-generated contributions are not, and may be closed.
License
Runic Gateway is licensed under the GNU General Public License v3.0 or later (see LICENSE.md). By submitting a contribution you agree that it is licensed under the same terms (inbound = outbound) and that you have the right to contribute it.