CI integration
Integrate preset AI into your CI/CD pipeline to enforce design system consistency automatically.
Overview
preset AI provides three ways to integrate with your CI pipeline:
- GitHub Action: one-line setup for GitHub repos
- CLI commands: direct CLI usage in any CI system
- SARIF output: GitHub Code Scanning integration
GitHub Action
The easiest way to add preset AI to your workflow.
Quick setup
Create .github/workflows/preset.yml:
name: Design System Check
on:
pull_request:
branches: [main]
permissions:
contents: read
pull-requests: write # post the results comment
id-token: write # mint the per-run OIDC token (recommended auth)
jobs:
preset:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check design system
uses: preset-ai/compliance-check@v1
with:
design-system-id: ${{ secrets.PRESET_DESIGN_SYSTEM_ID }}
What it does
On every pull request, the action validates the changed files against your design system and:
- Flags violations: native elements where a preset exists, hardcoded colors, arbitrary spacing, forbidden primitives, and token misuse
- Annotates the diff and posts a PR comment: results land inline and in a single updating comment
- Fails on errors: blocks merge when errors are found (configurable; warnings do not fail by default)
It checks changed .tsx, .jsx, .ts, .vue, and .svelte files by default. There is no command to choose: the action always runs design-system validation. (The drift, scan, and audit-copy workflows below are CLI commands, for non-GitHub CI.)
Authentication
The recommended path is keyless OIDC: install the Preset GitHub App (binding it to your design system during install) and set permissions: id-token: write. The action mints a fresh OIDC token per run, scoped to your repo, so there is no secret to rotate. If your runner cannot grant id-token: write, fall back to an API key: preset-api-key: ${{ secrets.PRESET_API_KEY }}.
Configuration options
- uses: preset-ai/compliance-check@v1
with:
design-system-id: ${{ secrets.PRESET_DESIGN_SYSTEM_ID }}
# Full DB-backed validation (optional; scopes the design system)
supabase-url: ${{ secrets.PRESET_SUPABASE_URL }}
supabase-key: ${{ secrets.PRESET_SUPABASE_ANON_KEY }}
# Which files to check (defaults shown)
include: '**/*.tsx,**/*.jsx,**/*.ts,**/*.vue,**/*.svelte'
exclude: '**/node_modules/**'
# Gating
fail-on-errors: 'true' # default
fail-on-warnings: 'false' # default
score-threshold: '0' # min health score to pass (0 = off)
regression-threshold: '0' # max score drop vs the last snapshot (0 = off)
# Surfaces
comment-on-pr: 'true' # default
upload-sarif: 'false' # GitHub Code Scanning
# API-key fallback (only if not using the OIDC App)
preset-api-key: ${{ secrets.PRESET_API_KEY }}
CLI integration
For non-GitHub CI systems or custom pipelines.
Installation
curl -fsSL https://presetai.dev/install.sh | sh
Commands
Drift detection
# Table output (human-readable)
preset drift src
# JSON output (for parsing)
preset drift src --format json --output drift.json
# SARIF output (for security tools)
preset drift src --format sarif --output drift.sarif
# With custom presets
preset drift src --presets ./my-presets.json
# Strict mode (exit 1 on any drift)
preset drift src --strict
Pattern scanning
# Find patterns in codebase
preset scan src
# JSON output with compliance score
preset scan src --format json --output scan.json
# Adjust threshold
preset scan src --threshold 5
Copy audit
# Check terminology
preset audit-copy src --config terminology.json
# SARIF output
preset audit-copy src --format sarif --output copy.sarif
# Strict mode
preset audit-copy src --strict
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success, no violations |
| 1 | Violations found (with --strict) or error |
JSON output structure
Drift report
{
"driftScore": 25,
"hasDrift": true,
"summary": {
"totalComponents": 156,
"usingPresets": 142,
"withDrift": 8,
"unknown": 6
},
"instances": [
{
"file": "src/components/Button.tsx",
"line": 15,
"column": 5,
"component": "Button",
"severity": "warning",
"driftType": "prop_override",
"actualProps": { "variant": "primary" },
"expectedPreset": "primary-action",
"expectedProps": { "variant": "default" },
"suggestedFix": "Use preset 'primary-action' instead"
}
]
}
Scan report
{
"meta": {
"fileCount": 42,
"duration": 1234
},
"summary": {
"filesScanned": 42,
"totalUsages": 156,
"patternsDetected": 12,
"proposalsGenerated": 8
},
"score": 85,
"proposals": [
{
"name": "primary-button",
"component": "Button",
"props": { "variant": "default" },
"usageCount": 24,
"confidence": 0.92
}
]
}
SARIF integration
SARIF (Static Analysis Results Interchange Format) enables integration with GitHub Code Scanning and other security tools.
GitHub Code Scanning
Upload SARIF results to see violations in the Security tab:
- name: Run preset AI drift
run: preset drift src --format sarif --output results.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
category: preset-drift
SARIF structure
preset AI generates SARIF 2.1.0 compliant output:
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [{
"tool": {
"driver": {
"name": "preset-drift",
"rules": [
{
"id": "drift/prop-override",
"name": "PropOverride",
"shortDescription": { "text": "Component props override preset" }
}
]
}
},
"results": [
{
"ruleId": "drift/prop-override",
"level": "warning",
"message": { "text": "Use preset 'primary-action' instead" },
"locations": [{
"physicalLocation": {
"artifactLocation": { "uri": "src/Button.tsx" },
"region": { "startLine": 15, "startColumn": 5 }
}
}]
}
]
}]
}
Example CI configurations
GitHub Actions (full)
name: Design System
on:
pull_request:
branches: [main]
push:
branches: [main]
permissions:
contents: read
pull-requests: write # post the results comment
id-token: write # mint the per-run OIDC token (recommended auth)
security-events: write # upload SARIF to Code Scanning
jobs:
preset:
name: Design system check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: preset-ai/compliance-check@v1
id: preset
with:
design-system-id: ${{ secrets.PRESET_DESIGN_SYSTEM_ID }}
upload-sarif: 'true'
- name: Report score
if: always()
run: |
echo "Design system score: ${{ steps.preset.outputs.health-score }}/100 (${{ steps.preset.outputs.health-grade }})"
To also run drift detection, terminology, or pattern analysis in CI, add CLI steps (see CLI integration above): preset drift src --strict and preset audit-copy src.
GitLab CI
preset-drift:
stage: test
image: node:20
script:
- curl -fsSL https://presetai.dev/install.sh | sh
- preset drift src --format json --output drift.json --strict
artifacts:
reports:
dotenv: drift.json
when: always
preset-copy:
stage: test
image: node:20
script:
- curl -fsSL https://presetai.dev/install.sh | sh
- preset audit-copy src --config terminology.json --strict
CircleCI
version: 2.1
jobs:
preset-check:
docker:
- image: cimg/node:20.0
steps:
- checkout
- run:
name: Install preset AI CLI
command: curl -fsSL https://presetai.dev/install.sh | sh
- run:
name: Run drift check
command: preset drift src --format sarif --output results.sarif --strict
- store_artifacts:
path: results.sarif
workflows:
main:
jobs:
- preset-check
Jenkins
pipeline {
agent { docker { image 'node:20' } }
stages {
stage('Design system check') {
steps {
sh 'curl -fsSL https://presetai.dev/install.sh | sh'
sh 'preset drift src --format json --output drift.json'
}
}
}
post {
always {
archiveArtifacts artifacts: 'drift.json'
}
}
}
Best practices
Start with warnings, graduate to errors
Begin with fail-on-errors: false to see the scope of issues without blocking merges, then tighten:
# Phase 1: visibility (the comment posts; nothing fails the build)
fail-on-errors: 'false'
comment-on-pr: 'true'
# Phase 2: enforcement
fail-on-errors: 'true'
Use SARIF for tracking
Upload SARIF to track trends over time in GitHub's Security tab.
Run on push to main
Run the check on push to main (not just PRs) so each merge records a health snapshot. That snapshot is the baseline regression-threshold and fail-on-new-only compare against on later PRs:
on:
push:
branches: [main]
jobs:
preset:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: preset-ai/compliance-check@v1
with:
design-system-id: ${{ secrets.PRESET_DESIGN_SYSTEM_ID }}
fail-on-errors: 'false' # observe on main; do not block the push
Version-control your presets and terminology
The CLI drift and audit-copy commands read local config you keep in the repo:
project/
├── src/
├── design-system/
│ ├── presets.json # passed to `preset drift --presets`
│ └── terminology.json # passed to `preset audit-copy --config`
└── .github/
└── workflows/
└── preset.yml
Staged file checks
For pre-commit hooks, use the --staged flag:
preset scan --staged --format json
Troubleshooting
"No presets found"
The drift command uses default presets if none are provided. To point it at your own:
preset drift src --presets ./path/to/presets.json
"SARIF upload failed"
Ensure your workflow has the required permission:
permissions:
security-events: write
"PR comment not appearing"
Check that:
- Event is
pull_request(notpush) - Token has
pull-requests: writepermission comment-on-pris'true'(string, not boolean)
"CLI command not found"
The preset binary is not on your PATH. Install it:
curl -fsSL https://presetai.dev/install.sh | sh
If the installer placed it in ~/.local/bin, add that directory to your PATH.