Archived
docs: add CONTRIBUTING guide (#6)
Co-authored-by: austin <[email protected]>
This commit was merged in pull request #6.
This commit is contained in:
+145
@@ -0,0 +1,145 @@
|
||||
# Contributing
|
||||
|
||||
This is one of the six sites on the [Bennett platform](https://git.thebennett.net/austin/platform):
|
||||
a Spring Boot 4 / Java 25 backend serving a Vite + React SPA from a single jar. The workflow below is
|
||||
the same for every app — once you know one, you know all of them.
|
||||
|
||||
**The one rule:** `main` is protected. You cannot push to it. Every change lands through a pull request
|
||||
whose CI passed. There is no exception, including for the repo owner — the server enforces it, so a
|
||||
`git push origin main` will be rejected.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
| Tool | Version | Notes |
|
||||
|---|---|---|
|
||||
| JDK | 25 | `java -version` should say 25 |
|
||||
| Maven | 3.9+ | or use the platform image below |
|
||||
| Docker | any recent | **required to run the tests** — they use Testcontainers (a real Postgres) |
|
||||
| Node | — | *not* installed by hand; the Maven build downloads a pinned Node and runs the SPA build |
|
||||
|
||||
You also need a **Gitea Maven token** to build, because the platform (`net.thebennett.platform:*`) is
|
||||
pulled from the Gitea package registry, not Maven Central. Create a token at
|
||||
`git.thebennett.net → Settings → Applications` with `read:package` scope, then export it:
|
||||
|
||||
```sh
|
||||
export MAVEN_USER=<your-gitea-username>
|
||||
export MAVEN_TOKEN=<the-token>
|
||||
```
|
||||
|
||||
The repo ships `.gitea/ci-settings.xml`, which wires that registry up using those two variables — pass
|
||||
it with `-s` and you never have to touch your global `~/.m2/settings.xml`.
|
||||
|
||||
---
|
||||
|
||||
## Build and test locally
|
||||
|
||||
```sh
|
||||
# Full verify — compiles, builds the SPA, runs every test (needs Docker running).
|
||||
mvn -B -s .gitea/ci-settings.xml verify
|
||||
|
||||
# Faster inner loop — skip the frontend build when you're only touching Java.
|
||||
mvn -B -s .gitea/ci-settings.xml -DskipFrontend=true verify
|
||||
|
||||
# Run the app locally (Postgres + any OIDC/storage config supplied via env — see the app's README).
|
||||
mvn -s .gitea/ci-settings.xml spring-boot:run
|
||||
```
|
||||
|
||||
If `verify` fails with a Docker or "could not start container" error, the daemon isn't running — the
|
||||
tests need it. Tests will not be skipped to work around this; a green build means the tests actually ran.
|
||||
|
||||
### Frontend
|
||||
|
||||
The SPA lives in `frontend/`. For a fast UI loop with hot reload:
|
||||
|
||||
```sh
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev # Vite dev server, proxying /api to the running backend
|
||||
```
|
||||
|
||||
For anything that ships, let Maven build it (`mvn verify` runs `npm ci && npm run build` and folds
|
||||
`dist/` into the jar) so what you test is what deploys. The frontend is **vendored per app** — there is
|
||||
no shared frontend package, so a shared component you change here does not change in the other apps.
|
||||
|
||||
### Tests
|
||||
|
||||
- Name test classes `*Test` — Surefire runs those. A class named `*IT` is silently skipped.
|
||||
- Integration tests use Testcontainers (`@ServiceConnection` + a real `postgres` container). No mocks
|
||||
for the database.
|
||||
- Every app inherits a set of **contract tests** from `platform-starter-test` (unknown `/api` paths 404,
|
||||
health is UP, probes exist, SPA routes forward to `index.html`). You get these for free by extending
|
||||
the platform's base test — don't reimplement them.
|
||||
- Protected-page paths return **302 for a browser** (`Accept: text/html`) and **401 for a fetch/XHR**
|
||||
(`Accept: */*`). If you're asserting that `/admin` "redirects to login," send the browser Accept
|
||||
header or the assertion will see a 401 and mislead you.
|
||||
|
||||
---
|
||||
|
||||
## The change workflow
|
||||
|
||||
```sh
|
||||
git switch -c feature/short-description # branch off main
|
||||
# ... make your change, add tests ...
|
||||
mvn -B -s .gitea/ci-settings.xml verify # green locally before you push
|
||||
git push -u origin feature/short-description # push the BRANCH, never main
|
||||
```
|
||||
|
||||
Then open a pull request against `main` (Gitea → *New Pull Request*, or `tea pr create`). CI runs on the
|
||||
PR. When the check is green, merge it. On merge the image builds, publishes, and deploys automatically.
|
||||
|
||||
### What CI does (`.gitea/workflows/build.yml`, job `build-and-publish / build`)
|
||||
|
||||
Runs on every PR **and** on push to `main`:
|
||||
|
||||
1. **Test** — runs `mvn verify` in a sibling Maven container (Testcontainers can't run inside
|
||||
`docker build`). Tests gate both the build and the merge.
|
||||
2. **Build image** — `docker build` (which also runs the SPA build); a broken Dockerfile fails here.
|
||||
3. **Scan image (Trivy)** — fails on any fixable **HIGH/CRITICAL** library CVE. A new bad CVE blocks
|
||||
the merge even if your code is fine — bump the offending dependency.
|
||||
4. **Push image** — **only on push to `main`** (`if: github.event_name != 'pull_request'`). PRs build
|
||||
and scan but never publish.
|
||||
|
||||
`main` requires the check **`build-and-publish / build (pull_request)`** to be green before merge. That
|
||||
context name is what branch protection is pinned to; if you rename the workflow or job, update the
|
||||
required-check setting to match or nothing will ever be mergeable.
|
||||
|
||||
Commits that only touch `**.md` or `renovate.json` skip the build on `main` (they can't change the
|
||||
image) — but a PR that touches them still runs the PR check.
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
Renovate watches this repo. Platform version bumps (`net.thebennett.platform`) are **auto-merged** once
|
||||
their PR is green — that's how a security fix in the platform reaches every app without hand-editing six
|
||||
`pom.xml` files. Other updates open PRs for a human to merge. Don't hand-bump the platform version to
|
||||
chase a fix; cut a platform release and let Renovate carry it here.
|
||||
|
||||
Some toolchain upgrades are intentionally pinned back (e.g. ESLint / TS majors blocked by plugin peer
|
||||
caps). If a Renovate PR is red on a peer-dependency conflict, that's why — don't force it.
|
||||
|
||||
---
|
||||
|
||||
## Deploy
|
||||
|
||||
Merge to `main` → CI publishes `:latest` and `:<sha>` to the Gitea registry → **Watchtower** pulls the
|
||||
new `:latest` and restarts the container → **Caddy** fronts it. There is no manual deploy step and no
|
||||
staging hop; the PR gate *is* the safety net, which is why the tests and scan are non-negotiable.
|
||||
|
||||
To force an urgent redeploy without a code change (e.g. rolling out a platform fix immediately), trigger
|
||||
the workflow manually (`workflow_dispatch`) rather than pushing an empty commit.
|
||||
|
||||
---
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Business logic lives in Java.** This is a Spring app, not a React app with a Java proxy — the server
|
||||
owns the data, the rules, and the validation. The SPA renders and calls `/api`. Prose and layout stay
|
||||
in the markup; only the parts that change without the page changing (prices, lists, links) become data.
|
||||
- Keep commits scoped and their messages in the imperative ("add booking link", not "added").
|
||||
- Don't commit secrets. Config comes from environment variables at deploy time (see the app's README for
|
||||
the list); there is no `.env` in the repo.
|
||||
- Update the README when you change what the server owns or what env vars it needs — that's the file the
|
||||
next person reads first.
|
||||
Reference in New Issue
Block a user