Skip to content

Commit 37e7e45

Browse files
committed
feat: automated revision history with commit messages
Replaces manual history tracking with automated revision tracking based on git commits. The revisions.json file is now part of 11ty's data layer, making revision history accessible to templates. Each revision now includes the commit message, and a new revision-history component displays this information on articles.
1 parent fef5946 commit 37e7e45

15 files changed

Lines changed: 219 additions & 71 deletions

File tree

.github/workflows/cd.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
run: |
5959
git config user.name "github-actions[bot]"
6060
git config user.email "github-actions[bot]@users.noreply.github.com"
61-
git add "www/**/*.md" .revisions.json
61+
git add "www/**/*.md" www/_data/revisions.json
6262
git diff --cached --quiet || \
6363
git commit -m "chore: auto-generate teasers [skip ci]" && git push --no-verify
6464
- run: npm run build

11ty/revisions.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { readFile, writeFile } from 'node:fs/promises'
44

55
export const HASH_ALGORITHM = 'shake128'
66
export const HASH_OUTPUT_BYTES = 6
7-
export const REVISIONS_PATH = '.revisions.json'
7+
export const REVISIONS_PATH = 'www/_data/revisions.json'
88

99
export interface Revision {
1010
hash: string
@@ -14,6 +14,7 @@ export interface Revision {
1414
separator: string
1515
date: string
1616
commit: string
17+
message?: string
1718
}
1819

1920
export interface RevisionEntry {
@@ -63,6 +64,16 @@ export function getCurrentCommit(): string {
6364
}
6465
}
6566

67+
export function getCommitMessage(commit: string): string {
68+
try {
69+
return execFileSync('git', ['log', '--format=%s', '-1', commit], {
70+
encoding: 'utf-8',
71+
}).trim()
72+
} catch {
73+
return ''
74+
}
75+
}
76+
6677
export async function loadRevisions(path: string = REVISIONS_PATH): Promise<RevisionsFile> {
6778
try {
6879
const content = await readFile(path, 'utf-8')

11ty/update-revisions.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
HASH_ALGORITHM,
77
HASH_OUTPUT_BYTES,
88
computeHash,
9+
getCommitMessage,
910
getCurrentCommit,
1011
hashFields,
1112
loadRevisions,
@@ -37,6 +38,7 @@ async function main() {
3738
const latestRevision = entry.revisions[entry.revisions.length - 1]
3839

3940
if (!latestRevision || latestRevision.hash !== hash) {
41+
const message = getCommitMessage(commit)
4042
entry.revisions.push({
4143
hash,
4244
algorithm: HASH_ALGORITHM,
@@ -45,6 +47,7 @@ async function main() {
4547
separator: '\\n',
4648
date: new Date().toISOString(),
4749
commit,
50+
...(message && { message }),
4851
})
4952
revisions[filePath] = entry
5053
revisionCount++

README.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,6 @@ Most content should be written in a Markdown file under the `www` directory.
123123
- `subhead`: A subhead or alternate title that appears below the title of an article. It is
124124
used as the page meta description if present.
125125
- `date`: The date and time of first publication, in ISO 8601 format
126-
- `history`: A list of significant edits to the page. The most recent edit must be listed
127-
first. Each entry in this list should contain:
128-
- `date`: The date and time of the edit, in ISO 8601 format
129-
- `change`: ⚠️ Not yet implemented but the idea would be to be an annotation that could be
130-
included on the page and RSS feed. This would remain optional for edits that are just
131-
fixes for typos or to fix search engine indexing.
132126
- `image`: Photo identifier used only for the preview images on article cards and Open Graph
133127
tags
134128
- `teaser`: A short description of the article or page used in previews and page metadata.

bin/backfill-revision-messages.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {
2+
getCommitMessage,
3+
loadRevisions,
4+
saveRevisions,
5+
REVISIONS_PATH,
6+
} from '../11ty/revisions.ts'
7+
8+
async function main() {
9+
const revisions = await loadRevisions()
10+
let updated = 0
11+
12+
for (const entry of Object.values(revisions)) {
13+
for (const revision of entry.revisions) {
14+
if (!revision.message) {
15+
const message = getCommitMessage(revision.commit)
16+
if (message) {
17+
revision.message = message
18+
updated++
19+
}
20+
}
21+
}
22+
}
23+
24+
await saveRevisions(revisions, REVISIONS_PATH)
25+
console.log(`Done. ${updated} revision message(s) backfilled.`)
26+
}
27+
28+
main().catch((error: unknown) => {
29+
console.error(error)
30+
process.exit(1)
31+
})

www/_data/eleventyComputed.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ export default {
6969
}
7070
},
7171

72+
// ISO date of the most recent revision, if modified after creation
73+
lastModified: (data) => {
74+
const filePath = data.page?.inputPath?.replace(/^\.\//, '')
75+
const revs = filePath && data.revisions?.[filePath]?.revisions
76+
if (revs?.length > 1) {
77+
return revs.at(-1).date
78+
}
79+
},
80+
7281
// Calculate reading time for articles
7382
readingTime(data) {
7483
if (data.tags?.includes('article') && data.page?.rawInput) {

0 commit comments

Comments
 (0)