spb/spbgit Public MIT
SPB Git — the platform hosting itself
JavaScript 73.9%
CSS 11.7%
Nunjucks 11.6%
Shell 2.7%
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