You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
3.6 KiB
TypeScript

import { promises as fs } from 'fs'
import path from 'path'
import wabtModule from 'wabt'
import frontmatter from 'front-matter'
export interface PostMeta {
slug: string,
date: Date,
title: string,
subtitle?: string,
unlisted: boolean,
filePath: string,
}
export interface Post extends Omit<PostMeta, "filePath"> {
body: string,
script?: Script,
wasm?: Wasm
}
export interface Script {
name: string,
path: string,
}
export interface Wasm extends Script {
contents: Uint8Array
}
type Attributes = { [key: string]: any }
const POST_FILE_PATTERN = /^(?<date>[0-9]{4}-[0-9]{2}-[0-9]{2}(-[0-9]{1,2})?)_(?<slug>[^\.]+)\.md$/
export async function getPostSlugs(): Promise<string[]> {
const postsDir = path.join(process.cwd(), 'posts')
const postPaths = await fs.readdir(postsDir)
return postPaths.map(getSlugFromFilePath).filter(slug => slug !== null) as string[]
}
export function getSlugFromFilePath(postPath: string): string | null {
const regexResult = POST_FILE_PATTERN.exec(postPath)
if (!regexResult?.groups) return null
return regexResult.groups.slug
}
export async function loadPageMetada(slug: string): Promise<PostMeta | null> {
const postsDir = path.join(process.cwd(), 'posts')
const postPaths = await fs.readdir(postsDir)
const postMatch: RegExpExecArray | null | undefined = postPaths
.map((postFile: string) => POST_FILE_PATTERN.exec(postFile))
.filter((regexResult: RegExpExecArray | null) => regexResult !== null)
.find((regexResult: RegExpExecArray) => regexResult.groups?.slug === slug)
if (!postMatch) return null;
const fileName = `${postMatch.groups?.date}_${postMatch.groups?.slug}.md`
const filePath = path.join(process.cwd(), 'posts', fileName)
const fileContents = (await fs.readFile(filePath)).toString('utf8')
const { attributes } = frontmatter(fileContents)
const data: Attributes = attributes as Attributes
if (!data.title) return null;
const [year, month, day] = postMatch.groups?.date.split('-') || []
const date = new Date(Date.UTC(parseInt(year), parseInt(month) - 1, parseInt(day)))
return {
date,
filePath,
slug: postMatch.groups?.slug || '',
title: data.title,
subtitle: data.subtitle,
unlisted: typeof data.unlisted === 'boolean' ? data.unlisted : false,
}
}
export async function loadSinglePage(slug: string): Promise<Post | null> {
const postMeta = await loadPageMetada(slug)
if (!postMeta) return null
const fileContents = (await fs.readFile(postMeta.filePath)).toString('utf8')
const { attributes, body } = frontmatter(fileContents)
const data: Attributes = attributes as Attributes
let script: Script | undefined = undefined;
if (data.script) {
const scriptPath = path.join(process.cwd(), 'public/scripts', data.script)
try {
await fs.readFile(scriptPath)
} catch {
throw new Error(`Could not find script ${scriptPath} referenced in post ${slug}`)
} finally {
script = {
name: data.script.replace(/^.*\//, ''),
path: `/scripts/${data.script}`
}
}
}
let wasm: Wasm | undefined = undefined;
if (data.wasm) {
const wabt = await wabtModule()
const wasmPath = path.join(process.cwd(), 'public/scripts', data.wasm)
const fileName = data.wasm.replace(/^.*\//, '')
const wasmText = (await fs.readFile(wasmPath))
const wasmIntermed = wabt.parseWat(fileName, wasmText)
const { buffer: wasmBinary } = wasmIntermed.toBinary({ write_debug_names: true })
wasm = {
name: fileName,
path: `/scripts/${data.wasm}`,
contents: wasmBinary
}
}
const { filePath, ...otherMeta } = postMeta
return {
...otherMeta,
script,
wasm,
body
}
}