spb/drive Public
SPB Drive — self-hosted personal cloud drive (files, previews, sharing) on the MacLustr cluster.
JavaScript 82.7%
CSS 10.6%
Nunjucks 3.6%
Shell 1.8%
SQL 1.3%
1#!/usr/bin/env node2/**3 * ─────────────────────────────────────────────4 * SPB Drive — Personal Cloud Drive5 * ─────────────────────────────────────────────6 * Author : Simon-Pierre Boucher7 * Contact : contact@spboucher.ai8 * File : scripts/check-headers.mjs9 * Purpose : CI gate — fail if any tracked source file lacks the author header10 * License : MIT © Simon-Pierre Boucher11 * ─────────────────────────────────────────────12 */1314import { execSync } from 'node:child_process';15import { readFileSync } from 'node:fs';1617/** Extensions that must carry the author header. */18const CHECKED_EXT = new Set([19 '.js', '.mjs', '.cjs', '.ts', '.css', '.scss', '.py', '.sh', '.bash',20 '.yml', '.yaml', '.toml', '.sql', '.html', '.njk', '.md',21]);2223/** Files checked regardless of extension. */24const CHECKED_NAMES = new Set(['Dockerfile', '.gitignore']);2526/** Files exempt from the header requirement (external formats / lockfiles). */27const EXEMPT = new Set([28 'package.json', 'package-lock.json', 'CLAUDE.md',29]);3031const REQUIRED = ['Simon-Pierre Boucher', 'contact@spboucher.ai', 'SPB Drive'];32const HEAD_BYTES = 1200;3334function trackedFiles() {35 const out = execSync('git ls-files', { encoding: 'utf8' });36 return out.split('\n').filter(Boolean);37}3839function needsHeader(file) {40 const base = file.split('/').pop();41 if (EXEMPT.has(base) || EXEMPT.has(file)) return false;42 if (CHECKED_NAMES.has(base)) return true;43 const dot = base.lastIndexOf('.');44 if (dot < 0) return false;45 return CHECKED_EXT.has(base.slice(dot).toLowerCase());46}4748const failures = [];49for (const file of trackedFiles()) {50 if (!needsHeader(file)) continue;51 let head = '';52 try {53 head = readFileSync(file, 'utf8').slice(0, HEAD_BYTES);54 } catch {55 continue; // deleted-but-tracked edge case56 }57 const missing = REQUIRED.filter((needle) => !head.includes(needle));58 if (missing.length > 0) failures.push({ file, missing });59}6061if (failures.length > 0) {62 console.error('✗ Author header missing or incomplete in:');63 for (const { file, missing } of failures) {64 console.error(` ${file} (missing: ${missing.join(', ')})`);65 }66 process.exit(1);67}68console.log('✓ check:headers — every tracked file carries the author header.');69