# DS Consumer Validation — Template Script

Copy this script into your own project as `scripts/validate-ds-consumer.js` and run it with `node scripts/validate-ds-consumer.js` before opening a PR.

It checks that your project uses BPL DS correctly (rules E1–E3 from [CfRs.md](https://ds.bepartnerlabs.com/CfRs.md)).

**Adapt `CSS_DIRS` and `TEMPLATE_DIRS` to your project's file layout.**

---

```js
#!/usr/bin/env node
import { readdirSync, readFileSync } from 'fs'
import { join, relative } from 'path'

const ROOT = process.cwd()
// Adapt these paths to your project
const CSS_DIRS = ['src/styles', 'src/assets/css']
const TEMPLATE_DIRS = ['src', 'templates', 'views']

let violations = 0

function report(rule, file, line, detail) {
  console.error(`[${rule}] ${relative(ROOT, file)}:${line} — ${detail}`)
  violations++
}

function readLines(file) {
  return readFileSync(file, 'utf8').split('\n')
}

function walk(dir, ext) {
  let files = []
  try {
    for (const entry of readdirSync(dir, { withFileTypes: true })) {
      const full = join(dir, entry.name)
      if (entry.isDirectory()) files = files.concat(walk(full, ext))
      else if (entry.name.endsWith(ext)) files.push(full)
    }
  } catch {}
  return files
}

// E1 — never modify .bp-* classes directly
function checkE1(file, lines) {
  for (let i = 0; i < lines.length; i++) {
    if (/^\s*\.bp-[a-z]/.test(lines[i]) && lines[i].includes('{')) {
      report('E1', file, i + 1, `.bp-* class modified directly — use a wrapper selector instead: ${lines[i].trim()}`)
    }
  }
}

// E2 — no Level 2 tokens on :root
function checkE2(file, lines) {
  let inRoot = false
  for (let i = 0; i < lines.length; i++) {
    if (/^:root\s*\{/.test(lines[i].trim())) inRoot = true
    if (inRoot && /--[a-z]+-[a-z]/.test(lines[i]) && !/--bp-/.test(lines[i])) {
      report('E2', file, i + 1, `Component token set on :root — use a scoped selector: ${lines[i].trim()}`)
    }
    if (inRoot && lines[i].includes('}')) inRoot = false
  }
}

// E3 — no style="" in templates
function checkE3(file, lines) {
  for (let i = 0; i < lines.length; i++) {
    if (/style="/.test(lines[i])) {
      report('E3', file, i + 1, `Inline style="" — use BEM modifier or component token: ${lines[i].trim()}`)
    }
  }
}

for (const dir of CSS_DIRS) {
  for (const file of walk(join(ROOT, dir), '.css')) {
    const lines = readLines(file)
    checkE1(file, lines)
    checkE2(file, lines)
  }
}

for (const dir of TEMPLATE_DIRS) {
  for (const ext of ['.html', '.njk', '.hbs', '.vue', '.jsx', '.tsx', '.mdx']) {
    for (const file of walk(join(ROOT, dir), ext)) {
      const lines = readLines(file)
      checkE3(file, lines)
    }
  }
}

if (violations === 0) {
  console.log('✓ validate-ds-consumer: all checks passed')
  process.exit(0)
} else {
  console.error(`\n✗ validate-ds-consumer: ${violations} violation(s) found`)
  process.exit(1)
}
```

---

### E4 — Manual check: import DS CSS before your own styles

This rule cannot be automated via grep — verify it manually when setting up your project.

**Correct import order:**
```css
@import "@be-partner-labs/ds"; /* first */
@import "./my-styles.css";     /* after */
```

Reversing the order causes the DS's `@layer` declarations to land after your styles, breaking the cascade.

---

## Visual Regression (Recommended for consumers)

CSS snapshots protect the DS internals. For your own project, add visual regression to catch how a DS version bump affects your rendered UI.

**Recommended tool:** [Playwright](https://playwright.dev/) with `toHaveScreenshot()`.

**Pattern:**

```js
// tests/visual/button.spec.js
import { test, expect } from '@playwright/test'

test('primary button matches snapshot', async ({ page }) => {
  await page.goto('/components/button')
  const btn = page.locator('.bp-btn')
  await expect(btn).toHaveScreenshot('bp-btn-primary.png')
})
```

Run `npx playwright test --update-snapshots` after an intentional DS version bump to update your baselines.

**What to snapshot:** only components where your project applies token overrides. A component with no overrides is already covered by the DS's own CSS snapshots.
