@williamthorsen/release-kit 18.2.0 → 18.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 (84) hide show
  1. package/.meta/changelog.json +90 -0
  2. package/.readyup/kits/default.js +2 -2
  3. package/.readyup/manifest.json +13 -13
  4. package/CHANGELOG.md +26 -0
  5. package/README.md +11 -12
  6. package/dist/esm/assertCleanWorkingTree.d.ts +2 -1
  7. package/dist/esm/assertCleanWorkingTree.js +4 -8
  8. package/dist/esm/assertReleasesOnNpm.js +2 -9
  9. package/dist/esm/assertTarballsComplete.js +2 -9
  10. package/dist/esm/buildChangelogEntries.d.ts +5 -3
  11. package/dist/esm/buildChangelogEntries.js +9 -4
  12. package/dist/esm/buildChangelogItem.d.ts +1 -1
  13. package/dist/esm/buildDependencyGraph.js +2 -16
  14. package/dist/esm/buildReleasedResult.d.ts +15 -3
  15. package/dist/esm/buildReleasedResult.js +23 -2
  16. package/dist/esm/changelogJsonFile.d.ts +3 -2
  17. package/dist/esm/changelogJsonFile.js +14 -17
  18. package/dist/esm/changelogJsonUtils.d.ts +22 -2
  19. package/dist/esm/changelogJsonUtils.js +19 -10
  20. package/dist/esm/commitCommand.d.ts +2 -1
  21. package/dist/esm/commitCommand.js +8 -11
  22. package/dist/esm/configSchema.d.ts +3 -1
  23. package/dist/esm/configSchema.js +1 -0
  24. package/dist/esm/createGithubRelease.d.ts +13 -10
  25. package/dist/esm/createGithubRelease.js +9 -13
  26. package/dist/esm/createGithubReleaseCommand.d.ts +1 -1
  27. package/dist/esm/createGithubReleaseCommand.js +25 -3
  28. package/dist/esm/createTags.d.ts +2 -1
  29. package/dist/esm/createTags.js +12 -12
  30. package/dist/esm/decideDirectReleases.d.ts +2 -1
  31. package/dist/esm/decideDirectReleases.js +5 -5
  32. package/dist/esm/decideRelease.d.ts +1 -1
  33. package/dist/esm/defaults.d.ts +4 -2
  34. package/dist/esm/defaults.js +1 -0
  35. package/dist/esm/deriveDirectoryConfig.js +5 -4
  36. package/dist/esm/deriveWorkspaceConfig.js +2 -9
  37. package/dist/esm/detectUndeclaredTagPrefixes.d.ts +2 -1
  38. package/dist/esm/detectUndeclaredTagPrefixes.js +4 -13
  39. package/dist/esm/determineBumpType.d.ts +2 -1
  40. package/dist/esm/determineBumpType.js +5 -0
  41. package/dist/esm/enumerateReleaseWindows.d.ts +3 -11
  42. package/dist/esm/enumerateReleaseWindows.js +17 -60
  43. package/dist/esm/findUnroutedEntryScopes.d.ts +1 -1
  44. package/dist/esm/git.d.ts +61 -0
  45. package/dist/esm/git.js +101 -0
  46. package/dist/esm/init/checks.d.ts +2 -1
  47. package/dist/esm/init/checks.js +5 -10
  48. package/dist/esm/init/eligibility.d.ts +12 -0
  49. package/dist/esm/init/eligibility.js +50 -0
  50. package/dist/esm/init/initCommand.d.ts +0 -2
  51. package/dist/esm/init/initCommand.js +4 -45
  52. package/dist/esm/init/templates.js +2 -2
  53. package/dist/esm/init/updateTemplatesCommand.js +4 -31
  54. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +29 -7
  55. package/dist/esm/injectReleaseNotesIntoReadme.js +13 -21
  56. package/dist/esm/loadConfig.js +3 -16
  57. package/dist/esm/mergeConfig.d.ts +2 -1
  58. package/dist/esm/packFromCleanCheckout.js +3 -9
  59. package/dist/esm/planVersionBump.js +2 -16
  60. package/dist/esm/prepareCommand.js +2 -1
  61. package/dist/esm/prepareComposite.js +1 -1
  62. package/dist/esm/prepareReleaseTargets.d.ts +2 -1
  63. package/dist/esm/prepareReleaseTargets.js +3 -2
  64. package/dist/esm/prepareRunContext.d.ts +2 -0
  65. package/dist/esm/prepareTarget.d.ts +6 -4
  66. package/dist/esm/prepareTarget.js +9 -3
  67. package/dist/esm/previewTagPrefixes.d.ts +2 -1
  68. package/dist/esm/previewTagPrefixes.js +16 -21
  69. package/dist/esm/publishCommand.d.ts +1 -1
  70. package/dist/esm/publishCommand.js +27 -2
  71. package/dist/esm/pushRelease.d.ts +2 -1
  72. package/dist/esm/pushRelease.js +11 -11
  73. package/dist/esm/readJsonFile.d.ts +2 -0
  74. package/dist/esm/readJsonFile.js +17 -0
  75. package/dist/esm/reportPrepare.js +68 -114
  76. package/dist/esm/resolveReleaseTags.d.ts +2 -1
  77. package/dist/esm/resolveReleaseTags.js +4 -12
  78. package/dist/esm/types.d.ts +8 -2
  79. package/docs/configuration.md +12 -3
  80. package/docs/labels.md +2 -0
  81. package/docs/releasing.md +3 -2
  82. package/docs/work-types.md +2 -0
  83. package/package.json +3 -3
  84. package/presets/labels/common.yaml +14 -0
@@ -19,42 +19,12 @@ function formatSingleWorkspace(result, options) {
19
19
  if (workspace === undefined || workspace.kind === 'composite') {
20
20
  return '';
21
21
  }
22
- const sinceLabel = workspace.previousTag === undefined ? 'the beginning' : workspace.previousTag;
23
- lines.push(dim(`Found ${workspace.commitCount} commits since ${sinceLabel}`));
24
- if (workspace.parsedCommitCount !== undefined) {
25
- lines.push(dim(` Parsed ${workspace.parsedCommitCount} typed commits`));
26
- }
27
- formatUnparseableWarning(lines, workspace, style);
28
- formatPolicyViolations(lines, workspace.policyViolations, style);
22
+ const sinceLabel = `since ${workspace.previousTag ?? 'the beginning'}`;
23
+ formatTargetSection(lines, workspace, { indent: '', sinceLabel }, isDryRun, style);
29
24
  if (workspace.status === 'skipped') {
30
- formatChangeRecordWarnings(lines, workspace, style);
31
- lines.push(formatStatusLine(style, 'skipped', workspace.skipReason));
32
25
  formatWarnings(lines, result, style);
33
26
  return lines.join('\n');
34
27
  }
35
- if (workspace.versionOverride !== undefined) {
36
- lines.push(` Using version override: ${workspace.versionOverride}`);
37
- }
38
- else if (workspace.bumpOverride !== undefined) {
39
- lines.push(` Using bump override: ${workspace.bumpOverride}`);
40
- }
41
- if (workspace.releaseType !== undefined) {
42
- lines.push(dim(`Bumping versions (${workspace.releaseType})...`));
43
- }
44
- else if (workspace.versionOverride !== undefined) {
45
- lines.push(dim(`Bumping versions (version override)...`));
46
- }
47
- if (workspace.versionOverride !== undefined) {
48
- lines.push(formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${workspace.currentVersion} → ${bold(workspace.newVersion)} (version override)`));
49
- }
50
- else if (workspace.releaseType !== undefined) {
51
- lines.push(formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${workspace.currentVersion} → ${bold(workspace.newVersion)} (${workspace.releaseType})`));
52
- }
53
- formatChangeRecordWarnings(lines, workspace, style);
54
- formatBumpFiles(lines, workspace, isDryRun);
55
- lines.push(dim('Generating changelogs...'));
56
- formatChangelogFiles(lines, workspace, isDryRun, style);
57
- formatPreviewFiles(lines, workspace, isDryRun);
58
28
  formatFormatCommand(lines, result, options);
59
29
  formatWarnings(lines, result, style);
60
30
  lines.push(formatStatusLine(style, 'passed', 'Release preparation complete.'), ` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(workspace.tag))}`);
@@ -65,11 +35,14 @@ function formatMultiWorkspace(result, options) {
65
35
  const { style } = options;
66
36
  const lines = [];
67
37
  for (const target of result.targets) {
68
- if (target.kind === 'composite') {
69
- formatProjectSection(lines, target, isDryRun, style);
38
+ const title = getSectionTitle(target);
39
+ if (title !== undefined) {
40
+ lines.push(`\n${renderSectionHeader(title)}`);
70
41
  }
71
- else {
72
- formatWorkspaceSection(lines, target, isDryRun, style);
42
+ const sinceLabel = target.previousTag === undefined ? '(no previous release found)' : `since ${target.previousTag}`;
43
+ formatTargetSection(lines, target, { indent: ' ', sinceLabel }, isDryRun, style);
44
+ if (target.status === 'released') {
45
+ lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(target.tag))}`);
73
46
  }
74
47
  }
75
48
  formatFormatCommand(lines, result, options);
@@ -85,98 +58,79 @@ function formatMultiWorkspace(result, options) {
85
58
  }
86
59
  return lines.join('\n');
87
60
  }
88
- function formatProjectSection(lines, project, isDryRun, style) {
89
- lines.push(`\n${renderSectionHeader(project.targetId === PROJECT_TARGET_ID ? 'project' : project.targetId)}`);
90
- const sinceLabel = project.previousTag === undefined ? '(no previous release found)' : `since ${project.previousTag}`;
91
- lines.push(dim(` Found ${project.commitCount} commits ${sinceLabel}`));
92
- formatPolicyViolations(lines, project.policyViolations, style, ' ');
93
- if (project.status === 'skipped') {
94
- formatUnparseableWarning(lines, project, style, ' ');
95
- formatChangeRecordWarnings(lines, project, style, ' ');
96
- lines.push(` ${formatStatusLine(style, 'skipped', project.skipReason)}`);
97
- return;
98
- }
99
- const { releaseType, currentVersion, newVersion, tag } = project;
100
- if (project.parsedCommitCount > 0) {
101
- lines.push(dim(` Parsed ${project.parsedCommitCount} typed commits`));
61
+ function getSectionTitle(target) {
62
+ if (target.kind === 'composite') {
63
+ return target.targetId === PROJECT_TARGET_ID ? 'project' : target.targetId;
102
64
  }
103
- if (project.bumpOverride !== undefined) {
104
- lines.push(` Using bump override: ${project.bumpOverride}`);
105
- }
106
- formatUnparseableWarning(lines, project, style, ' ');
107
- formatChangeRecordWarnings(lines, project, style, ' ');
108
- lines.push(dim(` Bumping versions (${releaseType})...`), ` ${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${currentVersion} → ${bold(newVersion)} (${releaseType})`)}`);
109
- for (const file of project.bumpedFiles) {
110
- if (isDryRun) {
111
- lines.push(dim(` [dry-run] Would bump ${file}`));
112
- }
113
- else {
114
- lines.push(dim(` Bumped ${file}`));
115
- }
116
- }
117
- lines.push(dim(' Generating changelogs...'));
118
- formatChangelogFiles(lines, project, isDryRun, style, ' ');
119
- formatPreviewFiles(lines, project, isDryRun, ' ');
120
- lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(tag))}`);
65
+ return target.name;
121
66
  }
122
- function formatWorkspaceSection(lines, workspace, isDryRun, style) {
123
- if (workspace.name !== undefined) {
124
- lines.push(`\n${renderSectionHeader(workspace.name)}`);
125
- }
126
- const sinceLabel = workspace.previousTag === undefined ? '(no previous release found)' : `since ${workspace.previousTag}`;
127
- lines.push(dim(` Found ${workspace.commitCount} commits ${sinceLabel}`));
128
- if (workspace.status === 'skipped') {
129
- formatUnparseableWarning(lines, workspace, style, ' ');
130
- formatPolicyViolations(lines, workspace.policyViolations, style, ' ');
131
- formatChangeRecordWarnings(lines, workspace, style, ' ');
132
- lines.push(` ${formatStatusLine(style, 'skipped', workspace.skipReason)}`);
67
+ function formatTargetSection(lines, target, layout, isDryRun, style) {
68
+ const { indent, sinceLabel } = layout;
69
+ lines.push(dim(`${indent}Found ${target.commitCount} commits ${sinceLabel}`));
70
+ if (target.status === 'skipped') {
71
+ formatTargetWarnings(lines, target, style, indent);
72
+ lines.push(`${indent}${formatStatusLine(style, 'skipped', target.skipReason)}`);
133
73
  return;
134
74
  }
135
- const propagatedFrom = workspace.kind === 'package' ? workspace.propagatedFrom : undefined;
136
- const isPropagatedOnly = workspace.kind === 'package' && workspace.propagatedOnly === true;
137
- formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly);
138
- formatUnparseableWarning(lines, workspace, style, ' ');
139
- formatPolicyViolations(lines, workspace.policyViolations, style, ' ');
140
- formatChangeRecordWarnings(lines, workspace, style, ' ');
141
- formatBumpLabels(lines, workspace, isPropagatedOnly);
142
- formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly, style);
143
- formatBumpFiles(lines, workspace, isDryRun, ' ');
144
- lines.push(dim(' Generating changelogs...'));
145
- formatChangelogFiles(lines, workspace, isDryRun, style, ' ');
146
- formatPreviewFiles(lines, workspace, isDryRun, ' ');
147
- lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(workspace.tag))}`);
75
+ const origin = describeBumpOrigin(target);
76
+ formatCommitSummary(lines, target, origin);
77
+ formatTargetWarnings(lines, target, style, indent);
78
+ formatBumpLabels(lines, target, origin, indent);
79
+ formatVersionLine(lines, target, origin, indent, style);
80
+ formatBumpFiles(lines, target, isDryRun, indent);
81
+ lines.push(dim(`${indent}Generating changelogs...`));
82
+ formatChangelogFiles(lines, target, isDryRun, style, indent);
83
+ formatPreviewFiles(lines, target, isDryRun, indent);
84
+ }
85
+ function formatTargetWarnings(lines, target, style, indent) {
86
+ formatUnparseableWarning(lines, target, style, indent);
87
+ formatPolicyViolations(lines, target.policyViolations, style, indent);
88
+ formatChangeRecordWarnings(lines, target, style, indent);
89
+ }
90
+ function describeBumpOrigin(target) {
91
+ if (target.kind === 'composite') {
92
+ return { propagatedFrom: undefined, isPropagatedOnly: false, versionOverride: undefined };
93
+ }
94
+ if (target.kind === 'directory') {
95
+ return { propagatedFrom: undefined, isPropagatedOnly: false, versionOverride: target.versionOverride };
96
+ }
97
+ return {
98
+ propagatedFrom: target.propagatedFrom,
99
+ isPropagatedOnly: target.propagatedOnly === true,
100
+ versionOverride: target.versionOverride,
101
+ };
148
102
  }
149
- function formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly) {
150
- if (isPropagatedOnly && propagatedFrom !== undefined) {
151
- const dependencyNames = propagatedFrom.map((p) => p.packageName).join(', ');
103
+ function formatCommitSummary(lines, target, origin) {
104
+ if (origin.isPropagatedOnly && origin.propagatedFrom !== undefined) {
105
+ const dependencyNames = origin.propagatedFrom.map((p) => p.packageName).join(', ');
152
106
  lines.push(dim(` Bumped via dependency: ${dependencyNames}`));
153
107
  }
154
- else if (workspace.parsedCommitCount !== undefined && workspace.parsedCommitCount > 0) {
155
- lines.push(dim(` Parsed ${workspace.parsedCommitCount} typed commits`));
108
+ else if (target.parsedCommitCount !== undefined && target.parsedCommitCount > 0) {
109
+ lines.push(dim(` Parsed ${target.parsedCommitCount} typed commits`));
156
110
  }
157
111
  }
158
- function formatBumpLabels(lines, workspace, isPropagatedOnly) {
159
- if (workspace.versionOverride !== undefined) {
160
- lines.push(` Using version override: ${workspace.versionOverride}`);
112
+ function formatBumpLabels(lines, target, origin, indent) {
113
+ if (origin.versionOverride !== undefined) {
114
+ lines.push(` Using version override: ${origin.versionOverride}`);
161
115
  }
162
- else if (workspace.bumpOverride !== undefined && !isPropagatedOnly) {
163
- lines.push(` Using bump override: ${workspace.bumpOverride}`);
116
+ else if (target.bumpOverride !== undefined && !origin.isPropagatedOnly) {
117
+ lines.push(` Using bump override: ${target.bumpOverride}`);
164
118
  }
165
- if (workspace.releaseType !== undefined) {
166
- lines.push(dim(` Bumping versions (${workspace.releaseType})...`));
119
+ if (target.releaseType !== undefined) {
120
+ lines.push(dim(`${indent}Bumping versions (${target.releaseType})...`));
167
121
  }
168
- else if (workspace.versionOverride !== undefined) {
169
- lines.push(dim(` Bumping versions (version override)...`));
122
+ else if (origin.versionOverride !== undefined) {
123
+ lines.push(dim(`${indent}Bumping versions (version override)...`));
170
124
  }
171
125
  }
172
- function formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly, style) {
173
- const versions = `${workspace.currentVersion} → ${bold(workspace.newVersion)}`;
174
- if (workspace.versionOverride !== undefined) {
175
- lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${versions} (version override)`)}`);
176
- }
177
- else if (workspace.releaseType !== undefined) {
178
- const suffix = isPropagatedOnly ? formatPropagationSuffix(propagatedFrom) : '';
179
- lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${versions} (${workspace.releaseType}${suffix})`)}`);
126
+ function formatVersionLine(lines, target, origin, indent, style) {
127
+ const versions = `${target.currentVersion} → ${bold(target.newVersion)}`;
128
+ if (origin.versionOverride !== undefined) {
129
+ lines.push(`${indent}${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${versions} (version override)`)}`);
130
+ }
131
+ else if (target.releaseType !== undefined) {
132
+ const suffix = origin.isPropagatedOnly ? formatPropagationSuffix(origin.propagatedFrom) : '';
133
+ lines.push(`${indent}${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${versions} (${target.releaseType}${suffix})`)}`);
180
134
  }
181
135
  }
182
136
  function formatPropagationSuffix(propagatedFrom) {
@@ -1,3 +1,4 @@
1
+ import { type Git } from './git.js';
1
2
  import type { DirectoryConfig, ResolvedProjectConfig, WorkspaceConfig } from './types.js';
2
3
  export interface ResolvedTag {
3
4
  tag: string;
@@ -41,5 +42,5 @@ type ResolveReleaseTagsArgs = {
41
42
  * matching without needing `isPublishable` propagation; resolved tags default to
42
43
  * `isPublishable: true`. Production callers should pass one of the named forms.
43
44
  */
44
- export declare function resolveReleaseTags(args?: ResolveReleaseTagsArgs): ResolvedTag[];
45
+ export declare function resolveReleaseTags(args?: ResolveReleaseTagsArgs, git?: Git): ResolvedTag[];
45
46
  export {};
@@ -1,25 +1,17 @@
1
- import { execFileSync } from 'node:child_process';
2
- import { GIT_OUTPUT_LIMIT_BYTES } from '@williamthorsen/nmr-core';
1
+ import { realGit } from "./git.js";
3
2
  import { getTargetName } from "./resolveReleaseDeclaration.js";
4
3
  import { PROJECT_TARGET_ID } from "./resolveReleaseTargets.js";
5
4
  import { asUserError } from "./user-error-chaining.js";
6
5
  const VERSION_PATTERN = /^v\d+\.\d+\.\d+/;
7
6
  const SEMVER_SUFFIX_PATTERN = /^\d+\.\d+\.\d+/;
8
- export function resolveReleaseTags(args) {
9
- let output;
7
+ export function resolveReleaseTags(args, git = realGit) {
8
+ let tags;
10
9
  try {
11
- output = execFileSync('git', ['tag', '--points-at', 'HEAD'], {
12
- encoding: 'utf8',
13
- maxBuffer: GIT_OUTPUT_LIMIT_BYTES,
14
- });
10
+ tags = git.listTagsAtHead();
15
11
  }
16
12
  catch (error) {
17
13
  throw asUserError(error);
18
14
  }
19
- const tags = output
20
- .split('\n')
21
- .map((line) => line.trim())
22
- .filter((line) => line.length > 0);
23
15
  if (args === undefined) {
24
16
  return resolveSinglePackageTags(tags);
25
17
  }
@@ -355,8 +355,9 @@ export interface WorkTypeConfig {
355
355
  excludedFromChangelog?: boolean | undefined;
356
356
  }
357
357
  /**
358
- * Defines which commit types trigger major or minor version bumps.
359
- * Any recognized commit type not listed defaults to a patch bump.
358
+ * Defines which commit types trigger major or minor version bumps, and which trigger none.
359
+ * Any recognized commit type not listed in `major`, `minor`, or `none` (the default list when omitted) defaults to a
360
+ * patch bump.
360
361
  * The sentinel `'!'` in `major` means "any breaking commit triggers a major bump".
361
362
  */
362
363
  export interface VersionPatterns {
@@ -364,6 +365,11 @@ export interface VersionPatterns {
364
365
  major: string[];
365
366
  /** Commit types that trigger a minor bump. */
366
367
  minor: string[];
368
+ /**
369
+ * Commit types that do not trigger a bump unless breaking. Defaults to `DEFAULT_VERSION_PATTERNS.none` when omitted;
370
+ * `[]` gives every unlisted type a patch bump.
371
+ */
372
+ none?: string[] | undefined;
367
373
  }
368
374
  /** A raw commit from the git log. */
369
375
  export interface Commit {
@@ -22,7 +22,7 @@ The two paths differ in what an absent file means. An absent default path means
22
22
  | `workspaces` | `WorkspaceOverride[]` | Override or exclude discovered workspaces (matched by `dir`) |
23
23
  | `devExportConditions` | `string[]` | Export conditions that resolve only inside the repo, such as one that points at unpublished source; the [tarball check](releasing.md#packages-whose-tarball-would-lack-a-manifest-file) does not require their targets in the tarball. `source` is exempt without being listed |
24
24
  | `formatCommand` | `string` | Shell command to run after changelog generation; modified file paths are appended as arguments |
25
- | `versionPatterns` | `VersionPatterns` | Rules for which commit types trigger major/minor bumps |
25
+ | `versionPatterns` | `VersionPatterns` | Rules for which commit types trigger major, minor, or no bumps |
26
26
  | `scopeAliases` | `Record<string, string>` | Maps shorthand scope names to canonical names in commit titles and in change-record entry `scopes`, which [route items to workspaces](changelogs.md#routing-to-workspaces). A renamed workspace directory or [release target](#releases) needs an entry from its old name to its new one |
27
27
  | `workTypes` | `Record<string, WorkTypeConfig>` | Work type definitions, merged with defaults by key |
28
28
  | `breakingPolicies` | `Record<string, 'forbidden' \| 'optional'>` | Per-type `!`-policy lookup. Defaults to `DEFAULT_BREAKING_POLICIES`. Replaces the default entirely when provided. Set to `{}` to disable enforcement |
@@ -170,16 +170,25 @@ In single-package mode, prints a single block for `.` with the derived prefix `v
170
170
 
171
171
  ## `VersionPatterns`
172
172
 
173
- Defines which work types trigger major or minor bumps. `prepare` applies it to each changelog item of the unreleased window and takes the highest level. An item of a type not listed bumps patch.
173
+ Defines which work types trigger major or minor bumps, and which trigger none. `prepare` applies it to each changelog item of the unreleased window and takes the highest level. An item of a type that `none` lists doesn't bump, and an item of any other type not listed bumps patch. A window whose items don't call for any bump doesn't release unless `--force` is passed, and its items wait in the unreleased window for the next release.
174
174
 
175
175
  ```typescript
176
176
  interface VersionPatterns {
177
177
  major: string[]; // Patterns triggering a major bump ('!' = any breaking change)
178
178
  minor: string[]; // Commit types triggering a minor bump
179
+ none?: string[]; // Commit types triggering no bump unless breaking
179
180
  }
180
181
  ```
181
182
 
182
- Default: `{ major: ['!'], minor: ['feat'] }`
183
+ Default: `{ major: ['!'], minor: ['feat'], none: ['ai', 'ci', 'docs', 'tests', 'tooling'] }`
184
+
185
+ The default `none` types don't change the code that a consumer runs. `refactor`, `internal`, and `deps` bump patch, because they change shipped code or installed dependencies. A `docs` change to a file that the package publishes reaches the registry with the next release.
186
+
187
+ A config that sets `versionPatterns` replaces `major` and `minor`, but an omitted `none` keeps the default list. To give every type not listed in `major` or `minor` a patch bump, as release-kit did before `none` existed, set `none: []`:
188
+
189
+ ```typescript
190
+ versionPatterns: { major: ['!'], minor: ['feat'], none: [] },
191
+ ```
183
192
 
184
193
  ## Output style
185
194
 
package/docs/labels.md CHANGED
@@ -38,6 +38,8 @@ The bundled file's `confidence:` family grades how advisable a recommendation is
38
38
 
39
39
  The `urgency:` family states how soon an item needs attention, apart from how valuable it is, which the `value:` family states. Its labels replace the earlier `priority:` family and name the old labels in `aliases`, so a repository that has `priority:` labels has them renamed in place by the first sync after it regenerates `.github/labels.yaml` (see [Renaming a label](#renaming-a-label)).
40
40
 
41
+ The `effort:` family states an item's size in points, apart from its urgency and value: one label for each value of the scale 1, 2, 3, 5, 8, and 13, from `effort:1` to `effort:13`.
42
+
41
43
  The `status:` family marks where an item stands in its work: `status:in-progress` once work on it has started, `status:blocked` while it waits on another item, and `status:on-hold` while it is deliberately paused.
42
44
 
43
45
  Names match case-insensitively, as GitHub matches them: `Bug` in the `labels` record replaces a preset's `bug`, and `Bug: null` removes it. A label takes the spelling of the last layer to write it.
package/docs/releasing.md CHANGED
@@ -31,13 +31,14 @@ The workflows are managed files: release-kit renders them, and a change made by
31
31
 
32
32
  Runs release preparation with automatic workspace discovery.
33
33
 
34
- `--only` names the release targets from which the release starts, by target name: a workspace's directory name (e.g., `arrays`, `release-kit`) unless a [`releases`](configuration.md#releases) entry renames it, or a `releases` composite's name. A named composite releases from its own window, whether or not the run includes its members; a run narrowed by `--only` skips every composite that it doesn't name. The run also releases every workspace that depends on one of them, directly or transitively: A dependent with commits of its own releases at its own bump, and one without releases at a propagated patch bump, so that it publishes against the new version. Workspaces outside that set are left out of the run.
34
+ `--only` names the release targets from which the release starts, by target name: a workspace's directory name (e.g., `arrays`, `release-kit`) unless a [`releases`](configuration.md#releases) entry renames it, or a `releases` composite's name. A named composite releases from its own window, whether or not the run includes its members; a run narrowed by `--only` skips every composite that it doesn't name. The run also releases every workspace that depends on one of them, directly or transitively: A dependent whose commits call for a bump releases at its own bump, and one whose commits don't releases at a propagated patch bump, so that it publishes against the new version; its changelog entry lists its own items, if any, with the dependencies. Workspaces outside that set are left out of the run.
35
35
 
36
36
  `prepare` reads each target's history once and builds the changelog items of its unreleased commits. The bump is the highest level that those items call for, under [`versionPatterns`](configuration.md#versionpatterns), so the bump and the changelog describe the same release: A `feat` entry in a [change-record block](changelogs.md#change-record-blocks) bumps minor under a `docs:` title, and a breaking item bumps major.
37
37
 
38
38
  - A commit that doesn't yield an item doesn't bump anything: one without a ticket-ID prefix, one of a type excluded from the changelog (such as `fmt`), and one whose change-record entries are all undeclared or excluded.
39
39
  - A `BREAKING CHANGE:` footer doesn't raise a major bump. Only a subject's `!` and an entry's `breaking: true` mark an item breaking; on a type whose policy forbids `!`, the footer is reported as a [policy violation](work-types.md#policy-enforcement).
40
- - A target whose commits don't yield an item is skipped. The prepare report lists the commits that could not be parsed and the change-record diagnostics, skipped targets included.
40
+ - An item of a type that [`versionPatterns.none`](configuration.md#versionpatterns) lists, such as `tests` or `ci` by default, doesn't bump anything unless it is breaking.
41
+ - A target whose items don't call for a bump is skipped, and so is one whose commits don't yield an item. The prepare report lists the commits that could not be parsed and the change-record diagnostics, skipped targets included.
41
42
 
42
43
  `--bump` and `--force` are orthogonal in every mode, single-package included: `--bump` is purely a level chooser; `--force` is purely a release trigger. Examples:
43
44
 
@@ -28,6 +28,8 @@ release-kit reads the taxonomy from `CANONICAL_TAXONOMY` in [`@williamthorsen/ch
28
28
  - **`internal`**: Dev-only. `internal`-tier sections appear in dev changelogs but not in public-facing release notes.
29
29
  - **`process`**: Dev-only. Same audience treatment as `internal`.
30
30
 
31
+ Tiers decide the audience, not the bump: [`versionPatterns`](configuration.md#versionpatterns) decides which types bump and which don't, and by default some `internal`- and `process`-tier types bump patch while others don't bump at all.
32
+
31
33
  Section render order is **tier order (`public` → `internal` → `process`), then row order within tier**. It comes from the declaration order of `CANONICAL_TAXONOMY`: `CANONICAL_SECTION_ORDER` in `buildChangelogEntries.ts` indexes each header by its row, and `transformReleases` sorts a release's sections by that index whatever order the commits arrived in.
32
34
 
33
35
  ## Subject forms
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/release-kit",
3
- "version": "18.2.0",
3
+ "version": "18.3.0",
4
4
  "description": "Version-bumping and changelog-generation toolkit for release workflows",
5
5
  "keywords": [
6
6
  "changelog",
@@ -61,8 +61,8 @@
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/semver": "7.8.0",
64
- "@williamthorsen/nmr": "2.2.1",
65
- "@williamthorsen/toolbelt.filesystem": "0.10.3",
64
+ "@williamthorsen/nmr": "2.2.2",
65
+ "@williamthorsen/toolbelt.filesystem": "0.11.0",
66
66
  "@williamthorsen/toolbelt.testing": "0.8.3",
67
67
  "@williamthorsen/toolbelt.vitest": "0.13.5",
68
68
  "actionlint": "2.0.6",
@@ -44,6 +44,20 @@
44
44
  color: cfd3d7
45
45
  aliases: ['priority:low']
46
46
 
47
+ # Effort
48
+ - name: 'effort:1'
49
+ color: dbe9ff
50
+ - name: 'effort:2'
51
+ color: b9d4ff
52
+ - name: 'effort:3'
53
+ color: 8fb8fb
54
+ - name: 'effort:5'
55
+ color: 5b93f0
56
+ - name: 'effort:8'
57
+ color: '3470e0'
58
+ - name: 'effort:13'
59
+ color: 1d4ed8
60
+
47
61
  # Status
48
62
  - name: 'status:blocked'
49
63
  color: b83280