Skip to content

feat: monorepo modernization — pnpm 11 + Node 24, Docusaurus docs, React <Apollon> API, VS Code rewrite #18

feat: monorepo modernization — pnpm 11 + Node 24, Docusaurus docs, React <Apollon> API, VS Code rewrite

feat: monorepo modernization — pnpm 11 + Node 24, Docusaurus docs, React <Apollon> API, VS Code rewrite #18

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'
library:
- 'library/**'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- '.github/workflows/pr-health-checks.yml'
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: Install pnpm
uses: pnpm/action-setup@ac6db6d3c1f721f886538a378a2d73e85697340a # v6.0.8
- name: Set up Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run Lint
run: pnpm run lint
- name: Check format
run: pnpm run format:check
- name: Check build
run: pnpm run build
- name: Run unit tests
run: pnpm 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: Install pnpm
uses: pnpm/action-setup@ac6db6d3c1f721f886538a378a2d73e85697340a # v6.0.8
- name: Set up Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build:lib
- name: Cache Playwright browsers
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ 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
- name: Install Playwright system deps
run: pnpm --filter @tumaet/webapp exec playwright install-deps chromium
- name: Run E2E tests
run: pnpm --filter @tumaet/webapp exec playwright test tests/e2e/
- 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
# 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: Install pnpm
uses: pnpm/action-setup@ac6db6d3c1f721f886538a378a2d73e85697340a # v6.0.8
- name: Set up Node 22 LTS
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: "22"
cache: "pnpm"
- name: Install dependencies
# `engine-strict=true` rejects Node 22 against the root `>=24.15.0`
# gate; library workspace is `>=22`, so install with the flag off.
run: pnpm install --frozen-lockfile --config.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
- name: Validate type resolution (arethetypeswrong)
working-directory: library
# ESM-only library, all exports flagged as such; CJS profile
# would force-flag dual-package issues that don't apply. The
# CLI binary `attw` ships from `@arethetypeswrong/cli`; pnpm
# dlx resolves the binary from the package name implicitly.
run: pnpm dlx @arethetypeswrong/cli@0.18.1 attw --pack . --profile esm-only
- 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
# The library declares `react: ^18.3 || ^19` as a peer. Back the upper
# bound with a build + test against React 19 so the range claim is
# actually verified.
library-react19-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: Install pnpm
uses: pnpm/action-setup@ac6db6d3c1f721f886538a378a2d73e85697340a # v6.0.8
- name: Set up Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Override library devDeps to React 19 via pnpm.overrides
# Run only inside the library workspace; the rest of the
# monorepo stays on React 18.3 (matches Artemis / standalone
# webapp consumers). MUI v6 peer-supports React 19 since 6.0.
run: |
pnpm --filter @tumaet/apollon add -D \
react@19 \
react-dom@19 \
@types/react@19 \
@types/react-dom@19 \
@testing-library/react@16 \
--save-exact --ignore-scripts
- name: Install dependencies
run: pnpm install --frozen-lockfile=false --config.engine-strict=false
- name: Build library against React 19
run: pnpm --filter @tumaet/apollon run build
- name: Run library unit tests against React 19
run: pnpm --filter @tumaet/apollon run test
# Baseline regeneration: see docs/development/visual-tests.md.
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
- name: Install Node 24
shell: bash
run: |
set -euo pipefail
curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
apt-get install -y nodejs
node --version
- name: Install pnpm
uses: pnpm/action-setup@ac6db6d3c1f721f886538a378a2d73e85697340a # v6.0.8
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build:lib
- name: Run visual regression tests
run: pnpm --filter @tumaet/webapp exec playwright test tests/visual/
- 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,
lint-and-format-check,
library-node22-compat,
library-react19-compat,
e2e-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"