Skip to content

Commit 9133269

Browse files
committed
revisionを生成するスクリプトを作成
1 parent 7a3bb8f commit 9133269

File tree

4 files changed

+132
-1
lines changed

4 files changed

+132
-1
lines changed

app/lib/docs.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import { join } from "node:path";
44
import yaml from "js-yaml";
55
import { isCloudflare } from "./detectCloudflare";
66
import { notFound } from "next/navigation";
7+
import crypto from "node:crypto";
78

89
export interface MarkdownSection {
10+
file: string; // ファイル名
911
id: string;
1012
level: number;
1113
title: string;
1214
rawContent: string; // 見出しも含めたもとのmarkdownの内容
15+
md5: string; // mdファイル全体のmd5
1316
}
1417

1518
export interface PageEntry {
@@ -36,6 +39,17 @@ interface IndexYml {
3639
}[];
3740
}
3841

42+
export interface RevisionYmlEntry {
43+
lang?: string;
44+
page?: string;
45+
rev: SectionRevision[];
46+
}
47+
export interface SectionRevision {
48+
md5: string; // mdファイル全体のmd5
49+
commit: string;
50+
path: string;
51+
}
52+
3953
async function readPublicFile(path: string): Promise<string> {
4054
try {
4155
if (isCloudflare()) {
@@ -119,6 +133,16 @@ export async function getSectionsList(
119133
.sort(naturalSortMdFiles);
120134
}
121135
}
136+
137+
export async function getRevisions(
138+
sectionId: string
139+
): Promise<RevisionYmlEntry | undefined> {
140+
const revisionsYml = await readPublicFile(`docs/revisions.yml`);
141+
return (yaml.load(revisionsYml) as Record<string, RevisionYmlEntry>)[
142+
sectionId
143+
];
144+
}
145+
122146
/**
123147
* public/docs/{lang}/{pageId}/ 以下のmdファイルを結合して MarkdownSection[] を返す。
124148
*/
@@ -134,10 +158,12 @@ export async function getMarkdownSections(
134158
if (file === "-intro.md") {
135159
// イントロセクションはフロントマターなし・見出しなし
136160
sections.push({
161+
file,
137162
id: `${lang}-${pageId}-intro`,
138163
level: 1,
139164
title: "",
140165
rawContent: raw,
166+
md5: "",
141167
});
142168
} else {
143169
sections.push(parseFrontmatter(raw, file));
@@ -166,9 +192,39 @@ function parseFrontmatter(content: string, file: string): MarkdownSection {
166192
// TODO: validation of frontmatter using zod
167193
const rawContent = content.slice(endIdx + 5);
168194
return {
195+
file,
169196
id: fm?.id ?? "",
170197
title: fm?.title ?? "",
171198
level: fm?.level ?? 2,
172199
rawContent,
200+
md5: crypto.createHash("md5").update(content).digest("base64"),
173201
};
174202
}
203+
204+
export async function getRevisionOfMarkdownSection(
205+
sectionId: string,
206+
md5: string
207+
): Promise<MarkdownSection> {
208+
const revisions = await getRevisions(sectionId);
209+
const targetRevision = revisions?.rev.find((r) => r.md5 === md5);
210+
if (targetRevision) {
211+
const rawRes = await fetch(
212+
`https://raw.githubusercontent.com/ut-code/my-code/${targetRevision.commit}/${targetRevision.path}`
213+
);
214+
if (rawRes.ok) {
215+
const raw = await rawRes.text();
216+
return parseFrontmatter(
217+
raw,
218+
`${targetRevision.commit}/${targetRevision.path}`
219+
);
220+
} else {
221+
throw new Error(
222+
`Failed to fetch ${targetRevision.commit}/${targetRevision.path}. ${rawRes.status}: ${await rawRes.text()}`
223+
);
224+
}
225+
} else {
226+
throw new Error(
227+
`Revision for sectionId=${sectionId}, md5=${md5} not found`
228+
);
229+
}
230+
}

public/docs/ruby/6-classes/3-0-accessor.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
id: ruby-classes-accessor
3-
title: '🔐 アクセサメソッド
3+
title: '🔐 アクセサメソッド'
44
level: 2
55
---
66

scripts/updateDocsRevisions.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { readFile, writeFile } from "node:fs/promises";
2+
import { join } from "node:path";
3+
import {
4+
getMarkdownSections,
5+
getPagesList,
6+
RevisionYmlEntry,
7+
} from "@/lib/docs";
8+
import yaml from "js-yaml";
9+
import { execFileSync } from "node:child_process";
10+
import { existsSync } from "node:fs";
11+
12+
const docsDir = join(process.cwd(), "public", "docs");
13+
14+
const commit = execFileSync("git", ["rev-parse", "--short", "HEAD"], {
15+
encoding: "utf8",
16+
}).trim();
17+
18+
const langEntries = await getPagesList();
19+
20+
const revisionsPrevYml = existsSync(join(docsDir, "revisions.yml"))
21+
? await readFile(join(docsDir, "revisions.yml"), "utf-8")
22+
: "{}";
23+
const revisions = yaml.load(revisionsPrevYml) as Record<
24+
string,
25+
RevisionYmlEntry
26+
>;
27+
28+
for (const id in revisions) {
29+
delete revisions[id].lang;
30+
delete revisions[id].page;
31+
}
32+
33+
for (const lang of langEntries) {
34+
for (const page of lang.pages) {
35+
const sections = await getMarkdownSections(lang.id, page.slug);
36+
for (const section of sections) {
37+
if (section.file === "-intro.md") continue;
38+
if (section.id in revisions) {
39+
revisions[section.id].lang = lang.id;
40+
revisions[section.id].page = page.slug;
41+
if (!revisions[section.id].rev.some((r) => r.md5 === section.md5)) {
42+
// ドキュメントが変更された場合
43+
console.log(`${section.id} has new md5: ${section.md5}`);
44+
revisions[section.id].rev.push({
45+
md5: section.md5,
46+
commit,
47+
path: `public/docs/${lang.id}/${page.slug}/${section.file}`,
48+
});
49+
}
50+
} else {
51+
// ドキュメントが新規追加された場合
52+
console.log(`${section.id} is new, adding to revisions.yml`);
53+
revisions[section.id] = {
54+
lang: lang.id,
55+
page: page.slug,
56+
rev: [
57+
{
58+
md5: section.md5,
59+
commit,
60+
path: `public/docs/${lang.id}/${page.slug}/${section.file}`,
61+
},
62+
],
63+
};
64+
}
65+
}
66+
}
67+
}
68+
69+
const revisionsYml = yaml.dump(revisions);
70+
await writeFile(
71+
join(docsDir, "revisions.yml"),
72+
"# This file will be updated by scripts/updateDocsRevisions.ts. Do not edit manually.\n" +
73+
revisionsYml,
74+
"utf-8"
75+
);

scripts/updateSectionHistory.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)