@riebeckite/plugin-changelog 0.0.32 → 0.0.33

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
@@ -117,7 +117,7 @@ import "@riebeckite/plugin-changelog/style.css";
117
117
  ## Exports
118
118
 
119
119
  - `changelog(options?)` — plugin factory
120
- - `changelogPlugin` — alias of `changelog`
120
+ - `changelog` — alias of `changelog`
121
121
  - `resolveChangelogOptions(options?)` — apply option defaults
122
122
  - `GitChangelogReader` — Git-backed history reader (`getFileHistory`,
123
123
  `getRecentCommits`, `isAvailable`)
package/README_ja.md CHANGED
@@ -8,9 +8,9 @@
8
8
 
9
9
  ## できること
10
10
 
11
- `changelog()` は [`@riebeckite/plugin-diff`](../diff/README_ja.md) と同じ方法でローカル Git を読み取ります(`execFile` で `git` を実行し、失敗時は安全に縮退)。独自のファイルシステム層は作りません。
11
+ `changelog()` は [`@riebeckite/plugin-diff`](../diff/README_ja.md) と同じ方法でローカル Git を読み取ります(`execFile` で `git` を実行し、失敗時は安全に縮退します)。独自のファイルシステム層は作りません。
12
12
 
13
- 結果はプラグインの組み込み HTML レンダラーを通じて公開されます。公開エントリはそれぞれ変更履歴セクションを受け取り、サイト側のレイアウトが描画するかどうかを決めます。`siteWide` を有効にすると、サイト全体の変更履歴データセットがビルドされ、アプリが自身で描画できます。プラグインは manifest entry に markup を注入しません。描画する変更履歴セクションは Site が選びます。クライアントスクリプトは登録しません。
13
+ 結果はプラグインの組み込み HTML レンダラーを通じて公開されます。公開エントリはそれぞれ変更履歴セクションを受け取り、サイト側のレイアウトが描画するかどうかを決めます。`siteWide` を有効にすると、サイト全体の変更履歴データセットがビルドされ、アプリが自身で描画できます。プラグインはマニフェストエントリにマークアップを挿入しません。描画する変更履歴セクションはサイト側が選びます。クライアントスクリプトは登録しません。
14
14
 
15
15
  ## 使い方
16
16
 
@@ -22,7 +22,7 @@ export default defineConfig({
22
22
  // ...
23
23
  plugins: [
24
24
  changelog({
25
- // ノートのリポジトリ上のパスが見えるよう、コンテンツのルートを指定します。
25
+ // Git からノートのリポジトリ上のパスが見えるよう、コンテンツルートを指定します。
26
26
  cwd: "./content",
27
27
  lookbackDays: 180,
28
28
  dateFormat: "iso",
@@ -33,7 +33,7 @@ export default defineConfig({
33
33
 
34
34
  ## オプション
35
35
 
36
- | オプション | 型 | 初期値 | 説明 |
36
+ | オプション | 型 | 既定値 | 説明 |
37
37
  | --- | --- | --- | --- |
38
38
  | `cwd` | `string` | `config.content.directory`、無ければ `process.cwd()` | Git リポジトリを探すためのコンテンツルート |
39
39
  | `lookbackDays` | `number` | なし(全履歴) | 直近この日数以内のコミットだけを含める |
@@ -52,17 +52,17 @@ export default defineConfig({
52
52
 
53
53
  ## 表示
54
54
 
55
- 各ノートの変更履歴セクションはビルド時の HTML として公開されます。`perNote` が有効な場合、記事の後に表示されます。サイト全体の変更履歴データセットはビルドされ、アプリが renderer を通じて描画できます。
55
+ 各ノートの変更履歴セクションはビルド時の HTML として公開されます。`perNote` が有効な場合、記事の後に表示されます。サイト全体の変更履歴データセットはビルドされ、アプリがエクスポートされたレンダラーを使って描画できます。
56
56
 
57
- `data-changelog-note` と `data-changelog-site` は、2 種類の断片をスタイリングと重複防止のために識別する属性です。
57
+ `data-changelog-note` と `data-changelog-site` は、スタイリングと冪等性チェックのために 2 種類の断片を識別する属性です。
58
58
 
59
- ## アプリ側の接続(サイト全体の変更履歴)
59
+ ## アプリへの組み込み(サイト全体の変更履歴)
60
60
 
61
- サイト全体の一覧はページではなくデータです。ルートはアプリが所有します。プラグインはデータセットを生成し、アプリがエクスポートされた renderer を使って描画します。
61
+ サイト全体の一覧はページではなくデータです。ルートはアプリが所有します。プラグインはデータセットを生成し、アプリがエクスポートされたレンダラーを使って描画します。
62
62
 
63
63
  ## 失敗時の挙動
64
64
 
65
- `git` を起動できない場合は `changelog-git-unavailable` 警告診断、コンテンツディレクトリが Git ワーキングツリーの外にある場合は `changelog-content-outside-repository` 警告診断を出し、どちらの場合もロガー警告を伴い、ビルド成果物には何も加えません。履歴が取得できないことを理由にビルドが失敗することはなく、空のリポジトリでも同じです。
65
+ `git` を起動できない場合は `changelog-git-unavailable`、コンテンツディレクトリが Git ワーキングツリーの外にある場合は `changelog-content-outside-repository` の警告診断を出します。どちらの場合もロガーにも警告を出し、ビルド成果物には何も加えません。履歴が取得できないことを理由にビルドが失敗することはなく、空のリポジトリでも同じです。
66
66
 
67
67
  ## スタイル
68
68
 
@@ -75,7 +75,7 @@ import "@riebeckite/plugin-changelog/style.css";
75
75
  ## 公開 API
76
76
 
77
77
  - `changelog(options?)`:プラグインファクトリ
78
- - `changelogPlugin`:`changelog` の別名
78
+ - `changelog`:`changelog` の別名
79
79
  - `resolveChangelogOptions(options?)`:既定値を適用
80
80
  - `GitChangelogReader`:Git 履歴リーダー(`getFileHistory`、`getRecentCommits`、`isAvailable`)
81
81
  - `buildNoteChangeHistory(entry, commits, options)`:ノート単位のデータセット
package/dist/index.d.ts CHANGED
@@ -25,5 +25,3 @@ export declare const CHANGELOG_PLUGIN_NAME = "changelog";
25
25
  * is reported instead of failing.
26
26
  */
27
27
  export declare function changelog(options?: ChangelogOptions): import("@riebeckite/core").RiebeckitePlugin<ChangelogOptions>;
28
- /** Alias matching the `*Plugin` suffix used by other plugin factories. */
29
- export declare const changelogPlugin: typeof changelog;
package/dist/index.js CHANGED
@@ -88,8 +88,8 @@ function resolveCommitNotes(commit, byPath, bySlug) {
88
88
  function invertContentIndex(contentIndex, entries) {
89
89
  const byPath = /* @__PURE__ */ new Map();
90
90
  for (const entry of entries) {
91
- const path3 = resolveContentFilePath(contentIndex, entry.slug);
92
- if (path3) byPath.set(path3, entry.slug);
91
+ const path = resolveContentFilePath(contentIndex, entry.slug);
92
+ if (path) byPath.set(path, entry.slug);
93
93
  }
94
94
  return byPath;
95
95
  }
@@ -159,149 +159,36 @@ var getChangelog = async ({ view, entry, loadCommits, options }) => {
159
159
  };
160
160
 
161
161
  // src/git/history_reader.ts
162
- import path2 from "node:path";
163
-
164
- // src/git/repo_paths.ts
165
- import path from "node:path";
166
-
167
- // src/git/run_git.ts
168
- import { execFile } from "node:child_process";
169
- import { promisify } from "node:util";
170
- var execFileAsync = promisify(execFile);
171
- function isGitCommandFailure(result) {
172
- return result.ok === false;
173
- }
174
- async function runGit(args, cwd) {
175
- try {
176
- const { stdout } = await execFileAsync(
177
- "git",
178
- ["-c", "core.quotePath=false", ...args],
179
- {
180
- ...cwd === void 0 ? {} : { cwd },
181
- maxBuffer: 1024 * 1024 * 10
182
- }
183
- );
184
- return { ok: true, stdout };
185
- } catch (error) {
186
- return {
187
- ok: false,
188
- spawnError: isSpawnFailure(readErrorCode(error)),
189
- detail: describeGitFailure(error)
190
- };
191
- }
192
- }
193
- function readErrorCode(error) {
194
- return typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
195
- }
196
- function isSpawnFailure(code) {
197
- return typeof code === "string" && !code.startsWith("ERR_");
198
- }
199
- function describeGitFailure(error) {
200
- if (typeof error !== "object" || error === null) {
201
- return error instanceof Error ? error.message : String(error);
202
- }
203
- const stderr = "stderr" in error ? error.stderr : void 0;
204
- if (typeof stderr === "string" && stderr.trim()) return stderr.trim();
205
- if (error instanceof Error) return error.message;
206
- return String(error);
207
- }
208
-
209
- // src/git/repo_paths.ts
210
- async function resolveRepositoryPaths(contentRoot) {
211
- const resolvedContentRoot = path.resolve(contentRoot);
212
- const result = await runGit([
213
- "-C",
214
- resolvedContentRoot,
215
- "rev-parse",
216
- "--show-toplevel",
217
- "--show-prefix"
218
- ]);
219
- if (isGitCommandFailure(result)) {
220
- return result.spawnError ? { kind: "git-unavailable", detail: result.detail } : { kind: "content-outside-repository", detail: result.detail };
221
- }
222
- const [toplevel, prefix] = parseRevParseOutput(result.stdout);
223
- if (!toplevel) {
224
- return {
225
- kind: "content-outside-repository",
226
- detail: "git rev-parse reported no work tree root"
227
- };
228
- }
229
- return {
230
- kind: "resolved",
231
- paths: {
232
- repositoryRoot: toPosixPath(path.normalize(toplevel)),
233
- contentRoot: toPosixPath(resolvedContentRoot),
234
- contentPrefix: toPosixPath(prefix).replace(/\/+$/, "")
235
- }
236
- };
237
- }
238
- function toRepositoryPath(paths, contentRelativePath) {
239
- const normalized = normalizeContentPath(contentRelativePath);
240
- if (!paths.contentPrefix) return normalized;
241
- if (!normalized) return paths.contentPrefix;
242
- return `${paths.contentPrefix}/${normalized}`;
243
- }
244
- function toContentPath(paths, repositoryRelativePath) {
245
- const normalized = normalizeContentPath(repositoryRelativePath);
246
- if (!normalized) return null;
247
- if (!paths.contentPrefix) return normalized;
248
- if (!normalized.startsWith(`${paths.contentPrefix}/`)) return null;
249
- return normalized.slice(paths.contentPrefix.length + 1);
250
- }
251
- function normalizeContentPath(value) {
252
- return value.replace(/\\/g, "/").replace(/^\.\//, "");
253
- }
254
- function isResolvedRepository(resolution) {
255
- return resolution.kind === "resolved";
256
- }
257
- function parseRevParseOutput(stdout) {
258
- const [toplevel = "", prefix = ""] = stdout.split(/\r?\n/);
259
- return [toplevel.trim(), prefix.trim()];
260
- }
261
- function toPosixPath(value) {
262
- return value.replace(/\\/g, "/");
263
- }
264
-
265
- // src/git/history_reader.ts
162
+ import {
163
+ GitRepository,
164
+ normalizeContentPath,
165
+ runGit,
166
+ toContentPath,
167
+ toRepositoryPath
168
+ } from "@riebeckite/git-history";
266
169
  var fieldSeparator = "";
267
170
  var recordSeparator = "";
268
171
  var logFormat = ["%H", "%h", "%cI", "%s", "%an"].join(fieldSeparator);
269
172
  var GitChangelogReader = class {
270
- #contentRoot;
173
+ #repository;
271
174
  #fileCache = /* @__PURE__ */ new Map();
272
175
  #recentCache;
273
- #repository;
274
176
  constructor(options = {}) {
275
- this.#contentRoot = path2.resolve(options.cwd ?? process.cwd());
177
+ this.#repository = new GitRepository(options);
276
178
  }
277
179
  /**
278
180
  * Resolves the Git work tree that owns the content directory, or the reason
279
181
  * it cannot be resolved. Memoized: every Git query reuses the same answer.
280
182
  */
281
183
  resolveRepository() {
282
- this.#repository ??= resolveRepositoryPaths(this.#contentRoot);
283
- return this.#repository;
184
+ return this.#repository.resolve();
284
185
  }
285
186
  /** True when the content directory lives inside a Git work tree. */
286
- async isAvailable() {
287
- return isResolvedRepository(await this.resolveRepository());
187
+ isAvailable() {
188
+ return this.#repository.isAvailable();
288
189
  }
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("");
190
+ getStateFingerprint() {
191
+ return this.#repository.getStateFingerprint();
305
192
  }
306
193
  /**
307
194
  * Commit history for one file, newest first, following renames.
@@ -355,10 +242,8 @@ var GitChangelogReader = class {
355
242
  }));
356
243
  }
357
244
  /** `null` when the content directory has no readable work tree. */
358
- async #resolvePaths() {
359
- const resolution = await this.resolveRepository();
360
- if (!isResolvedRepository(resolution)) return null;
361
- return resolution.paths;
245
+ #resolvePaths() {
246
+ return this.#repository.paths();
362
247
  }
363
248
  };
364
249
  function parseCommitRecords(stdout) {
@@ -439,7 +324,6 @@ function changelog(options = {}) {
439
324
  ]
440
325
  });
441
326
  }
442
- var changelogPlugin = changelog;
443
327
  function validateChangelogOptions(options) {
444
328
  if (!options) return [];
445
329
  const issues = [];
@@ -504,7 +388,6 @@ export {
504
388
  buildNoteChangeHistory,
505
389
  buildSiteChangelog,
506
390
  changelog,
507
- changelogPlugin,
508
391
  filterChangelogCommits,
509
392
  formatChangelogDate,
510
393
  getChangelog,
@@ -1,5 +1,5 @@
1
+ import { type RepositoryPathsResolution } from "@riebeckite/git-history";
1
2
  import type { ChangelogCommit, GitChangelogReaderOptions } from "../types.js";
2
- import { type RepositoryPathsResolution } from "./repo_paths.js";
3
3
  /** Optional window applied to a Git query. */
4
4
  export type GitLogWindow = {
5
5
  /** ISO timestamp passed to `git log --since`. */
@@ -1,12 +1,8 @@
1
- import type { ChangelogDateFormat, ChangelogOptions, ResolvedChangelogOptions } from "./types.js";
2
- export declare const DEFAULT_CHANGELOG_DATE_FORMAT: ChangelogDateFormat;
1
+ import type { ChangelogOptions, ResolvedChangelogOptions } from "./types.js";
3
2
  export declare const DEFAULT_CHANGELOG_LOCALE = "en";
4
- export declare const DEFAULT_CHANGELOG_PER_NOTE = true;
5
- export declare const DEFAULT_CHANGELOG_SITE_WIDE = false;
6
3
  export declare const DEFAULT_CHANGELOG_SITE_WIDE_SLUG = "changelog";
7
4
  export declare const DEFAULT_CHANGELOG_MAX_PER_NOTE = 10;
8
5
  export declare const DEFAULT_CHANGELOG_MAX_SITE_WIDE = 50;
9
- export declare const DEFAULT_CHANGELOG_SHOW_AUTHOR = true;
10
6
  export declare const DEFAULT_CHANGELOG_HEADING = true;
11
7
  export declare const DEFAULT_CHANGELOG_PER_NOTE_HEADING = "Change history";
12
8
  export declare const DEFAULT_CHANGELOG_SITE_WIDE_HEADING = "Changelog";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@riebeckite/plugin-changelog",
3
- "version": "0.0.32",
3
+ "version": "0.0.33",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,8 +21,9 @@
21
21
  "./style.css": "./style.css"
22
22
  },
23
23
  "dependencies": {
24
- "hono": "^4.12.25",
25
- "@riebeckite/core": "0.0.32"
24
+ "@riebeckite/core": "0.0.33",
25
+ "@riebeckite/git-history": "0.0.33",
26
+ "hono": "^4.12.25"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@types/node": "^24.5.2",
@@ -1,76 +0,0 @@
1
- /**
2
- * The paths that locate a content directory inside a Git work tree.
3
- *
4
- * Every field is a POSIX-separated path (`git` prints and accepts those on every
5
- * platform, including Windows).
6
- */
7
- export type GitRepositoryPaths = {
8
- /** Work tree root reported by `git rev-parse --show-toplevel`. */
9
- repositoryRoot: string;
10
- /** Absolute path of the content directory. */
11
- contentRoot: string;
12
- /**
13
- * Content directory relative to `repositoryRoot`, without a trailing slash.
14
- * Empty when the content directory is the work tree root itself.
15
- */
16
- contentPrefix: string;
17
- };
18
- /**
19
- * Why Git history cannot be read for a content directory.
20
- *
21
- * The reason is modelled explicitly so a plugin can report an actionable
22
- * diagnostic instead of quietly rendering nothing.
23
- */
24
- export type RepositoryPathsResolution = {
25
- kind: "resolved";
26
- paths: GitRepositoryPaths;
27
- }
28
- /** Git is not installed, or could not be started. */
29
- | {
30
- kind: "git-unavailable";
31
- detail: string;
32
- }
33
- /** Git ran, but the content directory does not live in a work tree. */
34
- | {
35
- kind: "content-outside-repository";
36
- detail: string;
37
- };
38
- /**
39
- * Locates the Git work tree that owns `contentRoot`.
40
- *
41
- * Both the work tree root and the content prefix come from Git itself, so their
42
- * casing and symlink resolution always agree with the paths `git log` prints —
43
- * the reason content-relative paths can be translated without guessing.
44
- *
45
- * The work tree is resolved through `git -C <contentRoot>` rather than by
46
- * running Git in `contentRoot`, so a missing directory surfaces as a Git
47
- * failure instead of an indistinguishable spawn failure.
48
- */
49
- export declare function resolveRepositoryPaths(contentRoot: string): Promise<RepositoryPathsResolution>;
50
- /**
51
- * Translates a content-relative path into the work tree-relative path that
52
- * `git log` pathspecs and `git show <rev>:<path>` expect.
53
- */
54
- export declare function toRepositoryPath(paths: GitRepositoryPaths, contentRelativePath: string): string;
55
- /**
56
- * Translates a work tree-relative path printed by `git` back into a
57
- * content-relative path, or returns `null` when the path lies outside the
58
- * content directory.
59
- */
60
- export declare function toContentPath(paths: GitRepositoryPaths, repositoryRelativePath: string): string | null;
61
- /** Normalizes a path to the POSIX form used for content and work tree paths. */
62
- export declare function normalizeContentPath(value: string): string;
63
- /**
64
- * True when the content directory has a usable work tree. Narrows the
65
- * resolution so callers can read `paths`.
66
- */
67
- export declare function isResolvedRepository(resolution: RepositoryPathsResolution): resolution is Extract<RepositoryPathsResolution, {
68
- kind: "resolved";
69
- }>;
70
- /**
71
- * True when history cannot be read and a reason is available. Narrows the
72
- * resolution so callers can read `detail`.
73
- */
74
- export declare function isRepositoryUnavailable(resolution: RepositoryPathsResolution): resolution is Exclude<RepositoryPathsResolution, {
75
- kind: "resolved";
76
- }>;
@@ -1,43 +0,0 @@
1
- /**
2
- * Result of a `git` invocation.
3
- *
4
- * Failures are returned instead of thrown: a site whose Git history cannot be
5
- * read must still build, so every caller decides what an unavailable repository
6
- * means for its own output.
7
- */
8
- export type GitCommandResult = {
9
- ok: true;
10
- stdout: string;
11
- }
12
- /**
13
- * `spawnError` separates "Git could not be started" (missing or unexecutable
14
- * binary) from "Git ran and rejected the command" (not a work tree, unknown
15
- * revision, unreadable path). The distinction drives which diagnostic a
16
- * plugin reports.
17
- */
18
- | {
19
- ok: false;
20
- spawnError: boolean;
21
- detail: string;
22
- };
23
- /**
24
- * Narrows a failed command so its failure fields can be read.
25
- *
26
- * Type predicates are used instead of `result.ok` checks throughout this
27
- * package because the build type-checks without `strictNullChecks`, where
28
- * discriminant narrowing of an inline condition does not apply.
29
- */
30
- export declare function isGitCommandFailure(result: GitCommandResult): result is Extract<GitCommandResult, {
31
- ok: false;
32
- }>;
33
- /**
34
- * Runs `git` with `args`.
35
- *
36
- * `core.quotePath=false` keeps non-ASCII paths verbatim so the printed paths
37
- * match the ones the content index reports; Git otherwise escapes them into
38
- * octal sequences that no path lookup can resolve.
39
- *
40
- * @param cwd Directory Git runs in. Omit when the command carries its own
41
- * `-C <dir>` argument.
42
- */
43
- export declare function runGit(args: readonly string[], cwd?: string): Promise<GitCommandResult>;