diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7f2b3b4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 diff --git a/apps/journal/app/routes/home.tsx b/apps/journal/app/routes/home.tsx index 681f32b..b38fab1 100644 --- a/apps/journal/app/routes/home.tsx +++ b/apps/journal/app/routes/home.tsx @@ -1,6 +1,6 @@ import type { Route } from "./+types/home"; -export function meta({}: Route.MetaArgs) { +export function meta(_args: Route.MetaArgs) { return [ { title: "trails.cool" }, { name: "description", content: "Your outdoor activity journal" }, diff --git a/apps/journal/package.json b/apps/journal/package.json index 20b850d..4102d19 100644 --- a/apps/journal/package.json +++ b/apps/journal/package.json @@ -7,7 +7,8 @@ "dev": "react-router dev", "build": "react-router build", "start": "react-router-serve ./build/server/index.js", - "typecheck": "react-router typegen && tsc" + "typecheck": "react-router typegen && tsc", + "lint": "eslint ." }, "dependencies": { "@trails-cool/ui": "workspace:*", diff --git a/apps/planner/app/routes/home.tsx b/apps/planner/app/routes/home.tsx index 994c164..8aa3380 100644 --- a/apps/planner/app/routes/home.tsx +++ b/apps/planner/app/routes/home.tsx @@ -1,6 +1,6 @@ import type { Route } from "./+types/home"; -export function meta({}: Route.MetaArgs) { +export function meta(_args: Route.MetaArgs) { return [ { title: "trails.cool Planner" }, { name: "description", content: "Collaborative route planning" }, diff --git a/apps/planner/package.json b/apps/planner/package.json index 482b36c..9c9cfcf 100644 --- a/apps/planner/package.json +++ b/apps/planner/package.json @@ -7,7 +7,8 @@ "dev": "react-router dev", "build": "react-router build", "start": "react-router-serve ./build/server/index.js", - "typecheck": "react-router typegen && tsc" + "typecheck": "react-router typegen && tsc", + "lint": "eslint ." }, "dependencies": { "@trails-cool/ui": "workspace:*", diff --git a/docs/architecture.md b/docs/architecture.md index e21d07c..5afba23 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -316,9 +316,28 @@ services: image: dxflrs/garage:v1.0 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: pgdata: 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) diff --git a/eslint.config.js b/eslint.config.js index 1baf4a4..f467894 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,5 +6,13 @@ export default tseslint.config( { ignores: ["**/build/", "**/dist/", "**/.react-router/", "**/node_modules/"] }, js.configs.recommended, ...tseslint.configs.recommended, + { + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], + }, + }, prettier, ); diff --git a/openspec/changes/phase-1-mvp/tasks.md b/openspec/changes/phase-1-mvp/tasks.md index eef01dc..3115f8d 100644 --- a/openspec/changes/phase-1-mvp/tasks.md +++ b/openspec/changes/phase-1-mvp/tasks.md @@ -23,8 +23,8 @@ - [ ] 3.2 Create Docker Compose for all services (Journal, Planner, BRouter, PostgreSQL+PostGIS, Garage) - [ ] 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.5 Configure DNS for trails.cool and planner.trails.cool with TLS (Caddy or Traefik) -- [ ] 3.6 Set up GitHub Actions CI pipeline (build, typecheck, lint) +- [ ] 3.5 Configure DNS for trails.cool and planner.trails.cool with TLS (Caddy) +- [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.8 Set up PostgreSQL backup cron (daily pg_dump to Hetzner Storage Box) diff --git a/package.json b/package.json index b2c0fa2..b0e3430 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "trails.cool", "private": true, "description": "Collaborative route planning + federated activity sharing for outdoor enthusiasts", + "type": "module", "license": "MIT", "packageManager": "pnpm@10.6.5", "engines": {