diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ca79ca5b..8b2c4c07 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,3 +4,8 @@ updates: directory: / schedule: interval: weekly + + - package-ecosystem: npm + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a949f581..86d3116c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,16 @@ on: branches: [main] pull_request: +# Least-privilege default for every job; widen per-job only when needed. +permissions: + contents: read + +# Cancel superseded runs on the same ref (e.g. rapid pushes to a PR) so CI +# only spends minutes on the newest commit. Pushes to main run to completion. +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: lint: name: Lint CSS @@ -20,6 +30,29 @@ jobs: - run: npm ci - run: npm run lint:css + commitlint: + name: Lint commit messages + runs-on: ubuntu-latest + # Commit-message linting only makes sense against a PR's commit range. + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + # Full history so commitlint can walk base..head. + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22 + cache: npm + - run: npm ci + - name: Validate PR commits against Conventional Commits + run: >- + npx --no -- commitlint + --from ${{ github.event.pull_request.base.sha }} + --to ${{ github.event.pull_request.head.sha }} + --verbose + build: name: Build bundle runs-on: ubuntu-latest @@ -56,6 +89,22 @@ jobs: - run: node scripts/check-artifacts.js --check - run: node scripts/check-version-sync.js + dependency-audit: + name: Dependency vulnerability audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22 + cache: npm + - run: npm ci + # Fail only on high/critical advisories so routine low-severity noise + # doesn't block merges. Tune the threshold as the project matures. + - run: npm audit --audit-level=high + test: name: Regression tests runs-on: ubuntu-latest @@ -68,6 +117,7 @@ jobs: node-version: 22 cache: npm - run: npm ci - - run: npm run build + # `npm test` runs the `pretest` hook (build + unit tests) before Playwright, + # so an explicit build step here would just build twice. - run: npx playwright install --with-deps chromium firefox webkit - run: npm test diff --git a/.github/workflows/publish-dist.yml b/.github/workflows/publish-dist.yml index da0a6df0..7332d2af 100644 --- a/.github/workflows/publish-dist.yml +++ b/.github/workflows/publish-dist.yml @@ -45,11 +45,13 @@ jobs: # credentials. We re-authenticate explicitly at push time below. persist-credentials: false + # No dependency cache on the publish path: a poisoned cache could be + # baked into the dist bundles served from the CDN. Fetch fresh from the + # registry with integrity verification (zizmor: cache-poisoning). - name: Setup Node uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 - cache: npm - name: Install dependencies run: npm ci diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 244967a1..e0f18cf5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,13 +21,15 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - uses: actions/setup-node@v6 + # No dependency cache on the release path: a poisoned cache could be + # baked into the published release artifacts. Fetch fresh from the + # registry with integrity verification (zizmor: cache-poisoning). + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 - cache: npm - run: npm ci - name: Sync version constants to tag env: @@ -93,7 +95,7 @@ jobs: echo "notes<> "$GITHUB_OUTPUT" echo "$NOTES" >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" - - uses: softprops/action-gh-release@v3 + - uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3 with: tag_name: ${{ steps.version.outputs.tag }} body: ${{ steps.changelog.outputs.notes }} @@ -151,12 +153,12 @@ jobs: contents: write steps: - name: Checkout main - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: main fetch-depth: 1 persist-credentials: false - - uses: actions/setup-node@v6 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 - name: Determine version