@williamthorsen/release-kit 5.2.1 → 5.3.0

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +62 -26
  2. package/README.md +251 -31
  3. package/cliff.toml.template +4 -39
  4. package/dist/esm/.cache +1 -1
  5. package/dist/esm/bin/release-kit.js +59 -0
  6. package/dist/esm/buildChangelogEntries.js +6 -0
  7. package/dist/esm/buildEmptyReleaseEntry.d.ts +2 -0
  8. package/dist/esm/buildEmptyReleaseEntry.js +16 -0
  9. package/dist/esm/changelogJsonFile.d.ts +2 -0
  10. package/dist/esm/changelogJsonFile.js +11 -1
  11. package/dist/esm/changelogJsonUtils.d.ts +2 -1
  12. package/dist/esm/changelogJsonUtils.js +9 -0
  13. package/dist/esm/changelogOverrides.d.ts +53 -0
  14. package/dist/esm/changelogOverrides.js +424 -0
  15. package/dist/esm/checkWorkTypesDrift.js +3 -2
  16. package/dist/esm/defaults.js +1 -1
  17. package/dist/esm/generateChangelogs.d.ts +0 -3
  18. package/dist/esm/generateChangelogs.js +1 -35
  19. package/dist/esm/index.d.ts +2 -0
  20. package/dist/esm/index.js +8 -0
  21. package/dist/esm/loadConfig.d.ts +1 -1
  22. package/dist/esm/releasePrepare.js +68 -11
  23. package/dist/esm/releasePrepareMono.js +103 -56
  24. package/dist/esm/releasePrepareProject.d.ts +4 -1
  25. package/dist/esm/releasePrepareProject.js +74 -18
  26. package/dist/esm/renderChangelogMarkdown.d.ts +12 -0
  27. package/dist/esm/renderChangelogMarkdown.js +32 -0
  28. package/dist/esm/renderReleaseNotes.js +6 -1
  29. package/dist/esm/resolveReleaseNotesConfig.js +3 -1
  30. package/dist/esm/runGitCliff.d.ts +1 -0
  31. package/dist/esm/runGitCliff.js +7 -0
  32. package/dist/esm/syncWorkTypes.js +3 -2
  33. package/dist/esm/types.d.ts +98 -31
  34. package/dist/esm/types.js +60 -0
  35. package/dist/esm/validateConfig.js +84 -345
  36. package/dist/esm/validateOverridesCommand.d.ts +13 -0
  37. package/dist/esm/validateOverridesCommand.js +119 -0
  38. package/dist/esm/work-types.json +23 -17
  39. package/dist/esm/work-types.schema.json +28 -1
  40. package/dist/esm/workTypesData.d.ts +8 -0
  41. package/dist/esm/workTypesData.js +20 -17
  42. package/dist/esm/workTypesUtils.d.ts +1 -0
  43. package/dist/esm/workTypesUtils.js +8 -0
  44. package/package.json +4 -3
  45. package/dist/esm/writeSyntheticChangelog.d.ts +0 -9
  46. package/dist/esm/writeSyntheticChangelog.js +0 -27
@@ -18,45 +18,10 @@
18
18
  # unique group (not per parser entry) — all parsers routing to the same group
19
19
  # share the same prefix.
20
20
 
21
- [changelog]
22
- header = """
23
- # Changelog
24
-
25
- All notable changes to this project will be documented in this file."""
26
- # Render each commit as a bullet with the description (stripped of ticket ID
27
- # and scope|type prefix). If the commit has a body, render it as indented
28
- # continuation paragraphs under the bullet.
29
- body = """{%- if version %}
30
- ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
31
- {%- else %}
32
- ## [unreleased]
33
- {%- endif -%}
34
- {%- set non_merge = commits | filter(attribute="merge_commit", value=false) -%}
35
- {%- for group, group_commits in non_merge | group_by(attribute="group") %}
36
-
37
- ### {{ group | striptags | trim | upper_first }}
38
-
39
- {% for commit in group_commits -%}
40
- {%- set subject = commit.message | split(pat="\n") | first -%}
41
- {%- set desc = subject | split(pat=": ") | slice(start=1) | join(sep=": ") -%}
42
- {%- set body_lines = commit.message | split(pat="\n") | slice(start=2) -%}
43
- {%- set body_text = body_lines | join(sep="\n") | trim -%}
44
- {%- if not loop.first %}
45
-
46
- {% endif -%}
47
- - {{ desc | upper_first }}
48
- {%- if body_text %}
49
-
50
- {{ body_text | split(pat="\n") | join(sep="\n ") }}
51
- {%- endif -%}
52
- {%- endfor -%}
53
- {%- endfor %}
54
- """
55
- footer = """
56
-
57
- <!-- generated by git-cliff -->
58
- """
59
- trim = false
21
+ # Markdown rendering is handled in-process by `renderChangelogMarkdown.ts`. cliff is invoked
22
+ # only for `--context` JSON output (see `buildChangelogEntries.ts`), so no `[changelog]` block
23
+ # is needed. The `[git].commit_parsers` section below remains load-bearing for group
24
+ # assignment in the `--context` payload.
60
25
 
61
26
  [git]
62
27
  conventional_commits = false
package/dist/esm/.cache CHANGED
@@ -1 +1 @@
1
- 704a70608ce284c9f8ef2dda2e6604a5f38ac0c995c9419ae39206ed053775c6
1
+ 18bd566d86a692ebdc23649a475e7d5e01562a58df99b6091e222e890cef8f80
@@ -13,6 +13,7 @@ import { syncLabelsInitCommand } from "../sync-labels/initCommand.js";
13
13
  import { syncLabelsCommand } from "../sync-labels/syncCommand.js";
14
14
  import { syncWorkTypes } from "../syncWorkTypes.js";
15
15
  import { tagCommand } from "../tagCommand.js";
16
+ import { validateOverridesCommand } from "../validateOverridesCommand.js";
16
17
  const VERSION = readPackageVersion(import.meta.url);
17
18
  function showUsage() {
18
19
  console.info(`
@@ -27,6 +28,7 @@ Commands:
27
28
  create-github-release Create GitHub Releases from changelog.json for tags on HEAD
28
29
  show-tag-prefixes Show derived and declared legacy tag prefixes per workspace
29
30
  init Initialize release-kit in the current repository
31
+ overrides Manage editorial changelog overrides
30
32
  sync-labels Manage GitHub label synchronization
31
33
  work-types Check for or sync work-type taxonomy drift against the upstream canonical
32
34
 
@@ -174,6 +176,35 @@ legacy tag prefixes. Surfaces any release-shaped tags whose prefix is neither a
174
176
  derived prefix nor declared in \`legacyIdentities\`, with a copy-pasteable
175
177
  config snippet.
176
178
 
179
+ Options:
180
+ --help, -h Show this help message
181
+ `);
182
+ }
183
+ function showOverridesHelp() {
184
+ console.info(`
185
+ Usage: release-kit overrides <subcommand> [options]
186
+
187
+ Manage editorial changelog overrides.
188
+
189
+ Subcommands:
190
+ validate Validate every \`.meta/changelog-overrides.json\` file across all scopes
191
+
192
+ Options:
193
+ --help, -h Show this help message
194
+ `);
195
+ }
196
+ function showOverridesValidateHelp() {
197
+ console.info(`
198
+ Usage: release-kit overrides validate
199
+
200
+ Validate every \`.meta/changelog-overrides.json\` file across the project and per-workspace
201
+ scopes. Reports schema/parse errors, ambiguous-prefix errors, and stale-key warnings.
202
+
203
+ Exit codes:
204
+ 0 Clean \u2014 no errors, no stale keys
205
+ 1 Stale-key warnings only (no errors)
206
+ 2 Schema/parse or ambiguous-prefix errors (errors dominate)
207
+
177
208
  Options:
178
209
  --help, -h Show this help message
179
210
  `);
@@ -389,6 +420,34 @@ if (command === "sync-labels") {
389
420
  showSyncLabelsHelp();
390
421
  process.exit(1);
391
422
  }
423
+ if (command === "overrides") {
424
+ const subcommand = flags[0];
425
+ const subflags = flags.slice(1);
426
+ if (subcommand === "--help" || subcommand === "-h" || subcommand === void 0) {
427
+ showOverridesHelp();
428
+ process.exit(0);
429
+ }
430
+ if (subcommand === "validate") {
431
+ if (subflags.some((f) => f === "--help" || f === "-h")) {
432
+ showOverridesValidateHelp();
433
+ process.exit(0);
434
+ }
435
+ if (subflags.length > 0) {
436
+ console.error(`Error: Unknown option: ${subflags[0]}`);
437
+ process.exit(1);
438
+ }
439
+ const result = await validateOverridesCommand();
440
+ if (result.exitCode === 0) {
441
+ console.info(result.message);
442
+ } else {
443
+ console.error(result.message);
444
+ }
445
+ process.exit(result.exitCode);
446
+ }
447
+ console.error(`Error: Unknown subcommand: ${subcommand}`);
448
+ showOverridesHelp();
449
+ process.exit(1);
450
+ }
392
451
  if (command === "work-types") {
393
452
  const subcommand = flags[0];
394
453
  const subflags = flags.slice(1);
@@ -69,6 +69,9 @@ function toCliffContextCommit(value) {
69
69
  if (typeof value.group === "string") {
70
70
  commit.group = value.group;
71
71
  }
72
+ if (typeof value.id === "string") {
73
+ commit.id = value.id;
74
+ }
72
75
  return commit;
73
76
  }
74
77
  function transformReleases(releases, devOnlySections) {
@@ -98,6 +101,9 @@ function transformReleases(releases, devOnlySections) {
98
101
  if (breaking) {
99
102
  item.breaking = true;
100
103
  }
104
+ if (commit.id !== void 0 && commit.id !== "") {
105
+ item.hash = commit.id;
106
+ }
101
107
  items.push(item);
102
108
  }
103
109
  const sections = [];
@@ -0,0 +1,2 @@
1
+ import type { ChangelogEntry } from './types.ts';
2
+ export declare function buildEmptyReleaseEntry(version: string, date: string): ChangelogEntry;
@@ -0,0 +1,16 @@
1
+ function buildEmptyReleaseEntry(version, date) {
2
+ return {
3
+ version,
4
+ date,
5
+ sections: [
6
+ {
7
+ title: "Notes",
8
+ audience: "dev",
9
+ items: [{ description: "Forced version bump." }]
10
+ }
11
+ ]
12
+ };
13
+ }
14
+ export {
15
+ buildEmptyReleaseEntry
16
+ };
@@ -2,3 +2,5 @@ import type { ChangelogEntry, ReleaseConfig } from './types.ts';
2
2
  export declare function resolveChangelogJsonPath(config: Pick<ReleaseConfig, 'changelogJson'>, changelogPath: string): string;
3
3
  export declare function writeChangelogJson(filePath: string, entries: ChangelogEntry[]): string;
4
4
  export declare function upsertChangelogJson(filePath: string, entries: ChangelogEntry[]): string;
5
+ export declare function upsertChangelogJsonAndReturn(filePath: string, entries: ChangelogEntry[]): ChangelogEntry[];
6
+ export declare function mergeChangelogEntriesWithDisk(filePath: string, entries: ChangelogEntry[]): ChangelogEntry[];
@@ -14,11 +14,19 @@ function writeChangelogJson(filePath, entries) {
14
14
  return filePath;
15
15
  }
16
16
  function upsertChangelogJson(filePath, entries) {
17
+ upsertChangelogJsonAndReturn(filePath, entries);
18
+ return filePath;
19
+ }
20
+ function upsertChangelogJsonAndReturn(filePath, entries) {
17
21
  const existing = readExistingEntries(filePath);
18
22
  const merged = mergeEntries(entries, existing);
19
23
  mkdirSync(dirname(filePath), { recursive: true });
20
24
  writeFileSync(filePath, stringify(merged, { maxLength: 100 }) + "\n", "utf8");
21
- return filePath;
25
+ return merged;
26
+ }
27
+ function mergeChangelogEntriesWithDisk(filePath, entries) {
28
+ const existing = readExistingEntries(filePath);
29
+ return mergeEntries(entries, existing);
22
30
  }
23
31
  function sortNewestFirst(entries) {
24
32
  return [...entries].sort((a, b) => compareVersionsDescending(a.version, b.version));
@@ -62,7 +70,9 @@ function compareVersionsDescending(a, b) {
62
70
  return 0;
63
71
  }
64
72
  export {
73
+ mergeChangelogEntriesWithDisk,
65
74
  resolveChangelogJsonPath,
66
75
  upsertChangelogJson,
76
+ upsertChangelogJsonAndReturn,
67
77
  writeChangelogJson
68
78
  };
@@ -1,4 +1,5 @@
1
- import type { ChangelogEntry } from './types.ts';
1
+ import type { ChangelogEntry, ChangelogItem } from './types.ts';
2
+ export declare function isChangelogItem(value: unknown): value is ChangelogItem;
2
3
  export declare function isChangelogEntry(value: unknown): value is ChangelogEntry;
3
4
  export declare function extractVersion(tag: string): string;
4
5
  export declare function readChangelogEntries(filePath: string): ChangelogEntry[] | undefined;
@@ -1,5 +1,13 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { isRecord, isUnknownArray } from "./typeGuards.js";
3
+ function isChangelogItem(value) {
4
+ if (!isRecord(value)) return false;
5
+ if (typeof value.description !== "string") return false;
6
+ if (value.body !== void 0 && typeof value.body !== "string") return false;
7
+ if (value.breaking !== void 0 && typeof value.breaking !== "boolean") return false;
8
+ if (value.hash !== void 0 && typeof value.hash !== "string") return false;
9
+ return true;
10
+ }
3
11
  function isChangelogEntry(value) {
4
12
  return isRecord(value) && typeof value.version === "string" && typeof value.date === "string" && isUnknownArray(value.sections);
5
13
  }
@@ -25,5 +33,6 @@ function readChangelogEntries(filePath) {
25
33
  export {
26
34
  extractVersion,
27
35
  isChangelogEntry,
36
+ isChangelogItem,
28
37
  readChangelogEntries
29
38
  };
@@ -0,0 +1,53 @@
1
+ import type { ChangelogEntry, ChangelogOverride, WorkspaceConfig } from './types.ts';
2
+ export type LoadChangelogOverridesResult = {
3
+ overrides: Map<string, ChangelogOverride>;
4
+ } | {
5
+ errors: string[];
6
+ };
7
+ export declare function loadChangelogOverrides(path: string): LoadChangelogOverridesResult;
8
+ export declare function validateChangelogOverrides(raw: unknown): {
9
+ overrides: Map<string, ChangelogOverride>;
10
+ errors: string[];
11
+ };
12
+ export declare function formatStaleOverrideKeyWarning(key: string): string;
13
+ export declare function applyChangelogOverrides(entries: ChangelogEntry[], overrides: Map<string, ChangelogOverride>): {
14
+ entries: ChangelogEntry[];
15
+ warnings: string[];
16
+ errors: string[];
17
+ matchedKeys: string[];
18
+ };
19
+ export declare function resolveOverridePath(scopeRoot: string): string;
20
+ export interface LoadOverridesForScopesResult {
21
+ project: Map<string, ChangelogOverride>;
22
+ perWorkspace: Map<string, Map<string, ChangelogOverride>>;
23
+ errors: string[];
24
+ }
25
+ export declare function loadOverridesForScopes(scopes: {
26
+ project?: string;
27
+ workspaces?: string[];
28
+ }): LoadOverridesForScopesResult;
29
+ export declare function composeOverrides(rootEntries: Map<string, ChangelogOverride>, workspaceEntries: Map<string, ChangelogOverride> | undefined): Map<string, ChangelogOverride>;
30
+ export interface OverrideContext {
31
+ project: Map<string, ChangelogOverride>;
32
+ perWorkspace: Map<string, Map<string, ChangelogOverride>>;
33
+ overrideWarnings: string[];
34
+ globalMatchedRootKeys: Set<string>;
35
+ }
36
+ export declare function createOverrideContext(workspaces: WorkspaceConfig[]): OverrideContext;
37
+ export interface ChangelogOverrideScope {
38
+ filePath: string;
39
+ hashes: readonly string[];
40
+ }
41
+ export interface ValidateAllChangelogOverridesInputs {
42
+ project?: {
43
+ filePath: string;
44
+ hashes?: readonly string[];
45
+ };
46
+ workspaces?: readonly ChangelogOverrideScope[];
47
+ }
48
+ export interface ValidateAllChangelogOverridesResult {
49
+ errors: string[];
50
+ warnings: string[];
51
+ }
52
+ export declare function validateAllChangelogOverrides(inputs: ValidateAllChangelogOverridesInputs): ValidateAllChangelogOverridesResult;
53
+ export declare function applyWorkspaceOverrides(newEntries: ChangelogEntry[], workspacePath: string, overrideContext: OverrideContext): ReturnType<typeof applyChangelogOverrides>;