@williamthorsen/release-kit 12.0.0 → 13.0.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 (168) hide show
  1. package/.meta/changelog.json +212 -0
  2. package/.readyup/kits/default.js +18 -17
  3. package/.readyup/kits/npm-auto-publish.js +24 -23
  4. package/.readyup/manifest.json +16 -11
  5. package/CHANGELOG.md +58 -0
  6. package/README.md +55 -48
  7. package/bin/release-kit.js +1 -1
  8. package/dist/esm/assertCleanWorkingTree.d.ts +1 -5
  9. package/dist/esm/assertTaggedBaseline.d.ts +29 -0
  10. package/dist/esm/assertTaggedBaseline.js +41 -0
  11. package/dist/esm/attachChangelogDiagnostics.d.ts +1 -1
  12. package/dist/esm/attachChangelogDiagnostics.js +3 -0
  13. package/dist/esm/bin/release-kit.js +85 -22
  14. package/dist/esm/buildChangelogEntries.d.ts +23 -13
  15. package/dist/esm/buildChangelogEntries.js +36 -17
  16. package/dist/esm/buildDependencyGraph.d.ts +6 -6
  17. package/dist/esm/buildEmptyReleaseEntry.d.ts +1 -8
  18. package/dist/esm/buildReleaseSummary.d.ts +2 -8
  19. package/dist/esm/buildSyntheticChangelogEntry.d.ts +3 -5
  20. package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
  21. package/dist/esm/bumpVersion.d.ts +1 -8
  22. package/dist/esm/changelogJsonFile.d.ts +5 -7
  23. package/dist/esm/changelogJsonFile.js +2 -17
  24. package/dist/esm/changelogJsonUtils.d.ts +7 -10
  25. package/dist/esm/changelogOverrides.d.ts +59 -98
  26. package/dist/esm/classifyChangelogCommit.d.ts +7 -9
  27. package/dist/esm/classifyChangelogCommit.js +3 -2
  28. package/dist/esm/commitCommand.d.ts +2 -4
  29. package/dist/esm/compareVersions.d.ts +10 -4
  30. package/dist/esm/compareVersions.js +16 -0
  31. package/dist/esm/configFlagSchema.d.ts +1 -1
  32. package/dist/esm/createGithubRelease.d.ts +7 -19
  33. package/dist/esm/createGithubReleaseCommand.d.ts +5 -5
  34. package/dist/esm/createGithubReleaseCommand.js +9 -9
  35. package/dist/esm/createTags.d.ts +2 -3
  36. package/dist/esm/decideRelease.d.ts +2 -6
  37. package/dist/esm/defaults.d.ts +5 -24
  38. package/dist/esm/defaults.js +4 -5
  39. package/dist/esm/defineConfig.d.ts +1 -1
  40. package/dist/esm/deleteFileIfExists.d.ts +1 -1
  41. package/dist/esm/deriveReleaseNotesConfig.d.ts +20 -0
  42. package/dist/esm/deriveReleaseNotesConfig.js +21 -0
  43. package/dist/esm/deriveWorkspaceConfig.d.ts +3 -5
  44. package/dist/esm/detectPackageManager.d.ts +2 -4
  45. package/dist/esm/detectUndeclaredTagPrefixes.d.ts +4 -6
  46. package/dist/esm/detectUndeclaredTagPrefixes.js +1 -1
  47. package/dist/esm/determineBumpType.d.ts +1 -1
  48. package/dist/esm/discoverWorkspaces.d.ts +14 -15
  49. package/dist/esm/discoverWorkspaces.js +12 -12
  50. package/dist/esm/enterRepoRoot.d.ts +12 -0
  51. package/dist/esm/enterRepoRoot.js +16 -0
  52. package/dist/esm/enumerateReleaseWindows.d.ts +8 -8
  53. package/dist/esm/extractMigration.d.ts +6 -8
  54. package/dist/esm/findUnroutedEntryScopes.d.ts +16 -0
  55. package/dist/esm/findUnroutedEntryScopes.js +57 -0
  56. package/dist/esm/formatExcludedSkip.d.ts +3 -0
  57. package/dist/esm/formatExcludedSkip.js +3 -0
  58. package/dist/esm/formatPrivateSkip.d.ts +1 -6
  59. package/dist/esm/generateChangelogs.d.ts +10 -5
  60. package/dist/esm/glyphs.d.ts +1 -1
  61. package/dist/esm/help/prepareHelp.d.ts +3 -4
  62. package/dist/esm/help/prepareHelp.js +4 -3
  63. package/dist/esm/init/checks.d.ts +3 -3
  64. package/dist/esm/init/checks.js +4 -1
  65. package/dist/esm/init/detectRepoType.d.ts +2 -3
  66. package/dist/esm/init/hasProvenance.d.ts +2 -0
  67. package/dist/esm/init/hasProvenance.js +3 -0
  68. package/dist/esm/init/initCommand.d.ts +3 -6
  69. package/dist/esm/init/initCommand.js +10 -10
  70. package/dist/esm/init/parseJsonRecord.d.ts +1 -4
  71. package/dist/esm/init/scaffold.d.ts +1 -1
  72. package/dist/esm/init/templates.d.ts +9 -11
  73. package/dist/esm/init/templates.js +12 -11
  74. package/dist/esm/init/updateTemplatesCommand.d.ts +13 -0
  75. package/dist/esm/init/updateTemplatesCommand.js +64 -0
  76. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +12 -17
  77. package/dist/esm/injectSection.d.ts +3 -3
  78. package/dist/esm/loadConfig.d.ts +16 -14
  79. package/dist/esm/loadConfig.js +2 -2
  80. package/dist/esm/loadValidatedConfig.d.ts +16 -19
  81. package/dist/esm/loadValidatedConfig.js +6 -1
  82. package/dist/esm/packsPath.d.ts +9 -9
  83. package/dist/esm/parseChangeRecordBlock.d.ts +2 -2
  84. package/dist/esm/parseCommitMessage.d.ts +17 -32
  85. package/dist/esm/parseCommitMessage.js +28 -37
  86. package/dist/esm/parseRequestedTags.d.ts +3 -3
  87. package/dist/esm/planReleaseNotesPreviews.d.ts +6 -6
  88. package/dist/esm/planVersionBump.d.ts +5 -3
  89. package/dist/esm/planVersionBump.js +3 -0
  90. package/dist/esm/prepareCommand.d.ts +4 -7
  91. package/dist/esm/prepareCommand.js +13 -6
  92. package/dist/esm/previewTagPrefixes.d.ts +8 -10
  93. package/dist/esm/propagateBumps.d.ts +6 -7
  94. package/dist/esm/publish.d.ts +1 -1
  95. package/dist/esm/publishCommand.d.ts +4 -3
  96. package/dist/esm/publishCommand.js +8 -8
  97. package/dist/esm/pushCommand.d.ts +4 -3
  98. package/dist/esm/pushCommand.js +8 -2
  99. package/dist/esm/pushRelease.d.ts +2 -2
  100. package/dist/esm/readChangelogSections.d.ts +33 -0
  101. package/dist/esm/readChangelogSections.js +80 -0
  102. package/dist/esm/releaseFiles.d.ts +5 -7
  103. package/dist/esm/releasePlan.d.ts +11 -13
  104. package/dist/esm/releasePrepare.d.ts +23 -22
  105. package/dist/esm/releasePrepare.js +31 -11
  106. package/dist/esm/releasePrepareMono.d.ts +9 -7
  107. package/dist/esm/releasePrepareMono.js +85 -33
  108. package/dist/esm/releasePrepareProject.d.ts +19 -29
  109. package/dist/esm/releasePrepareProject.js +31 -12
  110. package/dist/esm/renderChangelogMarkdown.d.ts +10 -4
  111. package/dist/esm/renderChangelogMarkdown.js +27 -2
  112. package/dist/esm/renderReleaseNotes.d.ts +3 -11
  113. package/dist/esm/renderReleaseNotes.js +2 -6
  114. package/dist/esm/reportPrepare.d.ts +4 -5
  115. package/dist/esm/reportPrepare.js +33 -19
  116. package/dist/esm/resolveCommandTags.d.ts +15 -11
  117. package/dist/esm/resolveCommandTags.js +35 -5
  118. package/dist/esm/resolveConfigFlag.d.ts +7 -0
  119. package/dist/esm/resolveConfigFlag.js +4 -0
  120. package/dist/esm/resolveReleaseTags.d.ts +6 -8
  121. package/dist/esm/resolveReleaseTags.js +1 -1
  122. package/dist/esm/resolveStylesOrExit.d.ts +2 -2
  123. package/dist/esm/showTagPrefixesCommand.d.ts +7 -10
  124. package/dist/esm/showTagPrefixesCommand.js +4 -4
  125. package/dist/esm/stripEmojiPrefix.d.ts +1 -0
  126. package/dist/esm/stripScope.d.ts +2 -2
  127. package/dist/esm/stripScope.js +3 -3
  128. package/dist/esm/sync-labels/generateCommand.d.ts +11 -10
  129. package/dist/esm/sync-labels/generateCommand.js +11 -8
  130. package/dist/esm/sync-labels/initCommand.d.ts +6 -7
  131. package/dist/esm/sync-labels/initCommand.js +2 -2
  132. package/dist/esm/sync-labels/presets.d.ts +5 -4
  133. package/dist/esm/sync-labels/presets.js +9 -9
  134. package/dist/esm/sync-labels/resolveLabels.d.ts +7 -7
  135. package/dist/esm/sync-labels/resolveLabels.js +1 -1
  136. package/dist/esm/sync-labels/retiredConfig.d.ts +4 -5
  137. package/dist/esm/sync-labels/syncCommand.d.ts +1 -4
  138. package/dist/esm/sync-labels/templates.d.ts +3 -3
  139. package/dist/esm/sync-labels/templates.js +3 -3
  140. package/dist/esm/sync-labels/typeLabels.d.ts +14 -0
  141. package/dist/esm/sync-labels/typeLabels.js +38 -0
  142. package/dist/esm/sync-labels/types.d.ts +1 -1
  143. package/dist/esm/tagCommand.d.ts +1 -1
  144. package/dist/esm/typeGuards.d.ts +2 -2
  145. package/dist/esm/types.d.ts +116 -91
  146. package/dist/esm/types.js +1 -1
  147. package/dist/esm/validateConfig.d.ts +2 -11
  148. package/dist/esm/validateConfig.js +2 -2
  149. package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -12
  150. package/dist/esm/validateOverridesCommand.d.ts +17 -20
  151. package/dist/esm/validateOverridesCommand.js +1 -1
  152. package/docs/api.md +6 -6
  153. package/docs/changelogs.md +63 -40
  154. package/docs/configuration.md +29 -27
  155. package/docs/editorial-overrides.md +23 -23
  156. package/docs/labels.md +21 -19
  157. package/docs/project-releases.md +13 -13
  158. package/docs/publishing.md +5 -5
  159. package/docs/readiness-checks.md +3 -3
  160. package/docs/releasing.md +50 -25
  161. package/docs/work-types.md +41 -57
  162. package/package.json +7 -6
  163. package/presets/labels/common.yaml +1 -41
  164. package/dist/esm/resolveReleaseNotesConfig.d.ts +0 -26
  165. package/dist/esm/resolveReleaseNotesConfig.js +0 -29
  166. package/dist/esm/workTypesData.d.ts +0 -50
  167. package/dist/esm/workTypesData.js +0 -145
  168. package/schemas/label-map.json +0 -24
@@ -10,64 +10,47 @@ export type LoadChangelogOverridesResult = {
10
10
  errors: string[];
11
11
  };
12
12
  /**
13
- * Load and validate the editorial overrides file at `path`.
14
- *
15
- * - Missing file resolves to an empty map (no-op default; matches "absent file → unchanged behavior").
16
- * - Malformed JSON, wrong top-level shape, or any per-entry validation failure surfaces as
17
- * structured errors. Callers decide how to surface them.
18
- *
19
- * Returns either `{ overrides: Map }` on success or `{ errors: string[] }` on failure. Pure
20
- * except for the single file read; performs no other I/O.
13
+ * Loads and validates the editorial overrides file at `path`. A missing file yields an empty map; an unreadable file,
14
+ * malformed JSON, a wrong top-level shape, or any per-entry validation failure yields errors.
21
15
  */
22
16
  export declare function loadChangelogOverrides(path: string): LoadChangelogOverridesResult;
23
17
  /**
24
- * Validate a parsed override record. Returns the parsed `Map` along with any error messages.
18
+ * Validates a parsed override record, returning the valid overrides and an error message for each problem.
25
19
  *
26
- * Each error names the offending key (e.g. `overrides['abc']: 'audience' must be one of …`)
27
- * so the consumer can locate it in their override file.
20
+ * Each error names the offending key (e.g. `overrides['abc']: 'audience' must be one of …`) so that the user can find
21
+ * it in the override file.
28
22
  */
29
23
  export declare function validateChangelogOverrides(raw: unknown): {
30
24
  overrides: Map<string, ChangelogOverride>;
31
25
  errors: string[];
32
26
  };
33
- /** Split a valid override key into its hash prefix and, for an ordinal key, its 1-based entry position. */
27
+ /** Splits a valid override key into its hash prefix and, for an ordinal key, its 1-based entry position. */
34
28
  export declare function parseOverrideKey(key: string): {
35
29
  hashPrefix: string;
36
30
  entry?: number;
37
31
  };
38
- /**
39
- * Format the standard "stale override key" warning. Callers compute the set of stale keys
40
- * (keys that didn't match anywhere) and use this helper to surface them uniformly. Centralized
41
- * so single-package and monorepo flows produce identical warning text.
42
- */
32
+ /** Formats the warning for an override key that did not match any item in the changelog. */
43
33
  export declare function formatStaleOverrideKeyWarning(key: string): string;
44
34
  /**
45
- * Apply overrides to a `ChangelogEntry[]`, returning a new array. Pure: no mutation, no I/O.
35
+ * Applies overrides to changelog entries, returning new entries without mutating the input.
46
36
  *
47
- * Match algorithm: each key's hash prefix is matched against the distinct `ChangelogItem.hash` values.
48
- * - 0 matches → key is recorded as unmatched in this batch (no warning emitted here).
49
- * - 1 match → a bare key resolves to every item of that commit, and an ordinal key `<hash>:<n>` to the items whose
50
- * `entry` is `n`. A key that resolves to no item is unmatched, so an ordinal key on a title-derived item goes stale.
51
- * - 2+ matches → error (ambiguous prefix).
37
+ * Each key's hash prefix is matched against the distinct `ChangelogItem.hash` values:
38
+ * - No match: The key is unmatched in this batch, and this function doesn't emit a warning.
39
+ * - One match: A bare key resolves to every item of that commit, and an ordinal key `<hash>:<n>` to the items whose
40
+ * `entry` is `n`. A key that doesn't resolve to any item is unmatched, so an ordinal key on a title-derived item
41
+ * goes stale.
42
+ * - Several matches: an error (ambiguous prefix).
52
43
  *
53
44
  * Two more conditions are errors, and the keys involved apply nowhere: a bare key that sets `description` or `body` on a
54
45
  * commit with several items, and two keys that resolve to the same item.
55
46
  *
56
- * `matchedKeys` lists the override keys that resolved to at least one item without error in this batch.
57
- * Callers are responsible for computing stale-key warnings: in a monorepo run, an override
58
- * may target a commit that lives in another workspace, so the per-batch zero-match signal is
59
- * insufficient on its own. Single-package and monorepo orchestrators format warnings using
60
- * `formatStaleOverrideKeyWarning` after aggregating `matchedKeys` across all apply calls.
61
- *
62
- * v1 audience semantics:
63
- * - `'skip'` removes the matched item from its containing section. Empty sections are pruned.
64
- * Versions with zero sections still appear (matches existing "empty workspace" behavior).
65
- * - `'all'` and `'dev'` are validated out before reaching the applier.
47
+ * `matchedKeys` lists the keys that resolved to at least one item without error. The caller computes stale-key
48
+ * warnings, because in a monorepo a key may target a commit in another workspace: A key unmatched in one batch is not
49
+ * necessarily stale.
66
50
  *
67
- * Items without a `hash` (synthetic propagation entries) are never matched and pass through.
68
- *
69
- * The function does not throw; warnings/errors accumulate and are returned alongside the
70
- * transformed entries so the caller decides whether to abort or log-and-continue.
51
+ * An override whose audience is `'skip'` removes its item, and a section left empty is dropped; an entry left without
52
+ * sections is kept. An item without a `hash`, such as a synthetic propagation item, passes through unmatched. Errors
53
+ * are returned rather than thrown, and the caller decides whether to abort.
71
54
  */
72
55
  export declare function applyChangelogOverrides(entries: ChangelogEntry[], overrides: Map<string, ChangelogOverride>): {
73
56
  entries: ChangelogEntry[];
@@ -76,10 +59,8 @@ export declare function applyChangelogOverrides(entries: ChangelogEntry[], overr
76
59
  matchedKeys: string[];
77
60
  };
78
61
  /**
79
- * Resolve the conventional override-file path for a given scope root.
80
- *
81
- * `scopeRoot` is the directory at which a scope's other artifacts live (e.g., `'.'` for the
82
- * project, `'packages/foo'` for a workspace). The returned path is repo-relative.
62
+ * Resolves the conventional override-file path for a scope root: the directory in which a scope's other artifacts live
63
+ * (e.g., `'.'` for the project, `'packages/foo'` for a workspace). The returned path is repo-relative.
83
64
  */
84
65
  export declare function resolveOverridePath(scopeRoot: string): string;
85
66
  /** Result of {@link loadOverridesForScopes}: per-scope maps plus accumulated load/validation errors. */
@@ -92,40 +73,27 @@ export interface LoadOverridesForScopesResult {
92
73
  errors: string[];
93
74
  }
94
75
  /**
95
- * Load and validate every requested override file in one pass.
96
- *
97
- * Iterates the configured scope roots, calls {@link loadChangelogOverrides} for each, and
98
- * aggregates errors across all files (rather than throwing on the first failure) so a
99
- * consumer who edits multiple files at once sees every problem in one report. Missing files
100
- * resolve to empty maps — matches the existing single-file behavior.
101
- *
102
- * The caller is expected to surface a combined error when `errors.length > 0` and abort the
103
- * release before any workspace begins writing.
76
+ * Loads and validates the override file of every requested scope, collecting the errors of all files rather than
77
+ * stopping at the first, so that a user who edits several files sees every problem in one report. A missing file
78
+ * yields an empty map.
104
79
  */
105
80
  export declare function loadOverridesForScopes(scopes: {
106
81
  project?: string;
107
82
  workspaces?: string[];
108
83
  }): LoadOverridesForScopesResult;
109
84
  /**
110
- * Compose root and workspace override maps into a single effective map for one workspace.
85
+ * Composes root and workspace override maps into the effective map for one workspace, without mutating either.
111
86
  *
112
- * Byte-equal-key shadowing: when a workspace key string-equals a root key, the workspace
113
- * entry wins entirely (no field-level merge) and supplants the root entry in the result.
114
- * Non-identical keys that resolve to the same item do NOT shadow here: {@link applyChangelogOverrides}
115
- * reports them as overlapping.
116
- *
117
- * Pure: never mutates inputs.
87
+ * A workspace key identical to a root key replaces the root entry entirely, without a field-level merge. Distinct keys
88
+ * that resolve to the same item do not shadow each other here: {@link applyChangelogOverrides} reports them as
89
+ * overlapping.
118
90
  */
119
91
  export declare function composeOverrides(rootEntries: Map<string, ChangelogOverride>, workspaceEntries: Map<string, ChangelogOverride> | undefined): Map<string, ChangelogOverride>;
120
92
  /**
121
- * Shared override-application context threaded through the per-workspace and project apply
122
- * sites. Bundles the loaded per-scope maps with the run-scoped warning aggregators so each
123
- * call site can both consume the maps it needs and report into the same warning channel.
93
+ * The loaded per-scope override maps and the run's warning aggregators, shared by every apply of a prepare run.
124
94
  *
125
- * `globalMatchedRootKeys` tracks ROOT keys matched in any apply call. Workspace-sourced
126
- * matched keys are not added here — they're tracked locally per workspace because their
127
- * stale-key semantics are local (a workspace key that doesn't match in its own workspace is
128
- * unambiguously stale and is warned immediately, not aggregated to end-of-run).
95
+ * `globalMatchedRootKeys` contains the root keys matched in any apply. Workspace keys are not added: A workspace key
96
+ * that matches nothing in its own workspace is stale, and it is warned about at once rather than at the end of the run.
129
97
  */
130
98
  export interface OverrideContext {
131
99
  project: Map<string, ChangelogOverride>;
@@ -134,10 +102,9 @@ export interface OverrideContext {
134
102
  globalMatchedRootKeys: Set<string>;
135
103
  }
136
104
  /**
137
- * Load all per-scope override files (root + every workspace) in one upfront pass, validate
138
- * them, and bundle the results with run-scoped warning aggregators. Aborts the release with
139
- * a clear error when any file is malformed — checked-in editorial config that does not parse
140
- * is a bug, and we want the prepare run to fail before any workspace begins writing.
105
+ * Loads and validates the override files of the root and every workspace, and bundles them with empty warning
106
+ * aggregators. Throws if any file fails to load, so that a malformed checked-in override file stops `prepare` before
107
+ * any workspace writes.
141
108
  */
142
109
  export declare function createOverrideContext(workspaces: WorkspaceConfig[]): OverrideContext;
143
110
  /** A changelog item as override matching sees it. `entry` is absent for a title-derived item. */
@@ -147,7 +114,10 @@ export interface OverrideTargetItem {
147
114
  }
148
115
  /** Per-scope input to {@link validateAllChangelogOverrides}. */
149
116
  export interface ChangelogOverrideScope {
150
- /** Path to the override file (relative to the repo root). Used to load the file and to attribute findings. */
117
+ /**
118
+ * Path to the override file, used to load the file and to attribute findings. A relative path resolves against the
119
+ * process working directory.
120
+ */
151
121
  filePath: string;
152
122
  /** Items in this scope's history window. Each override key is matched against these. */
153
123
  items: readonly OverrideTargetItem[];
@@ -162,7 +132,7 @@ export interface ValidateAllChangelogOverridesInputs {
162
132
  * - When `items` is provided, the project map is also applied directly to those items
163
133
  * (project release in monorepo mode, or the package's history in single-package mode).
164
134
  *
165
- * Omit when no project file exists (rare — most repos have a root file even if empty).
135
+ * Omit when the project file doesn't exist.
166
136
  */
167
137
  project?: {
168
138
  filePath: string;
@@ -171,44 +141,35 @@ export interface ValidateAllChangelogOverridesInputs {
171
141
  /** Per-workspace scopes. Each workspace's file applies only to its own items. */
172
142
  workspaces?: readonly ChangelogOverrideScope[];
173
143
  }
174
- /** Result of {@link validateAllChangelogOverrides}: aggregated errors and warnings, each prefixed with the file path it pertains to. */
144
+ /**
145
+ * Result of {@link validateAllChangelogOverrides}: aggregated errors and warnings, each prefixed with the path of the
146
+ * file to which it pertains.
147
+ */
175
148
  export interface ValidateAllChangelogOverridesResult {
176
149
  errors: string[];
177
150
  warnings: string[];
178
151
  }
179
152
  /**
180
- * End-to-end health check across every override file and scope. Pure: takes already-collected
181
- * item universes and returns aggregated findings. The CLI command and any other consumer
182
- * (programmatic library callers, future composite checks) wrap this with discovery and I/O.
153
+ * Validates every override file against the items of its scopes and returns the aggregated findings. It reads the
154
+ * override files but doesn't collect any items: The caller supplies each scope's items.
155
+ *
156
+ * Items built by `buildChangelogEntries`, as `release-kit prepare` builds them, yield the matches that `prepare`
157
+ * computes, tier asymmetry included: A workspace-tier key is stale when it matches nothing in its own workspace, and a
158
+ * root-tier key only when it matches nothing in any scope, the project release window included.
183
159
  *
184
- * When invoked via the standard `validateOverridesCommand` entry point, each scope's
185
- * `items` is built by `buildChangelogEntries` — the same path `release-kit prepare` walks —
186
- * so the match-set is byte-equal to what `prepare` would compute. The tier asymmetry is part
187
- * of that contract: workspace-tier keys are stale if they don't match in their own workspace;
188
- * root-tier keys are stale only if they don't match in any scope (no workspace AND not the
189
- * project release window). Library callers that construct `inputs` directly are responsible
190
- * for supplying the same item universes if they want this guarantee.
160
+ * Every returned string is prefixed with the path of the override file to which it pertains.
191
161
  *
192
- * Every returned string is prefixed with the relative override-file path it pertains to so
193
- * consumers can locate the offending file without further structuring.
162
+ * A relative `filePath`, in any scope, resolves against the process working directory, not against the repo root.
163
+ * A caller running from elsewhere passes absolute paths or changes directory first.
194
164
  */
195
165
  export declare function validateAllChangelogOverrides(inputs: ValidateAllChangelogOverridesInputs): ValidateAllChangelogOverridesResult;
196
166
  /**
197
- * Apply the composed (root + workspace) override map to a workspace's changelog entries and
198
- * report stale-key warnings tier-by-tier.
199
- *
200
- * The composed map is applied in a single {@link applyChangelogOverrides} call. Matched keys
201
- * are demultiplexed by source via `Map.has` lookups on the original (uncomposed) maps so the
202
- * stale-key semantics stay tier-aware:
203
- *
204
- * - Workspace-sourced keys (those present in the per-workspace map, regardless of whether
205
- * the root map also has the same byte-equal key) that did NOT match are unambiguously
206
- * stale in their own apply context — push an immediate stale warning naming the key.
207
- * - Root-sourced matched keys (present in root, absent from this workspace's map) are added
208
- * to `globalMatchedRootKeys` so the orchestrator's end-of-run loop can dedupe across
209
- * batches and warn only on root keys that matched nowhere.
167
+ * Applies the composed root and workspace override map to a workspace's changelog entries, and records stale keys by
168
+ * tier.
210
169
  *
211
- * Throws when any apply call surfaces an error (e.g. ambiguous prefix). Returns the applied
212
- * result so the caller can consume `applied.entries` for downstream rendering.
170
+ * A workspace key that matched nothing, whether or not the root map has the same key, is stale, and it is warned about
171
+ * at once. The function adds each root key that matched without being shadowed to `globalMatchedRootKeys`, from which
172
+ * the caller warns, at the end of the run, about the root keys that matched nowhere. Throws if applying reports any
173
+ * error, such as an ambiguous prefix.
213
174
  */
214
175
  export declare function applyWorkspaceOverrides(newEntries: ChangelogEntry[], workspacePath: string, overrideContext: OverrideContext): ReturnType<typeof applyChangelogOverrides>;
@@ -18,20 +18,18 @@ export type ChangelogClassification = {
18
18
  /**
19
19
  * Classifies a commit by its title.
20
20
  *
21
- * Four gates decide, in order: a `release:` or `Merge` subject is excluded; a subject carrying no ticket-ID prefix is
21
+ * Four gates decide, in order: A `release:` or `Merge` subject is excluded; a subject without a ticket-ID prefix is
22
22
  * unparseable; a type that `parseCommitMessage` cannot resolve against `workTypes` is unparseable; and a type that
23
- * `workTypes` excludes from the changelog is excluded. A commit that passes all four takes the header its type declares.
23
+ * `workTypes` excludes from the changelog is excluded. A commit that passes all four takes the header that its type
24
+ * declares.
24
25
  *
25
- * `options` reaches the parse, so a policy violation is reported for every ticketed commit whose type resolves,
26
- * excluded types included.
27
- *
28
- * The header carries no order encoding. `buildChangelogEntries` sorts sections by canonical priority, so
29
- * the position a section occupies comes from the taxonomy rather than from the header string.
26
+ * The function passes `options` to the parse, so a policy violation is reported for every ticketed commit whose type
27
+ * resolves, excluded types included.
30
28
  */
31
29
  export declare function classifyChangelogCommit(commit: Pick<Commit, 'hash' | 'message'>, workTypes: Record<string, WorkTypeConfig>, options?: ParseCommitMessageOptions): ChangelogClassification;
32
30
  /**
33
- * Reports whether a subject belongs to a commit that never reaches a changelog: a `release:` commit, which records
34
- * version bumps, or a git merge commit, whose content the merged commits already report.
31
+ * Reports whether a subject belongs to a commit that is never included in a changelog: a `release:` commit, which
32
+ * records version bumps, or a git merge commit, whose content the merged commits already report.
35
33
  */
36
34
  export declare function isNonChangeSubject(subject: string): boolean;
37
35
  /** Reports whether a subject belongs to a `release:` commit, which records version bumps rather than a change. */
@@ -1,4 +1,5 @@
1
- import { COMMIT_PREPROCESSOR_PATTERNS, parseCommitMessage, } from "./parseCommitMessage.js";
1
+ import { TICKET_PREFIX_PATTERNS } from '@williamthorsen/change-grammar';
2
+ import { parseCommitMessage } from "./parseCommitMessage.js";
2
3
  const RELEASE_SUBJECT_PATTERN = /^release:/;
3
4
  const MERGE_SUBJECT_PATTERN = /^Merge/;
4
5
  export function classifyChangelogCommit(commit, workTypes, options) {
@@ -29,5 +30,5 @@ export function isReleaseSubject(subject) {
29
30
  return RELEASE_SUBJECT_PATTERN.test(subject);
30
31
  }
31
32
  function hasTicketPrefix(subject) {
32
- return COMMIT_PREPROCESSOR_PATTERNS.some((pattern) => pattern.test(subject));
33
+ return TICKET_PREFIX_PATTERNS.some((pattern) => pattern.test(subject));
33
34
  }
@@ -1,7 +1,5 @@
1
1
  /**
2
- * Orchestrate the CLI `commit` command.
3
- *
4
- * Reads the tags and summary files produced by `prepare`, stages all
5
- * changes, and creates a release commit with a formatted message.
2
+ * Runs the CLI `commit` command: reads the tags and summary files written by `prepare`, stages all changes, and
3
+ * creates the release commit.
6
4
  */
7
5
  export declare function commitCommand(argv: string[]): void;
@@ -1,8 +1,14 @@
1
1
  /**
2
- * Return true when `target` is strictly greater than `current`, comparing major, minor, then patch.
2
+ * Compares two version strings in descending order (newest first).
3
3
  *
4
- * Equal versions return false. Comparison is numeric (not lexical), so `0.10.0` is greater than `0.9.0`.
5
- *
6
- * @throws If either version is not canonical `N.N.N` semver.
4
+ * Valid SemVer inputs are ordered per SemVer §11 (delegated to `semver.rcompare`): prerelease
5
+ * versions precede the corresponding release (`1.2.3-alpha < 1.2.3`), and build metadata is
6
+ * ignored for ordering. Inputs that fail `semver.valid` sort to the bottom of the descending
7
+ * list, ordered lexically among themselves. The comparator never throws.
8
+ */
9
+ export declare function compareVersionsDescending(a: string, b: string): number;
10
+ /**
11
+ * Reports whether `target` is strictly greater than `current`, comparing major, minor, then patch numerically; throws
12
+ * unless both are canonical `N.N.N` semver.
7
13
  */
8
14
  export declare function isForwardVersion(current: string, target: string): boolean;
@@ -1,3 +1,4 @@
1
+ import semver from 'semver';
1
2
  const CANONICAL_SEMVER_PATTERN = /^(\d+)\.(\d+)\.(\d+)$/;
2
3
  function parseCanonicalSemver(version) {
3
4
  const match = version.match(CANONICAL_SEMVER_PATTERN);
@@ -11,6 +12,21 @@ function parseCanonicalSemver(version) {
11
12
  patch: Number.parseInt(patch, 10),
12
13
  };
13
14
  }
15
+ export function compareVersionsDescending(a, b) {
16
+ const aValid = semver.valid(a);
17
+ const bValid = semver.valid(b);
18
+ if (aValid && bValid)
19
+ return semver.rcompare(aValid, bValid);
20
+ if (aValid)
21
+ return -1;
22
+ if (bValid)
23
+ return 1;
24
+ if (a > b)
25
+ return -1;
26
+ if (a < b)
27
+ return 1;
28
+ return 0;
29
+ }
14
30
  export function isForwardVersion(current, target) {
15
31
  const currentParsed = parseCanonicalSemver(current);
16
32
  const targetParsed = parseCanonicalSemver(target);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The `--config` flag, shared by every subcommand that reads the config file.
3
3
  *
4
- * Spread into a subcommand's own schema rather than repeated, so that seven subcommands cannot drift to seven
4
+ * Spread into a subcommand's own schema rather than repeated, so that the subcommands cannot drift to different
5
5
  * spellings of one flag.
6
6
  */
7
7
  export declare const configFlagSchema: {
@@ -7,12 +7,8 @@ export interface CreateGithubReleaseOptions {
7
7
  sectionOrder?: string[];
8
8
  }
9
9
  /**
10
- * Discriminated reason a single tag was skipped.
11
- *
12
- * `'no-entry'` is the umbrella for all "data missing" sub-cases (changelog file not found,
13
- * JSON unparseable, version not in changelog) — distinguishing them programmatically adds no
14
- * value to the consumer, while the existing `console.warn` messages already discriminate them
15
- * for human diagnostics.
10
+ * Why a tag was skipped. `'no-entry'` covers every case of missing data (no changelog file, unparseable JSON, no entry
11
+ * for the version); the warnings that `createGithubRelease` prints tell them apart.
16
12
  */
17
13
  export type CreateReleaseSkipReason = 'no-entry' | 'no-audience-content' | 'empty-body';
18
14
  /** Discriminated outcome of attempting to create a single GitHub Release. */
@@ -23,13 +19,11 @@ export type CreateReleaseResult = {
23
19
  reason: CreateReleaseSkipReason;
24
20
  };
25
21
  /**
26
- * Create a GitHub Release from changelog.json using the `gh` CLI.
22
+ * Creates a GitHub Release for a tag through the `gh` CLI, with the all-audience notes of the tag's `changelog.json`
23
+ * entry as its body.
27
24
  *
28
- * Reads the changelog JSON, finds the entry matching the tag's version, renders all-audience
29
- * release notes, and creates the release. Returns a discriminated `{status: 'skipped', reason}`
30
- * (with a warning) when the changelog entry is missing, the entry has no all-audience content,
31
- * or the rendered body is empty. Throws when the `gh` CLI invocation itself fails so callers
32
- * can surface the failure rather than exit 0 silently.
25
+ * Returns a skip when the entry is missing (with a warning), doesn't have any all-audience content, or renders an empty
26
+ * body. Throws when `gh` fails, so that the caller does not exit 0 on a failed release.
33
27
  */
34
28
  export declare function createGithubRelease(options: CreateGithubReleaseOptions): CreateReleaseResult;
35
29
  /** Outcome of a `createGithubReleases` invocation. */
@@ -42,13 +36,7 @@ export interface CreateGithubReleasesOutcome {
42
36
  reason: CreateReleaseSkipReason;
43
37
  }>;
44
38
  }
45
- /**
46
- * Create GitHub Releases for each provided tag.
47
- *
48
- * Per-tag soft-fails (missing changelog entry, no all-audience content, empty rendered body) are
49
- * recorded in `skipped` with their discriminated reason and do not throw. Hard failures from the
50
- * `gh` CLI itself throw and short-circuit the loop so callers can surface them.
51
- */
39
+ /** Creates a GitHub Release for each tag and records each skip with its reason; throws at once when `gh` fails. */
52
40
  export declare function createGithubReleases(tags: Array<{
53
41
  tag: string;
54
42
  workspacePath: string;
@@ -1,9 +1,9 @@
1
1
  import { type StreamStyles } from '@williamthorsen/nmr-core';
2
2
  /**
3
- * Orchestrate the CLI `create-github-release` command: Create GitHub Releases from changelog.json
4
- * for tags on HEAD (or a comma-separated `--tags` subset), without requiring npm publish.
3
+ * Runs the CLI `create-github-release` command: creates GitHub Releases from `changelog.json` for the tags on HEAD, or
4
+ * a comma-separated `--tags` subset, without publishing to npm.
5
5
  *
6
- * Private/unpublishable workspaces are skipped with a warning and never get a Release, matching
7
- * `release-kit publish`; an all-private tag set is a clean no-op.
6
+ * A private workspace's tag is skipped with a warning and doesn't get a Release; an all-private tag set is a clean
7
+ * no-op. A relative `--config` resolves against `invocationDir`.
8
8
  */
9
- export declare function createGithubReleaseCommand(argv: string[], styles: StreamStyles): Promise<void>;
9
+ export declare function createGithubReleaseCommand(argv: string[], styles: StreamStyles, invocationDir: string): Promise<void>;
@@ -2,22 +2,24 @@ import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
2
2
  import { describeError } from '@williamthorsen/toolbelt.errors';
3
3
  import { configFlagSchema } from "./configFlagSchema.js";
4
4
  import { createGithubReleases } from "./createGithubRelease.js";
5
+ import { deriveReleaseNotesConfig } from "./deriveReleaseNotesConfig.js";
5
6
  import { formatPrivateSkip } from "./formatPrivateSkip.js";
6
- import { assertConfigUsable } from "./loadValidatedConfig.js";
7
+ import { loadUsableConfig } from "./loadValidatedConfig.js";
7
8
  import { parseRequestedTags } from "./parseRequestedTags.js";
8
9
  import { resolveCommandTags } from "./resolveCommandTags.js";
9
- import { resolveReleaseNotesConfig } from "./resolveReleaseNotesConfig.js";
10
+ import { resolveConfigFlag } from "./resolveConfigFlag.js";
10
11
  const createGithubReleaseFlagSchema = {
11
12
  ...configFlagSchema,
12
13
  dryRun: { long: '--dry-run', type: 'boolean' },
13
14
  tags: { long: '--tags', type: 'string' },
14
15
  };
15
- export async function createGithubReleaseCommand(argv, styles) {
16
+ export async function createGithubReleaseCommand(argv, styles, invocationDir) {
16
17
  const parsed = parseArgsOrExit(argv, createGithubReleaseFlagSchema);
18
+ const configPath = resolveConfigFlag(parsed.flags.config, invocationDir);
17
19
  const { dryRun } = parsed.flags;
18
- await assertConfigUsable(styles.stderr, parsed.flags.config);
20
+ const userConfig = await loadUsableConfig(styles.stderr, configPath);
19
21
  const requestedTags = parseRequestedTags(parsed.flags.tags);
20
- const resolvedTags = resolveCommandTags(requestedTags);
22
+ const resolvedTags = resolveCommandTags(requestedTags, userConfig);
21
23
  const publishableTags = resolvedTags.filter((resolvedTag) => resolvedTag.isPublishable);
22
24
  for (const resolvedTag of resolvedTags) {
23
25
  if (!resolvedTag.isPublishable) {
@@ -27,9 +29,7 @@ export async function createGithubReleaseCommand(argv, styles) {
27
29
  if (publishableTags.length === 0) {
28
30
  return;
29
31
  }
30
- const { changelogJsonOutputPath, sectionOrder } = await resolveReleaseNotesConfig(styles.stderr, {
31
- ...(parsed.flags.config !== undefined && { configPath: parsed.flags.config }),
32
- });
32
+ const { changelogJsonOutputPath, sectionOrder } = deriveReleaseNotesConfig(userConfig);
33
33
  let outcome;
34
34
  try {
35
35
  outcome = createGithubReleases(publishableTags, changelogJsonOutputPath, dryRun, sectionOrder);
@@ -40,6 +40,6 @@ export async function createGithubReleaseCommand(argv, styles) {
40
40
  }
41
41
  if (outcome.skipped.length > 0) {
42
42
  const formatted = outcome.skipped.map((s) => `${s.tag} (${s.reason})`).join(', ');
43
- console.info(`Skipped ${outcome.skipped.length} tag(s) with no releasable content: ${formatted}.`);
43
+ console.info(`Skipped ${outcome.skipped.length} tag(s) without releasable content: ${formatted}.`);
44
44
  }
45
45
  }
@@ -5,8 +5,7 @@ export interface CreateTagsOptions {
5
5
  style: OutputStyle;
6
6
  }
7
7
  /**
8
- * Read tag names from the tags file produced by `prepare` and create annotated git tags.
9
- *
10
- * Returns the list of tag names that were created (or would be created in dry-run mode).
8
+ * Creates an annotated git tag for each name in the tags file written by `prepare`, then deletes the tags and summary
9
+ * files, and returns the tag names. In dry-run mode, it only lists the names.
11
10
  */
12
11
  export declare function createTags(options: CreateTagsOptions): string[];
@@ -10,8 +10,8 @@ export interface DecideReleaseArgs {
10
10
  /** Explicit `--bump=X` override. When set, the level is X regardless of the natural bump. */
11
11
  bumpOverride: ReleaseType | undefined;
12
12
  /**
13
- * Skip wordings to use when no commits exist (`noCommits`) or when commits exist but none yields a changelog item
14
- * (`noBumpWorthy`). Each path passes its own pre-rendered wordings.
13
+ * Skip wordings to use when the window doesn't contain any commits (`noCommits`) or when it contains commits but none
14
+ * yields a changelog item (`noBumpWorthy`). Each path passes its own pre-rendered wordings.
15
15
  */
16
16
  skipReasons: {
17
17
  noCommits: string;
@@ -29,10 +29,6 @@ export type DecideReleaseResult = {
29
29
  /**
30
30
  * Decides whether a window releases, and at which level.
31
31
  *
32
- * Algorithm:
33
- * shouldRelease = naturalBump !== undefined OR force === true
34
- * releaseLevel = bumpOverride ?? naturalBump ?? 'patch'
35
- *
36
32
  * A window without a natural bump releases only under `--force`, so `--bump=X` chooses a level without triggering a
37
33
  * release, and `--force` triggers one without choosing a level.
38
34
  */
@@ -1,33 +1,14 @@
1
+ import { type BreakingPolicy } from '@williamthorsen/change-grammar';
1
2
  import type { ChangelogJsonConfig, ReleaseNotesConfig, VersionPatterns, WorkTypeConfig } from './types.js';
2
- /** Re-export the runtime taxonomy so consumers don't have to depend on `workTypesData.ts` directly. */
3
- export type { WorkTypesData } from './workTypesData.js';
4
- export { WORK_TYPES_DATA } from './workTypesData.js';
5
- /**
6
- * Compose the rendered section heading for a work-type entry as `${emoji} ${label}`.
7
- *
8
- * Single-sourced here so callers (DEFAULT_WORK_TYPES derivation, devOnlySections
9
- * derivation) cannot disagree on the composition rule.
10
- */
3
+ /** Composes the rendered section heading of a work-type entry as `${emoji} ${label}`. */
11
4
  export declare function composeHeader(entry: {
12
5
  emoji: string;
13
6
  label: string;
14
7
  }): string;
15
- /**
16
- * Default work types ordered by canonical priority.
17
- *
18
- * Derived from `work-types.json` (the canonical SSOT, mirrored at runtime by
19
- * `workTypesData.ts`). To change the taxonomy, edit `work-types.json` and update
20
- * `workTypesData.ts` to match — a drift test enforces lockstep.
21
- */
8
+ /** Default work types, ordered by canonical priority. */
22
9
  export declare const DEFAULT_WORK_TYPES: Record<string, WorkTypeConfig>;
23
- /**
24
- * Per-canonical-type breaking-policy lookup derived from the canonical taxonomy.
25
- *
26
- * Pass this to `parseCommitMessage` (or an equivalent caller) so the parser knows which
27
- * types tolerate `!` and which trigger a policy-violation warning. Missing entries default
28
- * to `'optional'` to preserve back-compat for consumers that supply custom work-types.
29
- */
30
- export declare const DEFAULT_BREAKING_POLICIES: Record<string, 'forbidden' | 'optional' | 'required'>;
10
+ /** The breaking policy of each canonical work type, which decides whether a `!` on it is a policy violation. */
11
+ export declare const DEFAULT_BREAKING_POLICIES: Record<string, BreakingPolicy>;
31
12
  /** Default version bump patterns. */
32
13
  export declare const DEFAULT_VERSION_PATTERNS: VersionPatterns;
33
14
  /** Default configuration for structured changelog JSON generation. */
@@ -1,12 +1,11 @@
1
- import { WORK_TYPES_DATA } from "./workTypesData.js";
2
- export { WORK_TYPES_DATA } from "./workTypesData.js";
1
+ import { CANONICAL_TAXONOMY } from '@williamthorsen/change-grammar';
3
2
  export function composeHeader(entry) {
4
3
  return `${entry.emoji} ${entry.label}`;
5
4
  }
6
5
  const DEV_ONLY_TIERS = new Set(['internal', 'process']);
7
6
  function deriveDefaultWorkTypes() {
8
7
  const result = {};
9
- for (const entry of WORK_TYPES_DATA.types) {
8
+ for (const entry of CANONICAL_TAXONOMY.types) {
10
9
  const config = {
11
10
  header: composeHeader(entry),
12
11
  };
@@ -22,7 +21,7 @@ function deriveDefaultWorkTypes() {
22
21
  }
23
22
  function deriveDevOnlySections() {
24
23
  const sections = [];
25
- for (const entry of WORK_TYPES_DATA.types) {
24
+ for (const entry of CANONICAL_TAXONOMY.types) {
26
25
  if (!DEV_ONLY_TIERS.has(entry.tier)) {
27
26
  continue;
28
27
  }
@@ -34,7 +33,7 @@ function deriveDevOnlySections() {
34
33
  return sections;
35
34
  }
36
35
  export const DEFAULT_WORK_TYPES = deriveDefaultWorkTypes();
37
- export const DEFAULT_BREAKING_POLICIES = Object.fromEntries(WORK_TYPES_DATA.types.map((entry) => [entry.key, entry.breakingPolicy]));
36
+ export const DEFAULT_BREAKING_POLICIES = Object.fromEntries(CANONICAL_TAXONOMY.types.map((entry) => [entry.key, entry.breakingPolicy]));
38
37
  export const DEFAULT_VERSION_PATTERNS = {
39
38
  major: ['!'],
40
39
  minor: ['feat'],
@@ -1,7 +1,7 @@
1
1
  import type { ReleaseKitConfig } from './types.js';
2
2
  export type { LabelSpec, ReleaseKitConfig, RepoLabelsConfig } from './types.js';
3
3
  /**
4
- * Type-safe identity function for configuration files.
4
+ * Returns a configuration unchanged, typing it as a `ReleaseKitConfig`.
5
5
  *
6
6
  * Usage in `.config/release-kit.config.ts`:
7
7
  * ```ts
@@ -1,2 +1,2 @@
1
- /** Delete a file, silently ignoring the case where it does not exist. */
1
+ /** Deletes a file, ignoring one that does not exist. */
2
2
  export declare function deleteFileIfExists(filePath: string): void;
@@ -0,0 +1,20 @@
1
+ import type { ReleaseKitConfig, ReleaseNotesConfig } from './types.js';
2
+ export interface ResolvedReleaseNotesConfig {
3
+ releaseNotes: ReleaseNotesConfig;
4
+ changelogJsonOutputPath: string;
5
+ /** Section titles in priority order, derived from the merged workTypes record. */
6
+ sectionOrder: string[];
7
+ }
8
+ /**
9
+ * Returns the header of a merged `workTypes` record's `deps` entry, which titles the synthetic propagation section.
10
+ * Throws when the record lacks one, which `resolveWorkTypes` never produces.
11
+ */
12
+ export declare function deriveDependenciesHeader(workTypes: Record<string, {
13
+ header: string;
14
+ }>): string;
15
+ /** Derives the release-notes settings from a loaded config, falling back to the defaults for an absent one. */
16
+ export declare function deriveReleaseNotesConfig(config: ReleaseKitConfig | undefined): ResolvedReleaseNotesConfig;
17
+ /** Returns the section headers of a merged `workTypes` record, in declaration order. */
18
+ export declare function deriveSectionOrder(workTypes: Record<string, {
19
+ header: string;
20
+ }>): string[];