import path from 'path' import matter from 'gray-matter' import moment from 'moment' import fs from 'fs-extra' // @ts-ignore import shortHash from 'short-hash' import stringify from 'fast-json-stable-stringify' import glob from 'fast-glob' export type DataType = 'posts' | 'slides' export interface IData { teaser?: string full: string } export default class ApiBuilder { _hash: Record> = { posts: {}, slides: {}, } _tag: Record> = { posts: {}, slides: {}, } _header: Record = { posts: [], slides: [], } _data: Record> = { posts: {}, slides: {}, } loadFile (dirName: DataType, fileName: string) { const ROOT = path.join('data', dirName) const raw = fs.readFileSync(path.join(ROOT, fileName), 'utf8') const m = matter(raw) const p = path.parse(fileName) m.data = { ...m.data, name: p.name, path: fileName, } if (m.data.date) { let mm = moment(m.data.date) mm = mm.add(-mm.utcOffset(), 'minute') Object.assign(m.data, { date: mm.toISOString(), y: mm.format('YYYY'), mo: mm.format('MM'), }) } let data: IData = { full: m.content, } if (dirName === 'posts') { const [teaser, remaining] = m.content.split(/\n===\n(.+)/s) data = { teaser, full: `${teaser}\n${remaining || ''}`, } } return { header: m.data, data, } } loadDir (dirName: DataType) { const ROOT = path.join('data', dirName) const now = new Date().toISOString() glob.sync('**/*.md', { cwd: ROOT, }).map((f) => { const { header, data } = this.loadFile(dirName, f) this._hash[dirName][f] = shortHash(data.full) if (dirName === 'posts' && header.date && header.date < now && header.path.startsWith('blog/')) { if (Array.isArray(header.tag)) { for (const t of header.tag) { this._tag[dirName][t] = (this._tag[dirName][t] || 0) + 1 } } } this._header[dirName].push(header) this._data[dirName][f] = data }) this._header[dirName] = this._header[dirName].sort((h1, h2) => { if (typeof h1.date === 'string' && typeof h2.date === 'string') { return -h1.date.localeCompare(h2.date) } return 0 }) this._hash[dirName]._header = shortHash(stringify(this._header[dirName])) return { header: this._header[dirName], hash: this._hash[dirName]._header, tag: this._tag[dirName], } } emit () { const dst = 'static/build' this.loadDir('posts') this.loadDir('slides') /** * Emit hash */ console.log('Emitting hash') fs.ensureDirSync(`${dst}/hash`) fs.writeFileSync(`${dst}/hash/posts.json`, JSON.stringify(this._hash.posts)) fs.writeFileSync(`${dst}/hash/slides.json`, JSON.stringify(this._hash.slides)) /** * Emit header */ console.log('Emitting header') fs.ensureDirSync(`${dst}/header`) fs.writeFileSync(`${dst}/header/posts.json`, JSON.stringify(this._header.posts)) fs.writeFileSync(`${dst}/header/slides.json`, JSON.stringify(this._header.slides)) /** * Emit tag */ console.log('Emitting tag') fs.ensureDirSync(`${dst}/tag`) fs.writeFileSync(`${dst}/tag/posts.json`, JSON.stringify(this._tag.posts)) /** * Emit posts */ console.log('Emitting blog entries') for (const [p, data] of Object.entries(this._data.posts)) { fs.ensureFileSync(`${dst}/teaser/${p}`) fs.ensureFileSync(`${dst}/full/${p}`) fs.writeFileSync(`${dst}/teaser/${p}`, data.teaser) fs.writeFileSync(`${dst}/full/${p}`, data.full) } /** * Emit slides */ console.log('Emitting slides') for (const [p, data] of Object.entries(this._data.slides)) { fs.ensureFileSync(`${dst}/slides/${p}`) fs.writeFileSync(`${dst}/slides/${p}`, data.full) } } } if (typeof require !== 'undefined' && require.main === module) { new ApiBuilder().emit() }