Add GitHub Actions CI, wire ESLint, lock in Caddy

CI pipeline with parallel jobs for fast feedback:
- typecheck, lint, unit tests, build run in parallel
- e2e tests run after build
- Playwright artifacts uploaded on failure

ESLint wired into both apps with sensible defaults:
- underscore-prefixed unused vars allowed
- root package.json marked as type: module

Architecture updated: Caddy as reverse proxy with auto HTTPS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-03-22 12:55:12 +01:00
parent 9ad2f5785e
commit f58fe99cde
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
9 changed files with 123 additions and 6 deletions

87
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,87 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
typecheck:
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
test:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
e2e:
name: E2E Tests
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm exec playwright install chromium
- run: pnpm test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: |
e2e/results/
playwright-report/
retention-days: 7

View file

@ -1,6 +1,6 @@
import type { Route } from "./+types/home"; import type { Route } from "./+types/home";
export function meta({}: Route.MetaArgs) { export function meta(_args: Route.MetaArgs) {
return [ return [
{ title: "trails.cool" }, { title: "trails.cool" },
{ name: "description", content: "Your outdoor activity journal" }, { name: "description", content: "Your outdoor activity journal" },

View file

@ -7,7 +7,8 @@
"dev": "react-router dev", "dev": "react-router dev",
"build": "react-router build", "build": "react-router build",
"start": "react-router-serve ./build/server/index.js", "start": "react-router-serve ./build/server/index.js",
"typecheck": "react-router typegen && tsc" "typecheck": "react-router typegen && tsc",
"lint": "eslint ."
}, },
"dependencies": { "dependencies": {
"@trails-cool/ui": "workspace:*", "@trails-cool/ui": "workspace:*",

View file

@ -1,6 +1,6 @@
import type { Route } from "./+types/home"; import type { Route } from "./+types/home";
export function meta({}: Route.MetaArgs) { export function meta(_args: Route.MetaArgs) {
return [ return [
{ title: "trails.cool Planner" }, { title: "trails.cool Planner" },
{ name: "description", content: "Collaborative route planning" }, { name: "description", content: "Collaborative route planning" },

View file

@ -7,7 +7,8 @@
"dev": "react-router dev", "dev": "react-router dev",
"build": "react-router build", "build": "react-router build",
"start": "react-router-serve ./build/server/index.js", "start": "react-router-serve ./build/server/index.js",
"typecheck": "react-router typegen && tsc" "typecheck": "react-router typegen && tsc",
"lint": "eslint ."
}, },
"dependencies": { "dependencies": {
"@trails-cool/ui": "workspace:*", "@trails-cool/ui": "workspace:*",

View file

@ -316,9 +316,28 @@ services:
image: dxflrs/garage:v1.0 image: dxflrs/garage:v1.0
volumes: ["media:/var/lib/garage"] volumes: ["media:/var/lib/garage"]
caddy:
image: caddy:2
ports: ["80:80", "443:443"]
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
depends_on: [journal]
volumes: volumes:
pgdata: pgdata:
media: media:
caddy_data:
```
Caddy is the reverse proxy for all instances. It provides automatic HTTPS
via Let's Encrypt with zero configuration beyond the Caddyfile:
```
# Caddyfile (self-hosted example)
bob.trails.xyz {
reverse_proxy journal:3000
}
``` ```
### With Self-Hosted Planner (Advanced) ### With Self-Hosted Planner (Advanced)

View file

@ -6,5 +6,13 @@ export default tseslint.config(
{ ignores: ["**/build/", "**/dist/", "**/.react-router/", "**/node_modules/"] }, { ignores: ["**/build/", "**/dist/", "**/.react-router/", "**/node_modules/"] },
js.configs.recommended, js.configs.recommended,
...tseslint.configs.recommended, ...tseslint.configs.recommended,
{
rules: {
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
],
},
},
prettier, prettier,
); );

View file

@ -23,8 +23,8 @@
- [ ] 3.2 Create Docker Compose for all services (Journal, Planner, BRouter, PostgreSQL+PostGIS, Garage) - [ ] 3.2 Create Docker Compose for all services (Journal, Planner, BRouter, PostgreSQL+PostGIS, Garage)
- [ ] 3.3 Create BRouter Dockerfile with segment volume mount - [ ] 3.3 Create BRouter Dockerfile with segment volume mount
- [ ] 3.4 Create segment download script (Germany: E5_N45, E5_N50, E10_N45, E10_N50) - [ ] 3.4 Create segment download script (Germany: E5_N45, E5_N50, E10_N45, E10_N50)
- [ ] 3.5 Configure DNS for trails.cool and planner.trails.cool with TLS (Caddy or Traefik) - [ ] 3.5 Configure DNS for trails.cool and planner.trails.cool with TLS (Caddy)
- [ ] 3.6 Set up GitHub Actions CI pipeline (build, typecheck, lint) - [x] 3.6 Set up GitHub Actions CI pipeline (build, typecheck, lint, unit tests, e2e)
- [ ] 3.7 Set up GitHub Actions CD pipeline (build Docker images, push to ghcr.io, deploy to Hetzner) - [ ] 3.7 Set up GitHub Actions CD pipeline (build Docker images, push to ghcr.io, deploy to Hetzner)
- [ ] 3.8 Set up PostgreSQL backup cron (daily pg_dump to Hetzner Storage Box) - [ ] 3.8 Set up PostgreSQL backup cron (daily pg_dump to Hetzner Storage Box)

View file

@ -2,6 +2,7 @@
"name": "trails.cool", "name": "trails.cool",
"private": true, "private": true,
"description": "Collaborative route planning + federated activity sharing for outdoor enthusiasts", "description": "Collaborative route planning + federated activity sharing for outdoor enthusiasts",
"type": "module",
"license": "MIT", "license": "MIT",
"packageManager": "pnpm@10.6.5", "packageManager": "pnpm@10.6.5",
"engines": { "engines": {