SiteAssets SiteAssets
Resources CI

Brand audits in CI.

Gate releases on brand quality: audit every deploy preview for OG, favicon, manifest and SVG issues before it ships.

§01

How it works

  1. Deploy preview — your platform builds a shareable preview URL.
  2. Run brand audit — CI calls POST /api/v1/brand-audit with the preview URL.
  3. Compare threshold — score below 80 or any error finding fails the gate (strategy dependent).
text
deploy → audit → score < 80 or errors?
                          ├─ pass        → release
                          └─ fail        → report & block (strict only)
§02

Strategies

strict

Critical issues or a score under threshold fail the pipeline and block the release.

warn

Adds a warning annotation but never fails the build. Good first step.

report-only

Prints the report into the job log and job summary. Zero pipeline impact.

Important: an audit service timeout or 5xx never fails the pipeline — that would mislabel healthy sites. A 422 audit_failed response means the target site itself could not be fetched and counts as a real finding in strict mode.

§03

The audit script

One small script, shared by all three stacks. Store your API key as the SITEASSETS_API_KEY repository secret — never commit it.

js
// scripts/brand-audit.mjs
// Usage: node scripts/brand-audit.mjs <preview-url> [strict|warn|report-only]
const STRATEGY = process.argv[3] ?? 'warn';
const MIN_SCORE = 80;

const res = await fetch('https://siteassets.dev/api/v1/brand-audit', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.SITEASSETS_API_KEY}`,
  },
  body: JSON.stringify({ url: process.argv[2] }),
});

// Service unavailable (5xx / network) must NOT fail the pipeline by default.
if (res.status >= 500) {
  console.warn('::warning::SiteAssets audit service unavailable — skipping gate.');
  process.exit(0);
}

const report = await res.json();

// 422 audit_failed = the TARGET SITE could not be fetched. That is a real
// finding (broken preview URL), so treat it as a failure in strict mode.
if (res.status === 422) {
  console.error(`Audit failed: ${report.error.message} (${report.error.kind})`);
  process.exit(STRATEGY === 'strict' ? 1 : 0);
}

if (!res.ok) {
  console.error(`Audit request error: ${report.error?.code} ${report.error?.message}`);
  process.exit(0); // auth/quota problems should not block unrelated deploys
}

console.log(`Brand score: ${report.score}/100`);
for (const f of report.findings.filter((f) => f.severity === 'error')) {
  console.log(`::error::[${f.category}] ${f.title}`);
  for (const e of f.evidence ?? []) console.log(`   — ${e}`);
}
console.log(`Full report: https://siteassets.dev/brand-asset-audit`);

// Gate: strict fails on score below threshold; warn only annotates.
const errors = report.findings.filter((f) => f.severity === 'error').length;
if (STRATEGY === 'strict' && (report.score < MIN_SCORE || errors > 0)) {
  console.error(`::error::Brand audit failed (${report.score}/100, ${errors} errors).`);
  process.exit(1);
}
if (STRATEGY === 'warn' && (report.score < MIN_SCORE || errors > 0)) {
  console.warn('::warning::Brand audit below threshold — see logs.');
}
process.exit(0);
§04

Workflow files

Astro

yaml
# .github/workflows/brand-audit.yml
name: Brand audit
on:
  deployment_status:

jobs:
  brand-audit:
    if: github.event.deployment_status.state == 'success'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - name: Run SiteAssets brand audit
        env:
          SITEASSETS_API_KEY: ${{ secrets.SITEASSETS_API_KEY }}
        run: node scripts/brand-audit.mjs "${{ github.event.deployment.payload.url }}" strict

Next.js

yaml
# .github/workflows/brand-audit.yml  (Next.js on Vercel)
name: Brand audit
on: deployment_status

jobs:
  brand-audit:
    if: github.event.deployment_status.state == 'success'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - name: Audit deploy preview
        env:
          SITEASSETS_API_KEY: ${{ secrets.SITEASSETS_API_KEY }}
          # Vercel exposes the preview URL on the deployment event:
        run: node scripts/brand-audit.mjs "${{ github.event.deployment.payload.url }}" warn

Vite

yaml
# .github/workflows/brand-audit.yml  (Vite + Netlify)
name: Brand audit
on:
  workflow_run:
    workflows: ["Deploy preview"]
    types: [completed]

jobs:
  brand-audit:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - name: Audit preview URL
        env:
          SITEASSETS_API_KEY: ${{ secrets.SITEASSETS_API_KEY }}
        run: node scripts/brand-audit.mjs "${{ vars.PREVIEW_URL }}" report-only