Skip to content

feat(library): add Entity-Relationship diagrams (Chen and crow's-foot notations) #382

feat(library): add Entity-Relationship diagrams (Chen and crow's-foot notations)

feat(library): add Entity-Relationship diagrams (Chen and crow's-foot notations) #382

name: PR Health Checks
on:
pull_request:
types: [opened, synchronize, reopened]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: read
jobs:
detect-changes:
name: Detect changes
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
code: ${{ steps.filter.outputs.code }}
library: ${{ steps.filter.outputs.library }}
should-skip: ${{ steps.skip-check.outputs.should_skip }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- id: skip-check
uses: fkirc/skip-duplicate-actions@f75f66ce1886f00957d99748a42c724f4330bdcf # v5
with:
do_not_skip: '["workflow_dispatch"]'
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
id: filter
if: steps.skip-check.outputs.should_skip != 'true'
with:
filters: |
code:
- 'library/**'
- 'standalone/**'
- 'vscode-extension/**'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- '.npmrc'
- '.nvmrc'
- 'tsconfig*.json'
- '.github/workflows/pr-health-checks.yml'
- '.github/actions/**'
library:
- 'library/**'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- '.github/workflows/pr-health-checks.yml'
- '.github/actions/**'
# No path filter: a stale CDN version can drift in via a doc edit OR a bump,
# so check every PR. Pure Node — no pnpm install needed.
version-sync-check:
needs: [detect-changes]
if: needs.detect-changes.outputs.should-skip != 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: ".nvmrc"
- name: Check README + docs version sync
run: node scripts/sync-library-version.mjs --check
- name: Check release-note taxonomy sync
run: node scripts/check-release-taxonomy.mjs
lint-and-format-check:
needs: [detect-changes]
if: needs.detect-changes.outputs.should-skip != 'true' && needs.detect-changes.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm + Node + install
uses: ./.github/actions/setup
- name: Run Lint
run: pnpm run lint
- name: Check format
run: pnpm run format:check
- name: Check build
run: pnpm run build
- name: Verify the DEV perf probe is stripped from the library build
# Asserts the import.meta.env.DEV-gated instrumentation dead-code-
# eliminates from library/dist. Kept as its own step rather than in the
# library build script so the Docker image build (which doesn't copy
# scripts/) isn't affected.
run: node scripts/check-no-perf-hooks.mjs
- name: Typecheck docs
# `docs/` imports `@tumaet/apollon` for the live embed, so its
# `tsc --noEmit` resolves through the library's exports map and
# needs `library/dist/` to exist — keep this step after build.
run: pnpm --filter @tumaet/apollon-docs run typecheck
- name: Run unit tests
run: pnpm run test
- name: Run webapp unit tests
# Root `pnpm run test` covers only the library; run the webapp's vitest
# suite here too (it resolves @tumaet/apollon through the dist built
# above) so webapp logic like the autosaver is actually gated.
run: pnpm --filter @tumaet/webapp run test
# Root `pnpm run test` covers only the library. The server's vitest suite
# (incl. the PDF-conversion regression test) spawns the compiled worker and
# provisions Redis via testcontainers, so it gets a dedicated job: build the
# library + server first (the worker runs from dist and imports
# @tumaet/apollon), then run the suite against the Docker daemon the runner
# already provides.
server-tests:
needs: [detect-changes]
if: needs.detect-changes.outputs.should-skip != 'true' && needs.detect-changes.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm + Node + install
uses: ./.github/actions/setup
- name: Build library and server
run: pnpm run build:lib && pnpm run build:server
- name: Run server tests
run: pnpm --filter @tumaet/server run test
e2e-tests:
needs: [detect-changes]
if: needs.detect-changes.outputs.should-skip != 'true' && needs.detect-changes.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm + Node + install
uses: ./.github/actions/setup
- name: Build
run: pnpm run build:lib
- name: Cache Playwright browsers
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
id: playwright-cache
with:
path: ~/.cache/ms-playwright
# Browser-specific key: the chromium and firefox jobs share this cache
# path but install different browsers, so they must not share a key
# (a firefox-only cache would make this job skip its chromium install).
key: playwright-chromium-${{ runner.os }}-v1.59.1-${{ hashFiles('standalone/webapp/package.json') }}
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: pnpm --filter @tumaet/webapp exec playwright install chromium
# Shared with perf-firefox-tests; the dpkg-lock hang it guards is
# documented in the composite action.
- name: Install Playwright system deps (bounded + retried)
uses: ./.github/actions/playwright-install-deps
with:
browser: chromium
- name: Run E2E tests
run: pnpm --filter @tumaet/webapp exec playwright test tests/e2e/
# No chromium document-growth budget: the metric is engine-independent and
# the Firefox budget (perf-firefox-tests below) is the per-PR guard for the
# exam-freeze regression, since Firefox is the engine the freeze was
# reported on and its GC surfaces unbounded growth sooner.
- name: Upload Playwright report
if: ${{ failure() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: playwright-report-e2e
path: standalone/webapp/playwright-report/
retention-days: 14
- name: Upload Playwright test results
if: ${{ failure() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: playwright-test-results-e2e
path: standalone/webapp/test-results/
retention-days: 14
# Document-growth budget on Firefox too: its GC surfaces unbounded-growth
# regressions sooner. (The functional e2e suite isn't on Firefox yet — it has
# Firefox-only rendering quirks that need separate triage.)
#
# Bare runner, NOT the Playwright container: the container's bundled Firefox
# crashes (exitCode=1) under GitHub Actions' container sandbox, while the host
# runner launches it reliably. Do not "simplify" this into the container.
perf-firefox-tests:
needs: [detect-changes]
if: needs.detect-changes.outputs.should-skip != 'true' && needs.detect-changes.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm + Node + install
uses: ./.github/actions/setup
- name: Build
run: pnpm run build:lib
- name: Cache Playwright browsers
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
id: playwright-cache
with:
path: ~/.cache/ms-playwright
# Firefox-specific key — must differ from the chromium jobs' key (same
# cache path, different browser) so neither poisons the other's cache.
key: playwright-firefox-${{ runner.os }}-v1.59.1-${{ hashFiles('standalone/webapp/package.json') }}
- name: Install Playwright Firefox
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: pnpm --filter @tumaet/webapp exec playwright install firefox
# Shared with e2e-tests; the dpkg-lock hang it guards is documented in
# the composite action.
- name: Install Playwright system deps (bounded + retried)
uses: ./.github/actions/playwright-install-deps
with:
browser: firefox
- name: Run Firefox performance budget tests
run: pnpm --filter @tumaet/webapp run test:perf:firefox
- name: Upload Playwright report
if: ${{ failure() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: playwright-report-perf-firefox
path: standalone/webapp/playwright-report/
retention-days: 14
- name: Upload Playwright test results
if: ${{ failure() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: playwright-test-results-perf-firefox
path: standalone/webapp/test-results/
retention-days: 14
# Library has `engines.node: ">=22"` for its published consumers — back the
# promise with a build+test on Node 22 LTS so a Node-24-only feature can't
# silently land in the npm tarball.
library-node22-compat:
needs: [detect-changes]
if: needs.detect-changes.outputs.should-skip != 'true' && needs.detect-changes.outputs.library == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup pnpm + Node 22 + install
# Node 22 (the library's published floor) instead of the repo's .nvmrc,
# and engine-strict off so the root `>=24.15.0` gate doesn't reject it.
uses: ./.github/actions/setup
with:
node-version: "22"
engine-strict: "false"
- name: Build library
run: pnpm --filter @tumaet/apollon run build
- name: Run library unit tests
run: pnpm --filter @tumaet/apollon run test
- name: Verify ESM exports-map resolution
# The library is ESM-only. `import.meta.resolve` (sync, no side
# effects) validates every subpath in the exports map — actually
# evaluating the module would require jsdom.
working-directory: standalone/server
run: |
node --input-type=module -e '
const root = import.meta.resolve("@tumaet/apollon");
const react = import.meta.resolve("@tumaet/apollon/react");
const internals = import.meta.resolve("@tumaet/apollon/internals");
const css = import.meta.resolve("@tumaet/apollon/style.css");
console.log({ root, react, internals, css });
'
- name: Lint published package shape (publint)
working-directory: library
run: pnpm dlx publint
# NOTE: arethetypeswrong/cli (attw) is intentionally not run here.
# attw 0.16.x–0.18.x ship a buggy fflate Gunzip streaming workaround
# in @arethetypeswrong/core's `extractTarball` that overwrites the
# decompressed buffer on each chunk callback instead of accumulating.
# Any tarball that decompresses across multiple fflate chunks (ours
# is ~750 KB → 3 chunks) crashes with `Cannot read properties of
# undefined (reading 'filename')`. The standalone subpath
# deliberately bundles peers (~2.4 MB) so the tarball is over
# threshold by design. `publint` above plus the `import.meta.resolve`
# exports-map check below cover the same ground.
- name: Enforce bundle-size budget
working-directory: library
# Budgets in library/package.json#size-limit. Fails on regression
# of >10% (size-limit default) per artifact.
run: pnpm run size
# Pinned Playwright container so screenshot baselines diff against one Linux
# rendering stack regardless of contributor OS (see
# docs/contributor/development/visual-tests.md). Runs per-PR so a rendering
# change refreshes its own baselines in the same PR, instead of merging
# unguarded and surfacing later as an ownerless failure.
#
# Gated on `code`, not a curated render-only path filter: rendering output is
# not statically enumerable (an app-shell layout edit shifts every diagram
# baseline), so anything that could touch pixels must run. Docs-only PRs skip.
visual-regression-tests:
needs: [detect-changes]
if: needs.detect-changes.outputs.should-skip != 'true' && needs.detect-changes.outputs.code == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
container:
image: mcr.microsoft.com/playwright:v1.59.1-noble
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
# The container ships an older Node; the composite installs Node from
# .nvmrc via setup-node instead of curl-ing NodeSource on every run.
- name: Setup pnpm + Node + install
uses: ./.github/actions/setup
- name: Build
run: pnpm run build:lib
- name: Run visual regression tests
run: pnpm --filter @tumaet/webapp run test:visual
# Point an author whose UI change is intentional at the way to refresh
# baselines, right in the Checks UI. No extra permissions needed.
- name: Explain how to refresh baselines
if: ${{ failure() }}
run: |
{
echo "### Visual regression failed"
echo ""
echo "If this diff is an **intentional** UI change, refresh the baselines:"
echo "- A maintainer runs **Actions → Update Visual Baselines → Run workflow** against this PR's branch — it regenerates snapshots inside this exact pinned container and commits them back."
echo "- Or regenerate locally in the container — see \`docs/contributor/development/visual-tests.md\`."
echo ""
echo "Otherwise download the \`playwright-report-visual\` artifact to inspect the actual-vs-expected diff."
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload Playwright report
if: ${{ failure() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: playwright-report-visual
path: standalone/webapp/playwright-report/
retention-days: 14
- name: Upload Playwright test results
if: ${{ failure() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: playwright-test-results-visual
path: standalone/webapp/test-results/
retention-days: 14
# Gate job — set this as the required status check in branch protection.
# Correctly handles skipped jobs (docs-only, duplicate runs).
pr-health-gate:
name: PR Health Gate
runs-on: ubuntu-latest
timeout-minutes: 2
needs:
[
detect-changes,
version-sync-check,
lint-and-format-check,
server-tests,
library-node22-compat,
e2e-tests,
perf-firefox-tests,
visual-regression-tests,
]
if: always()
steps:
- name: Evaluate results
run: |
if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
echo "One or more checks failed"
exit 1
fi
if [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
echo "One or more checks were cancelled"
exit 1
fi
echo "All checks passed or were skipped"