@williamthorsen/release-kit 14.2.2 → 14.2.4

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 (85) hide show
  1. package/.meta/changelog.json +88 -0
  2. package/.readyup/kits/default.js +135 -145
  3. package/.readyup/kits/npm-auto-publish.js +179 -180
  4. package/.readyup/manifest.json +31 -17
  5. package/CHANGELOG.md +35 -0
  6. package/README.md +7 -1
  7. package/dist/esm/applyChangelogOverrides.d.ts +50 -0
  8. package/dist/esm/applyChangelogOverrides.js +162 -0
  9. package/dist/esm/attachChangelogDiagnostics.d.ts +1 -1
  10. package/dist/esm/bin/release-kit.js +6 -238
  11. package/dist/esm/buildChangelogEntries.d.ts +24 -35
  12. package/dist/esm/buildChangelogEntries.js +71 -188
  13. package/dist/esm/buildChangelogItem.d.ts +58 -0
  14. package/dist/esm/buildChangelogItem.js +122 -0
  15. package/dist/esm/buildReleasedResult.d.ts +20 -0
  16. package/dist/esm/buildReleasedResult.js +78 -0
  17. package/dist/esm/changelogJsonUtils.d.ts +1 -1
  18. package/dist/esm/changelogOverrides.d.ts +29 -143
  19. package/dist/esm/changelogOverrides.js +35 -467
  20. package/dist/esm/configSchema.d.ts +225 -0
  21. package/dist/esm/configSchema.js +103 -0
  22. package/dist/esm/decideDirectReleases.d.ts +30 -0
  23. package/dist/esm/decideDirectReleases.js +66 -0
  24. package/dist/esm/defineConfig.d.ts +2 -2
  25. package/dist/esm/deriveReleaseNotesConfig.d.ts +2 -1
  26. package/dist/esm/deriveReleaseNotesConfig.js +1 -1
  27. package/dist/esm/formatPrepareFiles.d.ts +11 -0
  28. package/dist/esm/formatPrepareFiles.js +55 -0
  29. package/dist/esm/formatPrepareWarnings.d.ts +26 -0
  30. package/dist/esm/formatPrepareWarnings.js +72 -0
  31. package/dist/esm/help/initHelp.d.ts +4 -0
  32. package/dist/esm/help/initHelp.js +31 -0
  33. package/dist/esm/help/overridesHelp.d.ts +4 -0
  34. package/dist/esm/help/overridesHelp.js +32 -0
  35. package/dist/esm/help/releaseStepsHelp.d.ts +12 -0
  36. package/dist/esm/help/releaseStepsHelp.js +91 -0
  37. package/dist/esm/help/syncLabelsHelp.d.ts +8 -0
  38. package/dist/esm/help/syncLabelsHelp.js +55 -0
  39. package/dist/esm/help/usage.d.ts +2 -0
  40. package/dist/esm/help/usage.js +29 -0
  41. package/dist/esm/index.d.ts +1 -1
  42. package/dist/esm/index.js +1 -1
  43. package/dist/esm/loadChangelogOverrides.d.ts +31 -0
  44. package/dist/esm/loadChangelogOverrides.js +138 -0
  45. package/dist/esm/loadConfig.d.ts +0 -40
  46. package/dist/esm/loadConfig.js +0 -240
  47. package/dist/esm/loadValidatedConfig.d.ts +1 -1
  48. package/dist/esm/mergeConfig.d.ts +41 -0
  49. package/dist/esm/mergeConfig.js +118 -0
  50. package/dist/esm/monorepoConfigAssertions.d.ts +49 -0
  51. package/dist/esm/monorepoConfigAssertions.js +126 -0
  52. package/dist/esm/planChangelogFiles.d.ts +16 -0
  53. package/dist/esm/planChangelogFiles.js +41 -0
  54. package/dist/esm/prepareArgs.d.ts +55 -0
  55. package/dist/esm/prepareArgs.js +69 -0
  56. package/dist/esm/prepareCommand.d.ts +0 -48
  57. package/dist/esm/prepareCommand.js +6 -73
  58. package/dist/esm/prepareComposite.d.ts +16 -0
  59. package/dist/esm/prepareComposite.js +39 -0
  60. package/dist/esm/prepareRunContext.d.ts +38 -0
  61. package/dist/esm/prepareRunContext.js +39 -0
  62. package/dist/esm/prepareTarget.d.ts +2 -7
  63. package/dist/esm/prepareTarget.js +2 -118
  64. package/dist/esm/previewTagPrefixes.d.ts +1 -1
  65. package/dist/esm/releasePrepareTargets.js +13 -205
  66. package/dist/esm/reportPrepare.js +3 -125
  67. package/dist/esm/resolveCommandTags.d.ts +1 -1
  68. package/dist/esm/resolveCommandTags.js +2 -1
  69. package/dist/esm/resolveReleaseDeclaration.d.ts +2 -1
  70. package/dist/esm/resolveReleaseTargets.d.ts +2 -1
  71. package/dist/esm/resolveReleaseTargets.js +2 -1
  72. package/dist/esm/sync-labels/generateCommand.d.ts +1 -1
  73. package/dist/esm/sync-labels/resolveLabels.d.ts +1 -1
  74. package/dist/esm/topologicalSort.d.ts +12 -0
  75. package/dist/esm/topologicalSort.js +60 -0
  76. package/dist/esm/types.d.ts +1 -225
  77. package/dist/esm/types.js +1 -103
  78. package/dist/esm/validateAllChangelogOverrides.d.ts +56 -0
  79. package/dist/esm/validateAllChangelogOverrides.js +138 -0
  80. package/dist/esm/validateConfig.d.ts +1 -1
  81. package/dist/esm/validateConfig.js +7 -9
  82. package/dist/esm/validateOverridesCommand.d.ts +1 -1
  83. package/dist/esm/validateOverridesCommand.js +2 -1
  84. package/docs/changelogs.md +6 -4
  85. package/package.json +4 -4
@@ -0,0 +1,225 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Schema for a single historical identity snapshot for a workspace.
4
+ *
5
+ * Captures what the package looked like at some earlier point: the full npm `name`
6
+ * (e.g., `'@williamthorsen/nmr-core'`) and the `tagPrefix` under which tags were
7
+ * published (e.g., `'core-v'`).
8
+ */
9
+ export declare const legacyIdentitySchema: z.ZodObject<{
10
+ name: z.ZodString;
11
+ tagPrefix: z.ZodString;
12
+ }, z.core.$strict>;
13
+ /**
14
+ * A single historical identity snapshot for a workspace. Both fields are required, because a full
15
+ * tuple stays unambiguous across any number of renames.
16
+ */
17
+ export type LegacyIdentity = z.infer<typeof legacyIdentitySchema>;
18
+ /**
19
+ * Schema for a package that once lived in this repo but has since been extracted or removed.
20
+ *
21
+ * Unlike `legacyIdentitySchema`, retired packages are never consulted for baseline lookup
22
+ * or changelog attribution; they acknowledge historical tag prefixes and suppress
23
+ * undeclared-candidate warnings.
24
+ */
25
+ export declare const retiredPackageSchema: z.ZodObject<{
26
+ name: z.ZodString;
27
+ tagPrefix: z.ZodString;
28
+ successor: z.ZodOptional<z.ZodString>;
29
+ }, z.core.$strict>;
30
+ /** A package that once lived in this repo but has since been extracted or removed. */
31
+ export type RetiredPackage = z.infer<typeof retiredPackageSchema>;
32
+ /** Schema for a single workspace override entry in the config file. */
33
+ export declare const workspaceOverrideSchema: z.ZodObject<{
34
+ dir: z.ZodString;
35
+ shouldExclude: z.ZodOptional<z.ZodBoolean>;
36
+ legacyIdentities: z.ZodOptional<z.ZodArray<z.ZodObject<{
37
+ name: z.ZodString;
38
+ tagPrefix: z.ZodString;
39
+ }, z.core.$strict>>>;
40
+ }, z.core.$strict>;
41
+ /** Override for a single workspace in the config file. Matches a discovered workspace by `dir`. */
42
+ export type WorkspaceOverride = z.infer<typeof workspaceOverrideSchema>;
43
+ /** Schema for the optional `project` block. */
44
+ export declare const projectConfigSchema: z.ZodObject<{
45
+ paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
46
+ tagPrefix: z.ZodOptional<z.ZodString>;
47
+ }, z.core.$strict>;
48
+ /** Consumer-facing project-release config (`project` block). Empty object opts in to project releases. */
49
+ export type ProjectConfig = z.infer<typeof projectConfigSchema>;
50
+ /**
51
+ * Schema for a `releases` entry that names the target at `path`: the discovered workspace there, or otherwise the
52
+ * directory, whose `tagPrefix` defaults to its basename followed by `-v`.
53
+ */
54
+ export declare const pathReleaseSchema: z.ZodObject<{
55
+ path: z.ZodString;
56
+ tagPrefix: z.ZodOptional<z.ZodString>;
57
+ legacyIdentities: z.ZodOptional<z.ZodArray<z.ZodObject<{
58
+ name: z.ZodString;
59
+ tagPrefix: z.ZodString;
60
+ }, z.core.$strict>>>;
61
+ }, z.core.$strict>;
62
+ /** A `releases` entry that names the discovered workspace or the directory at `path`. */
63
+ export type PathRelease = z.infer<typeof pathReleaseSchema>;
64
+ /** Schema for a `releases` entry that declares a composite of the named targets. */
65
+ export declare const compositeReleaseSchema: z.ZodObject<{
66
+ includes: z.ZodArray<z.ZodString>;
67
+ tagPrefix: z.ZodOptional<z.ZodString>;
68
+ }, z.core.$strict>;
69
+ /** A `releases` entry that declares a composite of the named targets, versioned by the root `package.json`. */
70
+ export type CompositeRelease = z.infer<typeof compositeReleaseSchema>;
71
+ /**
72
+ * Schema for one `releases` entry: `false` excludes the discovered workspace of that name, and the object shapes name
73
+ * a workspace or a directory, or declare the composite. `validateConfig` relies on this branch order to report a failed entry.
74
+ */
75
+ export declare const releaseEntrySchema: z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodObject<{
76
+ path: z.ZodString;
77
+ tagPrefix: z.ZodOptional<z.ZodString>;
78
+ legacyIdentities: z.ZodOptional<z.ZodArray<z.ZodObject<{
79
+ name: z.ZodString;
80
+ tagPrefix: z.ZodString;
81
+ }, z.core.$strict>>>;
82
+ }, z.core.$strict>, z.ZodObject<{
83
+ includes: z.ZodArray<z.ZodString>;
84
+ tagPrefix: z.ZodOptional<z.ZodString>;
85
+ }, z.core.$strict>]>;
86
+ /** One `releases` entry, keyed in the config by the target's name. */
87
+ export type ReleaseEntry = z.infer<typeof releaseEntrySchema>;
88
+ /** Schema for the optional `changelogJson` block (input shape, all fields optional). */
89
+ export declare const changelogJsonInputSchema: z.ZodObject<{
90
+ enabled: z.ZodOptional<z.ZodBoolean>;
91
+ outputPath: z.ZodOptional<z.ZodString>;
92
+ devOnlySections: z.ZodOptional<z.ZodArray<z.ZodString>>;
93
+ }, z.core.$strict>;
94
+ /** Schema for the optional `changelogMarkdown` block (input shape, all fields optional). */
95
+ export declare const changelogMarkdownInputSchema: z.ZodObject<{
96
+ keepVersions: z.ZodOptional<z.ZodNumber>;
97
+ }, z.core.$strict>;
98
+ /** Schema for the optional `releaseNotes` block (input shape, all fields optional). */
99
+ export declare const releaseNotesInputSchema: z.ZodObject<{
100
+ shouldInjectIntoReadme: z.ZodOptional<z.ZodBoolean>;
101
+ }, z.core.$strict>;
102
+ /** Schema for a single `workTypes` entry. */
103
+ export declare const workTypeConfigSchema: z.ZodObject<{
104
+ header: z.ZodString;
105
+ aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
106
+ excludedFromChangelog: z.ZodOptional<z.ZodBoolean>;
107
+ }, z.core.$strict>;
108
+ /** Schema for the `versionPatterns` block. Both arrays are required when the field is provided. */
109
+ export declare const versionPatternsSchema: z.ZodObject<{
110
+ major: z.ZodArray<z.ZodString>;
111
+ minor: z.ZodArray<z.ZodString>;
112
+ }, z.core.$strict>;
113
+ /** Schema for a single `breakingPolicies` value. */
114
+ export declare const breakingPolicyValueSchema: z.ZodEnum<{
115
+ optional: "optional";
116
+ forbidden: "forbidden";
117
+ }>;
118
+ /** Schema for a single label's spec in the `repoLabels.labels` record. */
119
+ export declare const labelSpecSchema: z.ZodObject<{
120
+ color: z.ZodString;
121
+ description: z.ZodOptional<z.ZodString>;
122
+ aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
123
+ archived: z.ZodOptional<z.ZodLiteral<true>>;
124
+ }, z.core.$strict>;
125
+ /**
126
+ * A label's color, optional description, optional aliases, and optional archived mark; the name is the record key in
127
+ * `repoLabels.labels`. An omitted description resolves to empty, which the generated labels file declares explicitly
128
+ * so that a sync clears whatever description the label has on the repo. Each alias names an existing label that the
129
+ * sync renames to this one in place, keeping it on the issues and pull requests that have it. `archived: true` retires
130
+ * the label: The sync archives it, keeping it on the issues and pull requests that have it.
131
+ */
132
+ export type LabelSpec = z.infer<typeof labelSpecSchema>;
133
+ /**
134
+ * Schema for the optional `repoLabels` block, which declares the repository's label registry (the labels defined on
135
+ * the GitHub repo, distinct from labels applied to PRs and issues).
136
+ *
137
+ * Resolution is an ordered fold with last-writer-wins: presets in `extends` order, then the `labels` record,
138
+ * in which an entry adds a label, replaces one that an earlier layer defined, or removes it (`null`). Names match
139
+ * case-insensitively, as GitHub matches them, and the replacing layer's spelling wins.
140
+ * Three misstatements are resolve-time errors (`resolveLabels`), not schema errors: `labels` keys that differ only in
141
+ * case, a `null` naming a label not defined by any earlier layer, and an alias that names a resolved label or repeats
142
+ * another alias.
143
+ */
144
+ export declare const repoLabelsSchema: z.ZodObject<{
145
+ extends: z.ZodOptional<z.ZodArray<z.ZodString>>;
146
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodObject<{
147
+ color: z.ZodString;
148
+ description: z.ZodOptional<z.ZodString>;
149
+ aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
150
+ archived: z.ZodOptional<z.ZodLiteral<true>>;
151
+ }, z.core.$strict>>>>;
152
+ }, z.core.$strict>;
153
+ /** Consumer-facing `repoLabels` block: bundled presets to extend plus per-name adjustments. */
154
+ export type RepoLabelsConfig = z.infer<typeof repoLabelsSchema>;
155
+ /** Schema for the consumer-facing config file shape (`.config/release-kit.config.ts`). */
156
+ export declare const releaseKitConfigSchema: z.ZodObject<{
157
+ breakingPolicies: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEnum<{
158
+ optional: "optional";
159
+ forbidden: "forbidden";
160
+ }>>>;
161
+ changelogJson: z.ZodOptional<z.ZodObject<{
162
+ enabled: z.ZodOptional<z.ZodBoolean>;
163
+ outputPath: z.ZodOptional<z.ZodString>;
164
+ devOnlySections: z.ZodOptional<z.ZodArray<z.ZodString>>;
165
+ }, z.core.$strict>>;
166
+ changelogMarkdown: z.ZodOptional<z.ZodObject<{
167
+ keepVersions: z.ZodOptional<z.ZodNumber>;
168
+ }, z.core.$strict>>;
169
+ formatCommand: z.ZodOptional<z.ZodString>;
170
+ project: z.ZodOptional<z.ZodObject<{
171
+ paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
172
+ tagPrefix: z.ZodOptional<z.ZodString>;
173
+ }, z.core.$strict>>;
174
+ releaseNotes: z.ZodOptional<z.ZodObject<{
175
+ shouldInjectIntoReadme: z.ZodOptional<z.ZodBoolean>;
176
+ }, z.core.$strict>>;
177
+ releases: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodObject<{
178
+ path: z.ZodString;
179
+ tagPrefix: z.ZodOptional<z.ZodString>;
180
+ legacyIdentities: z.ZodOptional<z.ZodArray<z.ZodObject<{
181
+ name: z.ZodString;
182
+ tagPrefix: z.ZodString;
183
+ }, z.core.$strict>>>;
184
+ }, z.core.$strict>, z.ZodObject<{
185
+ includes: z.ZodArray<z.ZodString>;
186
+ tagPrefix: z.ZodOptional<z.ZodString>;
187
+ }, z.core.$strict>]>>>;
188
+ repoLabels: z.ZodOptional<z.ZodObject<{
189
+ extends: z.ZodOptional<z.ZodArray<z.ZodString>>;
190
+ labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodObject<{
191
+ color: z.ZodString;
192
+ description: z.ZodOptional<z.ZodString>;
193
+ aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
194
+ archived: z.ZodOptional<z.ZodLiteral<true>>;
195
+ }, z.core.$strict>>>>;
196
+ }, z.core.$strict>>;
197
+ retiredPackages: z.ZodOptional<z.ZodArray<z.ZodObject<{
198
+ name: z.ZodString;
199
+ tagPrefix: z.ZodString;
200
+ successor: z.ZodOptional<z.ZodString>;
201
+ }, z.core.$strict>>>;
202
+ scopeAliases: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
203
+ versionPatterns: z.ZodOptional<z.ZodObject<{
204
+ major: z.ZodArray<z.ZodString>;
205
+ minor: z.ZodArray<z.ZodString>;
206
+ }, z.core.$strict>>;
207
+ workspaces: z.ZodOptional<z.ZodArray<z.ZodObject<{
208
+ dir: z.ZodString;
209
+ shouldExclude: z.ZodOptional<z.ZodBoolean>;
210
+ legacyIdentities: z.ZodOptional<z.ZodArray<z.ZodObject<{
211
+ name: z.ZodString;
212
+ tagPrefix: z.ZodString;
213
+ }, z.core.$strict>>>;
214
+ }, z.core.$strict>>>;
215
+ workTypes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
216
+ header: z.ZodString;
217
+ aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
218
+ excludedFromChangelog: z.ZodOptional<z.ZodBoolean>;
219
+ }, z.core.$strict>>>;
220
+ }, z.core.$strict>;
221
+ /**
222
+ * Consumer-facing config file shape (`.config/release-kit.config.ts`).
223
+ * All fields are optional; defaults are applied during config merging.
224
+ */
225
+ export type ReleaseKitConfig = z.infer<typeof releaseKitConfigSchema>;
@@ -0,0 +1,103 @@
1
+ import { z } from 'zod';
2
+ export const legacyIdentitySchema = z
3
+ .object({
4
+ name: z.string().min(1),
5
+ tagPrefix: z.string().min(1),
6
+ })
7
+ .strict();
8
+ export const retiredPackageSchema = z
9
+ .object({
10
+ name: z.string().min(1),
11
+ tagPrefix: z.string().min(1),
12
+ successor: z.string().min(1).optional(),
13
+ })
14
+ .strict();
15
+ export const workspaceOverrideSchema = z
16
+ .object({
17
+ dir: z.string().min(1),
18
+ shouldExclude: z.boolean().optional(),
19
+ legacyIdentities: z.array(legacyIdentitySchema).optional(),
20
+ })
21
+ .strict();
22
+ export const projectConfigSchema = z
23
+ .object({
24
+ paths: z.array(z.string().min(1)).min(1).optional(),
25
+ tagPrefix: z.string().min(1).optional(),
26
+ })
27
+ .strict();
28
+ export const pathReleaseSchema = z
29
+ .object({
30
+ path: z.string().min(1),
31
+ tagPrefix: z.string().min(1).optional(),
32
+ legacyIdentities: z.array(legacyIdentitySchema).optional(),
33
+ })
34
+ .strict();
35
+ export const compositeReleaseSchema = z
36
+ .object({
37
+ includes: z.array(z.string().min(1)).min(1),
38
+ tagPrefix: z.string().min(1).optional(),
39
+ })
40
+ .strict();
41
+ export const releaseEntrySchema = z.union([z.literal(false), pathReleaseSchema, compositeReleaseSchema]);
42
+ export const changelogJsonInputSchema = z
43
+ .object({
44
+ enabled: z.boolean().optional(),
45
+ outputPath: z.string().optional(),
46
+ devOnlySections: z.array(z.string()).optional(),
47
+ })
48
+ .strict();
49
+ export const changelogMarkdownInputSchema = z
50
+ .object({
51
+ keepVersions: z.number().int().min(1).optional(),
52
+ })
53
+ .strict();
54
+ export const releaseNotesInputSchema = z
55
+ .object({
56
+ shouldInjectIntoReadme: z.boolean().optional(),
57
+ })
58
+ .strict();
59
+ export const workTypeConfigSchema = z
60
+ .object({
61
+ header: z.string(),
62
+ aliases: z.array(z.string()).optional(),
63
+ excludedFromChangelog: z.boolean().optional(),
64
+ })
65
+ .strict();
66
+ export const versionPatternsSchema = z
67
+ .object({
68
+ major: z.array(z.string()),
69
+ minor: z.array(z.string()),
70
+ })
71
+ .strict();
72
+ export const breakingPolicyValueSchema = z.enum(['forbidden', 'optional']);
73
+ export const labelSpecSchema = z
74
+ .object({
75
+ color: z.string().min(1),
76
+ description: z.string().optional(),
77
+ aliases: z.array(z.string().min(1)).min(1).optional(),
78
+ archived: z.literal(true).optional(),
79
+ })
80
+ .strict();
81
+ export const repoLabelsSchema = z
82
+ .object({
83
+ extends: z.array(z.string().min(1)).optional(),
84
+ labels: z.record(z.string().min(1), labelSpecSchema.nullable()).optional(),
85
+ })
86
+ .strict();
87
+ export const releaseKitConfigSchema = z
88
+ .object({
89
+ breakingPolicies: z.record(z.string(), breakingPolicyValueSchema).optional(),
90
+ changelogJson: changelogJsonInputSchema.optional(),
91
+ changelogMarkdown: changelogMarkdownInputSchema.optional(),
92
+ formatCommand: z.string().min(1).optional(),
93
+ project: projectConfigSchema.optional(),
94
+ releaseNotes: releaseNotesInputSchema.optional(),
95
+ releases: z.record(z.string().min(1), releaseEntrySchema).optional(),
96
+ repoLabels: repoLabelsSchema.optional(),
97
+ retiredPackages: z.array(retiredPackageSchema).optional(),
98
+ scopeAliases: z.record(z.string(), z.string()).optional(),
99
+ versionPatterns: versionPatternsSchema.optional(),
100
+ workspaces: z.array(workspaceOverrideSchema).optional(),
101
+ workTypes: z.record(z.string(), workTypeConfigSchema).optional(),
102
+ })
103
+ .strict();
@@ -0,0 +1,30 @@
1
+ import type { UntaggedBaseline } from './assertTaggedBaseline.js';
2
+ import type { ReleaseHistory } from './buildChangelogEntries.js';
3
+ import { type MemberTarget } from './prepareRunContext.js';
4
+ import { type TargetDecision, type TargetPrepareSettings } from './prepareTarget.js';
5
+ import type { ReleaseEntry } from './propagateBumps.js';
6
+ import type { ReleaseType } from './types.js';
7
+ /** The direct releases of the planned package targets, with the reads from which they were decided. */
8
+ export interface DirectReleases {
9
+ readings: Map<string, ReleaseHistory>;
10
+ decisions: Map<string, TargetDecision>;
11
+ /** The bumps from which propagation starts: one per target whose decision releases. */
12
+ bumps: Map<string, ReleaseEntry>;
13
+ untaggedBaselines: Map<string, UntaggedBaseline>;
14
+ }
15
+ /**
16
+ * Reads each planned package and directory target's history and decides its direct release. `force` applies only to
17
+ * the targets from which the release starts.
18
+ */
19
+ export declare function decideDirectReleases(planned: readonly MemberTarget[], settings: TargetPrepareSettings, options: {
20
+ force?: boolean;
21
+ bumpOverride?: ReleaseType;
22
+ setVersions?: ReadonlyMap<string, string>;
23
+ }, startIds: ReadonlySet<string>): DirectReleases;
24
+ /**
25
+ * Records on each planned workspace's and directory's history the entry scopes that route nowhere in its unreleased
26
+ * window, which only a pass across every target's window can find. The windows go to the finder with the releasing
27
+ * targets first, in planned order, which sets the order of each target's findings. The repo-root directory target
28
+ * renders every entry in its window, so it records none, but its window still routes the scope that names it.
29
+ */
30
+ export declare function reportUnroutedEntryScopes(planned: readonly MemberTarget[], direct: DirectReleases, settings: TargetPrepareSettings, memberTargets: readonly MemberTarget[]): void;
@@ -0,0 +1,66 @@
1
+ import { detectUndeclaredTagPrefixes } from "./detectUndeclaredTagPrefixes.js";
2
+ import { findUnroutedEntryScopes } from "./findUnroutedEntryScopes.js";
3
+ import { getAllTagPrefixes } from "./generateChangelogs.js";
4
+ import { describeStage, isRepoRootTarget, readHistory, runStage } from "./prepareRunContext.js";
5
+ import { decideTargetRelease, readTargetHistory, } from "./prepareTarget.js";
6
+ export function decideDirectReleases(planned, settings, options, startIds) {
7
+ const { force, bumpOverride, setVersions } = options;
8
+ const direct = {
9
+ readings: new Map(),
10
+ decisions: new Map(),
11
+ bumps: new Map(),
12
+ untaggedBaselines: new Map(),
13
+ };
14
+ const hintState = { emitted: false };
15
+ const knownPrefixes = planned.flatMap(getAllTagPrefixes);
16
+ for (const target of planned) {
17
+ const { history, untagged } = runStage(describeStage(target), () => readTargetHistory(target, settings));
18
+ direct.readings.set(target.id, history);
19
+ if (untagged !== undefined) {
20
+ direct.untaggedBaselines.set(target.id, untagged);
21
+ }
22
+ if (history.previousTag === undefined && target.kind === 'package' && target.isWorkspace) {
23
+ maybeEmitBaselineHint(target, knownPrefixes, hintState);
24
+ }
25
+ const setVersion = setVersions?.get(target.id);
26
+ const decision = decideTargetRelease(target, history, {
27
+ force: force === true && startIds.has(target.id),
28
+ bumpOverride,
29
+ setVersion,
30
+ });
31
+ direct.decisions.set(target.id, decision);
32
+ if (decision.outcome === 'skip') {
33
+ continue;
34
+ }
35
+ direct.bumps.set(target.id, decision.setVersion === undefined
36
+ ? { releaseType: decision.releaseType }
37
+ : { releaseType: 'patch', newVersionOverride: decision.setVersion });
38
+ }
39
+ return direct;
40
+ }
41
+ export function reportUnroutedEntryScopes(planned, direct, settings, memberTargets) {
42
+ const releasing = planned.filter((target) => direct.decisions.get(target.id)?.outcome === 'release');
43
+ const windows = [...releasing, ...planned.filter((target) => !releasing.includes(target))].map((target) => ({
44
+ target,
45
+ history: readHistory(direct.readings, target),
46
+ }));
47
+ const findings = findUnroutedEntryScopes(windows.map(({ target, history }) => ({ dir: target.id, commits: history.unreleased.commits })), memberTargets.map((target) => target.id), settings.scopeAliases ?? {});
48
+ for (const { target, history } of windows) {
49
+ history.unreleased.diagnostics.unroutedEntryScopes = isRepoRootTarget(target)
50
+ ? []
51
+ : (findings.get(target.id) ?? []);
52
+ }
53
+ }
54
+ function maybeEmitBaselineHint(target, knownPrefixes, state) {
55
+ if (state.emitted || (target.legacyIdentities?.length ?? 0) > 0)
56
+ return;
57
+ const candidates = detectUndeclaredTagPrefixes(knownPrefixes);
58
+ if (candidates.length === 0)
59
+ return;
60
+ const totalTags = candidates.reduce((sum, candidate) => sum + candidate.tagCount, 0);
61
+ const example = candidates[0]?.exampleTags[0] ?? `${candidates[0]?.prefix ?? ''}?`;
62
+ process.stderr.write(`Hint: no baseline tag found for ${target.id} under '${target.tagPrefix}', but ` +
63
+ `${totalTags} candidate-shaped tags exist (e.g., ${example}). ` +
64
+ "Run 'release-kit show-tag-prefixes' to check for undeclared legacy prefixes.\n");
65
+ state.emitted = true;
66
+ }
@@ -1,5 +1,5 @@
1
- import type { ReleaseKitConfig } from './types.js';
2
- export type { LabelSpec, ReleaseKitConfig, RepoLabelsConfig } from './types.js';
1
+ import type { ReleaseKitConfig } from './configSchema.js';
2
+ export type { LabelSpec, ReleaseKitConfig, RepoLabelsConfig } from './configSchema.js';
3
3
  /**
4
4
  * Returns a configuration unchanged, typing it as a `ReleaseKitConfig`.
5
5
  *
@@ -1,4 +1,5 @@
1
- import type { ReleaseKitConfig, ReleaseNotesConfig } from './types.js';
1
+ import type { ReleaseKitConfig } from './configSchema.js';
2
+ import type { ReleaseNotesConfig } from './types.js';
2
3
  export interface ResolvedReleaseNotesConfig {
3
4
  releaseNotes: ReleaseNotesConfig;
4
5
  changelogJsonOutputPath: string;
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_CHANGELOG_JSON_CONFIG, DEFAULT_RELEASE_NOTES_CONFIG } from "./defaults.js";
2
- import { resolveWorkTypes } from "./loadConfig.js";
2
+ import { resolveWorkTypes } from "./mergeConfig.js";
3
3
  export function deriveDependenciesHeader(workTypes) {
4
4
  const header = workTypes['deps']?.header;
5
5
  if (header === undefined) {
@@ -0,0 +1,11 @@
1
+ import { type OutputStyle } from '@williamthorsen/nmr-core';
2
+ import type { ReleasedPackageResult } from './types.js';
3
+ /** Appends bump file detail lines. */
4
+ export declare function formatBumpFiles(lines: string[], workspace: Pick<ReleasedPackageResult, 'bumpedFiles'>, dryRun: boolean, indent?: string): void;
5
+ /**
6
+ * Appends a line per generated changelog file, each followed by the versions kept from the existing changelog and a
7
+ * warning naming the sections dropped for lacking a version, then a line per removed changelog archive file.
8
+ */
9
+ export declare function formatChangelogFiles(lines: string[], release: Pick<ReleasedPackageResult, 'changelogFiles' | 'changelogPreservation' | 'removedChangelogFiles'>, dryRun: boolean, style: OutputStyle, indent?: string): void;
10
+ /** Appends release-notes preview file lines, which are present only under `--with-release-notes`. */
11
+ export declare function formatPreviewFiles(lines: string[], release: Pick<ReleasedPackageResult, 'previewFiles'>, dryRun: boolean, indent?: string): void;
@@ -0,0 +1,55 @@
1
+ import { formatStatusLine } from '@williamthorsen/nmr-core';
2
+ import { dim } from "./format.js";
3
+ export function formatBumpFiles(lines, workspace, dryRun, indent = '') {
4
+ for (const file of workspace.bumpedFiles) {
5
+ if (dryRun) {
6
+ lines.push(dim(`${indent} [dry-run] Would bump ${file}`));
7
+ }
8
+ else {
9
+ lines.push(dim(`${indent} Bumped ${file}`));
10
+ }
11
+ }
12
+ }
13
+ export function formatChangelogFiles(lines, release, dryRun, style, indent = '') {
14
+ for (const file of release.changelogFiles) {
15
+ if (dryRun) {
16
+ lines.push(dim(`${indent} [dry-run] Would generate changelog: ${file}`));
17
+ }
18
+ else {
19
+ lines.push(dim(`${indent} Generating changelog: ${file}`));
20
+ }
21
+ const preservation = release.changelogPreservation?.find((candidate) => candidate.file === file);
22
+ if (preservation === undefined) {
23
+ continue;
24
+ }
25
+ if (preservation.preservedVersions.length > 0) {
26
+ lines.push(dim(`${indent} Kept from the existing changelog: ${preservation.preservedVersions.join(', ')}`));
27
+ }
28
+ const dropped = preservation.droppedUnversionedHeadings;
29
+ if (dropped.length === 0) {
30
+ continue;
31
+ }
32
+ const message = `Dropped ${dropped.length} section${dropped.length === 1 ? '' : 's'} without a version: ${dropped.join(', ')}`;
33
+ lines.push(`${indent} ${formatStatusLine(style, 'warning', message)}`);
34
+ }
35
+ const removedFiles = release.removedChangelogFiles ?? [];
36
+ for (const file of removedFiles) {
37
+ if (dryRun) {
38
+ lines.push(dim(`${indent} [dry-run] Would remove changelog: ${file}`));
39
+ }
40
+ else {
41
+ lines.push(dim(`${indent} Removing changelog: ${file}`));
42
+ }
43
+ }
44
+ }
45
+ export function formatPreviewFiles(lines, release, dryRun, indent = '') {
46
+ const previewFiles = release.previewFiles ?? [];
47
+ for (const file of previewFiles) {
48
+ if (dryRun) {
49
+ lines.push(dim(`${indent} [dry-run] Would write ${file}`));
50
+ }
51
+ else {
52
+ lines.push(dim(`${indent} Wrote ${file}`));
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,26 @@
1
+ import { type OutputStyle } from '@williamthorsen/nmr-core';
2
+ import type { PolicyViolation, PrepareResult, ReleasedPackageResult } from './types.js';
3
+ /**
4
+ * Appends warning lines for the change-record blocks that could not be read, the entries whose type is undeclared, and
5
+ * the entry scopes that don't match any workspace in the commit's window.
6
+ *
7
+ * `indent` follows the same convention as {@link formatUnparseableWarning}.
8
+ */
9
+ export declare function formatChangeRecordWarnings(lines: string[], release: Pick<ReleasedPackageResult, 'malformedBlocks' | 'undeclaredEntryTypes' | 'unroutedEntryScopes'>, style: OutputStyle, indent?: string): void;
10
+ /**
11
+ * Appends policy-violation lines when applicable: one header line plus one bullet per violation.
12
+ *
13
+ * `indent` follows the same convention as {@link formatUnparseableWarning}.
14
+ */
15
+ export declare function formatPolicyViolations(lines: string[], violations: PolicyViolation[] | undefined, style: OutputStyle, indent?: string): void;
16
+ /**
17
+ * Appends unparseable commit warning lines when applicable.
18
+ *
19
+ * `indent` is an additional outer indent prepended to both the header and bullet lines; base
20
+ * spacing (2 spaces for the header, 4 for bullets) is encoded in the format strings, so the
21
+ * caller passes only the section-level indent (e.g., `''` for single-package, `' '` for
22
+ * multi-workspace).
23
+ */
24
+ export declare function formatUnparseableWarning(lines: string[], release: Pick<ReleasedPackageResult, 'unparseableCommits'>, style: OutputStyle, indent?: string): void;
25
+ /** Appends warning lines when the prepare result includes warnings. */
26
+ export declare function formatWarnings(lines: string[], result: PrepareResult, style: OutputStyle): void;
@@ -0,0 +1,72 @@
1
+ import { formatStatusLine, truncateToWidth } from '@williamthorsen/nmr-core';
2
+ const SUBJECT_COLUMN_BUDGET = 72;
3
+ export function formatChangeRecordWarnings(lines, release, style, indent = '') {
4
+ const { malformedBlocks = [], undeclaredEntryTypes = [], unroutedEntryScopes = [] } = release;
5
+ if (malformedBlocks.length > 0) {
6
+ const count = malformedBlocks.length;
7
+ const header = `${count} change-record block${count === 1 ? '' : 's'} could not be read (item taken from the title)`;
8
+ lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
9
+ for (const block of malformedBlocks) {
10
+ lines.push(`${indent} · ${block.commitHash.slice(0, 7)} '${truncateSubject(block.commitSubject)}': ${block.reason}`);
11
+ }
12
+ }
13
+ if (undeclaredEntryTypes.length > 0) {
14
+ const count = undeclaredEntryTypes.length;
15
+ const header = `${count} change-record entr${count === 1 ? 'y has an' : 'ies have'} undeclared type${count === 1 ? '' : 's'} (no item)`;
16
+ lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
17
+ for (const entry of undeclaredEntryTypes) {
18
+ const shortHash = entry.commitHash.slice(0, 7);
19
+ lines.push(`${indent} · ${shortHash} '${truncateSubject(entry.commitSubject)}': type '${entry.type}' at entry ${entry.entryPosition}`);
20
+ }
21
+ }
22
+ if (unroutedEntryScopes.length === 0) {
23
+ return;
24
+ }
25
+ const count = unroutedEntryScopes.length;
26
+ const header = `${count} change-record entry scope${count === 1 ? ' does not match' : 's do not match'} any workspace in the commit's window (no item there)`;
27
+ lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
28
+ for (const finding of unroutedEntryScopes) {
29
+ const shortHash = finding.commitHash.slice(0, 7);
30
+ lines.push(`${indent} · ${shortHash} '${truncateSubject(finding.commitSubject)}': scope '${finding.scope}' at entry ${finding.entryPosition}`);
31
+ }
32
+ }
33
+ export function formatPolicyViolations(lines, violations, style, indent = '') {
34
+ if (violations === undefined || violations.length === 0) {
35
+ return;
36
+ }
37
+ const count = violations.length;
38
+ lines.push(`${indent} ${formatStatusLine(style, 'warning', `${count} policy violation${count === 1 ? '' : 's'}:`)}`);
39
+ for (const violation of violations) {
40
+ const shortHash = violation.commitHash.slice(0, 7);
41
+ const subject = violation.commitSubject;
42
+ const truncatedSubject = truncateSubject(subject);
43
+ const location = violation.surface === 'entry' ? `entry ${violation.entryPosition ?? '?'}` : `${violation.surface} surface`;
44
+ lines.push(`${indent} · ${shortHash} '${truncatedSubject}': type '${violation.type}' at ${location}`);
45
+ }
46
+ }
47
+ export function formatUnparseableWarning(lines, release, style, indent = '') {
48
+ const unparseable = release.unparseableCommits;
49
+ if (unparseable === undefined || unparseable.length === 0) {
50
+ return;
51
+ }
52
+ const count = unparseable.length;
53
+ lines.push(`${indent} ${formatStatusLine(style, 'warning', `${count} commit${count === 1 ? '' : 's'} could not be parsed`)}`);
54
+ for (const commit of unparseable) {
55
+ const shortHash = commit.hash.slice(0, 7);
56
+ const truncatedSubject = truncateSubject(commit.subject);
57
+ lines.push(`${indent} · ${shortHash} ${truncatedSubject}`);
58
+ }
59
+ }
60
+ export function formatWarnings(lines, result, style) {
61
+ const { warnings } = result;
62
+ if (warnings === undefined || warnings.length === 0) {
63
+ return;
64
+ }
65
+ lines.push('');
66
+ for (const warning of warnings) {
67
+ lines.push(formatStatusLine(style, 'warning', warning));
68
+ }
69
+ }
70
+ function truncateSubject(subject) {
71
+ return truncateToWidth(subject, { width: SUBJECT_COLUMN_BUDGET });
72
+ }
@@ -0,0 +1,4 @@
1
+ /** Prints the `init` command's help text. */
2
+ export declare function showInitHelp(): void;
3
+ /** Prints the `update-templates` command's help text. */
4
+ export declare function showUpdateTemplatesHelp(): void;
@@ -0,0 +1,31 @@
1
+ export function showInitHelp() {
2
+ console.info(`
3
+ Usage: release-kit init [options]
4
+
5
+ Initialize release-kit in the current repository.
6
+ By default, scaffolds only the GitHub Actions workflow files. When every
7
+ package is private, skips publish.yaml and create-github-release.yaml.
8
+
9
+ Options:
10
+ --with-config Also scaffold .config/release-kit.config.ts
11
+ --force Overwrite existing files instead of skipping them
12
+ --dry-run Preview changes without writing files
13
+ --help, -h Show this help message
14
+ `);
15
+ }
16
+ export function showUpdateTemplatesHelp() {
17
+ console.info(`
18
+ Usage: release-kit update-templates [options]
19
+
20
+ Update the GitHub Actions workflows that release-kit scaffolds to the templates
21
+ of the installed version, showing what changed in each file. Creates a missing
22
+ workflow that \`init\` would create, and refreshes sync-labels.yaml only when it
23
+ exists. When every package is private, refreshes publish.yaml and
24
+ create-github-release.yaml only when they exist. Keeps publish.yaml's provenance
25
+ setting. Never touches config files.
26
+
27
+ Options:
28
+ --dry-run Preview changes without writing files
29
+ --help, -h Show this help message
30
+ `);
31
+ }
@@ -0,0 +1,4 @@
1
+ /** Prints the `overrides` command's help text. */
2
+ export declare function showOverridesHelp(): void;
3
+ /** Prints the `overrides validate` subcommand's help text. */
4
+ export declare function showOverridesValidateHelp(): void;