Skip to content

Phase 3 - Code Examples

biome.json
{
"$schema": "https://biomejs.dev/schemas/2.4.9/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"includes": [
"src/**/*.{astro,ts,tsx,js,jsx,md,mdx}",
"docs/**/*.{md,mdx}",
"scripts/**/*.{ts,js,mjs}",
"*.{md,mdx,ts,js,mjs}"
]
},
"formatter": {
"enabled": true,
"formatWithErrors": true,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 100,
"attributePosition": "auto"
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "all",
"semicolons": "always",
"arrowParentheses": "always",
"bracketSpacing": true,
"bracketSameLine": false,
"quoteStyle": "double"
}
},
"css": {
"formatter": {
"enabled": true
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noBannedTypes": "error",
"noUselessTypeConstraint": "error"
},
"correctness": {
"noUnusedVariables": "error",
"useExhaustiveDependencies": "error"
},
"suspicious": {
"noExplicitAny": "error",
"noImplicitAnyLet": "error"
},
"a11y": {
"recommended": true
},
"style": {
"noNonNullAssertion": "warn",
"useAsConstAssertion": "warn",
"useBlockStatements": "warn",
"noParameterAssign": "warn",
"useDefaultParameterLast": "warn",
"useEnumInitializers": "warn",
"useSelfClosingElements": "warn",
"useSingleVarDeclarator": "warn",
"useNumberNamespace": "warn",
"noInferrableTypes": "warn",
"noUselessElse": "warn"
}
}
}
}

The project config also defines a useNamingConvention rule and an overrides array (per-glob rules for Markdown, scripts, Astro, CSS, tests, and config files). See the repo's biome.json for the authoritative, complete configuration.

.commitlintrc.cjs
module.exports = {
extends: ["@commitlint/config-conventional"],
};

The project relies on the conventional preset's defaults — it already enforces the standard type set (feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert), lower-case subjects, and a 100-character header limit. No custom rules block is needed.

Installation:

Terminal window
pnpm add -D @commitlint/cli @commitlint/config-conventional
Terminal window
# Install Husky
pnpm add -D husky lint-staged
pnpm exec husky init
# Create pre-commit hook
echo 'pnpm exec lint-staged' > .husky/pre-commit

.husky/pre-commit:

Terminal window
pnpm exec lint-staged

.husky/commit-msg:

Terminal window
pnpm exec commitlint --edit "$1"

.husky/pre-push:

#!/usr/bin/env sh
# Run the unit test suite before pushing so failures surface locally
# instead of in CI. Skip with --no-verify if you have a deliberate reason
# (e.g. pushing WIP for backup).
pnpm run test:unit

Husky 9 hook files are plain shell commands — the husky.sh sourcing boilerplate from Husky 8 is gone.

// package.json — everyday scripts (the ones a cloner actually runs).
// See package.json for the full maintainer & advanced set (ADR-052 taxonomy).
{
"scripts": {
"predev": "pnpm run tokens:build",
"dev": "astro dev",
"dev:host": "astro dev --host",
"dev:debug": "astro dev --verbose",
"build": "pnpm run env:validate && pnpm run tokens:build && astro build",
"build:ci": "pnpm run env:validate && pnpm run tokens:build && astro build --verbose",
"preview": "astro preview",
"preview:build": "pnpm run build && astro preview",
"tokens:build": "tsx scripts/src/build-tokens.ts",
"format": "biome format . --write",
"format:check": "biome format .",
"lint": "biome check .",
"lint:md": "markdownlint-cli2 \"**/*.md\" \"**/*.mdx\"",
"lint:md:fix": "markdownlint-cli2 \"**/*.md\" \"**/*.mdx\" --fix",
"check": "SITE_URL=${SITE_URL:-http://localhost:4321} astro check",
"check:types": "tsc --noEmit",
"quality": "pnpm run format && pnpm run lint && pnpm run lint:md && pnpm run check",
"quality:ci": "pnpm run format:check && pnpm run lint && pnpm run lint:md && pnpm run check && pnpm run test:unit && pnpm run agents:check && pnpm run version:check && pnpm run og:check && pnpm run docs:count",
"test": "SITE_URL=http://localhost:4321 vitest",
"test:unit": "SITE_URL=http://localhost:4321 vitest run",
"test:coverage": "SITE_URL=http://localhost:4321 vitest run --coverage",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:a11y": "playwright test --grep=\"@a11y\"",
"clean": "rm -rf dist .astro tokens/dist",
"clean:all": "rm -rf dist .astro tokens/dist node_modules/.cache",
"prepare": "husky"
}
}

Note on Local vs. CI Scripts: The quality script is designed for fast local checks (it formats in place). The quality:ci script is the CI variant: format:check, lint, lint:md, check, test:unit, plus the repo’s consistency gates (agents:check, version:check, og:check, docs:count). End-to-end tests are not part of quality:ci — CI runs Playwright as a separate step in ci.yml.

// package.json - lint-staged configuration
{
"lint-staged": {
"*.{astro,ts,tsx,js,jsx,json,yml,yaml}": "biome check --write --no-errors-on-unmatched",
"*.{md,mdx}": "markdownlint-cli2 --fix"
}
}

This is the starter’s actual workflow. A single build-test job runs the full quality gate (quality:ci), unit tests with coverage, budget and contrast validation, the build, the JS/image/font budget gates, Playwright e2e, and the dependency/SBOM security scans; link-check, semgrep, and gitleaks run as separate jobs.

.github/workflows/ci.yml
name: CI
on:
push:
branches: [master, develop]
pull_request:
branches: [master, develop]
permissions:
contents: read
security-events: write
jobs:
build-test:
runs-on: ubuntu-latest
env:
SITE_URL: https://${{ github.repository_owner }}.github.io
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: '.nvmrc'
- name: Setup PNPM
uses: pnpm/action-setup@v6
with:
run_install: false
- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
id: pnpm-cache
- name: Cache pnpm store
uses: actions/cache@v6
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: ${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint, format & type-check
run: pnpm run quality:ci
- name: Unit tests with coverage
run: pnpm run test:coverage
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v7
with:
name: coverage-report
path: coverage/
retention-days: 14
if-no-files-found: ignore
- name: Validate budget overrides
run: pnpm run budgets:validate
- name: Validate semantic color contrast
run: pnpm run design:validate
- name: Build site
run: pnpm run build
- name: Enforce JS bundle size budget
shell: bash
run: |
set -e
JS_BUNDLE_PATH="dist/_astro"
JS_SIZE_LIMIT_BYTES=163840 # 160 KB raw JS
if [ ! -d "$JS_BUNDLE_PATH" ]; then
echo "JS bundle path not found. Skipping size check."
exit 0
fi
JS_SIZE=$(find "$JS_BUNDLE_PATH" -name "*.js" -type f -exec stat -c%s {} + | awk '{sum+=$1} END {print sum}')
JS_SIZE=${JS_SIZE:-0}
echo "Total raw JS size: $JS_SIZE bytes (limit: $JS_SIZE_LIMIT_BYTES)"
if [ "$JS_SIZE" -gt "$JS_SIZE_LIMIT_BYTES" ]; then
echo "::error::JavaScript bundle size ($JS_SIZE bytes) exceeds limit ($JS_SIZE_LIMIT_BYTES bytes)"
exit 1
fi
- name: Enforce per-image size budget — source (ADR-057)
run: pnpm run images:gate
- name: Enforce per-image size budget — build output (ADR-057)
# Catches oversized emitted raster, e.g. heavyweight PNG fallbacks
# generated alongside AVIF/WebP.
run: IMAGE_GATE_ROOTS=dist pnpm run images:gate
- name: Enforce font preload budget (ADR-058)
run: pnpm run fonts:gate
- name: Install Playwright browsers
run: pnpm exec playwright install --with-deps chromium
- name: Run E2E tests (Chromium)
run: pnpm exec playwright test --project=chromium
- name: Security audit (high severity)
run: pnpm run audit:ci
- name: Trivy SBOM scan
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: 'fs'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload security results
if: success() && (hashFiles('trivy-results.sarif') != '')
uses: github/codeql-action/upload-sarif@v4
continue-on-error: true
with:
sarif_file: 'trivy-results.sarif'
link-check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Check markdown links
uses: gaurav-nelson/github-action-markdown-link-check@v1
continue-on-error: true
with:
use-quiet-mode: 'yes'
use-verbose-mode: 'no'
config-file: '.markdown-link-check.json'
folder-path: 'docs/'
file-extension: '.md'
# SAST + secret scanning (ADR-046). Both are halt-on-violation gates (ADR-039):
# the scan command exits non-zero on a finding and fails the build.
# Run in their official containers — Semgrep's docs mandate the semgrep/semgrep
# image, and the gitleaks CLI avoids the gitleaks-action org-license requirement.
semgrep:
name: Semgrep SAST
runs-on: ubuntu-latest
# Dependabot PRs only touch manifests and run with a restricted token; skip.
if: github.actor != 'dependabot[bot]'
container:
image: semgrep/semgrep
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Semgrep scan
# --error exits 1 on findings; --config p/* pulls public registry rulesets.
run: semgrep scan --config p/javascript --config p/typescript --config p/secrets --error
gitleaks:
name: Secret scan
runs-on: ubuntu-latest
if: github.actor != 'dependabot[bot]'
container:
image: ghcr.io/gitleaks/gitleaks:v8.30.1
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Gitleaks scan
# `dir` scans the working tree; --exit-code 1 fails the build on any leak.
run: gitleaks dir . --config .gitleaks.toml --exit-code 1 --verbose

Implementing branch protection ensures that your master branch always stays in a releasable state. The rules below apply to both MVP and Showcase tracks.

  1. Create a protection rule

    • Navigate to Repository → Settings → Branches → Branch protection rules.
    • Click Add rule and set Branch name pattern to master (or your default branch).
  2. Require status checks to pass

    • Enable Require status checks to pass before merging.
    • Select the checks created by the CI workflow above (context names follow <workflow> / <job name>; jobs without a name: key surface under their job id):
      • CI / build-test – quality gate, unit tests, budgets, build, e2e, security audit
      • CI / link-check – markdown link check
      • CI / Semgrep SAST and CI / Secret scan – security gates
    • Keep Require branches to be up to date before merging enabled to prevent stale merges.
  3. Additional recommended settings

    • Require a pull-request review before merging1 approving review.
    • Dismiss stale pull request approvals when new commits are pushed.
    • Require linear history to avoid merge commits.
    • Include administrators so that rules apply to everyone.
  4. Automating with gh CLI (optional)

    The same rule can be applied programmatically:

    Terminal window
    gh api \
    --method PUT \
    -H "Accept: application/vnd.github+json" \
    /repos/:owner/:repo/branches/master/protection \
    -F required_status_checks.strict=true \
    -F required_status_checks.contexts[]='CI / build-test' \
    -F required_status_checks.contexts[]='CI / link-check' \
    -F required_status_checks.contexts[]='CI / Semgrep SAST' \
    -F required_status_checks.contexts[]='CI / Secret scan' \
    -F enforce_admins=true \
    -F required_pull_request_reviews.dismiss_stale_reviews=true \
    -F required_pull_request_reviews.required_approving_review_count=1 \
    -F restrictions=null

Once the rule is in place, any pull request that does not pass the CI workflow will be blocked from merging, completing step 3.10 of this phase.

The starter does not ship a .vscode/ directory — editor configuration is left to each developer. If you want format-on-save with Biome in VSCode, a suggested settings.json:

// .vscode/settings.json (not shipped with the starter — create it yourself if wanted)
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "biomejs.biome",
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"[astro]": {
"editor.defaultFormatter": "astro-build.astro-vscode"
},
"files.associations": {
"*.css": "tailwindcss"
},
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"tailwindCSS.experimental.classRegex": [
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["clsx\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
]
}
# Contributing Guide
## Development Setup
1. Install dependencies:
```bash
pnpm install
```
2. Build design tokens:
```bash
pnpm run tokens:build # Note: You'll need to create the './scripts/src/build-tokens.ts' file as part of implementing the design token system (Phase 2).
```
3. Start development server:
```bash
pnpm run dev
```