@riebeckite/plugin-changelog 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -34
- package/README_ja.md +7 -43
- package/dist/components/changelog.d.ts +7 -0
- package/dist/components/changelog.js +20 -0
- package/dist/index.d.ts +10 -10
- package/dist/index.js +91 -141
- package/dist/src/changelog.d.ts +1 -1
- package/dist/src/component-data.d.ts +9 -0
- package/dist/src/git/history_reader.d.ts +1 -0
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @riebeckite/plugin-changelog
|
|
2
2
|
|
|
3
|
+
<!-- Generated from docs/docs/plugins/changelog.md. Edit the canonical documentation in docs/docs/plugins and run `pnpm docs:sync`. -->
|
|
4
|
+
|
|
3
5
|
Build-time change history derived from local Git history. For every published
|
|
4
6
|
note the plugin adds a "change history" section with commit dates, subjects,
|
|
5
7
|
and authors, and it can build a site-wide changelog dataset from the notes'
|
|
@@ -13,13 +15,12 @@ commits. No client-side JavaScript is required.
|
|
|
13
15
|
`git` access as [`@riebeckite/plugin-diff`](../diff/README.md) (`execFile` with
|
|
14
16
|
graceful failure). It never invents its own filesystem layer.
|
|
15
17
|
|
|
16
|
-
Results are published through the
|
|
17
|
-
entry receives its own history
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
author, not by this plugin. No client script is registered.
|
|
18
|
+
Results are published through the plugin's built-in HTML renderers. Each public
|
|
19
|
+
entry receives its own change history section, and the site's layout decides
|
|
20
|
+
whether and where to render it. When `siteWide` is enabled, the site-wide
|
|
21
|
+
changelog dataset is built and available for the app to render itself. The
|
|
22
|
+
plugin contributes no markup to the manifest entry; the Site renders the
|
|
23
|
+
changelog sections it wants. No client script is registered.
|
|
23
24
|
|
|
24
25
|
## Usage
|
|
25
26
|
|
|
@@ -59,33 +60,20 @@ export default defineConfig({
|
|
|
59
60
|
| `siteWideHeading` | `string` | `"Changelog"` | Site-wide heading text |
|
|
60
61
|
| `className` | `string` | `"rr-changelog"` | Root CSS class |
|
|
61
62
|
|
|
62
|
-
##
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<h2 class="rr-changelog__heading">Change history</h2>
|
|
69
|
-
<ol class="rr-changelog__list">
|
|
70
|
-
<li class="rr-changelog__item">
|
|
71
|
-
<time class="rr-changelog__date" datetime="2026-09-30T09:00:00+09:00">2026-09-30</time>
|
|
72
|
-
<span class="rr-changelog__subject">Fix the sidebar offset</span>
|
|
73
|
-
<span class="rr-changelog__author">Author Name</span>
|
|
74
|
-
<code class="rr-changelog__hash" title="…full hash…">abc1234</code>
|
|
75
|
-
</li>
|
|
76
|
-
</ol>
|
|
77
|
-
</section>
|
|
78
|
-
```
|
|
63
|
+
## Display
|
|
64
|
+
|
|
65
|
+
Each note's change history section is published as build-time HTML. When
|
|
66
|
+
`perNote` is enabled, it appears after the article on eligible pages. The
|
|
67
|
+
site-wide changelog dataset is built and available for the app to render
|
|
68
|
+
itself via the exported renderers.
|
|
79
69
|
|
|
80
70
|
`data-changelog-note` and `data-changelog-site` mark the two fragments for
|
|
81
71
|
styling and idempotency checks.
|
|
82
72
|
|
|
83
73
|
## App wiring (site-wide changelog)
|
|
84
74
|
|
|
85
|
-
The site-wide list is data, not a page. The app owns the route
|
|
86
|
-
|
|
87
|
-
(`siteWide: true` + `siteWideSlug`, which requires a public note at that slug)
|
|
88
|
-
or exposes the dataset for the app to render itself:
|
|
75
|
+
The site-wide list is data, not a page. The app owns the route. The plugin
|
|
76
|
+
exposes the dataset for the app to render itself via the exported renderers:
|
|
89
77
|
|
|
90
78
|
```ts
|
|
91
79
|
import {
|
|
@@ -108,10 +96,6 @@ const dataset = buildSiteChangelog({
|
|
|
108
96
|
const html = renderSiteChangelog(dataset, options);
|
|
109
97
|
```
|
|
110
98
|
|
|
111
|
-
The reference app renders manifest `bodySlots` in
|
|
112
|
-
`apps/web/app/components/article/article.tsx`; a dedicated route can render the
|
|
113
|
-
exported HTML directly.
|
|
114
|
-
|
|
115
99
|
## Failure behavior
|
|
116
100
|
|
|
117
101
|
When `git` cannot be started, the plugin reports a `changelog-git-unavailable`
|
|
@@ -152,5 +136,4 @@ import "@riebeckite/plugin-changelog/style.css";
|
|
|
152
136
|
## See also
|
|
153
137
|
|
|
154
138
|
- [plugin-diff](../diff/README.md) — revision history and line diffs
|
|
155
|
-
- [Plugin guide](
|
|
156
|
-
|
|
139
|
+
- [Plugin guide](https://github.com/Rerurate514/riebeckite/blob/main/docs/docs/reference/plugin-api.md)
|
package/README_ja.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @riebeckite/plugin-changelog
|
|
2
2
|
|
|
3
|
+
<!-- Generated from docs/docs/plugins/changelog.ja.md. Edit the canonical documentation in docs/docs/plugins and run `pnpm docs:sync`. -->
|
|
4
|
+
|
|
3
5
|
ローカル Git の履歴からビルド時に変更履歴を生成するプラグインです。公開ノートごとにコミット日・件名・作成者を並べた「変更履歴」セクションを追加し、サイト全体の変更履歴データセットも組み立てられます。クライアント JavaScript は不要です。
|
|
4
6
|
|
|
5
7
|
[English](./README.md)
|
|
@@ -8,7 +10,7 @@
|
|
|
8
10
|
|
|
9
11
|
`changelog()` は [`@riebeckite/plugin-diff`](../diff/README_ja.md) と同じ方法でローカル Git を読み取ります(`execFile` で `git` を実行し、失敗時は安全に縮退)。独自のファイルシステム層は作りません。
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
結果はプラグインの組み込み HTML レンダラーを通じて公開されます。公開エントリはそれぞれ変更履歴セクションを受け取り、サイト側のレイアウトが描画するかどうかを決めます。`siteWide` を有効にすると、サイト全体の変更履歴データセットがビルドされ、アプリが自身で描画できます。プラグインは manifest entry に markup を注入しません。描画する変更履歴セクションは Site が選びます。クライアントスクリプトは登録しません。
|
|
12
14
|
|
|
13
15
|
## 使い方
|
|
14
16
|
|
|
@@ -48,23 +50,9 @@ export default defineConfig({
|
|
|
48
50
|
| `siteWideHeading` | `string` | `"Changelog"` | サイト全体の見出し文言 |
|
|
49
51
|
| `className` | `string` | `"rr-changelog"` | ルート CSS クラス |
|
|
50
52
|
|
|
51
|
-
##
|
|
52
|
-
|
|
53
|
-
`
|
|
54
|
-
|
|
55
|
-
```html
|
|
56
|
-
<section class="rr-changelog rr-changelog--note" data-changelog-note>
|
|
57
|
-
<h2 class="rr-changelog__heading">Change history</h2>
|
|
58
|
-
<ol class="rr-changelog__list">
|
|
59
|
-
<li class="rr-changelog__item">
|
|
60
|
-
<time class="rr-changelog__date" datetime="2026-09-30T09:00:00+09:00">2026-09-30</time>
|
|
61
|
-
<span class="rr-changelog__subject">Fix the sidebar offset</span>
|
|
62
|
-
<span class="rr-changelog__author">Author Name</span>
|
|
63
|
-
<code class="rr-changelog__hash" title="…完全なハッシュ…">abc1234</code>
|
|
64
|
-
</li>
|
|
65
|
-
</ol>
|
|
66
|
-
</section>
|
|
67
|
-
```
|
|
53
|
+
## 表示
|
|
54
|
+
|
|
55
|
+
各ノートの変更履歴セクションはビルド時の HTML として公開されます。`perNote` が有効な場合、記事の後に表示されます。サイト全体の変更履歴データセットはビルドされ、アプリが renderer を通じて描画できます。
|
|
68
56
|
|
|
69
57
|
`data-changelog-note` と `data-changelog-site` は、2 種類の断片をスタイリングと重複防止のために識別する属性です。
|
|
70
58
|
|
|
@@ -72,29 +60,6 @@ export default defineConfig({
|
|
|
72
60
|
|
|
73
61
|
サイト全体の一覧はページではなくデータです。ルートはアプリが所有します。プラグインは既存ノートの `article.after-content` スロットに書き込む(`siteWide: true` と `siteWideSlug`。その slug の公開ノートが必要)か、データセットを公開してアプリ自身に描画させます。
|
|
74
62
|
|
|
75
|
-
```ts
|
|
76
|
-
import {
|
|
77
|
-
buildSiteChangelog,
|
|
78
|
-
GitChangelogReader,
|
|
79
|
-
renderSiteChangelog,
|
|
80
|
-
resolveChangelogOptions,
|
|
81
|
-
} from "@riebeckite/plugin-changelog";
|
|
82
|
-
|
|
83
|
-
const options = resolveChangelogOptions({ lookbackDays: 90 });
|
|
84
|
-
const manifest = await content.getManifest();
|
|
85
|
-
const reader = new GitChangelogReader({ cwd: "./content" });
|
|
86
|
-
const commits = await reader.getRecentCommits();
|
|
87
|
-
const dataset = buildSiteChangelog({
|
|
88
|
-
entries: manifest.publicEntries,
|
|
89
|
-
commits,
|
|
90
|
-
contentIndex: manifest.contentIndex,
|
|
91
|
-
options,
|
|
92
|
-
});
|
|
93
|
-
const html = renderSiteChangelog(dataset, options);
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
参照アプリはマニフェストの `bodySlots` を `apps/web/app/components/article/article.tsx` で描画します。専用ルートでは公開された HTML をそのまま描画できます。
|
|
97
|
-
|
|
98
63
|
## 失敗時の挙動
|
|
99
64
|
|
|
100
65
|
`git` を起動できない場合は `changelog-git-unavailable` 警告診断、コンテンツディレクトリが Git ワーキングツリーの外にある場合は `changelog-content-outside-repository` 警告診断を出し、どちらの場合もロガー警告を伴い、ビルド成果物には何も加えません。履歴が取得できないことを理由にビルドが失敗することはなく、空のリポジトリでも同じです。
|
|
@@ -122,5 +87,4 @@ import "@riebeckite/plugin-changelog/style.css";
|
|
|
122
87
|
## 関連資料
|
|
123
88
|
|
|
124
89
|
- [plugin-diff](../diff/README_ja.md) — リビジョン履歴と行差分
|
|
125
|
-
- [プラグインシステム](
|
|
126
|
-
|
|
90
|
+
- [プラグインシステム](https://github.com/Rerurate514/riebeckite/blob/main/docs/docs/reference/plugin-api.ja.md)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { NoteChangeHistory } from "../src/types.js";
|
|
2
|
+
export type ChangelogProps = {
|
|
3
|
+
history: NoteChangeHistory | null;
|
|
4
|
+
className?: string;
|
|
5
|
+
};
|
|
6
|
+
declare const Changelog: ({ history, className, }: Readonly<ChangelogProps>) => import("hono/jsx/jsx-dev-runtime").JSX.Element;
|
|
7
|
+
export default Changelog;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// components/changelog.tsx
|
|
2
|
+
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
3
|
+
var Changelog = (({
|
|
4
|
+
history,
|
|
5
|
+
className = "rb-changelog"
|
|
6
|
+
}) => {
|
|
7
|
+
if (!history || history.commits.length === 0) return null;
|
|
8
|
+
return /* @__PURE__ */ jsxs("section", { class: className, "data-changelog-note": true, children: [
|
|
9
|
+
/* @__PURE__ */ jsx("h2", { children: "Change history" }),
|
|
10
|
+
/* @__PURE__ */ jsx("ol", { children: history.commits.map((record) => /* @__PURE__ */ jsxs("li", { children: [
|
|
11
|
+
/* @__PURE__ */ jsx("time", { datetime: record.dateIso, children: record.date }),
|
|
12
|
+
" ",
|
|
13
|
+
record.subject
|
|
14
|
+
] }, record.hash)) })
|
|
15
|
+
] });
|
|
16
|
+
});
|
|
17
|
+
var changelog_default = Changelog;
|
|
18
|
+
export {
|
|
19
|
+
changelog_default as default
|
|
20
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import type { ChangelogOptions } from "./src/types.js";
|
|
2
|
+
export type { ChangelogProps } from "./components/changelog.js";
|
|
3
|
+
export { default as Changelog } from "./components/changelog.js";
|
|
2
4
|
export { buildNoteChangeHistory, buildSiteChangelog, filterChangelogCommits, formatChangelogDate, resolveContentFilePath, resolveLookbackSince, } from "./src/changelog.js";
|
|
5
|
+
export type { ChangelogHistoryLoader } from "./src/component-data.js";
|
|
6
|
+
export { getChangelog } from "./src/component-data.js";
|
|
3
7
|
export { GitChangelogReader, type GitLogWindow, } from "./src/git/history_reader.js";
|
|
4
8
|
export { DEFAULT_CHANGELOG_CLASS_NAME, DEFAULT_CHANGELOG_HEADING, DEFAULT_CHANGELOG_LOCALE, DEFAULT_CHANGELOG_MAX_PER_NOTE, DEFAULT_CHANGELOG_MAX_SITE_WIDE, DEFAULT_CHANGELOG_PER_NOTE_HEADING, DEFAULT_CHANGELOG_SITE_WIDE_HEADING, DEFAULT_CHANGELOG_SITE_WIDE_SLUG, resolveChangelogOptions, } from "./src/options.js";
|
|
5
9
|
export { CHANGELOG_NOTE_ATTRIBUTE, CHANGELOG_SITE_ATTRIBUTE, renderNoteChangeHistory, renderSiteChangelog, } from "./src/render.js";
|
|
6
10
|
export type { ChangelogCommit, ChangelogDateFormat, ChangelogOptions, ChangelogRecord, GitChangelogReaderOptions, NoteChangeHistory, ResolvedChangelogOptions, SiteChangelog, SiteChangelogEntry, SiteChangelogNote, } from "./src/types.js";
|
|
7
11
|
export declare const CHANGELOG_PLUGIN_NAME = "changelog";
|
|
8
|
-
/** Site layout slot the generated history is written to. */
|
|
9
|
-
export declare const CHANGELOG_BODY_SLOT = "article.after-content";
|
|
10
12
|
/**
|
|
11
13
|
* Git-backed change history for Riebeckite sites.
|
|
12
14
|
*
|
|
13
|
-
*
|
|
14
|
-
* fragments through the manifest `bodySlots` mechanism:
|
|
15
|
+
* The plugin exposes data helpers and components the Site composes directly:
|
|
15
16
|
*
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
17
|
+
* - `getChangelog` / `Changelog` render a note's own "change history" section;
|
|
18
|
+
* - `buildSiteChangelog` / `renderSiteChangelog` render the site-wide changelog
|
|
19
|
+
* for a designated note.
|
|
18
20
|
*
|
|
19
|
-
* The plugin never creates a route. The Site owns the note and the layout
|
|
20
|
-
*
|
|
21
|
-
* exported (`buildSiteChangelog`, `renderSiteChangelog`) so a Site can render
|
|
22
|
-
* it from its own route. No client JavaScript is required.
|
|
21
|
+
* The plugin never creates a route. The Site owns the note and the layout, and
|
|
22
|
+
* decides where the sections appear. No client JavaScript is required.
|
|
23
23
|
*
|
|
24
24
|
* When Git is unavailable the build is left untouched and a warning diagnostic
|
|
25
25
|
* is reported instead of failing.
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
// index.ts
|
|
2
2
|
import {
|
|
3
|
-
appendContentBodySlot,
|
|
4
3
|
createStyleAsset,
|
|
5
4
|
definePlugin
|
|
6
5
|
} from "@riebeckite/core";
|
|
7
6
|
|
|
7
|
+
// components/changelog.tsx
|
|
8
|
+
import { jsx, jsxs } from "hono/jsx/jsx-runtime";
|
|
9
|
+
var Changelog = (({
|
|
10
|
+
history,
|
|
11
|
+
className = "rb-changelog"
|
|
12
|
+
}) => {
|
|
13
|
+
if (!history || history.commits.length === 0) return null;
|
|
14
|
+
return /* @__PURE__ */ jsxs("section", { class: className, "data-changelog-note": true, children: [
|
|
15
|
+
/* @__PURE__ */ jsx("h2", { children: "Change history" }),
|
|
16
|
+
/* @__PURE__ */ jsx("ol", { children: history.commits.map((record) => /* @__PURE__ */ jsxs("li", { children: [
|
|
17
|
+
/* @__PURE__ */ jsx("time", { datetime: record.dateIso, children: record.date }),
|
|
18
|
+
" ",
|
|
19
|
+
record.subject
|
|
20
|
+
] }, record.hash)) })
|
|
21
|
+
] });
|
|
22
|
+
});
|
|
23
|
+
var changelog_default = Changelog;
|
|
24
|
+
|
|
8
25
|
// src/changelog.ts
|
|
9
26
|
function resolveLookbackSince(lookbackDays, now = /* @__PURE__ */ new Date()) {
|
|
10
27
|
if (lookbackDays === void 0) return void 0;
|
|
@@ -93,6 +110,54 @@ function normalizePath(filePath) {
|
|
|
93
110
|
return filePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
94
111
|
}
|
|
95
112
|
|
|
113
|
+
// src/options.ts
|
|
114
|
+
var DEFAULT_CHANGELOG_DATE_FORMAT = "iso";
|
|
115
|
+
var DEFAULT_CHANGELOG_LOCALE = "en";
|
|
116
|
+
var DEFAULT_CHANGELOG_PER_NOTE = true;
|
|
117
|
+
var DEFAULT_CHANGELOG_SITE_WIDE = false;
|
|
118
|
+
var DEFAULT_CHANGELOG_SITE_WIDE_SLUG = "changelog";
|
|
119
|
+
var DEFAULT_CHANGELOG_MAX_PER_NOTE = 10;
|
|
120
|
+
var DEFAULT_CHANGELOG_MAX_SITE_WIDE = 50;
|
|
121
|
+
var DEFAULT_CHANGELOG_SHOW_AUTHOR = true;
|
|
122
|
+
var DEFAULT_CHANGELOG_HEADING = true;
|
|
123
|
+
var DEFAULT_CHANGELOG_PER_NOTE_HEADING = "Change history";
|
|
124
|
+
var DEFAULT_CHANGELOG_SITE_WIDE_HEADING = "Changelog";
|
|
125
|
+
var DEFAULT_CHANGELOG_CLASS_NAME = "rr-changelog";
|
|
126
|
+
function resolveChangelogOptions(options = {}) {
|
|
127
|
+
return {
|
|
128
|
+
cwd: optionalText(options.cwd),
|
|
129
|
+
lookbackDays: options.lookbackDays,
|
|
130
|
+
dateFormat: options.dateFormat ?? DEFAULT_CHANGELOG_DATE_FORMAT,
|
|
131
|
+
locale: optionalText(options.locale) ?? DEFAULT_CHANGELOG_LOCALE,
|
|
132
|
+
perNote: options.perNote ?? DEFAULT_CHANGELOG_PER_NOTE,
|
|
133
|
+
siteWide: options.siteWide ?? DEFAULT_CHANGELOG_SITE_WIDE,
|
|
134
|
+
siteWideSlug: optionalText(options.siteWideSlug) ?? DEFAULT_CHANGELOG_SITE_WIDE_SLUG,
|
|
135
|
+
maxPerNote: options.maxPerNote ?? DEFAULT_CHANGELOG_MAX_PER_NOTE,
|
|
136
|
+
maxSiteWide: options.maxSiteWide ?? DEFAULT_CHANGELOG_MAX_SITE_WIDE,
|
|
137
|
+
showAuthor: options.showAuthor ?? DEFAULT_CHANGELOG_SHOW_AUTHOR,
|
|
138
|
+
heading: options.heading ?? DEFAULT_CHANGELOG_HEADING,
|
|
139
|
+
perNoteHeading: optionalText(options.perNoteHeading) ?? DEFAULT_CHANGELOG_PER_NOTE_HEADING,
|
|
140
|
+
siteWideHeading: optionalText(options.siteWideHeading) ?? DEFAULT_CHANGELOG_SITE_WIDE_HEADING,
|
|
141
|
+
className: optionalText(options.className) ?? DEFAULT_CHANGELOG_CLASS_NAME
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function optionalText(value) {
|
|
145
|
+
if (typeof value !== "string") return void 0;
|
|
146
|
+
const trimmed = value.trim();
|
|
147
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/component-data.ts
|
|
151
|
+
var getChangelog = async ({ view, entry, loadCommits, options }) => {
|
|
152
|
+
if (!view.findRoutableBySlug(entry.slug)) return null;
|
|
153
|
+
const commits = await loadCommits(entry);
|
|
154
|
+
return buildNoteChangeHistory(
|
|
155
|
+
entry,
|
|
156
|
+
commits,
|
|
157
|
+
resolveChangelogOptions(options)
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
96
161
|
// src/git/history_reader.ts
|
|
97
162
|
import path2 from "node:path";
|
|
98
163
|
|
|
@@ -189,9 +254,6 @@ function normalizeContentPath(value) {
|
|
|
189
254
|
function isResolvedRepository(resolution) {
|
|
190
255
|
return resolution.kind === "resolved";
|
|
191
256
|
}
|
|
192
|
-
function isRepositoryUnavailable(resolution) {
|
|
193
|
-
return resolution.kind !== "resolved";
|
|
194
|
-
}
|
|
195
257
|
function parseRevParseOutput(stdout) {
|
|
196
258
|
const [toplevel = "", prefix = ""] = stdout.split(/\r?\n/);
|
|
197
259
|
return [toplevel.trim(), prefix.trim()];
|
|
@@ -224,6 +286,23 @@ var GitChangelogReader = class {
|
|
|
224
286
|
async isAvailable() {
|
|
225
287
|
return isResolvedRepository(await this.resolveRepository());
|
|
226
288
|
}
|
|
289
|
+
async getStateFingerprint() {
|
|
290
|
+
const repository = await this.#resolvePaths();
|
|
291
|
+
if (!repository) return null;
|
|
292
|
+
const results = await Promise.all([
|
|
293
|
+
runGit(["rev-parse", "HEAD"], repository.repositoryRoot),
|
|
294
|
+
runGit(
|
|
295
|
+
["status", "--porcelain=v1", "--untracked-files=all"],
|
|
296
|
+
repository.repositoryRoot
|
|
297
|
+
),
|
|
298
|
+
runGit(
|
|
299
|
+
["for-each-ref", "--format=%(refname):%(objectname)"],
|
|
300
|
+
repository.repositoryRoot
|
|
301
|
+
)
|
|
302
|
+
]);
|
|
303
|
+
if (results.some((result) => !result.ok)) return null;
|
|
304
|
+
return results.filter((result) => result.ok).map((result) => result.stdout).join("");
|
|
305
|
+
}
|
|
227
306
|
/**
|
|
228
307
|
* Commit history for one file, newest first, following renames.
|
|
229
308
|
*
|
|
@@ -300,43 +379,6 @@ function parseCommitRecord(chunk) {
|
|
|
300
379
|
};
|
|
301
380
|
}
|
|
302
381
|
|
|
303
|
-
// src/options.ts
|
|
304
|
-
var DEFAULT_CHANGELOG_DATE_FORMAT = "iso";
|
|
305
|
-
var DEFAULT_CHANGELOG_LOCALE = "en";
|
|
306
|
-
var DEFAULT_CHANGELOG_PER_NOTE = true;
|
|
307
|
-
var DEFAULT_CHANGELOG_SITE_WIDE = false;
|
|
308
|
-
var DEFAULT_CHANGELOG_SITE_WIDE_SLUG = "changelog";
|
|
309
|
-
var DEFAULT_CHANGELOG_MAX_PER_NOTE = 10;
|
|
310
|
-
var DEFAULT_CHANGELOG_MAX_SITE_WIDE = 50;
|
|
311
|
-
var DEFAULT_CHANGELOG_SHOW_AUTHOR = true;
|
|
312
|
-
var DEFAULT_CHANGELOG_HEADING = true;
|
|
313
|
-
var DEFAULT_CHANGELOG_PER_NOTE_HEADING = "Change history";
|
|
314
|
-
var DEFAULT_CHANGELOG_SITE_WIDE_HEADING = "Changelog";
|
|
315
|
-
var DEFAULT_CHANGELOG_CLASS_NAME = "rr-changelog";
|
|
316
|
-
function resolveChangelogOptions(options = {}) {
|
|
317
|
-
return {
|
|
318
|
-
cwd: optionalText(options.cwd),
|
|
319
|
-
lookbackDays: options.lookbackDays,
|
|
320
|
-
dateFormat: options.dateFormat ?? DEFAULT_CHANGELOG_DATE_FORMAT,
|
|
321
|
-
locale: optionalText(options.locale) ?? DEFAULT_CHANGELOG_LOCALE,
|
|
322
|
-
perNote: options.perNote ?? DEFAULT_CHANGELOG_PER_NOTE,
|
|
323
|
-
siteWide: options.siteWide ?? DEFAULT_CHANGELOG_SITE_WIDE,
|
|
324
|
-
siteWideSlug: optionalText(options.siteWideSlug) ?? DEFAULT_CHANGELOG_SITE_WIDE_SLUG,
|
|
325
|
-
maxPerNote: options.maxPerNote ?? DEFAULT_CHANGELOG_MAX_PER_NOTE,
|
|
326
|
-
maxSiteWide: options.maxSiteWide ?? DEFAULT_CHANGELOG_MAX_SITE_WIDE,
|
|
327
|
-
showAuthor: options.showAuthor ?? DEFAULT_CHANGELOG_SHOW_AUTHOR,
|
|
328
|
-
heading: options.heading ?? DEFAULT_CHANGELOG_HEADING,
|
|
329
|
-
perNoteHeading: optionalText(options.perNoteHeading) ?? DEFAULT_CHANGELOG_PER_NOTE_HEADING,
|
|
330
|
-
siteWideHeading: optionalText(options.siteWideHeading) ?? DEFAULT_CHANGELOG_SITE_WIDE_HEADING,
|
|
331
|
-
className: optionalText(options.className) ?? DEFAULT_CHANGELOG_CLASS_NAME
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
function optionalText(value) {
|
|
335
|
-
if (typeof value !== "string") return void 0;
|
|
336
|
-
const trimmed = value.trim();
|
|
337
|
-
return trimmed === "" ? void 0 : trimmed;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
382
|
// src/render.ts
|
|
341
383
|
import { escapeHtml, escapeHtmlAttribute } from "@riebeckite/core";
|
|
342
384
|
var CHANGELOG_NOTE_ATTRIBUTE = "data-changelog-note";
|
|
@@ -384,113 +426,20 @@ function renderNotes(notes, className) {
|
|
|
384
426
|
|
|
385
427
|
// index.ts
|
|
386
428
|
var CHANGELOG_PLUGIN_NAME = "changelog";
|
|
387
|
-
var CHANGELOG_BODY_SLOT = "article.after-content";
|
|
388
|
-
var PER_NOTE_CONCURRENCY = 4;
|
|
389
429
|
function changelog(options = {}) {
|
|
390
|
-
const resolved = resolveChangelogOptions(options);
|
|
391
430
|
return definePlugin({
|
|
392
431
|
name: CHANGELOG_PLUGIN_NAME,
|
|
393
|
-
outputDependencies: [{ type: "unknown" }],
|
|
394
432
|
options,
|
|
395
433
|
validateOptions: validateChangelogOptions,
|
|
396
|
-
|
|
397
|
-
|
|
434
|
+
assets: [
|
|
435
|
+
createStyleAsset(
|
|
436
|
+
CHANGELOG_PLUGIN_NAME,
|
|
437
|
+
"@riebeckite/plugin-changelog/style.css"
|
|
438
|
+
)
|
|
439
|
+
]
|
|
398
440
|
});
|
|
399
441
|
}
|
|
400
442
|
var changelogPlugin = changelog;
|
|
401
|
-
async function applyChangelog(context, options) {
|
|
402
|
-
if (!options.perNote && !options.siteWide) return;
|
|
403
|
-
const reader = new GitChangelogReader({
|
|
404
|
-
cwd: resolveContentRoot(context, options)
|
|
405
|
-
});
|
|
406
|
-
const resolution = await reader.resolveRepository();
|
|
407
|
-
if (isRepositoryUnavailable(resolution)) {
|
|
408
|
-
reportRepositoryUnavailable(context, resolution);
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
const since = resolveLookbackSince(options.lookbackDays);
|
|
412
|
-
if (options.perNote) {
|
|
413
|
-
await forEachWithConcurrency(
|
|
414
|
-
context.manifest.publicEntries,
|
|
415
|
-
PER_NOTE_CONCURRENCY,
|
|
416
|
-
async (entry) => {
|
|
417
|
-
if (options.siteWide && entry.slug === options.siteWideSlug) return;
|
|
418
|
-
await applyNoteHistory(context, options, reader, entry, since);
|
|
419
|
-
}
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
if (options.siteWide) {
|
|
423
|
-
await applySiteChangelog(context, options, reader, since);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
async function applyNoteHistory(context, options, reader, entry, since) {
|
|
427
|
-
if (hasSlot(entry, CHANGELOG_NOTE_ATTRIBUTE)) return;
|
|
428
|
-
const filePath = resolveEntryFilePath(entry, context.manifest.contentIndex);
|
|
429
|
-
const commits = await reader.getFileHistory(filePath, { since });
|
|
430
|
-
const history = buildNoteChangeHistory(entry, commits, options);
|
|
431
|
-
const html = renderNoteChangeHistory(history, options);
|
|
432
|
-
if (html) appendContentBodySlot(entry, CHANGELOG_BODY_SLOT, html);
|
|
433
|
-
}
|
|
434
|
-
async function applySiteChangelog(context, options, reader, since) {
|
|
435
|
-
const target = context.manifest.publicEntries.find(
|
|
436
|
-
(entry) => entry.slug === options.siteWideSlug
|
|
437
|
-
);
|
|
438
|
-
if (!target) {
|
|
439
|
-
context.diagnostics.push({
|
|
440
|
-
code: "changelog-target-missing",
|
|
441
|
-
severity: "warning",
|
|
442
|
-
pluginName: CHANGELOG_PLUGIN_NAME,
|
|
443
|
-
message: `changelog: the site-wide target note "${options.siteWideSlug}" was not found; the site-wide changelog was skipped.`,
|
|
444
|
-
suggestion: `Create a public note with slug "${options.siteWideSlug}" or set siteWideSlug to an existing note.`
|
|
445
|
-
});
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
if (hasSlot(target, CHANGELOG_SITE_ATTRIBUTE)) return;
|
|
449
|
-
const commits = await reader.getRecentCommits({ since });
|
|
450
|
-
const changelog2 = buildSiteChangelog({
|
|
451
|
-
entries: context.manifest.publicEntries,
|
|
452
|
-
commits,
|
|
453
|
-
contentIndex: context.manifest.contentIndex,
|
|
454
|
-
options
|
|
455
|
-
});
|
|
456
|
-
const html = renderSiteChangelog(changelog2, options);
|
|
457
|
-
if (html) appendContentBodySlot(target, CHANGELOG_BODY_SLOT, html);
|
|
458
|
-
}
|
|
459
|
-
function resolveEntryFilePath(entry, contentIndex) {
|
|
460
|
-
return resolveContentFilePath(contentIndex, entry.slug) ?? `${entry.slug}.md`;
|
|
461
|
-
}
|
|
462
|
-
function hasSlot(entry, attribute) {
|
|
463
|
-
return entry.bodySlots?.[CHANGELOG_BODY_SLOT]?.includes(attribute) ?? false;
|
|
464
|
-
}
|
|
465
|
-
function resolveContentRoot(context, options) {
|
|
466
|
-
return options.cwd ?? context.config?.content.directory ?? process.cwd();
|
|
467
|
-
}
|
|
468
|
-
function reportRepositoryUnavailable(context, resolution) {
|
|
469
|
-
const gitUnavailable = resolution.kind === "git-unavailable";
|
|
470
|
-
const message = gitUnavailable ? `changelog: Git could not be started; change history was skipped. (${resolution.detail})` : `changelog: the content directory is not inside a Git working tree; change history was skipped. (${resolution.detail})`;
|
|
471
|
-
context.diagnostics.push({
|
|
472
|
-
code: gitUnavailable ? "changelog-git-unavailable" : "changelog-content-outside-repository",
|
|
473
|
-
severity: "warning",
|
|
474
|
-
pluginName: CHANGELOG_PLUGIN_NAME,
|
|
475
|
-
message,
|
|
476
|
-
suggestion: gitUnavailable ? "Install Git and make sure it is available on PATH." : "Move the content directory inside a Git working tree, or set the plugin's cwd option to a path inside one."
|
|
477
|
-
});
|
|
478
|
-
context.logger.warn(message);
|
|
479
|
-
}
|
|
480
|
-
async function forEachWithConcurrency(items, limit, work) {
|
|
481
|
-
let index = 0;
|
|
482
|
-
const workers = Array.from(
|
|
483
|
-
{ length: Math.min(limit, items.length) },
|
|
484
|
-
async () => {
|
|
485
|
-
while (index < items.length) {
|
|
486
|
-
const current = items[index];
|
|
487
|
-
index += 1;
|
|
488
|
-
await work(current);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
);
|
|
492
|
-
await Promise.all(workers);
|
|
493
|
-
}
|
|
494
443
|
function validateChangelogOptions(options) {
|
|
495
444
|
if (!options) return [];
|
|
496
445
|
const issues = [];
|
|
@@ -539,10 +488,10 @@ function validateChangelogOptions(options) {
|
|
|
539
488
|
return issues;
|
|
540
489
|
}
|
|
541
490
|
export {
|
|
542
|
-
CHANGELOG_BODY_SLOT,
|
|
543
491
|
CHANGELOG_NOTE_ATTRIBUTE,
|
|
544
492
|
CHANGELOG_PLUGIN_NAME,
|
|
545
493
|
CHANGELOG_SITE_ATTRIBUTE,
|
|
494
|
+
changelog_default as Changelog,
|
|
546
495
|
DEFAULT_CHANGELOG_CLASS_NAME,
|
|
547
496
|
DEFAULT_CHANGELOG_HEADING,
|
|
548
497
|
DEFAULT_CHANGELOG_LOCALE,
|
|
@@ -558,6 +507,7 @@ export {
|
|
|
558
507
|
changelogPlugin,
|
|
559
508
|
filterChangelogCommits,
|
|
560
509
|
formatChangelogDate,
|
|
510
|
+
getChangelog,
|
|
561
511
|
renderNoteChangeHistory,
|
|
562
512
|
renderSiteChangelog,
|
|
563
513
|
resolveChangelogOptions,
|
package/dist/src/changelog.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare function formatChangelogDate(isoDate: string, options: ResolvedCh
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function filterChangelogCommits(commits: readonly ChangelogCommit[], options: ResolvedChangelogOptions): ChangelogCommit[];
|
|
16
16
|
/** Builds the per-note history for one entry. */
|
|
17
|
-
export declare function buildNoteChangeHistory(entry: ContentManifestEntry, commits: readonly ChangelogCommit[], options: ResolvedChangelogOptions): NoteChangeHistory;
|
|
17
|
+
export declare function buildNoteChangeHistory(entry: Pick<ContentManifestEntry, "slug" | "permalink" | "title">, commits: readonly ChangelogCommit[], options: ResolvedChangelogOptions): NoteChangeHistory;
|
|
18
18
|
/**
|
|
19
19
|
* Builds the site-wide changelog dataset. Each commit is linked to the notes
|
|
20
20
|
* it touched by matching changed paths against the manifest content index.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PluginDataHelper, PublicContentEntry, PublicContentView } from "@riebeckite/core";
|
|
2
|
+
import type { ChangelogCommit, ChangelogOptions, NoteChangeHistory } from "./types.js";
|
|
3
|
+
export type ChangelogHistoryLoader = (entry: PublicContentEntry) => Promise<readonly ChangelogCommit[]>;
|
|
4
|
+
export declare const getChangelog: PluginDataHelper<{
|
|
5
|
+
view: PublicContentView;
|
|
6
|
+
entry: PublicContentEntry;
|
|
7
|
+
loadCommits: ChangelogHistoryLoader;
|
|
8
|
+
options?: ChangelogOptions;
|
|
9
|
+
}, Promise<NoteChangeHistory | null>>;
|
|
@@ -25,6 +25,7 @@ export declare class GitChangelogReader {
|
|
|
25
25
|
resolveRepository(): Promise<RepositoryPathsResolution>;
|
|
26
26
|
/** True when the content directory lives inside a Git work tree. */
|
|
27
27
|
isAvailable(): Promise<boolean>;
|
|
28
|
+
getStateFingerprint(): Promise<string | null>;
|
|
28
29
|
/**
|
|
29
30
|
* Commit history for one file, newest first, following renames.
|
|
30
31
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riebeckite/plugin-changelog",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,10 +12,17 @@
|
|
|
12
12
|
"source": "./index.ts",
|
|
13
13
|
"default": "./dist/index.js"
|
|
14
14
|
},
|
|
15
|
+
"./components": {
|
|
16
|
+
"types": "./dist/components/changelog.d.ts",
|
|
17
|
+
"import": "./dist/components/changelog.js",
|
|
18
|
+
"source": "./components/changelog.tsx",
|
|
19
|
+
"default": "./dist/components/changelog.js"
|
|
20
|
+
},
|
|
15
21
|
"./style.css": "./style.css"
|
|
16
22
|
},
|
|
17
23
|
"dependencies": {
|
|
18
|
-
"
|
|
24
|
+
"hono": "^4.12.25",
|
|
25
|
+
"@riebeckite/core": "0.0.23"
|
|
19
26
|
},
|
|
20
27
|
"devDependencies": {
|
|
21
28
|
"@types/node": "^24.5.2",
|