Repository navigation
Site: take sharp 0.35.5 (GHSA-wq5f-xc86-pv6w) #816
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: Site | |
| on: | |
| push: | |
| branches: [main] | |
| # Not just site/. The build reads four files from outside it: CHANGELOG.md | |
| # (changelog.astro imports it with ?raw), scripts/install.ps1 and | |
| # scripts/install.sh (served at /install.ps1 and /install.sh), and | |
| # apps/netscli-gui/package.json (site-content/version.ts reads the version | |
| # from it). Keep this list, the `changes` job's grep below, and | |
| # site-preview.yml's two filters in step -- they are the same list three | |
| # times and a media query cannot read a variable here either. | |
| paths: | |
| - 'site/**' | |
| - 'CHANGELOG.md' | |
| - 'scripts/install.ps1' | |
| - 'scripts/install.sh' | |
| - 'apps/netscli-gui/package.json' | |
| # NOTE: deliberately unfiltered, unlike the push trigger above. `Site | |
| # Gate` below is a required status check, and a required check that | |
| # never *reports* leaves a PR stuck on "Expected — waiting for status" | |
| # forever. A `paths:` filter here would do exactly that for every PR | |
| # that does not touch site/. So the workflow always runs and the real | |
| # jobs skip themselves via `changes`. A skipped job satisfies a | |
| # required check; an absent one does not. | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| concurrency: | |
| group: site-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| changes: | |
| name: Detect site changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| site: ${{ steps.filter.outputs.site }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - id: filter | |
| shell: bash | |
| # Through `env:` rather than straight into the script — see the | |
| # equivalent note in ci.yml's `changes` job. | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "${{ github.event_name }}" != "pull_request" ]]; then | |
| echo "site=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| files=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA") | |
| echo "Changed files:" | |
| echo "$files" | sed 's/^/ /' | |
| # Same set as the push trigger's paths list above. | |
| if echo "$files" | grep -qE '^(site/|CHANGELOG\.md$|scripts/install\.(ps1|sh)$|apps/netscli-gui/package\.json$)'; then | |
| echo "site=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "site=false" >> "$GITHUB_OUTPUT" | |
| echo "No site/ changes — site jobs will skip." | |
| fi | |
| maintainability: | |
| name: File size guard | |
| needs: changes | |
| if: needs.changes.outputs.site == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version-file: .nvmrc | |
| - run: node scripts/check-file-size.mjs | |
| # Also runs in ci.yml, deliberately. That job is gated on `code`, which a | |
| # site-only pull request sets to false, so without this the site's own | |
| # contrast could regress with nothing looking at it. The script checks | |
| # both surfaces and takes milliseconds, so running it twice costs nothing | |
| # and neither workflow can be the one that forgot. | |
| - name: Design token contrast | |
| run: node scripts/design-tokens.mjs | |
| # Deleting or renaming a docs page can strand a link inside the desktop | |
| # app, which no site check would otherwise notice. | |
| - name: App doc links | |
| run: node scripts/check-app-doc-links.mjs | |
| # A ratchet on dead CSS, not a target. `css-shadowing.mjs` proves a | |
| # declaration can never affect the page -- same selector, same media | |
| # context, a later rule of equal-or-greater importance. It was written | |
| # for B-23, run once in the pull request that added it, and then never | |
| # again, so the count it had driven down grew back to 210 unnoticed. | |
| # Lower the budget when you prune; it must never rise. | |
| - name: Dead CSS budget | |
| working-directory: site | |
| run: npm run check:css | |
| - name: CSS region guard | |
| working-directory: site | |
| run: npm run check:regions | |
| # Reads the wordmark PNG and checks the token that cancels its | |
| # transparent left margin still matches it. Needs no build and no | |
| # browser: it is here rather than in the build job so a bad re-export | |
| # fails in seconds. See the script for the five drifted values that | |
| # made it worth checking. | |
| - name: Wordmark inset | |
| working-directory: site | |
| run: npm run check:wordmark | |
| build: | |
| name: Build + typecheck | |
| needs: [changes, maintainability] | |
| if: needs.changes.outputs.site == 'true' | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: site | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| # Tags, not just the tip. `check:changelog` asks git which versions | |
| # are actually tagged so it can catch a changelog entry dated before | |
| # its release exists -- and with the default `fetch-depth: 1` there | |
| # are no tags to ask about, so it would report that it could not | |
| # check rather than passing on nothing. | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version-file: .nvmrc | |
| cache: npm | |
| cache-dependency-path: site/package-lock.json | |
| - run: npm ci | |
| - run: npm run build | |
| - run: npm run check | |
| # Reads the built changelog page and compares it against CHANGELOG.md. | |
| # Both halves of this went wrong in one day and neither was visible in | |
| # `astro check` or the a11y pass -- see the header of the script. | |
| - name: Changelog dates | |
| run: npm run check:changelog | |
| # JSON-LD renders as nothing, so a malformed breadcrumb shipped on every | |
| # docs page until Search Console flagged it. See the script's header. | |
| - name: Structured data | |
| run: npm run check:structured-data | |
| # Runs axe against both themes; see site/scripts/a11y.mjs for why | |
| # both are needed, and why it prefers the runner's matched | |
| # chromedriver over the one the npm package downloads. | |
| - name: Accessibility check | |
| run: npm run test:a11y | |
| # Runs alongside axe rather than instead of it, because it covers the | |
| # one thing axe cannot: axe files text over a gradient or a | |
| # pseudo-element under `incomplete`, and `--exit` only fails on | |
| # `violations`. On the landing page that was 48 unchecked nodes reported | |
| # as a clean pass -- including a heading at 1.03:1. This composites the | |
| # ancestor stack itself. See the header of the script. | |
| - name: Rendered contrast sweep | |
| run: npm run check:contrast | |
| # Where the docs header's controls sit, at seventeen widths. | |
| # | |
| # Two failures shipped that nothing above could see, because both were | |
| # about position rather than markup, colour or speed: the search control | |
| # stranded beside the wordmark with up to 861px of empty bar beside it | |
| # from 901px to 1152px, and no theme control reachable anywhere between | |
| # 800px and 1152px. Both came from a breakpoint moving in one file while | |
| # the rule depending on it stayed put in another. See the header of the | |
| # script. | |
| - name: Docs header controls | |
| run: npm run check:header | |
| # Lighthouse across every built route, with a floor per category. | |
| # | |
| # Catches the class of regression the checks above cannot see at all: a | |
| # hero image shipped without dimensions, a render-blocking script, a | |
| # heading level skipped inside generated markup. The last of those was | |
| # live on the changelog page and no existing gate reported it -- axe | |
| # passes it, and the contrast sweep only reads colour. | |
| # | |
| # The performance floor sits at 85 rather than near the 100 the site | |
| # scores, because it is the one number here that moves with how busy the | |
| # machine is: the same three pages measured 91-93 under load and 100 | |
| # idle, on identical code. See the floors in the script for the numbers. | |
| - name: Lighthouse | |
| run: npm run check:lighthouse | |
| # ─── Required status check ──────────────────────────────────────────── | |
| # The only job here that should be marked required in branch protection. | |
| # See the equivalent note in ci.yml. | |
| site-gate: | |
| name: Site Gate | |
| if: always() | |
| needs: | |
| - changes | |
| - maintainability | |
| - build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Verify no upstream job failed | |
| shell: bash | |
| env: | |
| NEEDS: ${{ toJSON(needs) }} | |
| run: | | |
| set -euo pipefail | |
| echo "$NEEDS" | jq -r 'to_entries[] | " \(.key): \(.value.result)"' | |
| # Allowlist success/skipped rather than denylisting failure/ | |
| # cancelled — see the equivalent note in ci.yml. A job whose runner | |
| # never starts reports "abandoned", which matched neither arm and | |
| # let the required check pass over a failed job. | |
| bad=$(echo "$NEEDS" | jq -r ' | |
| to_entries | |
| | map(select(.value.result != "success" and .value.result != "skipped")) | |
| | map("\(.key)=\(.value.result)") | |
| | join(", ") | |
| ') | |
| if [[ -n "$bad" ]]; then | |
| echo "::error::Site Gate failed — these jobs did not succeed: ${bad}" | |
| exit 1 | |
| fi | |
| echo "Site Gate passed (all jobs succeeded or were legitimately skipped)." |