clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

main
forge / lib / motd.ts
motd.ts 1.7K
1// The motd: the quiet line under the header on the home page, which an2// admin sets (bin/forge motd). It is kept in .forge/motd: no file is the3// default, an empty one no line at all.4//5// node lib/motd.ts default the default line6// node lib/motd.ts check <text> the text as stored (trimmed), or why not7import fs from 'node:fs';8import path from 'node:path';9import { fileURLToPath } from 'node:url';1011export const MOTD_DEFAULT = 'a personal forge on plain git';12export const MOTD_MAX = 72;1314// What the index shows: null for no line.15export function motd(state: string): string | null {16 let text: string;17 try {18 text = fs.readFileSync(path.join(state, 'motd'), 'utf8');19 } catch {20 return MOTD_DEFAULT;21 }22 return text.split('\n')[0].trim() || null;23}2425// Plain text on one line, counted in characters; the page escapes it.26export function check(text: string): { text: string } | { error: string } {27 const s = text.trim();28 const n = [...s].length;29 if (/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/.test(s)) return { error: 'one line of plain text, without control characters' };30 if (!n) return { error: 'empty; motd --clear shows no line' };31 if (n > MOTD_MAX) return { error: `${n} characters; it can have ${MOTD_MAX} at most` };32 return { text: s };33}3435if (process.argv[1] && fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {36 const [cmd, text = ''] = process.argv.slice(2);37 if (cmd === 'default') console.log(MOTD_DEFAULT);38 else if (cmd === 'check') {39 const r = check(text);40 if ('error' in r) {41 console.error(`forge: motd: ${r.error}`);42 process.exit(1);43 }44 console.log(r.text);45 } else {46 console.error('usage: node lib/motd.ts default | check <text>');47 process.exit(2);48 }49}