Skip to content

test: keep the manifest and shell theme colors on the brand token #585

test: keep the manifest and shell theme colors on the brand token

test: keep the manifest and shell theme colors on the brand token #585

Workflow file for this run

name: CI
on:
# Main only, since a branch push and its pull request fall in different
# concurrency groups and would each run the whole job.
push:
branches:
- main
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
verify:
name: Verify
runs-on: ubuntu-latest
# A green job runs about four and a half minutes. Ten catches a hang without
# holding the machine for the six-hour platform default, and five more are the
# Lighthouse step's own cap.
timeout-minutes: 15
# A boolean rather than the token, because `secrets` is not an allowed
# context in a step's `if`. The token itself stays on the one step that
# needs it, out of the environment of the install and of every action here.
env:
HAS_CHROMATIC: ${{ secrets.CHROMATIC_PROJECT_TOKEN != '' }}
outputs:
ui-checks: ${{ steps.scope.outputs.ui }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Sonar reads blame to decide what counts as new code. A shallow clone
# leaves it none, so every line looks new.
fetch-depth: 0
# Turns off the browser suites, Lighthouse, the uploads and Chromatic when
# nothing they check changed: a release PR, or a PR touching only Markdown
# and docs/. Lighthouse, the bundle stats and Chromatic also skip when every
# change is on a path known not to reach the built page or Chromatic's specs.
# Verify itself still runs, so the required check reports.
- name: Decide which checks run
id: scope
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
# Same-repo only, so a fork cannot skip the gates by naming its branch.
RELEASE_PR: ${{ github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'release-please--') }}
run: |
heavy=true
ui=true
if [ "$RELEASE_PR" = true ]; then
heavy=false
ui=false
elif [ -n "$BASE_SHA" ]; then
changed="$(git diff --name-only "$BASE_SHA" HEAD)"
if ! grep -qvE '(\.md$|^docs/)' <<< "$changed"; then
heavy=false
ui=false
# Paths known not to reach the built page; anything else runs the checks.
elif ! grep -vE '(\.md$|^docs/|\.test\.tsx?$|^src/test/|^bench/|^\.husky/|^scripts/(films\.rq|generate-|package-smoke)|^\.github/(CODEOWNERS|dependabot\.yml)$|^(\.fallowrc\.json|\.git-blame-ignore-revs|\.gitattributes|\.gitignore|\.prettierignore|\.release-please-manifest\.json|\.stylelintrc\.json|LICENSE|eslint\.config\.js|release-please-config\.json|sonar-project\.properties|stryker\.conf\.js|vitest\.setup\.ts|vitest\.stryker\.config\.ts)$)' <<< "$changed" > /dev/null; then
ui=false
fi
fi
echo "ui=$ui" >> "$GITHUB_OUTPUT"
echo "HEAVY_CHECKS=$heavy" >> "$GITHUB_ENV"
echo "UI_CHECKS=$ui" >> "$GITHUB_ENV"
# Pull requests only, since a push has no base to diff against. Before the
# install, so a known advisory stops the job while the change is in review.
- name: Dependency review
if: github.event_name == 'pull_request'
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
fail-on-severity: high
- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .nvmrc
cache: npm
- name: Assert Node major matches .nvmrc
run: |
expected="$(tr -d 'v \t\r\n' < .nvmrc)"
actual="$(node -p 'process.versions.node.split(".")[0]')"
if [ "$expected" != "$actual" ]; then
echo "Node major mismatch: .nvmrc asks for ${expected}, the runner is on ${actual}"
exit 1
fi
echo "Node major ${actual} matches .nvmrc"
- name: Install
run: npm ci
- name: Lint
run: npm run lint
- name: Typecheck
run: npm run typecheck
- name: Format check
run: npm run format:check
- name: Test
run: npm run test:coverage
- name: Build
run: npm run build
# Packs the tarball a consumer installs and checks it. Nothing else resolves the
# published branch of exports; dev, tests and the demo all read src/.
- name: Package checks
run: npm run test:package
# The browser download is the most expensive thing here, so it sits behind
# every cheap gate, and the accessibility sweep runs only in this browser.
- name: Install Chromium
if: env.HEAVY_CHECKS == 'true'
run: npx playwright install --with-deps --only-shell chromium
- name: Accessibility sweep
if: env.HEAVY_CHECKS == 'true'
run: npm run test:browser
# After the sweep, which leaves both things this needs in place: the browser
# binary and the build. Before the scan, so a broken flow reports as itself.
- name: End-to-end tests
if: env.HEAVY_CHECKS == 'true'
run: npm run test:e2e
# A runner's filesystem is gone when the job ends. Only a failure writes
# traces, and failure() still runs after the step above stops the job.
- name: Upload end-to-end failure artifacts
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: e2e-failure-artifacts
path: test-results/
retention-days: 7
# Advisory: a slow score or a failed audit never fails the job. Reuses the
# build and the headless shell the suites above already have in place.
- name: Lighthouse (advisory)
if: env.UI_CHECKS == 'true'
run: npm run lighthouse
continue-on-error: true
timeout-minutes: 5
- name: Upload Lighthouse reports
if: env.UI_CHECKS == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
continue-on-error: true
with:
name: lighthouse-reports
path: lighthouse/
retention-days: 7
if-no-files-found: ignore
# Sends the report the test step wrote, from here so a failing gate
# reports as itself. Skipped for the updater, which reads an empty token.
- name: Upload coverage to Codecov
if: github.actor != 'dependabot[bot]' && env.HEAVY_CHECKS == 'true'
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/lcov.info
disable_search: true
# Otherwise the pull request waits on a status that never arrives.
fail_ci_if_error: true
# Runs even after a red step, because a failed suite is the run flake
# detection needs to see. The directory is searched rather than the three
# reports named, so a run that stopped early uploads what it wrote, and the
# file check skips the step when it wrote nothing.
#
# Every report carries junit in its file name because the uploader matches
# names against *junit*.xml and reads the directory name as nothing.
# Measured: without it the step finds zero files, uploads nothing, and
# still exits green under fail_ci_if_error.
#
# The same action as the step above; codecov/test-results-action would be a
# second action to pin for an input this one already takes.
- name: Upload test results to Codecov
if: ${{ !cancelled() && github.actor != 'dependabot[bot]' && env.HEAVY_CHECKS == 'true' && hashFiles('junit/*.xml') != '' }}
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: test_results
directory: junit
fail_ci_if_error: true
# The CLI rather than @codecov/vite-plugin, which peers vite 4, 5 and 6 and
# will not install beside this tree's vite 8 (upstream issue #264). It
# reports asset sizes and not the per-module breakdown the plugin gets from
# bundler hooks.
#
# Without the normalize pattern every build reports a full set of new
# assets, because the assets here are content-hashed and the analyzer's
# fallback matcher only recognizes a hex hash. Keep the pattern in step with
# the bundler's asset file names.
- name: Upload bundle stats to Codecov
if: github.actor != 'dependabot[bot]' && env.UI_CHECKS == 'true'
run: >-
npx bundle-analyzer ./dist
--bundle-name=yart
--upload-token=${{ secrets.CODECOV_TOKEN }}
--normalize-assets-pattern="[name]-[hash].js"
# The scanner's JRE and analyzer plugins, which it otherwise downloads on
# every run. ponytail: a fixed key never refreshes, so a plugin Sonar
# updates is fetched each run again; bump the key when that shows in the log.
- name: Cache SonarQube packages
if: github.actor != 'dependabot[bot]' && env.HEAVY_CHECKS == 'true'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
# After the browser suites, so a failing test reports as itself, not as an
# absent coverage report. Skipped for the updater, whose empty token would
# fail the job; a version bump touches no source for the scan to read.
- name: SonarQube scan
if: github.actor != 'dependabot[bot]' && env.HEAVY_CHECKS == 'true'
uses: SonarSource/sonarqube-scan-action@d209202bc7d53ff1cc128f7f907dac145c9d6ae9 # v8.3.0
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# Last, so the deploy gets a bundle that cleared every gate. The `if` carries
# no status function, which keeps the implicit success(). Pushes to main only,
# so a pull request builds and publishes nothing.
- name: Upload Pages artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: dist
# Last, because every step above carries no status function and so inherits
# success(): a Chromatic failure placed earlier would skip the two uploads,
# the scan and the Pages artifact. It reads the archives the end-to-end
# tests already wrote, so it rebuilds and re-runs nothing.
#
# Exits once the build is uploaded rather than waiting out the capture, so
# a slow queue cannot spend the job's budget. The accept gate is
# Chromatic's own pull request check rather than this exit code. The token
# is guarded rather than the actor, because this repository is public and a
# fork pull request gets no secret at all.
#
# The action rather than the CLI, because on a pull request the checkout is
# a merge ref and the CLI files the build against a commit that exists in
# no branch and is later collected. The action reads the head commit off
# the event payload. It carries its own copy of the CLI, so keep this pin
# and the one in package.json on the same version.
- name: Chromatic
if: env.HAS_CHROMATIC == 'true' && env.UI_CHECKS == 'true'
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
uses: chromaui/action@a18e9f57b71eb05f941848c0b927aab2ee644e3e # v18.10.2
with:
playwright: true
exitOnceUploaded: true
# A build on the baseline branch does not accept its own snapshots.
# Without this they stay unreviewed, and a later pull request finds no
# accepted ancestor to compare against, so it re-reports every one.
autoAcceptChanges: main
# Posts the Lighthouse table on the pull request, editing its own comment on
# later runs. A job of its own so the write token never meets `npm ci`. Skipped
# for forks and the updater, whose tokens are read-only.
report:
name: Lighthouse report
needs: verify
if: >-
github.event_name == 'pull_request'
&& github.event.pull_request.head.repo.full_name == github.repository
&& github.actor != 'dependabot[bot]'
&& needs.verify.outputs.ui-checks == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
continue-on-error: true
permissions:
actions: read
pull-requests: write
steps:
- name: Comment Lighthouse scores
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
gh run download "$GITHUB_RUN_ID" --repo "$GITHUB_REPOSITORY" --name lighthouse-reports --dir lighthouse
marker="<!-- lighthouse -->"
body="$(printf '%s\n%s\n\n[Full reports](%s)\n' "$marker" "$(cat lighthouse/summary.md)" "$RUN_URL")"
id="$(gh api --paginate "repos/$GITHUB_REPOSITORY/issues/$PR/comments" \
--jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$marker\"))) | .id" | head -n 1)"
if [ -n "$id" ]; then
gh api -X PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$id" -f body="$body" > /dev/null
else
gh api "repos/$GITHUB_REPOSITORY/issues/$PR/comments" -f body="$body" > /dev/null
fi
# A job rather than its own workflow, because `needs` is what makes the publish
# wait on the gate. The write permissions sit here so the job running untrusted
# pull request code keeps its read-only token.
deploy:
name: Deploy
needs: verify
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
# Publishes the artifact verify uploaded, so the site and the gate cannot
# describe different commits.
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1