Files
dc/.gitea/workflows/lighthouse.yaml
T
dc 9d40fc56df
Build / Build-and-ng-test (pull_request) Successful in 5m44s
Lighthouse Checks / lighthouse (pull_request) Successful in 9m0s
Build / Build-and-test-development (pull_request) Successful in 29m54s
ci: decide "run the suite" on every failure path, and cover .gitea/scripts
Review findings on the change-detection steps and the shared build artefact.

- A failed fetch or diff in "Detect client changes" used to fail the job: the
  fail-safe only covered an output that was absent, not a step that errored.
  Both workflows now exit 0 without writing client=false on any failure, so an
  undecidable change set runs the suite instead of blocking the pull request.
- .gitea/scripts/ joins the change set in both workflows: it carries the spec
  list (cypress-specs.sh) and the runner gate (check-runner-memory.sh), so a
  change to what the suite runs, or to the gate that lets it run, must run it.
- A skipped job now says so in a named step and a notice annotation, rather than
  reporting success from a list of greyed-out steps.
- The dist upload sets if-no-files-found: error, so an empty artefact fails the
  producing job instead of surfacing later as "webSourcePath ... doesn't exist".
- lighthouserc.js records the cost of numberOfRuns: 1 - no median, so one slow
  navigation is a re-run rather than a fix.
2026-11-03 19:13:08 +00:00

201 lines
8.1 KiB
YAML

name: Lighthouse Checks
run-name: Running Lighthouse Performance and Accessibility Checks on Pull Request
on: [pull_request]
env:
# the server the mocked services and app tests run against - bump deliberately
SASJS_SERVER_VERSION: '3.6.2'
NODE_VERSION: '24.15.0'
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Detect client changes
id: changes
# The audit measures the client, served from the mocked estate. A pull
# request that changes none of what it reads cannot move a score, so it
# does not need nineteen minutes of runner (measured median) to prove it.
# As in build.yaml, the gate is `!= 'false'` and every failure exits 0
# without writing client=false, so a decision that cannot be made runs
# the audit rather than failing the job.
run: |
set -euo pipefail
BASE="${{ github.base_ref }}"
if [ -z "$BASE" ]; then
echo "no base ref (not a pull request) - running the audit"
exit 0
fi
if ! git fetch --no-tags --depth=1 origin "+refs/heads/${BASE}:refs/remotes/origin/${BASE}"; then
echo "::warning::could not fetch ${BASE} - running the audit rather than deciding"
exit 0
fi
if ! CHANGED="$(git diff --name-only "refs/remotes/origin/${BASE}" HEAD)"; then
echo "::warning::could not diff against ${BASE} - running the audit rather than deciding"
exit 0
fi
# .gitea/scripts/ carries the runner-memory gate this job runs before
# it starts, so a change there has to be exercised too.
if printf '%s\n' "$CHANGED" | grep -qE '^(client/|sas/mocks/|\.gitea/scripts/|package-lock\.json|\.gitea/workflows/lighthouse\.yaml)'; then
echo "client=true" >> "$GITHUB_OUTPUT"
else
echo "client=false" >> "$GITHUB_OUTPUT"
echo "::notice::Lighthouse audit skipped - nothing under client/, sas/mocks/ or .gitea/scripts/ changed"
printf 'Skipping the audit - nothing it reads changed:\n%s\n' "$CHANGED"
fi
- name: Lighthouse audit not run - no client changes
if: steps.changes.outputs.client == 'false'
run: |
echo "No client, sas/mocks or .gitea/scripts change in this pull request - the audit was skipped by design."
echo "The detection step above lists the files it saw, and warns instead if it could not decide (in which case the audit runs)."
- name: Check runner size
if: steps.changes.outputs.client != 'false'
# This job runs a production build, then a SASjs server, a dev server
# and Chrome. An undersized runner dies inside the build with exitcode
# 137; fail here in seconds instead - see the script.
run: bash .gitea/scripts/check-runner-memory.sh
- name: Use Node.js ${{ env.NODE_VERSION }}
if: steps.changes.outputs.client != 'false'
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install Google Chrome
if: steps.changes.outputs.client != 'false'
run: |
apt-get update
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt install -y ./google-chrome*.deb
- name: Install global packages
if: steps.changes.outputs.client != 'false'
run: npm i -g pm2 @sasjs/cli wait-on
- name: Cache the npm downloads
if: steps.changes.outputs.client != 'false'
# The server is installed from npm at a pinned version, so the tarball is
# immutable and the key never needs invalidating by hand: a hit skips
# the 10.7MB download, a miss costs exactly what the zip cost before.
# continue-on-error, because a runner without cache support should still
# run the pipeline - this step only ever saves time.
continue-on-error: true
uses: actions/cache@v4
with:
path: ~/.npm
key: npm-sasjs-server-${{ env.SASJS_SERVER_VERSION }}
- name: Setup and start SASjs server
if: steps.changes.outputs.client != 'false'
run: |
touch .env
echo RUN_TIMES=js >> .env
echo NODE_PATH=node >> .env
echo CORS=enable >> .env
echo WHITELIST=http://localhost:4200 >> .env
echo HTTP_PORT=5000 >> .env
# The server refuses to run as root unless RUN_AS names an account
# (a security change in the recent releases). This job runs as root
# in a throwaway container, so opt in explicitly.
echo RUN_AS=root >> .env
cat .env
pm2 start npx --name sasjs-server -- --yes @sasjs/server@${{ env.SASJS_SERVER_VERSION }}
# `pm2 start` returns before the app is listening, and the next step
# hits :5000 immediately. Wait for the server to answer, and dump its
# log if it never does.
for i in $(seq 1 60); do
if curl -sf -o /dev/null http://localhost:5000/; then
echo "SASjs server answered on attempt ${i}"
exit 0
fi
sleep 2
done
echo "SASjs server did not come up within 120s"
pm2 list
pm2 logs sasjs-server --lines 100 --nostream
exit 1
- name: Write .npmrc file
if: steps.changes.outputs.client != 'false'
run: |
# the registry secret stays outside the repo: client/.npmrc is
# tracked now, and one git add -A would commit a live token
echo "$NPMRC" > "$RUNNER_TEMP/npmrc"
echo "NPM_CONFIG_USERCONFIG=$RUNNER_TEMP/npmrc" >> "$GITHUB_ENV"
shell: bash
env:
NPMRC: ${{ secrets.NPMRC}}
- name: Install npm dependencies
if: steps.changes.outputs.client != 'false'
run: |
cd client
# Decrypt and Install sheet
echo "${{ secrets.SHEET_PWD }}" | \
gpg --batch --yes --passphrase-fd 0 \
--output ./libraries/sheet-crypto.tgz \
--decrypt ./libraries/sheet-crypto.tgz.gpg
npm ci
# client install scripts are off (client/.npmrc); the generator that
# creates src/environments/version.ts runs explicitly
npm run postinstall
npm install -g replace-in-files-cli
- name: Update appLoc in index.html
if: steps.changes.outputs.client != 'false'
run: |
cd client
replace-in-files --regex='appLoc=".*?"' --replacement='appLoc="/proj/sasjs/genesis-mocks"' ./src/index.html
- name: Build Frontend
if: steps.changes.outputs.client != 'false'
run: |
cd client
npm run build
- name: Deploy JS mocked services and frontend to the local SASjs Server instance
if: steps.changes.outputs.client != 'false'
run: |
cd sas/mocks
npm ci
sasjs cbd -t server-ci
- name: Start frontend server
if: steps.changes.outputs.client != 'false'
run: |
cd client
npx ng serve --host 0.0.0.0 --port 4200 &
wait-on http://localhost:4200
- name: Run Lighthouse CI
if: steps.changes.outputs.client != 'false'
# One run per URL: the assertions aggregate by median, so three runs
# bought a steadier median at three times the navigations (13.6 minutes
# of this job, measured). A pull request gate only needs to know the
# page still clears the thresholds it cleared before; a regression that
# only shows in a median of three is a flake to chase separately.
run: |
cd client
npx lhci autorun
- name: Lighthouse Result Artifacts
if: always() && steps.changes.outputs.client != 'false'
uses: actions/upload-artifact@v3
with:
name: Lighthouse results
path: client/lighthouse-reports
include-hidden-files: true
- name: Remove registry config
# written earlier in this job; nothing deletes it on the way out
if: always()
run: rm -f "$RUNNER_TEMP/npmrc"