72 lines
2.2 KiB
JavaScript
72 lines
2.2 KiB
JavaScript
import { execFileSync } from 'node:child_process'
|
|
import { readdir, readFile, stat } from 'node:fs/promises'
|
|
import { join } from 'node:path'
|
|
|
|
export const stripSidenotes = (text) => {
|
|
let result = ''
|
|
for (let index = 0; index < text.length;) {
|
|
if (text[index] === '^' && text[index + 1] === '[') {
|
|
const end = text.indexOf(']', index + 2)
|
|
if (end >= 0) {
|
|
index = end + 1
|
|
continue
|
|
}
|
|
}
|
|
result += text[index++]
|
|
}
|
|
return result
|
|
}
|
|
|
|
export const dateFor = async (file) => {
|
|
try {
|
|
const gitDate = execFileSync(
|
|
'git',
|
|
['log', '-1', '--date=format-local:%Y-%m-%dT%H:%M:%SZ', '--format=%ad', '--', file],
|
|
{ env: { ...process.env, TZ: 'UTC' }, stdio: ['ignore', 'pipe', 'ignore'] },
|
|
).toString().trim()
|
|
if (gitDate) return gitDate
|
|
} catch {}
|
|
try {
|
|
return (await stat(file)).mtime.toISOString()
|
|
} catch {
|
|
return ''
|
|
}
|
|
}
|
|
|
|
export const readPages = async (dir, { includeDrafts = false } = {}) => {
|
|
let entries
|
|
try {
|
|
entries = await readdir(dir, { withFileTypes: true })
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') return []
|
|
throw error
|
|
}
|
|
const markdownEntries = entries
|
|
.filter((entry) => entry.isFile() && entry.name.endsWith('.md') && entry.name !== '.md'
|
|
&& (includeDrafts || !entry.name.startsWith('.')))
|
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
const pages = await Promise.all(markdownEntries.map(async (entry) => {
|
|
const file = join(dir, entry.name)
|
|
const source = await readFile(file, 'utf8')
|
|
const titleLine = source.split(/\r?\n/).find((line) => line.startsWith('# '))
|
|
if (!titleLine) return undefined
|
|
const draft = entry.name.startsWith('.')
|
|
const name = (draft ? entry.name.slice(1) : entry.name).slice(0, -3)
|
|
if (!name) return undefined
|
|
return {
|
|
name,
|
|
title: stripSidenotes(titleLine.slice(2).trim()),
|
|
file,
|
|
source,
|
|
date: await dateFor(file),
|
|
draft,
|
|
}
|
|
}))
|
|
const byName = new Map()
|
|
for (const page of pages.filter(Boolean)) {
|
|
const existing = byName.get(page.name)
|
|
if (!existing || (existing.draft && !page.draft)) byName.set(page.name, page)
|
|
}
|
|
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name))
|
|
}
|