diff options
Diffstat (limited to 'src/plugins')
| -rw-r--r-- | src/plugins/updates.ts | 77 |
1 files changed, 57 insertions, 20 deletions
diff --git a/src/plugins/updates.ts b/src/plugins/updates.ts index 4b383c8..6078cd1 100644 --- a/src/plugins/updates.ts +++ b/src/plugins/updates.ts @@ -4,34 +4,65 @@ import { readFileSync, writeFileSync, mkdirSync } from "node:fs"; /** * ホームページの「更新情報」をビルド時に生成する。 * - * git の履歴から「新しく公開したページ」だけを拾い(--diff-filter=A)、 - * そのページのフロントマターの title と URL を組みにして src/data/updates.json に書き出す。 - * 加筆や内部の修正は拾わないので、一覧が作業ログで埋まらない。 + * git の履歴から、ページごとに次の2つを調べて、イベントとして書き出す。 + * 新規: そのページが追加されたコミットの日付 + * 更新: そのページを最後に変更したコミットの日付(追加より新しい場合だけ) + * ただし、一度に多くのファイルを変更したコミット(表記の一括修正、画像の整理など)は + * 「更新」とはみなさない。作業ログで一覧が埋まらないようにするため。 * * 生成結果はコミットしておく(git の履歴がない環境でもビルドできるように)。 */ +const BULK_FILES = 5; // これを超える数のファイルを変更したコミットは一括修正とみなす + +type Item = { + date: string; + title: string; + url: string; + group: string; + kind: "new" | "update"; +}; + +type Commit = { date: string; files: { status: string; path: string }[] }; + export default function updatesIntegration() { return { name: "build-updates", hooks: { "astro:config:setup": () => { - const items: { date: string; title: string; url: string; group: string }[] = []; + const items: Item[] = []; try { const out = execSync( - 'git log --diff-filter=A --name-only --date=short --format="%x01%ad" -- src/content/docs', + 'git log --no-merges --date=short --format="%x01%ad" --name-status', { encoding: "utf-8", maxBuffer: 64 * 1024 * 1024 }, ); - // 新しく追加されたファイル → そのコミットの日付(git log は新しい順なので最初の出現が追加時) - const added = new Map<string, string>(); - let date = ""; + + // 新しい順のコミットを、日付と変更ファイルの一覧に組み直す + const commits: Commit[] = []; + let cur: Commit | null = null; for (const line of out.split("\n")) { if (line.startsWith("\u0001")) { - date = line.slice(1).trim(); + cur = { date: line.slice(1).trim(), files: [] }; + commits.push(cur); continue; } - const file = line.trim(); - if (!file.endsWith(".mdx") || file.endsWith("/index.mdx")) continue; - if (!added.has(file)) added.set(file, date); + if (!line.trim() || !cur) continue; + const parts = line.split("\t"); + const status = parts[0][0]; + if (status === "R" || status === "C") continue; // リネーム・コピーは対象外 + cur.files.push({ status, path: parts[parts.length - 1] }); + } + + const added = new Map<string, string>(); // 追加されたコミットの日付(新しい順なので最初のAが追加時) + const modified = new Map<string, string>(); // 最後に変更された日付(一括修正は除く) + for (const c of commits) { + // 一括修正かどうかは、そのコミットが変更した全ファイルの数で判定する + const bulk = c.files.length > BULK_FILES; + for (const f of c.files) { + if (!f.path.startsWith("src/content/docs/")) continue; + if (!f.path.endsWith(".mdx") || f.path.endsWith("/index.mdx")) continue; + if (f.status === "A" && !added.has(f.path)) added.set(f.path, c.date); + if (!bulk && !modified.has(f.path)) modified.set(f.path, c.date); + } } const reiwa = (dir: string) => { @@ -39,7 +70,8 @@ export default function updatesIntegration() { return n === 1 ? "令和元年" : `令和${n}年`; }; - for (const [file, d] of added) { + for (const [file, addDate] of added) { + const modDate = modified.get(file) ?? ""; try { const src = readFileSync(file, "utf-8"); // 下書き(draft: true)は本番に出ないので除外する @@ -50,26 +82,31 @@ export default function updatesIntegration() { const slug = file .replace(/^src\/content\/docs\//, "") .replace(/\.mdx$/, ""); - const url = `/${slug}/`; let group = ""; const mm = slug.match(/^ippan-situmon\/(r\d+d)\/(\d+)gatu\//); if (mm) group = `${reiwa(mm[1])}${mm[2]}月定例会`; - items.push({ date: d, title: m[1], url, group }); + const base = { title: m[1], url: `/${slug}/`, group }; + // 追加より後に更新があれば、更新のイベントも出す + if (modDate > addDate) { + items.push({ ...base, date: modDate, kind: "update" }); + } + items.push({ ...base, date: addDate, kind: "new" }); } catch { // 追加後に改名・削除されたファイルは飛ばす } } - items.sort((a, b) => b.date.localeCompare(a.date)); + items.sort( + (a, b) => b.date.localeCompare(a.date) || a.url.localeCompare(b.url), + ); } catch (e) { console.warn("[updates] git から取得できませんでした:", (e as Error).message); } try { mkdirSync("src/data", { recursive: true }); - writeFileSync( - "src/data/updates.json", - JSON.stringify(items.slice(0, 40), null, 2) + "\n", + writeFileSync("src/data/updates.json", JSON.stringify(items.slice(0, 40), null, 2) + "\n"); + console.log( + `[updates] ${items.length}件のうち直近${Math.min(items.length, 40)}件を書き出しました`, ); - console.log(`[updates] ${items.length}件のうち直近${Math.min(items.length, 40)}件を書き出しました`); } catch (e) { console.warn("[updates] 書き出しに失敗しました:", (e as Error).message); } |
