import { execSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; // Get staged files from git const stagedFiles = execSync("git diff --cached --name-only --diff-filter=ACMR") .toString() .split("\n") .filter(Boolean); // Filter to only new or modified posts const postFiles = stagedFiles.filter( (file) => file.startsWith("content/posts/") && file.endsWith(".md") && !file.endsWith("_index.md"), ); if (postFiles.length === 0) { process.exit(0); } let hasError = false; for (const file of postFiles) { const basename = path.basename(file, ".md"); // 1. Slug validation (kebab-case: lowercase letters, numbers, and hyphens) if (!/^[a-z0-9-]+$/.test(basename)) { console.error(`[ERROR] in ${file}:`); console.error(` Filename must be kebab-case (e.g. 'my-cool-post.md').`); hasError = true; } // 2. Front-matter validation const content = fs.readFileSync(file, "utf8"); // Zola uses +++ for TOML front-matter const frontMatterMatch = content.match(/^\+\+\+([\s\S]*?)\+\+\+/); if (!frontMatterMatch) { console.error(`[ERROR] in ${file}:`); console.error(` Missing TOML front-matter (must be enclosed in +++).`); hasError = true; continue; } const frontMatter = frontMatterMatch[1]; const hasTitle = /^title\s*=\s*".+"/m.test(frontMatter); const hasDate = /^date\s*=\s*\d{4}-\d{2}-\d{2}/m.test(frontMatter); if (!hasTitle) { console.error(`[ERROR] in ${file}:`); console.error( ` Missing 'title' field in front-matter (e.g. title = "My Post").`, ); hasError = true; } if (!hasDate) { console.error(`[ERROR] in ${file}:`); console.error( ` Missing 'date' field in front-matter (e.g. date = 2024-01-01).`, ); hasError = true; } } if (hasError) { console.error( "\n[FATAL] Commit rejected due to validation errors. Please fix the issues above and try again.\n", ); process.exit(1); } else { console.log("[SUCCESS] Post validation passed!"); }