Color mode

Changelog

Build-time change history derived from local Git history. For every published note the plugin adds a "change history" section with commit dates, subjects, and authors, and it can build a site-wide changelog dataset from the notes' commits. No client-side JavaScript is required.

日本語

Overview

changelog() reads the local Git repository at build time using the same git access as @riebeckite/plugin-diff (execFile with graceful failure). It never invents its own filesystem layer.

Results are published through the manifest bodySlots mechanism. Each public entry receives its own history in the article.after-content slot. The site's layout decides whether and where to render that slot, so the plugin stays inside the Plugin boundary and does not create application routes. When siteWide is enabled, the site-wide changelog is written to the slot of the note named by siteWideSlug; that note (the route) is owned by the app or the author, not by this plugin. No client script is registered.

Usage

ts
import { defineConfig } from "@riebeckite/core";
import { changelog } from "@riebeckite/plugin-changelog";
 
export default defineConfig({
  // ...
  plugins: [
    changelog({
      // Point at the content root so Git sees the notes' repository paths.
      cwd: "./content",
      lookbackDays: 180,
      dateFormat: "iso",
    }),
  ],
});

Options

Option Type Default Description
cwd string config.content.directory, else process.cwd() Content root used to locate the Git work tree
lookbackDays number — (full history) Only include commits newer than this many days
dateFormat "iso" | "long" | "short" "iso" How dates are rendered
locale string "en" Locale for "long" / "short" dates
perNote boolean true Add a history section to every public note
siteWide boolean false Build and inject the site-wide changelog
siteWideSlug string "changelog" Note that receives the site-wide changelog
maxPerNote number 10 Maximum commits listed per note
maxSiteWide number 50 Maximum commits listed site-wide
showAuthor boolean true Show the commit author
heading boolean true Render the <h2> heading
perNoteHeading string "Change history" Per-note heading text
siteWideHeading string "Changelog" Site-wide heading text
className string "rr-changelog" Root CSS class

Output

The plugin appends a fragment like this to the article.after-content slot:

html
<section class="rr-changelog rr-changelog--note" data-changelog-note>
  <h2 class="rr-changelog__heading">Change history</h2>
  <ol class="rr-changelog__list">
    <li class="rr-changelog__item">
      <time class="rr-changelog__date" datetime="2026-09-30T09:00:00+09:00">2026-09-30</time>
      <span class="rr-changelog__subject">Fix the sidebar offset</span>
      <span class="rr-changelog__author">Author Name</span>
      <code class="rr-changelog__hash" title="…full hash…">abc1234</code>
    </li>
  </ol>
</section>

data-changelog-note and data-changelog-site mark the two fragments for styling and idempotency checks.

App wiring (site-wide changelog)

The site-wide list is data, not a page. The app owns the route; the plugin either fills the article.after-content slot of an existing note (siteWide: true + siteWideSlug, which requires a public note at that slug) or exposes the dataset for the app to render itself:

ts
import {
  buildSiteChangelog,
  GitChangelogReader,
  renderSiteChangelog,
  resolveChangelogOptions,
} from "@riebeckite/plugin-changelog";
 
const options = resolveChangelogOptions({ lookbackDays: 90 });
const manifest = await content.getManifest();
const reader = new GitChangelogReader({ cwd: "./content" });
const commits = await reader.getRecentCommits();
const dataset = buildSiteChangelog({
  entries: manifest.publicEntries,
  commits,
  contentIndex: manifest.contentIndex,
  options,
});
const html = renderSiteChangelog(dataset, options);

The reference app renders manifest bodySlots in apps/web/app/components/article/article.tsx; a dedicated route can render the exported HTML directly.

Failure behavior

When git cannot be started, the plugin reports a changelog-git-unavailable warning diagnostic. When the content directory is not inside a Git working tree, it reports changelog-content-outside-repository. Either way a logger warning accompanies it and the build output is left untouched. The build never fails because history is unavailable, and the same guarantee holds for an empty repository.

Style

The package ships style.css with the stable .rr-changelog root hook. Themes can restyle it without editing the plugin:

ts
import "@riebeckite/plugin-changelog/style.css";

Exports

  • changelog(options?) — plugin factory
  • changelogPlugin — alias of changelog
  • resolveChangelogOptions(options?) — apply option defaults
  • GitChangelogReader — Git-backed history reader (getFileHistory, getRecentCommits, isAvailable)
  • buildNoteChangeHistory(entry, commits, options) — per-note dataset
  • buildSiteChangelog({ entries, commits, contentIndex, options }) — site-wide dataset
  • renderNoteChangeHistory(history, options) / renderSiteChangelog(changelog, options) — HTML renderers
  • filterChangelogCommits(commits, options) / formatChangelogDate(date, options) / resolveLookbackSince(days, now?) — pure helpers
  • Types: ChangelogOptions, ResolvedChangelogOptions, ChangelogCommit, ChangelogRecord, NoteChangeHistory, SiteChangelog, SiteChangelogEntry, SiteChangelogNote, ChangelogDateFormat, GitChangelogReaderOptions

See also

History

1 changesCollapseExpand
1 + <!-- Generated from packages/plugins/changelog/README.md. Do not edit this page directly; edit the package README and run `pnpm docs:sync`. -->
2 +
3 + # Changelog
4 +
5 + Build-time change history derived from local Git history. For every published
6 + note the plugin adds a "change history" section with commit dates, subjects,
7 + and authors, and it can build a site-wide changelog dataset from the notes'
8 + commits. No client-side JavaScript is required.
9 +
10 + [日本語](./changelog.md)
11 +
12 + ## Overview
13 +
14 + `changelog()` reads the local Git repository at build time using the same
15 + `git` access as [`@riebeckite/plugin-diff`](./diff.en.md) (`execFile` with
16 + graceful failure). It never invents its own filesystem layer.
17 +
18 + Results are published through the manifest `bodySlots` mechanism. Each public
19 + entry receives its own history in the `article.after-content` slot. The site's
20 + layout decides whether and where to render that slot, so the plugin stays
21 + inside the Plugin boundary and does not create application routes. When
22 + `siteWide` is enabled, the site-wide changelog is written to the slot of the
23 + note named by `siteWideSlug`; that note (the route) is owned by the app or the
24 + author, not by this plugin. No client script is registered.
25 +
26 + ## Usage
27 +
28 + ```ts
29 + import { defineConfig } from "@riebeckite/core";
30 + import { changelog } from "@riebeckite/plugin-changelog";
31 +
32 + export default defineConfig({
33 + // ...
34 + plugins: [
35 + changelog({
36 + // Point at the content root so Git sees the notes' repository paths.
37 + cwd: "./content",
38 + lookbackDays: 180,
39 + dateFormat: "iso",
40 + }),
41 + ],
42 + });
43 + ```
44 +
45 + ## Options
46 +
47 + | Option | Type | Default | Description |
48 + | ------ | ---- | ------- | ----------- |
49 + | `cwd` | `string` | `config.content.directory`, else `process.cwd()` | Content root used to locate the Git work tree |
50 + | `lookbackDays` | `number` | — (full history) | Only include commits newer than this many days |
51 + | `dateFormat` | `"iso" \| "long" \| "short"` | `"iso"` | How dates are rendered |
52 + | `locale` | `string` | `"en"` | Locale for `"long"` / `"short"` dates |
53 + | `perNote` | `boolean` | `true` | Add a history section to every public note |
54 + | `siteWide` | `boolean` | `false` | Build and inject the site-wide changelog |
55 + | `siteWideSlug` | `string` | `"changelog"` | Note that receives the site-wide changelog |
56 + | `maxPerNote` | `number` | `10` | Maximum commits listed per note |
57 + | `maxSiteWide` | `number` | `50` | Maximum commits listed site-wide |
58 + | `showAuthor` | `boolean` | `true` | Show the commit author |
59 + | `heading` | `boolean` | `true` | Render the `<h2>` heading |
60 + | `perNoteHeading` | `string` | `"Change history"` | Per-note heading text |
61 + | `siteWideHeading` | `string` | `"Changelog"` | Site-wide heading text |
62 + | `className` | `string` | `"rr-changelog"` | Root CSS class |
63 +
64 + ## Output
65 +
66 + The plugin appends a fragment like this to the `article.after-content` slot:
67 +
68 + ```html
69 + <section class="rr-changelog rr-changelog--note" data-changelog-note>
70 + <h2 class="rr-changelog__heading">Change history</h2>
71 + <ol class="rr-changelog__list">
72 + <li class="rr-changelog__item">
73 + <time class="rr-changelog__date" datetime="2026-09-30T09:00:00+09:00">2026-09-30</time>
74 + <span class="rr-changelog__subject">Fix the sidebar offset</span>
75 + <span class="rr-changelog__author">Author Name</span>
76 + <code class="rr-changelog__hash" title="…full hash…">abc1234</code>
77 + </li>
78 + </ol>
79 + </section>
80 + ```
81 +
82 + `data-changelog-note` and `data-changelog-site` mark the two fragments for
83 + styling and idempotency checks.
84 +
85 + ## App wiring (site-wide changelog)
86 +
87 + The site-wide list is data, not a page. The app owns the route; the plugin
88 + either fills the `article.after-content` slot of an existing note
89 + (`siteWide: true` + `siteWideSlug`, which requires a public note at that slug)
90 + or exposes the dataset for the app to render itself:
91 +
92 + ```ts
93 + import {
94 + buildSiteChangelog,
95 + GitChangelogReader,
96 + renderSiteChangelog,
97 + resolveChangelogOptions,
98 + } from "@riebeckite/plugin-changelog";
99 +
100 + const options = resolveChangelogOptions({ lookbackDays: 90 });
101 + const manifest = await content.getManifest();
102 + const reader = new GitChangelogReader({ cwd: "./content" });
103 + const commits = await reader.getRecentCommits();
104 + const dataset = buildSiteChangelog({
105 + entries: manifest.publicEntries,
106 + commits,
107 + contentIndex: manifest.contentIndex,
108 + options,
109 + });
110 + const html = renderSiteChangelog(dataset, options);
111 + ```
112 +
113 + The reference app renders manifest `bodySlots` in
114 + `apps/web/app/components/article/article.tsx`; a dedicated route can render the
115 + exported HTML directly.
116 +
117 + ## Failure behavior
118 +
119 + When `git` cannot be started, the plugin reports a `changelog-git-unavailable`
120 + warning diagnostic. When the content directory is not inside a Git working
121 + tree, it reports `changelog-content-outside-repository`. Either way a logger
122 + warning accompanies it and the build output is left untouched. The build never
123 + fails because history is unavailable, and the same guarantee holds for an empty
124 + repository.
125 +
126 + ## Style
127 +
128 + The package ships `style.css` with the stable `.rr-changelog` root hook. Themes
129 + can restyle it without editing the plugin:
130 +
131 + ```ts
132 + import "@riebeckite/plugin-changelog/style.css";
133 + ```
134 +
135 + ## Exports
136 +
137 + - `changelog(options?)` — plugin factory
138 + - `changelogPlugin` — alias of `changelog`
139 + - `resolveChangelogOptions(options?)` — apply option defaults
140 + - `GitChangelogReader` — Git-backed history reader (`getFileHistory`,
141 + `getRecentCommits`, `isAvailable`)
142 + - `buildNoteChangeHistory(entry, commits, options)` — per-note dataset
143 + - `buildSiteChangelog({ entries, commits, contentIndex, options })` — site-wide
144 + dataset
145 + - `renderNoteChangeHistory(history, options)` /
146 + `renderSiteChangelog(changelog, options)` — HTML renderers
147 + - `filterChangelogCommits(commits, options)` /
148 + `formatChangelogDate(date, options)` / `resolveLookbackSince(days, now?)` —
149 + pure helpers
150 + - Types: `ChangelogOptions`, `ResolvedChangelogOptions`, `ChangelogCommit`,
151 + `ChangelogRecord`, `NoteChangeHistory`, `SiteChangelog`, `SiteChangelogEntry`,
152 + `SiteChangelogNote`, `ChangelogDateFormat`, `GitChangelogReaderOptions`
153 +
154 + ## See also
155 +
156 + - [plugin-diff](./diff.en.md) — revision history and line diffs
157 + - [Plugin guide](../reference/plugin-api.en.md)
158 +