SPB Git

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%
2.5 KB · 69 lines javascript
Raw Blame History
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