@williamthorsen/release-kit 9.0.0 → 9.1.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
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
+
13
+ ## 9.0.1 — 2026-07-30
14
+
15
+ ### ⚙️ Tooling
16
+
17
+ - Adopt Vitest projects in this repo (#530)
18
+
19
+ Simplifies test configurations so that every package exposes the same test commands, which select suites by test category: `nmr test` runs everything except integration tests, `nmr test:integration` runs only those, and `nmr test:all` runs both. `nmr test:integration` now succeeds in a package that has no integration tests instead of failing. release-kit's drift checks can now be run on their own, apart from unit and integration tests. A fresh clone now has to run `pnpm run bootstrap` before any test run, not just before `nmr` commands.
20
+
5
21
  ## 9.0.0 — 2026-07-28
6
22
 
7
23
  ### 🎉 Features
package/README.md CHANGED
@@ -5,19 +5,13 @@ Version-bumping and changelog-generation toolkit for release workflows.
5
5
  Provides a self-contained CLI that auto-discovers workspaces from `pnpm-workspace.yaml`, parses conventional commits, determines version bumps, updates `package.json` files, and generates changelogs from `git-cliff --context` output rendered in-process (with optional [editorial overrides](#editorial-overrides)).
6
6
 
7
7
  <!-- section:release-notes -->
8
- ## Release notes — v9.0.0 (2026-07-28)
8
+ ## Release notes — v9.1.0 (2026-07-31)
9
9
 
10
10
  ### 🎉 Features
11
11
 
12
- - 🚨 **Breaking:** Unify label configuration in release-kit.config.ts (#505)
12
+ - Make label descriptions optional and generate none for scope labels (#539)
13
13
 
14
- Repositories now declare their label set alongside the rest of their release-kit configuration in `.config/release-kit.config.ts`, and the declaration is validated and completed in the editor as it is written. A label supplied by a bundled preset can now be recolored, reworded, or dropped. `sync-labels generate --check` now reports when the committed label file has fallen behind the declaration, writing nothing. The standalone `.config/sync-labels.config.ts` is no longer read; while it remains in place, every `sync-labels` command refuses to run, naming what to move and where.
15
-
16
- ### 🐛 Bug fixes
17
-
18
- - Include commits scoped to `*` in the changelog (#496)
19
-
20
- Fixes an issue where commits scoped to `*` raised the version but were silently dropped from the generated changelog.
14
+ `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.
21
15
  <!-- /section:release-notes -->
22
16
 
23
17
  ## Installation
@@ -744,7 +738,7 @@ export default defineConfig({
744
738
  repoLabels: {
745
739
  extends: ['common'],
746
740
  labels: {
747
- 'scope:my-package': { color: '00ff96', description: 'my-package package' }, // add
741
+ 'scope:my-package': { color: '00ff96' }, // add, with no description
748
742
  bug: { color: 'b60205', description: 'Something broken' }, // replace the preset's `bug`
749
743
  wontfix: null, // remove the preset's `wontfix`
750
744
  },
@@ -755,7 +749,9 @@ export default defineConfig({
755
749
  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:
756
750
 
757
751
  1. Presets, in `extends` order — a later preset wins on a shared name.
758
- 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 }`.
752
+ 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.
753
+
754
+ `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.
759
755
 
760
756
  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.
761
757
 
@@ -764,7 +760,7 @@ Overlaps are never errors; order resolves them, and the committed `.github/label
764
760
  Label configuration formerly lived in a standalone `.config/sync-labels.config.ts` (`presets` + a `labels` array). To migrate:
765
761
 
766
762
  1. Move the preset list to `repoLabels.extends` in `.config/release-kit.config.ts`.
767
- 2. Convert each `labels` array entry to a `'name': { color, description }` record entry under `repoLabels.labels`.
763
+ 2. Convert each `labels` array entry to a `'name': { color }` record entry under `repoLabels.labels`, adding `description` where the label carries one.
768
764
  3. Delete `.config/sync-labels.config.ts` and run `release-kit sync-labels generate`.
769
765
 
770
766
  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.0",
3
+ "version": "9.1.0",
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",
@@ -39,7 +39,7 @@
39
39
  "semver": "7.8.5",
40
40
  "yaml": "2.9.0",
41
41
  "zod": "4.4.3",
42
- "@williamthorsen/nmr-core": "0.7.2"
42
+ "@williamthorsen/nmr-core": "0.7.3"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/semver": "7.7.1",