All checks were successful
CD / Build and push images (push) Successful in 3m4s
CI / Lint, typecheck, test (push) Successful in 2m27s
CI / Auth e2e pack (push) Successful in 3m6s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 11s
Live editing now obeys the same rules as REST: the collab-token mode comes from the shared grant resolution, anonymous visitors can join public pages, and revoking write access flips a running session to read-only within seconds. - Anonymous public tokens: `GET /pages/:id/collab-token` is `@Public()` but still permission-guarded, so a logged-out visitor gets an `ro` token where a `public` grant makes the page readable (404 otherwise). The token's `userId` is nullable (shared schema + collab context) for anonymous subjects. - Prompt revocation: the pond-level NOTIFY (#39) now also fires on label tree/assignment changes (LabelsService move/remove/assign/unassign), and the collab server closes the *actual* WebSocket instead of only sending an application-level close message. Hocuspocus' `closeConnections` leaves the socket open so the client only re-checks on its ~30s message timeout; `closeDocumentConnections` drops the socket so the client reconnects and re-authenticates with a freshly-resolved token at once — the "within seconds" downgrade the milestone promises. - Tests: the #52 fixture matrix gains anonymous cases (public grant → `ro`, none → 404); a collab db test proves an editor downgraded to reader goes read-only on reconnect (its post-downgrade edits no longer reach a peer); a new browser `collab-permissions` pack covers the read-only participant and the live downgrade end to end (new plain `fixture-editor` account). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
252 lines
9.0 KiB
YAML
252 lines
9.0 KiB
YAML
# CI: every pull request and every push to main must pass these checks
|
|
# (ADR 0014). The deploy pipeline (CD) lives in cd.yml and only runs on
|
|
# main after this workflow's quality bar.
|
|
#
|
|
# Runner requirements: an act_runner with the `ubuntu-latest` label and
|
|
# Docker access (see deploy/stages.md, issue #9).
|
|
|
|
name: CI
|
|
|
|
on:
|
|
pull_request:
|
|
push:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
checks:
|
|
name: Lint, typecheck, test
|
|
runs-on: ubuntu-latest
|
|
services:
|
|
postgres:
|
|
image: postgres:17.5-alpine
|
|
env:
|
|
POSTGRES_USER: test
|
|
POSTGRES_PASSWORD: test
|
|
POSTGRES_DB: test
|
|
env:
|
|
# Enables the database-backed test suites (vitest.global-setup.ts).
|
|
TEST_DATABASE_URL: postgresql://test:test@postgres:5432/test
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up pnpm
|
|
uses: pnpm/action-setup@v4
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# Build first: package type checks resolve @dorfteich/shared through
|
|
# its built dist, and i18n:check imports the built helpers.
|
|
- name: Build all packages
|
|
run: pnpm build
|
|
|
|
- name: Lint (ESLint + Prettier)
|
|
run: pnpm lint
|
|
|
|
- name: Typecheck
|
|
run: pnpm typecheck
|
|
|
|
- name: Unit and integration tests
|
|
run: pnpm test
|
|
|
|
- name: Translation key parity (de/en)
|
|
run: pnpm i18n:check
|
|
|
|
# Job id/display name kept stable (branch-protection/status-check
|
|
# matching uses the reported "CI / Auth e2e pack" context) even though
|
|
# it now also runs the content pack (issue #32) against the same
|
|
# already-built-and-seeded stack, instead of spinning up a second one.
|
|
auth-e2e:
|
|
name: Auth e2e pack
|
|
runs-on: ubuntu-latest
|
|
services:
|
|
postgres:
|
|
image: postgres:17.5-alpine
|
|
env:
|
|
POSTGRES_USER: e2e
|
|
POSTGRES_PASSWORD: e2e
|
|
POSTGRES_DB: e2e
|
|
mailpit:
|
|
image: axllent/mailpit:latest
|
|
env:
|
|
DATABASE_URL: postgresql://e2e:e2e@postgres:5432/e2e
|
|
APP_BASE_URL: http://localhost:5173
|
|
SMTP_HOST: mailpit
|
|
SMTP_PORT: '1025'
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up pnpm
|
|
uses: pnpm/action-setup@v4
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: pnpm
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Build packages
|
|
run: pnpm build
|
|
|
|
- name: Apply migrations
|
|
run: pnpm --filter @dorfteich/api exec prisma migrate deploy
|
|
|
|
- name: Seed fixtures
|
|
run: pnpm --filter @dorfteich/api db:seed
|
|
|
|
- name: Start api, collab, and static web server
|
|
run: |
|
|
(cd apps/api && PORT=3001 node dist/main.js > /tmp/api.log 2>&1 &)
|
|
(cd apps/collab && PORT=3002 node dist/index.js > /tmp/collab.log 2>&1 &)
|
|
(PORT=5173 COLLAB_TARGET=http://127.0.0.1:3002 node scripts/e2e-static-server.mjs > /tmp/web.log 2>&1 &)
|
|
for i in $(seq 1 30); do
|
|
curl -sf http://localhost:3001/api/v1/readyz >/dev/null && break
|
|
sleep 2
|
|
done
|
|
for i in $(seq 1 30); do
|
|
curl -sf http://localhost:3002/healthz >/dev/null && break
|
|
sleep 2
|
|
done
|
|
curl -sf http://localhost:5173/ >/dev/null
|
|
|
|
- name: Install Playwright browser
|
|
run: pnpm --filter @dorfteich/web exec playwright install --with-deps chromium
|
|
|
|
- name: Run auth pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 E2E_MAILPIT_URL=http://mailpit:8025 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/auth.spec.ts
|
|
|
|
# auth.spec.ts's own logins (contextForUser per test) already spend
|
|
# a good chunk of the login rate limit (10/min/IP, operations.md) —
|
|
# reset it before content.spec.ts's five more, or its later tests
|
|
# 429 (found by reproducing this job's failure locally, issue #32).
|
|
- name: Reset login rate limit between e2e packs
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run content pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/content.spec.ts
|
|
|
|
# The collab pack opens two browser contexts per test (more logins),
|
|
# so reset the login rate limit before it as well (see note above).
|
|
- name: Reset login rate limit before collab pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run collab pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/collab.spec.ts
|
|
|
|
# Two contexts per test (owner + a second regular account) and grant
|
|
# changes → reset the login rate limit first (see note above).
|
|
- name: Reset login rate limit before collab-permissions pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run collab-permissions pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/collab-permissions.spec.ts
|
|
|
|
- name: Reset login rate limit before offline pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run offline pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/offline.spec.ts
|
|
|
|
- name: Reset login rate limit before labels pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run labels pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/labels.spec.ts
|
|
|
|
- name: Reset login rate limit before reorder pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run reorder pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/reorder.spec.ts
|
|
|
|
- name: Reset login rate limit before wikilink pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run wikilink pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/wikilink.spec.ts
|
|
|
|
- name: Reset login rate limit before backlinks pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run backlinks pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/backlinks.spec.ts
|
|
|
|
- name: Reset login rate limit before search pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
- name: Run search pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/search.spec.ts
|
|
|
|
- name: Dump server logs on failure
|
|
if: failure()
|
|
run: tail -50 /tmp/api.log /tmp/collab.log /tmp/web.log || true
|
|
|
|
images:
|
|
name: Build container images
|
|
# PR-only: on main the CD workflow builds and pushes the same images —
|
|
# building twice would waste the runner (ADR 0014: build once, promote).
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v4
|
|
|
|
# PRs prove the Dockerfiles still build; pushing happens in cd.yml.
|
|
- name: Build web image
|
|
run: docker build -f apps/web/Dockerfile --build-arg APP_VERSION=${{ github.sha }} -t dorfteich-web:ci .
|
|
|
|
- name: Build api image
|
|
run: docker build -f apps/api/Dockerfile --build-arg APP_VERSION=${{ github.sha }} -t dorfteich-api:ci .
|
|
|
|
- name: Build collab image
|
|
run: docker build -f apps/collab/Dockerfile --build-arg APP_VERSION=${{ github.sha }} -t dorfteich-collab:ci .
|