Repository navigation
test: keep the manifest and shell theme colors on the brand token #585
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |