#!/usr/bin/env node /** * ───────────────────────────────────────────── * SPB Drive — Personal Cloud Drive * ───────────────────────────────────────────── * Author : Simon-Pierre Boucher * Contact : contact@spboucher.ai * File : scripts/check-headers.mjs * Purpose : CI gate — fail if any tracked source file lacks the author header * License : MIT © Simon-Pierre Boucher * ───────────────────────────────────────────── */ import { execSync } from 'node:child_process'; import { readFileSync } from 'node:fs'; /** Extensions that must carry the author header. */ const CHECKED_EXT = new Set([ '.js', '.mjs', '.cjs', '.ts', '.css', '.scss', '.py', '.sh', '.bash', '.yml', '.yaml', '.toml', '.sql', '.html', '.njk', '.md', ]); /** Files checked regardless of extension. */ const CHECKED_NAMES = new Set(['Dockerfile', '.gitignore']); /** Files exempt from the header requirement (external formats / lockfiles). */ const EXEMPT = new Set([ 'package.json', 'package-lock.json', 'CLAUDE.md', ]); const REQUIRED = ['Simon-Pierre Boucher', 'contact@spboucher.ai', 'SPB Drive']; const HEAD_BYTES = 1200; function trackedFiles() { const out = execSync('git ls-files', { encoding: 'utf8' }); return out.split('\n').filter(Boolean); } function needsHeader(file) { const base = file.split('/').pop(); if (EXEMPT.has(base) || EXEMPT.has(file)) return false; if (CHECKED_NAMES.has(base)) return true; const dot = base.lastIndexOf('.'); if (dot < 0) return false; return CHECKED_EXT.has(base.slice(dot).toLowerCase()); } const failures = []; for (const file of trackedFiles()) { if (!needsHeader(file)) continue; let head = ''; try { head = readFileSync(file, 'utf8').slice(0, HEAD_BYTES); } catch { continue; // deleted-but-tracked edge case } const missing = REQUIRED.filter((needle) => !head.includes(needle)); if (missing.length > 0) failures.push({ file, missing }); } if (failures.length > 0) { console.error('✗ Author header missing or incomplete in:'); for (const { file, missing } of failures) { console.error(` ${file} (missing: ${missing.join(', ')})`); } process.exit(1); } console.log('✓ check:headers — every tracked file carries the author header.');