You are reading the documentation of Guestroll v0.4.0. The latest version is v0.5.0. Latest docs · What changed · Changelog
Skip to content

Development ​

Run locally ​

bash
git clone https://github.com/achedon12/guestroll && cd guestroll
nvm use            # Node 22, see .nvmrc
npm ci
echo "ADMIN_PASSWORD=dev-password-123" > .env.local
npm run dev        # http://localhost:3000

Fill it with demo events and photos (through the public API, in another terminal):

bash
BASE_URL=http://localhost:3000 ADMIN_PASSWORD=dev-password-123 npm run seed:demo

To try the camera on a real phone, the page must be HTTPS. The quickest way is a tunnel (cloudflared tunnel --url http://localhost:3000) or next dev --experimental-https on your LAN.

Checks ​

bash
npm run lint
npm run typecheck      # generates Next's route types first
npm test               # unit + integration (Vitest)
npm run test:coverage  # the same, with a coverage report in coverage/
npm run build
npm run test:e2e       # functional tests on the production build (Playwright)

npm run test:e2e starts the production build on port 3210 with a temporary data folder, so it never touches your data. Locally it uses your installed Chrome; in CI, the Chromium installed by npx playwright install.

Three layers of tests ​

LayerWhereWhat it runs against
Unittests/unit/Pure modules: access policy, validation, rate limiter, QR rendering…
Integrationtests/integration/Real SQLite in a temp folder, real images through sharp, real ZIPs, QR codes decoded by ZXing
Functionaltests/e2e/api/The HTTP API of a running production build: guest and admin scenarios, security, quotas
Browsertests/e2e/browser/Real journeys: a guest on a phone, an admin on a desktop

Unit-level files:

FileWhat it locks
policy.test.tsEvery access rule: who sees which photo, in which size
routeGuards.test.tsEvery admin page and route checks the session; every guest mutation checks the origin
qrScan.test.tsEvery QR preset and shape is rendered and decoded with ZXing
qr.test.tsStyle parsing, escaping, logo excavation
request.test.tsClient IP and origin rules, the streaming body cap
rateLimit.test.ts, validation.test.ts, ids.test.ts, negotiate.test.ts, translate.test.tsThe rest of the pure modules

Branches and CI ​

develop is the default branch, where pull requests land; main is production. Every change on either branch runs:

WorkflowWhat it does
QualityLint, types, production build
TestsVitest with coverage, then Playwright on a production build
SecurityCodeQL (code and workflows) and npm audit on production dependencies, plus a weekly run
Docker imageBuilds the image, runs it on a custom port and user, uploads a photo end to end, then upgrades: the latest release fills a data folder and the new image must start on it with every event and photo intact
Workflowsactionlint and zizmor on .github/

Publish to production (manual) fast-forwards main to develop once Quality and Tests passed there; Align develop carries anything that lands on main back to develop. Tagging main with vX.Y.Z runs Release: the multi-arch image on GHCR (and Docker Hub when its secrets are set) and a GitHub release with generated notes. Docs builds the website and the wiki from docs/ (see below) when develop changes them and when a release is tagged.

Upgrades ​

tests/integration/upgrade.test.ts rebuilds the database of every release from the migrations it shipped, fills it the way that version did, and opens it with today's code. When a release adds migrations, add it to SHIPPED_MIGRATIONS. Never edit a migration once it's released: instances already applied it.

Releasing ​

  1. Move the Unreleased entries of CHANGELOG.md under a new ## [X.Y.Z] - date heading, with an Upgrading list when something needs the admin's attention, and update the compare links at the bottom.
  2. Bump version in package.json, push to develop, wait for the checks.
  3. Run Publish to production, then tag main with vX.Y.Z.

The release notes are that changelog section (the release fails without one), and the admin's update page shows it to everyone running an older version.

Documentation website ​

docs/ is both the documentation read on GitHub and the source of the website (VitePress) and of the wiki. Write Markdown there, nowhere else: the wiki is regenerated by the Docs workflow, edits made on it are lost.

bash
cd docs
npm install
npm run dev        # http://localhost:5173/guestroll/

.github/scripts/build-docs.py builds every release's docs (the latest at the root, develop under /next/ once it differs, older ones under /vX.Y.Z/), with today's configuration and theme so they all share the version switcher. .github/scripts/build-wiki.py does the same for the wiki, with a page per release under vX.Y.Z-Page. Links to files outside docs/ (like ../SECURITY.md) point at GitHub on the website.

Housekeeping: Dependabot (grouped, one-week cooldown), stale issues, a welcome message on first pull requests, a "merge conflict" label, and pull requests sent back to draft when changes are requested.

Database changes ​

Edit src/lib/db/schema.ts, then:

bash
npm run db:generate -- --name short_description

Commit the generated folder under drizzle/. Migrations run automatically on startup. Never edit a migration that has been released.

Conventions ​

  • TypeScript strict, ESM, the @/ alias for src/.
  • Access rules go in lib/policy.ts with a test. Routes stay thin.
  • UI text goes in lib/i18n/messages.ts, in all four languages.
  • Colors only through the tokens in globals.css (bg-surface, text-ink-muted…), so both themes keep working.
  • Comments explain why, not what.
  • Commits follow Conventional Commits.

The visual direction is described in design-prompt.md: read it before changing the interface.

Released under the MIT License.