@williamthorsen/release-kit 9.0.1 → 9.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 9.1.0 — 2026-07-31
6
+
7
+ ### 🎉 Features
8
+
9
+ - Make label descriptions optional and generate none for scope labels (#539)
10
+
11
+ `release-kit` now supports labels without a description, and `release-kit sync-labels init` generates scope labels without one. Applying a label without a description clears any existing description the repository carries for that label.
12
+
5
13
  ## 9.0.1 — 2026-07-30
6
14
 
7
15
  ### ⚙️ Tooling
package/README.md CHANGED
@@ -730,7 +730,7 @@ export default defineConfig({
730
730
  repoLabels: {
731
731
  extends: ['common'],
732
732
  labels: {
733
- 'scope:my-package': { color: '00ff96', description: 'my-package package' }, // add
733
+ 'scope:my-package': { color: '00ff96' }, // add, with no description
734
734
  bug: { color: 'b60205', description: 'Something broken' }, // replace the preset's `bug`
735
735
  wontfix: null, // remove the preset's `wontfix`
736
736
  },
@@ -741,7 +741,9 @@ export default defineConfig({
741
741
  The block declares the repository's label registry — the set of labels defined on the GitHub repo, distinct from labels applied to PRs and issues. Resolution is an ordered fold with last-writer-wins:
742
742
 
743
743
  1. Presets, in `extends` order — a later preset wins on a shared name.
744
- 2. The `labels` record — an entry adds a label, replaces one an earlier layer defined, or removes it (`null`). A replacing entry supplies the full `{ color, description }`.
744
+ 2. The `labels` record — an entry adds a label, replaces one an earlier layer defined, or removes it (`null`). Replacement is wholesale: an entry omitting `description` resolves to no description rather than inheriting the one an earlier layer supplied.
745
+
746
+ `description` is optional throughout, in a preset as in the `labels` record, and `sync-labels init` generates none for a scope label. The generated file spells an absent description `''` because `github-label-sync` reads an omitted description as "leave the label's current one alone"; the empty form clears it.
745
747
 
746
748
  Overlaps are never errors; order resolves them, and the committed `.github/labels.yaml` diff is where an unexpected change surfaces at review. The one config error is a dangling `null` — removing a name no preset defines — because that misstatement is invisible in the output diff.
747
749
 
@@ -750,7 +752,7 @@ Overlaps are never errors; order resolves them, and the committed `.github/label
750
752
  Label configuration formerly lived in a standalone `.config/sync-labels.config.ts` (`presets` + a `labels` array). To migrate:
751
753
 
752
754
  1. Move the preset list to `repoLabels.extends` in `.config/release-kit.config.ts`.
753
- 2. Convert each `labels` array entry to a `'name': { color, description }` record entry under `repoLabels.labels`.
755
+ 2. Convert each `labels` array entry to a `'name': { color }` record entry under `repoLabels.labels`, adding `description` where the label carries one.
754
756
  3. Delete `.config/sync-labels.config.ts` and run `release-kit sync-labels generate`.
755
757
 
756
758
  The regenerated `.github/labels.yaml` differs only in its `# Source:` header line. Name collisions with preset labels, which the old format rejected, now mean your entry replaces the preset label.
@@ -13,7 +13,8 @@ export function formatLabelsYaml(labels, presetHashes) {
13
13
  for (const [name, hash] of [...presetHashes].sort(([a], [b]) => a.localeCompare(b))) {
14
14
  headerLines.push(`# ${name} preset hash: ${hash}`);
15
15
  }
16
- const yamlBody = stringify(labels, { singleQuote: true, lineWidth: 0 });
16
+ const declared = labels.map((label) => ({ ...label, description: label.description ?? '' }));
17
+ const yamlBody = stringify(declared, { singleQuote: true, lineWidth: 0 });
17
18
  return headerLines.join('\n') + '\n' + yamlBody;
18
19
  }
19
20
  export async function loadRepoLabelsConfig() {
@@ -4,10 +4,6 @@ import { resolve } from 'node:path';
4
4
  import { findPackageRoot } from '@williamthorsen/nmr-core';
5
5
  import { parse } from 'yaml';
6
6
  import { isRecord } from "../typeGuards.js";
7
- function resolvePresetPath(presetName) {
8
- const root = findPackageRoot(import.meta.url);
9
- return resolve(root, 'presets', 'labels', `${presetName}.yaml`);
10
- }
11
7
  export function hashPresetFile(presetName) {
12
8
  const presetPath = resolvePresetPath(presetName);
13
9
  if (!existsSync(presetPath)) {
@@ -38,10 +34,17 @@ export function loadPreset(presetName) {
38
34
  if (!isRecord(entry)) {
39
35
  throw new Error(`Preset "${presetName}" contains an invalid label entry: ${JSON.stringify(entry)}`);
40
36
  }
41
- if (typeof entry.name !== 'string' || typeof entry.color !== 'string' || typeof entry.description !== 'string') {
37
+ const { description } = entry;
38
+ if (typeof entry.name !== 'string' ||
39
+ typeof entry.color !== 'string' ||
40
+ (description !== undefined && typeof description !== 'string')) {
42
41
  throw new TypeError(`Preset "${presetName}" contains a label with invalid fields: ${JSON.stringify(entry)}`);
43
42
  }
44
- labels.push({ name: entry.name, color: entry.color, description: entry.description });
43
+ labels.push({ name: entry.name, color: entry.color, ...(description !== undefined && { description }) });
45
44
  }
46
45
  return labels;
47
46
  }
47
+ function resolvePresetPath(presetName) {
48
+ const root = findPackageRoot(import.meta.url);
49
+ return resolve(root, 'presets', 'labels', `${presetName}.yaml`);
50
+ }
@@ -14,7 +14,11 @@ export function resolveLabels(config) {
14
14
  resolved.delete(name);
15
15
  }
16
16
  else {
17
- resolved.set(name, { name, color: spec.color, description: spec.description });
17
+ resolved.set(name, {
18
+ name,
19
+ color: spec.color,
20
+ ...(spec.description !== undefined && { description: spec.description }),
21
+ });
18
22
  }
19
23
  }
20
24
  return sortLabels([...resolved.values()]);
@@ -39,17 +39,8 @@ jobs:
39
39
  `;
40
40
  }
41
41
  export function buildScopeLabels(workspacePaths, retiredNames = []) {
42
- const labels = [
43
- { name: 'scope:root', color: '00ff96', description: 'Monorepo root configuration' },
44
- ];
45
- for (const workspacePath of workspacePaths) {
46
- const name = basename(workspacePath);
47
- labels.push({ name: `scope:${name}`, color: '00ff96', description: `${name} package` });
48
- }
49
- for (const retiredName of retiredNames) {
50
- labels.push({ name: `scope:${retiredName}`, color: '00ff96', description: `${retiredName} package (retired)` });
51
- }
52
- return labels;
42
+ const scopes = ['root', ...workspacePaths.map((workspacePath) => basename(workspacePath)), ...retiredNames];
43
+ return scopes.map((scope) => ({ name: `scope:${scope}`, color: '00ff96' }));
53
44
  }
54
45
  function escapeForSingleQuotedString(value) {
55
46
  return value.replace(/\\/g, '\\\\').replace(/'/g, String.raw `\'`);
@@ -59,8 +50,8 @@ export function renderRepoLabelsBlock(scopeLabels) {
59
50
  .map((label) => {
60
51
  const name = escapeForSingleQuotedString(label.name);
61
52
  const color = escapeForSingleQuotedString(label.color);
62
- const description = escapeForSingleQuotedString(label.description);
63
- return ` '${name}': { color: '${color}', description: '${description}' },`;
53
+ const descriptionClause = label.description === undefined ? '' : `, description: '${escapeForSingleQuotedString(label.description)}'`;
54
+ return ` '${name}': { color: '${color}'${descriptionClause} },`;
64
55
  })
65
56
  .join('\n');
66
57
  const labelsBlock = entries === '' ? ' labels: {},' : ` labels: {\n${entries}\n },`;
@@ -1,5 +1,5 @@
1
1
  export interface LabelDefinition {
2
2
  name: string;
3
3
  color: string;
4
- description: string;
4
+ description?: string;
5
5
  }
@@ -173,14 +173,14 @@ export declare const breakingPolicyValueSchema: z.ZodEnum<{
173
173
  }>;
174
174
  export declare const labelSpecSchema: z.ZodObject<{
175
175
  color: z.ZodString;
176
- description: z.ZodString;
176
+ description: z.ZodOptional<z.ZodString>;
177
177
  }, z.core.$strict>;
178
178
  export type LabelSpec = z.infer<typeof labelSpecSchema>;
179
179
  export declare const repoLabelsSchema: z.ZodObject<{
180
180
  extends: z.ZodOptional<z.ZodArray<z.ZodString>>;
181
181
  labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodObject<{
182
182
  color: z.ZodString;
183
- description: z.ZodString;
183
+ description: z.ZodOptional<z.ZodString>;
184
184
  }, z.core.$strict>>>>;
185
185
  }, z.core.$strict>;
186
186
  export type RepoLabelsConfig = z.infer<typeof repoLabelsSchema>;
@@ -207,7 +207,7 @@ export declare const releaseKitConfigSchema: z.ZodObject<{
207
207
  extends: z.ZodOptional<z.ZodArray<z.ZodString>>;
208
208
  labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodObject<{
209
209
  color: z.ZodString;
210
- description: z.ZodString;
210
+ description: z.ZodOptional<z.ZodString>;
211
211
  }, z.core.$strict>>>>;
212
212
  }, z.core.$strict>>;
213
213
  retiredPackages: z.ZodOptional<z.ZodArray<z.ZodObject<{
package/dist/esm/types.js CHANGED
@@ -52,7 +52,7 @@ export const breakingPolicyValueSchema = z.enum(['forbidden', 'optional', 'requi
52
52
  export const labelSpecSchema = z
53
53
  .object({
54
54
  color: z.string().min(1),
55
- description: z.string(),
55
+ description: z.string().optional(),
56
56
  })
57
57
  .strict();
58
58
  export const repoLabelsSchema = z
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/release-kit",
3
- "version": "9.0.1",
3
+ "version": "9.1.1",
4
4
  "description": "Version-bumping and changelog-generation toolkit for release workflows",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/williamthorsen/node-monorepo-tools/tree/main/packages/release-kit#readme",