diff --git a/package.json b/package.json index 5f755b4..fb4789e 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "scripts": { "start": "vite", - "build": "node ../scripts/generate-update-manifest.mjs && vite build", + "build": "node scripts/generate-update-manifest.mjs && vite build", "lint": "yarn check:eslint && yarn check:prettier", "check:eslint": "eslint src/*", "check:prettier": "prettier --check .", diff --git a/scripts/generate-update-manifest.mjs b/scripts/generate-update-manifest.mjs new file mode 100644 index 0000000..aeb44fa --- /dev/null +++ b/scripts/generate-update-manifest.mjs @@ -0,0 +1,114 @@ +#!/usr/bin/env node +/** + * Regenerates public/update/manifest.json from markdown files. + * + * - currentupdate.md is always the "current" entry + * - Other *.md files matching X.Y.Z.md become "older" (sorted newest first) + * - title, date, version come from YAML frontmatter when present + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, '..'); +const UPDATE_DIR = path.join(ROOT, 'public', 'update'); +const MANIFEST_PATH = path.join(UPDATE_DIR, 'manifest.json'); +const CURRENT_FILE = 'currentupdate.md'; +const VERSION_FILE_RE = /^(\d+\.\d+\.\d+)\.md$/; + +const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/; + +function parseFrontmatter(source) { + const match = FRONTMATTER_RE.exec(source.trimStart()); + if (!match) return {}; + + const frontmatter = {}; + match[1].split('\n').forEach((line) => { + const colon = line.indexOf(':'); + if (colon <= 0) return; + const key = line.slice(0, colon).trim(); + const raw = line.slice(colon + 1).trim(); + frontmatter[key] = raw.replace(/^['"]|['"]$/g, ''); + }); + return frontmatter; +} + +function compareVersions(a, b) { + const pa = a.split('.').map((part) => Number.parseInt(part, 10)); + const pb = b.split('.').map((part) => Number.parseInt(part, 10)); + const len = Math.max(pa.length, pb.length); + + for (let i = 0; i < len; i += 1) { + const na = pa[i] ?? 0; + const nb = pb[i] ?? 0; + if (na !== nb) return nb - na; + } + return 0; +} + +function readMarkdownMeta(fileName) { + const filePath = path.join(UPDATE_DIR, fileName); + const source = fs.readFileSync(filePath, 'utf8'); + const frontmatter = parseFrontmatter(source); + const versionFromName = VERSION_FILE_RE.exec(fileName)?.[1]; + + return { + file: fileName, + version: frontmatter.version ?? versionFromName, + title: frontmatter.title, + date: frontmatter.date, + }; +} + +function buildManifest() { + if (!fs.existsSync(UPDATE_DIR)) { + throw new Error(`Update directory not found: ${UPDATE_DIR}`); + } + + const currentPath = path.join(UPDATE_DIR, CURRENT_FILE); + if (!fs.existsSync(currentPath)) { + throw new Error(`Missing ${CURRENT_FILE} in ${UPDATE_DIR}`); + } + + const older = fs + .readdirSync(UPDATE_DIR, { withFileTypes: true }) + .filter((entry) => entry.isFile() && VERSION_FILE_RE.test(entry.name)) + .map((entry) => readMarkdownMeta(entry.name)) + .filter((entry) => entry.version) + .sort((a, b) => compareVersions(a.version, b.version)); + + return { + current: CURRENT_FILE, + older: older.map(({ file, version, title, date }) => { + const item = { file }; + if (version) item.version = version; + if (title) item.title = title; + if (date) item.date = date; + return item; + }), + }; +} + +function main() { + const manifest = buildManifest(); + const json = `${JSON.stringify(manifest, null, 2)}\n`; + + let previous = null; + if (fs.existsSync(MANIFEST_PATH)) { + previous = fs.readFileSync(MANIFEST_PATH, 'utf8'); + } + + if (previous === json) { + console.log('[update-manifest] manifest.json is already up to date'); + return; + } + + fs.writeFileSync(MANIFEST_PATH, json, 'utf8'); + console.log( + `[update-manifest] wrote manifest.json (${manifest.older.length} older version(s))` + ); +} + +main();