All checks were successful
CD / Build and push images (push) Successful in 3m57s
CI / Lint, typecheck, test (push) Successful in 3m7s
CI / Auth e2e pack (push) Successful in 3m58s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
Two export paths, both permission-aware (permissions.md):
- `GET /ponds/:id/export/markdown` streams a ZIP of the pond's readable
pages as Markdown (one `<slug>.md` per page, a `media/` directory,
wikilinks rewritten to relative `[text](slug.md)` links, image sources to
`media/<id>.<ext>`). The `reader` guard is "may see the pond"; the service
filters to the pages the requester may actually read, so a label-restricted
reader gets only their slice. Media is appended as read streams and pages as
small strings, so memory stays bounded for a large pond (500-page test).
- `POST /pages/:id/export {format: docx|odt}` enqueues a `markdown → pandoc →
file` conversion job (the #62 queue): embedded images are inlined as data
URIs so the sidecar embeds them, wikilinks flatten to text. The client polls
`GET /jobs/:id` and downloads `GET /jobs/:id/result`.
Frontend: office-export buttons in the page menu (`.docx`/`.odt` run the job
and download the result; PDF is a disabled placeholder for Gotenberg, #67) and
a "Download pond as ZIP" link in pond settings. New `export` i18n namespace
(de+en). Markdown copy/download stay as-is (#30).
Robustness: the pond ZIP skips an attachment whose bytes are missing on disk
(data drift) rather than letting an unhandled read-stream error crash the api;
`FileStorageService.exists` gates inclusion, with a defensive stream error
handler. The per-page export drops an unreadable image the same way.
- shared: EXPORT_FORMATS + pageExportInputSchema; export-markdown transform
helpers (image/wikilink rewrites, MIME→extension).
- deps: archiver (streaming ZIP; v7 for CommonJS compat), fflate (dev, reads
ZIPs in tests).
- tests: export-markdown unit + export.service.db (ZIP contents & relative
links, label-restricted omission, docx job with inlined images, 500-page
streaming, missing-media skip); e2e export pack (ZIP download; `.docx`
self-skips without a pandoc sidecar, as in the import pack, #64).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
347 lines
13 KiB
YAML
347 lines
13 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
|
|
|
|
# Two contexts per test (owner + a second regular account) → reset first.
|
|
- name: Reset login rate limit before members 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 members pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/members.spec.ts
|
|
|
|
# Three contexts per test (owner + editor + viewer) → reset first.
|
|
- name: Reset login rate limit before access-rules 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 access-rules pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/access-rules.spec.ts
|
|
|
|
- name: Reset login rate limit before public 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 public pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/public.spec.ts
|
|
|
|
- name: Reset login rate limit before admin-quotas 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 admin-quotas pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/admin-quotas.spec.ts
|
|
|
|
- name: Reset login rate limit before admin-users 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 admin-users pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/admin-users.spec.ts
|
|
|
|
- name: Reset login rate limit before permission-matrix 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 permission-matrix pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/permission-matrix.spec.ts
|
|
|
|
- name: Reset login rate limit before attachments 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 attachments pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/attachments.spec.ts
|
|
|
|
- name: Reset login rate limit before import pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
# The .docx case self-skips without a reachable pandoc sidecar (no
|
|
# E2E_PANDOC here); the .md, failure, and concurrent cases run (#64).
|
|
- name: Run import pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/import.spec.ts
|
|
|
|
- name: Reset login rate limit before export pack
|
|
run: |
|
|
echo "DELETE FROM rate_limits WHERE key LIKE 'login%';" | \
|
|
pnpm --filter @dorfteich/api exec prisma db execute --stdin --url "$DATABASE_URL"
|
|
|
|
# The ZIP case runs; the .docx case self-skips without a pandoc sidecar (#65).
|
|
- name: Run export pack
|
|
run: |
|
|
E2E_BASE_URL=http://localhost:5173 \
|
|
pnpm --filter @dorfteich/web exec playwright test e2e/export.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 .
|