SPB Git

spb/spbgit Public MIT

SPB Git — the platform hosting itself

JavaScript 73.9% CSS 11.7% Nunjucks 11.6% Shell 2.7%
3.2 KB · 98 lines javascript
Raw Blame History
1/**2 * ─────────────────────────────────────────────3 *  SPB Git — Personal Git Platform4 * ─────────────────────────────────────────────5 *  Author  : Simon-Pierre Boucher6 *  Contact : contact@spboucher.ai7 *  File    : scripts/check-headers.mjs8 *  Purpose : CI gate — fail if any tracked file lacks the author header9 *  License : MIT © Simon-Pierre Boucher10 * ─────────────────────────────────────────────11 */1213import { execFileSync } from 'node:child_process';14import { readFileSync, readdirSync, statSync } from 'node:fs';15import { extname, join, relative } from 'node:path';16import process from 'node:process';1718const ROOT = new URL('..', import.meta.url).pathname;1920/** Files that structurally cannot (or should not) carry a comment header. */21const EXEMPT_PATTERNS = [22  /(^|\/)package(-lock)?\.json$/,23  /\.json$/,24  /\.jsonl$/,25  /\.lock$/,26  /(^|\/)LICENSE$/,27  /\.(woff2?|ttf|otf|eot|png|jpe?g|gif|ico|webp|zip|gz|zst|pdf)$/i,28  /(^|\/)\.DS_Store$/,29  /(^|\/)\.env$/,30  /(^|\/)node_modules\//,31  /(^|\/)dev\//,32  /(^|\/)coverage\//,33  /(^|\/)dist\//,34  /(^|\/)logs\//,35  /(^|\/)\.git\//,36  /(^|\/)\.claude\//,37  /(^|\/)src\/web\/assets\/vendor\//,38];3940const REQUIRED_MARKERS = ['SPB Git', 'Simon-Pierre Boucher', 'contact@spboucher.ai'];41const HEAD_LINES = 40;4243/** @returns {string[]} repo-relative paths of files to check */44function listFiles() {45  try {46    const out = execFileSync('git', ['ls-files'], { cwd: ROOT, encoding: 'utf8' });47    const files = out.split('\n').filter(Boolean);48    if (files.length > 0) return files;49  } catch {50    /* not a git repo yet — fall through to fs walk */51  }52  const acc = [];53  const walk = (dir) => {54    for (const entry of readdirSync(dir)) {55      const full = join(dir, entry);56      const rel = relative(ROOT, full);57      if (EXEMPT_PATTERNS.some((re) => re.test(rel + (statSync(full).isDirectory() ? '/' : '')))) continue;58      if (statSync(full).isDirectory()) walk(full);59      else acc.push(rel);60    }61  };62  walk(ROOT);63  return acc;64}6566function isExempt(path) {67  return EXEMPT_PATTERNS.some((re) => re.test(path));68}6970function hasHeader(path) {71  let content;72  try {73    content = readFileSync(join(ROOT, path), 'utf8');74  } catch {75    return false;76  }77  const head = content.split('\n').slice(0, HEAD_LINES).join('\n');78  return REQUIRED_MARKERS.every((marker) => head.includes(marker));79}8081const failures = [];82for (const file of listFiles()) {83  if (isExempt(file)) continue;84  const ext = extname(file);85  if (ext === '' && !/(^|\/)(Dockerfile|Makefile|\.gitignore|\.gitattributes)$/.test(file)) {86    // extensionless files other than well-known ones: still required to carry a header87  }88  if (!hasHeader(file)) failures.push(file);89}9091if (failures.length > 0) {92  console.error('✗ Missing SPB Git author header in:');93  for (const f of failures) console.error(`  - ${f}`);94  console.error(`\n${failures.length} file(s) failed. Run: npm run inject:headers`);95  process.exit(1);96}97console.log('✓ All files carry the SPB Git author header.');98