@riebeckite/plugin-changelog 0.0.22 → 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 CHANGED
@@ -15,13 +15,12 @@ commits. No client-side JavaScript is required.
15
15
  `git` access as [`@riebeckite/plugin-diff`](../diff/README.md) (`execFile` with
16
16
  graceful failure). It never invents its own filesystem layer.
17
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.
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.
25
24
 
26
25
  ## Usage
27
26
 
@@ -63,17 +62,18 @@ export default defineConfig({
63
62
 
64
63
  ## Display
65
64
 
66
- The plugin appends each note's change history to the `article.after-content` slot. When enabled, it is rendered after the article on eligible pages.
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.
67
69
 
68
70
  `data-changelog-note` and `data-changelog-site` mark the two fragments for
69
71
  styling and idempotency checks.
70
72
 
71
73
  ## App wiring (site-wide changelog)
72
74
 
73
- The site-wide list is data, not a page. The app owns the route; the plugin
74
- either fills the `article.after-content` slot of an existing note
75
- (`siteWide: true` + `siteWideSlug`, which requires a public note at that slug)
76
- 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:
77
77
 
78
78
  ```ts
79
79
  import {
@@ -96,10 +96,6 @@ const dataset = buildSiteChangelog({
96
96
  const html = renderSiteChangelog(dataset, options);
97
97
  ```
98
98
 
99
- The reference app renders manifest `bodySlots` in
100
- `apps/web/app/components/article/article.tsx`; a dedicated route can render the
101
- exported HTML directly.
102
-
103
99
  ## Failure behavior
104
100
 
105
101
  When `git` cannot be started, the plugin reports a `changelog-git-unavailable`
package/README_ja.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  `changelog()` は [`@riebeckite/plugin-diff`](../diff/README_ja.md) と同じ方法でローカル Git を読み取ります(`execFile` で `git` を実行し、失敗時は安全に縮退)。独自のファイルシステム層は作りません。
12
12
 
13
- 結果はマニフェストの `bodySlots` に出力します。公開エントリは `article.after-content` スロットに自分の履歴を受け取り、サイト側のレイアウトがそのスロットを描画するかどうかを決めます。プラグインは Plugin の境界を越えず、アプリケーションのルートも作りません。`siteWide` を有効にすると、`siteWideSlug` で指定したノートのスロットにサイト全体の変更履歴を書き込みます。そのノート(=ルート)はアプリまたは作者が所有するもので、プラグインが作るものではありません。クライアントスクリプトは登録しません。
13
+ 結果はプラグインの組み込み HTML レンダラーを通じて公開されます。公開エントリはそれぞれ変更履歴セクションを受け取り、サイト側のレイアウトが描画するかどうかを決めます。`siteWide` を有効にすると、サイト全体の変更履歴データセットがビルドされ、アプリが自身で描画できます。プラグインは manifest entry に markup を注入しません。描画する変更履歴セクションは Site が選びます。クライアントスクリプトは登録しません。
14
14
 
15
15
  ## 使い方
16
16
 
@@ -52,7 +52,7 @@ export default defineConfig({
52
52
 
53
53
  ## 表示
54
54
 
55
- ノートごとの変更履歴は `article.after-content` スロットへ追加され、有効にした対象ページの記事下で実際に描画されます。
55
+ 各ノートの変更履歴セクションはビルド時の HTML として公開されます。`perNote` が有効な場合、記事の後に表示されます。サイト全体の変更履歴データセットはビルドされ、アプリが renderer を通じて描画できます。
56
56
 
57
57
  `data-changelog-note` と `data-changelog-site` は、2 種類の断片をスタイリングと重複防止のために識別する属性です。
58
58
 
@@ -60,29 +60,6 @@ export default defineConfig({
60
60
 
61
61
  サイト全体の一覧はページではなくデータです。ルートはアプリが所有します。プラグインは既存ノートの `article.after-content` スロットに書き込む(`siteWide: true` と `siteWideSlug`。その slug の公開ノートが必要)か、データセットを公開してアプリ自身に描画させます。
62
62
 
63
- ```ts
64
- import {
65
- buildSiteChangelog,
66
- GitChangelogReader,
67
- renderSiteChangelog,
68
- resolveChangelogOptions,
69
- } from "@riebeckite/plugin-changelog";
70
-
71
- const options = resolveChangelogOptions({ lookbackDays: 90 });
72
- const manifest = await content.getManifest();
73
- const reader = new GitChangelogReader({ cwd: "./content" });
74
- const commits = await reader.getRecentCommits();
75
- const dataset = buildSiteChangelog({
76
- entries: manifest.publicEntries,
77
- commits,
78
- contentIndex: manifest.contentIndex,
79
- options,
80
- });
81
- const html = renderSiteChangelog(dataset, options);
82
- ```
83
-
84
- 参照アプリはマニフェストの `bodySlots` を `apps/web/app/components/article/article.tsx` で描画します。専用ルートでは公開された HTML をそのまま描画できます。
85
-
86
63
  ## 失敗時の挙動
87
64
 
88
65
  `git` を起動できない場合は `changelog-git-unavailable` 警告診断、コンテンツディレクトリが Git ワーキングツリーの外にある場合は `changelog-content-outside-repository` 警告診断を出し、どちらの場合もロガー警告を伴い、ビルド成果物には何も加えません。履歴が取得できないことを理由にビルドが失敗することはなく、空のリポジトリでも同じです。
@@ -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
- * At build time the plugin reads local Git history and publishes HTML
14
- * fragments through the manifest `bodySlots` mechanism:
15
+ * The plugin exposes data helpers and components the Site composes directly:
15
16
  *
16
- * - every public note gets its own "change history" section;
17
- * - optionally, a designated note gets the site-wide changelog.
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; the
20
- * plugin only fills the `article.after-content` slot. The same dataset is also
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
- onManifestCreated: (context) => applyChangelog(context, resolved),
397
- assets: [createStyleAsset(CHANGELOG_PLUGIN_NAME)]
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,
@@ -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.22",
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
- "@riebeckite/core": "0.0.22"
24
+ "hono": "^4.12.25",
25
+ "@riebeckite/core": "0.0.23"
19
26
  },
20
27
  "devDependencies": {
21
28
  "@types/node": "^24.5.2",