Contributing to the codebase
This project has two application services: a Node/Express backend and a React/Vite frontend. The backend manages Caddy through its admin API and Docker or local file access. See the architecture guide for the runtime request flow.
Repository responsibilities
| Location | Owns |
|---|---|
src/backend/ | Backend source, unit/API tests, package dependencies and Dockerfile |
src/frontend/ | Frontend source, component tests, build scripts and Dockerfile |
dev/dev-caddy/ | Disposable Caddy lifecycle commands and ownership checks |
dev/integration/ | Real-service fixtures, scenarios and lifecycle/readiness tests |
dev/e2e/ | Browser fixtures and scenarios |
dev/playground/ | Localization preview, captured responses and capture tools |
docs/ | Book content, theme and documentation tooling |
The root package.json provides stable development commands. Application packages,
shared development tooling and documentation have separate manifests and lockfiles.
There are no npm workspaces or additional build orchestrators. The generated
project structure shows the current files.
Getting set up
Use Node.js 22.16+ or 24 and npm. From the repository root:
npm ci --prefix src/backend
npm ci --prefix src/frontend
npm ci --prefix dev
For a ready-to-run environment with real Caddy containers, follow the Disposable Caddy integration environment guide. It covers Docker prerequisites, startup, managing test instances, exercising failures and cleanup.
You can also develop against an existing Caddy instance. Its admin API must be
reachable through CADDY_ADMIN_URL (default http://caddy:2019). Run the backend
and frontend in separate terminals:
npm run dev --prefix src/backend
npm run dev --prefix src/frontend
The backend listens on port 3001 by default; the frontend’s Vite server proxies
/api there. Relevant environment variables are documented in the
configuration reference.
Frontend predev and prebuild fetch the generated CodeMirror grammar into
src/frontend/src/lib/caddyfileMode.js from
zackwag/caddyfile-codemirror.
The file is ignored by Git. Run development or a build with network access before
working offline; later runs can reuse the local copy.
Tests and lint
From the repository root:
npm test --prefix src/backend
npm run lint --prefix src/backend
npm test --prefix src/frontend
npm run lint --prefix src/frontend -- --max-warnings=0
npm run lint:dev-caddy
npm run test:dev-caddy
Backend tests use Vitest for helpers and HTTP contracts. Frontend tests cover
component contracts, API/error behavior, localization and playground logic. The
playground tests currently remain under src/frontend/test/ and require the dev
package; moving them to their owning module is a separate follow-up.
Shared dev lint borrows backend ESLint and frontend rule configuration, so both application packages must be installed. It covers harness, integration, browser and playground code. Lifecycle/readiness unit tests need no Docker Engine.
Real-Caddy integration and Chromium checks use the same root commands locally and
in CI. Follow the integration testing guide
for browser installation, fixture ownership and cleanup. The required Test check
includes both application tests and the real-service/browser integration job.
Verifying UI changes
Run the app and exercise affected views in a browser before opening a PR. Unit tests do not establish rendering or interaction correctness. Check both dark and light themes when changing styling, and include a narrow viewport when changing layout.
Code style and boundaries
- Match the existing file style: ES modules, four-space indentation, single quotes and semicolons.
- Keep application-specific unit tests and build scripts with their package.
Shared development tools belong under
dev/; documentation tooling stays with the book; GitHub-specific orchestration stays under.github/. - Before moving files, identify their imports, runtime paths, package commands, workflow filters, cache paths, build contexts and documentation consumers.
- Preserve root command names and generated-file boundaries. Update generators or their canonical inputs rather than editing generated output.
- Keep changes focused. Dependency upgrades, behavior changes and additional architecture changes should have their own justification and review scope.
Opening a PR
- Branch off
mainand keep the diff focused on one change. - Explain the problem, resulting behavior and affected boundaries.
- Run the tests and lint for the packages you changed. For shared fixtures or
tooling, also run the affected root suite and
npm run lint:dev-caddy. - Use a Conventional Commit PR title,
such as
fix:,feat:,test:,build:ordocs:. - Do not edit
CHANGELOG.mdor bump versions by hand. Release-please manages both after squash-merging. - Open the PR against
main; the Test workflow runs automatically.
Browser-origin security checks
The focused Chromium suite needs backend and dev dependencies plus a Playwright browser; it does not need Docker or a Caddy instance:
npm exec --prefix dev -- playwright install chromium
npm run test:cross-site:browser
The same cases run through npm run test:dev-caddy:e2e in CI. They use the real
backend middleware and local HTTP servers, with no request interception or
injected browser headers. An IPv4-mapped loopback URL reproduces Chromium’s
untrustworthy HTTP destination behavior without an external LAN. Assertions check
that Fetch Metadata is actually absent, forged fetches/forms never reach the
write handler, and configured same-origin HTTP writes still work. Each case owns
its browser and servers and closes them on completion or failure. Backend policy
and route assertions also run with npm test --prefix src/backend.