From 8bb374fa9e30a7301176f0573b8d6cca1a58f509 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Tue, 24 Feb 2026 18:39:03 -0800 Subject: [PATCH] Feat: Static site build pipeline from md to dist --- build.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 build.js diff --git a/build.js b/build.js new file mode 100644 index 0000000..c45db23 --- /dev/null +++ b/build.js @@ -0,0 +1,68 @@ +import{readFileSync,writeFileSync,mkdirSync,cpSync,readdirSync,existsSync,statSync}from'fs' +import{join,extname}from'path' +import matter from'gray-matter' +import{marked}from'marked' + +const ARTICLES='articles',DIST='dist',TEMPLATES='templates',STATIC='static' + +const template=n=>readFileSync(join(TEMPLATES,n),'utf-8') +const articleTmpl=template('article.html') +const homeTmpl=template('home.html') + +mkdirSync(DIST,{recursive:true}) + +if(existsSync(STATIC)) + cpSync(STATIC,DIST,{recursive:true}) + +const articles=[] + +for(const slug of readdirSync(ARTICLES)){ + const dir=join(ARTICLES,slug) + if(!statSync(dir).isDirectory()) continue + + const mdPath=join(dir,'index.md') + if(!existsSync(mdPath)) continue + + const{data,content}=matter(readFileSync(mdPath,'utf-8')) + const html=marked(content) + + const outDir=join(DIST,slug) + mkdirSync(outDir,{recursive:true}) + + // copy co-located assets + for(const f of readdirSync(dir)){ + if(f==='index.md') continue + cpSync(join(dir,f),join(outDir,f)) + } + + const page=articleTmpl + .replace('{{title}}',data.title||slug) + .replace('{{date}}',data.date?new Date(data.date).toLocaleDateString('en-US',{month:'long',day:'numeric',year:'numeric'}):'') + .replace('{{date_iso}}',data.date?new Date(data.date).toISOString().split('T')[0]:'') + .replace('{{tags}}', (data.tags||[]).map(t=>`${t}`).join(' ')) + .replace('{{excerpt}}',data.excerpt||'') + .replace('{{slug}}',slug) + .replace('{{body}}',html) + + writeFileSync(join(outDir,'index.html'),page) + articles.push({slug,...data}) + console.log(`āœ… ${slug}`) +} + +articles.sort((a,b)=>new Date(b.date)-new Date(a.date)) + +const articleListHtml=articles.map(a=>` + +
+
+

${a.title}

+ +
+ ${a.excerpt?`

${a.excerpt}

`:''} +
${(a.tags||[]).map(t=>`${t}`).join('')}
+
+
`).join('\n') + +const home=homeTmpl.replace('{{articles}}',articleListHtml) +writeFileSync(join(DIST,'index.html'),home) +console.log(`\nšŸ  index.html → ${articles.length} article(s)`)