@williamthorsen/release-kit 18.1.0 → 18.2.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.
@@ -1,6 +1,6 @@
1
1
  /** @noformat -- @generated. Do not edit. Compiled by rdy. */
2
2
  /* eslint-disable */
3
- export const __readyupVersion = "0.45.1";
3
+ export const __readyupVersion = "0.46.2";
4
4
 
5
5
 
6
6
  // .readyup/kits/npm-auto-publish.ts
@@ -3,20 +3,19 @@
3
3
  "kits": [
4
4
  {
5
5
  "bundledDependencies": {
6
- "@williamthorsen/toolbelt.datetime": "4.1.2",
7
- "@williamthorsen/toolbelt.errors": "0.9.1",
8
- "@williamthorsen/toolbelt.filesystem": "0.10.1",
9
- "@williamthorsen/toolbelt.numbers": "7.3.2",
10
- "@williamthorsen/toolbelt.objects": "4.4.1",
11
- "@williamthorsen/toolbelt.packaging": "0.7.1",
12
- "@williamthorsen/toolbelt.terminal": "0.3.0",
6
+ "@williamthorsen/toolbelt.datetime": "4.1.4",
7
+ "@williamthorsen/toolbelt.errors": "0.10.1",
8
+ "@williamthorsen/toolbelt.filesystem": "0.10.3",
9
+ "@williamthorsen/toolbelt.numbers": "8.0.1",
10
+ "@williamthorsen/toolbelt.objects": "4.4.3",
11
+ "@williamthorsen/toolbelt.packaging": "0.7.3",
12
+ "@williamthorsen/toolbelt.terminal": "0.3.1",
13
13
  "ansi-regex": "6.6.0",
14
- "ansi-styles": "6.2.3",
15
- "cli-truncate": "6.1.1",
14
+ "ansi-styles": "6.2.3, 7.0.0",
15
+ "cli-truncate": "6.2.0",
16
16
  "diff": "9.0.0",
17
17
  "get-east-asian-width": "1.7.0",
18
- "is-fullwidth-code-point": "5.1.0",
19
- "slice-ansi": "9.0.1",
18
+ "slice-ansi": "10.0.1",
20
19
  "string-width": "8.3.0",
21
20
  "strip-ansi": "7.2.0",
22
21
  "wrap-ansi": "10.0.2"
@@ -26,7 +25,7 @@
26
25
  "checklist": "release-kit",
27
26
  "checks": [
28
27
  "@williamthorsen/release-kit in devDependencies",
29
- "@williamthorsen/release-kit >= 18.1.0",
28
+ "@williamthorsen/release-kit >= 18.2.0",
30
29
  "release.yaml workflow exists",
31
30
  "release.yaml matches template",
32
31
  "release.yaml does not reference deprecated tag ref",
@@ -51,8 +50,7 @@
51
50
  "@changesets/cli not in devDependencies",
52
51
  "sync-labels.yaml workflow exists",
53
52
  "sync-labels.yaml matches template",
54
- "sync-labels.yaml does not reference deprecated tag ref",
55
- "retired .config/sync-labels.config.ts is absent"
53
+ "sync-labels.yaml does not reference deprecated tag ref"
56
54
  ]
57
55
  }
58
56
  ],
@@ -142,7 +140,7 @@
142
140
  "path": "../../nmr-core/dist/esm/timer.js"
143
141
  },
144
142
  {
145
- "hash": "56566d0b",
143
+ "hash": "ee8d2dc8",
146
144
  "kind": "module",
147
145
  "path": "kits/default.ts"
148
146
  },
@@ -157,12 +155,17 @@
157
155
  "path": "lib/config-checks.ts"
158
156
  },
159
157
  {
160
- "hash": "9a4023ad",
158
+ "hash": "70633d7f",
159
+ "kind": "module",
160
+ "path": "lib/template-checks.ts"
161
+ },
162
+ {
163
+ "hash": "ec55cfef",
161
164
  "kind": "module",
162
165
  "path": "lib/workflow-hashes.ts"
163
166
  },
164
167
  {
165
- "hash": "1a475407",
168
+ "hash": "ec16f961",
166
169
  "kind": "inline",
167
170
  "path": "../package.json",
168
171
  "paths": [
@@ -179,11 +182,31 @@
179
182
  "kind": "module",
180
183
  "path": "../src/init/detectRepoType.ts"
181
184
  },
185
+ {
186
+ "hash": "d360febf",
187
+ "kind": "module",
188
+ "path": "../src/init/hasProvenance.ts"
189
+ },
190
+ {
191
+ "hash": "3238f7e0",
192
+ "kind": "module",
193
+ "path": "../src/init/managed-workflows.ts"
194
+ },
182
195
  {
183
196
  "hash": "133be525",
184
197
  "kind": "module",
185
198
  "path": "../src/init/parseJsonRecord.ts"
186
199
  },
200
+ {
201
+ "hash": "5950f562",
202
+ "kind": "module",
203
+ "path": "../src/init/scaffold.ts"
204
+ },
205
+ {
206
+ "hash": "e10ddb57",
207
+ "kind": "module",
208
+ "path": "../src/init/templates.ts"
209
+ },
187
210
  {
188
211
  "hash": "2210df36",
189
212
  "kind": "module",
@@ -192,27 +215,26 @@
192
215
  ],
193
216
  "name": "default",
194
217
  "path": "kits/default.js",
195
- "readyupVersion": "0.45.1",
218
+ "readyupVersion": "0.46.2",
196
219
  "source": "kits/default.ts",
197
- "sourceHash": "56566d0b",
198
- "targetHash": "c0c399e8"
220
+ "sourceHash": "ee8d2dc8",
221
+ "targetHash": "dfbfab4f"
199
222
  },
200
223
  {
201
224
  "bundledDependencies": {
202
- "@williamthorsen/toolbelt.datetime": "4.1.2",
203
- "@williamthorsen/toolbelt.errors": "0.9.1",
204
- "@williamthorsen/toolbelt.filesystem": "0.10.1",
205
- "@williamthorsen/toolbelt.numbers": "7.3.2",
206
- "@williamthorsen/toolbelt.objects": "4.4.1",
207
- "@williamthorsen/toolbelt.packaging": "0.7.1",
208
- "@williamthorsen/toolbelt.terminal": "0.3.0",
225
+ "@williamthorsen/toolbelt.datetime": "4.1.4",
226
+ "@williamthorsen/toolbelt.errors": "0.10.1",
227
+ "@williamthorsen/toolbelt.filesystem": "0.10.3",
228
+ "@williamthorsen/toolbelt.numbers": "8.0.1",
229
+ "@williamthorsen/toolbelt.objects": "4.4.3",
230
+ "@williamthorsen/toolbelt.packaging": "0.7.3",
231
+ "@williamthorsen/toolbelt.terminal": "0.3.1",
209
232
  "ansi-regex": "6.6.0",
210
- "ansi-styles": "6.2.3",
211
- "cli-truncate": "6.1.1",
233
+ "ansi-styles": "6.2.3, 7.0.0",
234
+ "cli-truncate": "6.2.0",
212
235
  "diff": "9.0.0",
213
236
  "get-east-asian-width": "1.7.0",
214
- "is-fullwidth-code-point": "5.1.0",
215
- "slice-ansi": "9.0.1",
237
+ "slice-ansi": "10.0.1",
216
238
  "string-width": "8.3.0",
217
239
  "strip-ansi": "7.2.0",
218
240
  "wrap-ansi": "10.0.2"
@@ -345,10 +367,10 @@
345
367
  ],
346
368
  "name": "npm-auto-publish",
347
369
  "path": "kits/npm-auto-publish.js",
348
- "readyupVersion": "0.45.1",
370
+ "readyupVersion": "0.46.2",
349
371
  "source": "kits/npm-auto-publish.ts",
350
372
  "sourceHash": "682fdd4a",
351
- "targetHash": "884c7f0a"
373
+ "targetHash": "2bfaa0b5"
352
374
  }
353
375
  ]
354
376
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 18.2.0 — 2026-11-13
6
+
7
+ ### 🎉 Features
8
+
9
+ - Changes `release-kit show-tag-prefixes` to print one block per target that lists the derived prefix and each declared legacy identity with their tag counts in aligned columns, and to print the "Undeclared tag prefixes" section on every run, showing `(none)` when the scan does not find any candidates. (#1332)
10
+ - Adds a "Retired packages" section to `release-kit show-tag-prefixes`, which lists each entry declared in `retiredPackages` with its tag prefix, tag count, name, and successor. (#1332)
11
+ - Adds a closing note to `release-kit show-tag-prefixes` when a derived prefix does not have any tags yet, which explains that `release-kit tag` creates the first tag from the tags file that `release-kit prepare` writes. (#1332)
12
+ - Makes the template checks for `release.yaml`, `publish.yaml`, and `create-github-release.yaml` report the differing hunks, and distinguish a missing file from an unreadable one, rather than report only that a hash differs. (#1336)
13
+
14
+ ### 🪦 Removed
15
+
16
+ - Removes the check for the retired `.config/sync-labels.config.ts`, which made `sync-labels init`, `generate`, and `sync` exit with an error and made the release-kit readyup kit fail while the file existed. (#1333)
17
+
18
+ ### 🐛 Bug fixes
19
+
20
+ - Fixes the issue that `release-kit`'s ReadyUp kit reported the warning `releaseNotes.shouldInjectIntoReadme is true`, which the consumer could not clear, in a repo without any publishable package, by making the check skip with the reason `no publishable packages`. (#1334)
21
+ - Fixes the issue that the `publish.yaml matches template` check in `rdy run --from npm:@williamthorsen/release-kit` failed a `publish.yaml` that omits provenance, a setting that `release-kit update-templates` preserves. (#1336)
22
+
23
+ ## 18.1.1 — 2026-11-12
24
+
25
+ ### ⚙️ Tooling
26
+
27
+ - Declares `@williamthorsen/nmr` as a `workspace:*` devDependency of every workspace package but nmr and nmr-core, so that pnpm builds nmr first and a package's build cannot load `packages/nmr/dist` while nmr is replacing it, and adds a root unit test that fails when a package omits the declaration. (#1302)
28
+
5
29
  ## 18.1.0 — 2026-11-11
6
30
 
7
31
  ### 🎉 Features
package/README.md CHANGED
@@ -7,11 +7,23 @@ Version-bumping and changelog-generation toolkit for release workflows.
7
7
  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 the same commit history that decides each bump (with optional [editorial overrides](docs/editorial-overrides.md)).
8
8
 
9
9
  <!-- section:release-notes -->
10
- ## Release notes — v18.1.0 (2026-11-11)
10
+ ## Release notes — v18.2.0 (2026-11-13)
11
11
 
12
12
  ### 🎉 Features
13
13
 
14
- - Adds the `status:in-progress` label to the common label preset of `release-kit sync-labels`, for an issue on which work has started, beside the existing `status:blocked` and `status:on-hold`. (#1280)
14
+ - Changes `release-kit show-tag-prefixes` to print one block per target that lists the derived prefix and each declared legacy identity with their tag counts in aligned columns, and to print the "Undeclared tag prefixes" section on every run, showing `(none)` when the scan does not find any candidates. (#1332)
15
+ - Adds a "Retired packages" section to `release-kit show-tag-prefixes`, which lists each entry declared in `retiredPackages` with its tag prefix, tag count, name, and successor. (#1332)
16
+ - Adds a closing note to `release-kit show-tag-prefixes` when a derived prefix does not have any tags yet, which explains that `release-kit tag` creates the first tag from the tags file that `release-kit prepare` writes. (#1332)
17
+ - Makes the template checks for `release.yaml`, `publish.yaml`, and `create-github-release.yaml` report the differing hunks, and distinguish a missing file from an unreadable one, rather than report only that a hash differs. (#1336)
18
+
19
+ ### 🪦 Removed
20
+
21
+ - Removes the check for the retired `.config/sync-labels.config.ts`, which made `sync-labels init`, `generate`, and `sync` exit with an error and made the release-kit readyup kit fail while the file existed. (#1333)
22
+
23
+ ### 🐛 Bug fixes
24
+
25
+ - Fixes the issue that `release-kit`'s ReadyUp kit reported the warning `releaseNotes.shouldInjectIntoReadme is true`, which the consumer could not clear, in a repo without any publishable package, by making the check skip with the reason `no publishable packages`. (#1334)
26
+ - Fixes the issue that the `publish.yaml matches template` check in `rdy run --from npm:@williamthorsen/release-kit` failed a `publish.yaml` that omits provenance, a setting that `release-kit update-templates` preserves. (#1336)
15
27
  <!-- /section:release-notes -->
16
28
 
17
29
  ## Installation
@@ -12,7 +12,7 @@ import { CONFIG_FLAG, runReportingFailures } from "./commandKit.js";
12
12
  const { defineCommand, defineGroup } = createCli();
13
13
  export const showTagPrefixes = defineCommand({
14
14
  summary: 'Show derived and declared legacy tag prefixes per workspace',
15
- description: 'Print a per-workspace table of derived tag prefixes, tag counts, and declared legacy tag prefixes. Reports any release-shaped tags whose prefix is neither a derived prefix nor declared in `legacyIdentities`, with a copy-pasteable config snippet.',
15
+ description: 'Print one block per workspace with its derived tag prefix, declared legacy identities, and tag counts, followed by the declared retired packages. Reports any release-shaped tags whose prefix is neither derived nor declared, with a copy-pasteable config snippet.',
16
16
  flags: CONFIG_FLAG,
17
17
  run: ({ flags, context }) => runReportingFailures(context.styles, () => {
18
18
  enterRepoRoot();
@@ -0,0 +1,10 @@
1
+ import type { RepoType } from './detectRepoType.js';
2
+ /** Renders the create-github-release workflow that the repo should contain. */
3
+ export declare function renderExpectedCreateGithubReleaseWorkflow(repoType: RepoType): string;
4
+ /**
5
+ * Renders the publish workflow that the repo should contain, keeping the provenance setting of the existing
6
+ * `publish.yaml` and enabling provenance when the file is missing.
7
+ */
8
+ export declare function renderExpectedPublishWorkflow(repoType: RepoType): string;
9
+ /** Renders the release workflow that the repo should contain. */
10
+ export declare function renderExpectedReleaseWorkflow(repoType: RepoType): string;
@@ -0,0 +1,18 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { hasProvenance } from "./hasProvenance.js";
3
+ import { PUBLISH_WORKFLOW_PATH } from "./scaffold.js";
4
+ import { renderCreateGithubReleaseWorkflow, renderPublishWorkflow, renderReleaseWorkflow } from "./templates.js";
5
+ export function renderExpectedCreateGithubReleaseWorkflow(repoType) {
6
+ return renderCreateGithubReleaseWorkflow(repoType);
7
+ }
8
+ export function renderExpectedPublishWorkflow(repoType) {
9
+ return renderPublishWorkflow(repoType, { shouldGenerateProvenance: readProvenanceSetting() });
10
+ }
11
+ export function renderExpectedReleaseWorkflow(repoType) {
12
+ return renderReleaseWorkflow(repoType);
13
+ }
14
+ function readProvenanceSetting() {
15
+ if (!existsSync(PUBLISH_WORKFLOW_PATH))
16
+ return true;
17
+ return hasProvenance(readFileSync(PUBLISH_WORKFLOW_PATH, 'utf8'));
18
+ }
@@ -1,13 +1,12 @@
1
- import { existsSync, readFileSync } from 'node:fs';
1
+ import { existsSync } from 'node:fs';
2
2
  import { isAnticipatedFailure, printError, printSkip, printStep, printSuccess, reportTemplateUpdate, updateManagedFile, } from '@williamthorsen/nmr-core';
3
3
  import { describeError } from '@williamthorsen/toolbelt.errors';
4
4
  import { readSyncLabelsWorkflowTemplate } from "../sync-labels/templates.js";
5
5
  import { detectRepoType } from "./detectRepoType.js";
6
- import { hasProvenance } from "./hasProvenance.js";
7
6
  import { formatPrivateWorkflowSkip, hasPublishablePackage } from "./hasPublishablePackage.js";
8
7
  import { checkEligibility } from "./initCommand.js";
8
+ import { renderExpectedCreateGithubReleaseWorkflow, renderExpectedPublishWorkflow, renderExpectedReleaseWorkflow, } from "./managed-workflows.js";
9
9
  import { CREATE_GITHUB_RELEASE_WORKFLOW_PATH, PUBLISH_WORKFLOW_PATH, PUBLISHING_WORKFLOW_PATHS } from "./scaffold.js";
10
- import { renderCreateGithubReleaseWorkflow, renderPublishWorkflow, renderReleaseWorkflow } from "./templates.js";
11
10
  const SYNC_LABELS_WORKFLOW_PATH = '.github/workflows/sync-labels.yaml';
12
11
  export function updateTemplatesCommand({ isDryRun, styles }) {
13
12
  if (isDryRun) {
@@ -65,17 +64,12 @@ function buildManagedFiles(repoType, publishable) {
65
64
  ? []
66
65
  : PUBLISHING_WORKFLOW_PATHS.filter((filePath) => !existsSync(filePath));
67
66
  const files = [
68
- [CREATE_GITHUB_RELEASE_WORKFLOW_PATH, renderCreateGithubReleaseWorkflow(repoType)],
69
- [PUBLISH_WORKFLOW_PATH, renderPublishWorkflow(repoType, { shouldGenerateProvenance: readProvenanceSetting() })],
70
- ['.github/workflows/release.yaml', renderReleaseWorkflow(repoType)],
67
+ [CREATE_GITHUB_RELEASE_WORKFLOW_PATH, renderExpectedCreateGithubReleaseWorkflow(repoType)],
68
+ [PUBLISH_WORKFLOW_PATH, renderExpectedPublishWorkflow(repoType)],
69
+ ['.github/workflows/release.yaml', renderExpectedReleaseWorkflow(repoType)],
71
70
  ];
72
71
  if (existsSync(SYNC_LABELS_WORKFLOW_PATH)) {
73
72
  files.push([SYNC_LABELS_WORKFLOW_PATH, readSyncLabelsWorkflowTemplate()]);
74
73
  }
75
74
  return { files: files.filter(([filePath]) => !skippedPaths.includes(filePath)), skippedPaths };
76
75
  }
77
- function readProvenanceSetting() {
78
- if (!existsSync(PUBLISH_WORKFLOW_PATH))
79
- return true;
80
- return hasProvenance(readFileSync(PUBLISH_WORKFLOW_PATH, 'utf8'));
81
- }
@@ -6,6 +6,11 @@ export interface TagPrefixPreviewRow {
6
6
  workspacePath: string;
7
7
  /** Directory basename (e.g., `core`). */
8
8
  dir: string;
9
+ /**
10
+ * The workspace's `package.json` name or the directory target's `releases` key; `null` when a workspace's derivation
11
+ * failed.
12
+ */
13
+ name: string | null;
9
14
  /** The derived tag prefix, or `null` when derivation failed. */
10
15
  derivedPrefix: string | null;
11
16
  /** Human-readable reason derivation failed, or `null` when it succeeded. */
@@ -15,8 +20,9 @@ export interface TagPrefixPreviewRow {
15
20
  /** One entry per declared legacy prefix, with its current tag count. */
16
21
  legacyEntries: LegacyTagPrefixEntry[];
17
22
  }
18
- /** A single declared legacy prefix and its tag count. */
23
+ /** A single declared legacy identity's prefix and its tag count. */
19
24
  export interface LegacyTagPrefixEntry {
25
+ name: string;
20
26
  prefix: string;
21
27
  tagCount: number;
22
28
  }
@@ -17,7 +17,7 @@ export function previewTagPrefixes(config) {
17
17
  const workspacePaths = workspace.kind === 'packages' ? workspace.packageDirs : [];
18
18
  const overridesByPath = buildOverrideMap(config, workspacePaths);
19
19
  const workspaces = [
20
- ...Array.from(workspacePaths, (workspacePath) => buildPreviewRow(workspacePath, () => deriveWorkspaceConfig(workspacePath).tagPrefix, overridesByPath)),
20
+ ...Array.from(workspacePaths, (workspacePath) => buildPreviewRow(workspacePath, null, () => deriveWorkspaceConfig(workspacePath), overridesByPath)),
21
21
  ...buildDirectoryRows(config, workspacePaths, overridesByPath),
22
22
  ];
23
23
  const retiredPackages = buildRetiredPreviewEntries(config?.retiredPackages ?? []);
@@ -59,15 +59,18 @@ function buildDirectoryRows(config, workspacePaths, overridesByPath) {
59
59
  path: directoryPath,
60
60
  ...(entry.tagPrefix !== undefined && { tagPrefix: entry.tagPrefix }),
61
61
  };
62
- return [buildPreviewRow(directoryPath, () => deriveDirectoryConfig(declaredDirectory).tagPrefix, overridesByPath)];
62
+ return [buildPreviewRow(directoryPath, name, () => deriveDirectoryConfig(declaredDirectory), overridesByPath)];
63
63
  });
64
64
  }
65
- function buildPreviewRow(workspacePath, derivePrefix, overridesByPath) {
65
+ function buildPreviewRow(workspacePath, knownName, deriveTarget, overridesByPath) {
66
66
  const dir = basename(workspacePath);
67
+ let name = knownName;
67
68
  let derivedPrefix = null;
68
69
  let derivationError = null;
69
70
  try {
70
- derivedPrefix = derivePrefix();
71
+ const target = deriveTarget();
72
+ name = target.name;
73
+ derivedPrefix = target.tagPrefix;
71
74
  }
72
75
  catch (error) {
73
76
  derivationError = describeError(error);
@@ -75,12 +78,14 @@ function buildPreviewRow(workspacePath, derivePrefix, overridesByPath) {
75
78
  const derivedTagCount = derivedPrefix === null ? 0 : countTagsMatching(derivedPrefix);
76
79
  const declaredIdentities = overridesByPath.get(workspacePath) ?? [];
77
80
  const legacyEntries = declaredIdentities.map((identity) => ({
81
+ name: identity.name,
78
82
  prefix: identity.tagPrefix,
79
83
  tagCount: countTagsMatching(identity.tagPrefix),
80
84
  }));
81
85
  return {
82
86
  workspacePath,
83
87
  dir,
88
+ name,
84
89
  derivedPrefix,
85
90
  derivationError,
86
91
  derivedTagCount,
@@ -2,11 +2,11 @@ import { type StreamStyles } from '@williamthorsen/nmr-core';
2
2
  /**
3
3
  * Runs the CLI `show-tag-prefixes` command and returns its exit code.
4
4
  *
5
- * Prints a per-workspace table of derived prefixes, tag counts, and declared legacy entries, followed by an
6
- * "Undeclared tag prefixes" section when candidate-shaped tags exist outside the known set. Returns `1` on any
7
- * derivation failure or collision and `0` otherwise; undeclared candidates do not affect the exit code. When a config
8
- * exists and either fails to load or fails validation, the command reports it to stderr and returns `1` in both repo
9
- * modes, because a default-config preview would understate the declared legacy prefixes. When the default config is
10
- * absent, the command previews against derived defaults.
5
+ * Prints one block per target, with its derived prefix and declared legacy identities and their tag counts, then the
6
+ * declared retired packages, the candidate-shaped tags outside the known set, and a footer on creating the first tag
7
+ * when a target does not have any. Returns `1` on any derivation failure or collision and `0` otherwise; undeclared
8
+ * candidates do not affect the exit code. When a config exists and either fails to load or fails validation, the
9
+ * command reports it to stderr and returns `1` in both repo modes, because a default-config preview would understate
10
+ * the declared legacy prefixes. When the default config is absent, the command previews against derived defaults.
11
11
  */
12
12
  export declare function showTagPrefixesCommand(styles: StreamStyles, configPath?: string): Promise<number>;
@@ -2,7 +2,7 @@ import { formatStatusLine, isAnticipatedFailure, reportError, } from '@williamth
2
2
  import { BIN } from "./bin.js";
3
3
  import { detectRepoType } from "./init/detectRepoType.js";
4
4
  import { loadValidatedConfig, reportConfigProblem } from "./loadValidatedConfig.js";
5
- import { previewTagPrefixes } from "./previewTagPrefixes.js";
5
+ import { previewTagPrefixes, } from "./previewTagPrefixes.js";
6
6
  export async function showTagPrefixesCommand(styles, configPath) {
7
7
  const result = await loadValidatedConfig(configPath);
8
8
  if (result.status === 'invalid') {
@@ -27,59 +27,114 @@ export async function showTagPrefixesCommand(styles, configPath) {
27
27
  process.stdout.write(renderMonorepo(preview, styles.stdout));
28
28
  return computeExitCode(preview);
29
29
  }
30
- function renderSinglePackage() {
31
- const lines = [
32
- 'Workspace Derived prefix Status',
33
- '. v (single-package mode)',
34
- '',
35
- ];
36
- return lines.join('\n');
30
+ const LABEL_WIDTH = 'legacy identity'.length;
31
+ const FIRST_TAG_FOOTER = 'A workspace marked "no derived tags yet" has not been released under its derived prefix. `release-kit tag` creates its first tag from the tags file that `release-kit prepare` writes.';
32
+ function computeExitCode(preview) {
33
+ const hasDerivationFailure = preview.workspaces.some((row) => row.derivedPrefix === null);
34
+ const hasCollision = preview.collisions.length > 0;
35
+ return hasDerivationFailure || hasCollision ? 1 : 0;
36
+ }
37
+ function formatPrefixColumns(columns, prefix, tagCount, note) {
38
+ const cells = `${prefix.padEnd(columns.prefixWidth)} ${String(tagCount).padStart(columns.countDigits)} tags`;
39
+ return note === undefined ? cells : `${cells} ${note}`;
40
+ }
41
+ function formatRow(label, body) {
42
+ return ` ${label.padEnd(LABEL_WIDTH)} ${body}`;
43
+ }
44
+ function measureColumns(preview) {
45
+ const entries = preview.retiredPackages.map((entry) => ({
46
+ prefix: entry.tagPrefix,
47
+ tagCount: entry.tagCount,
48
+ }));
49
+ for (const row of preview.workspaces) {
50
+ if (row.derivedPrefix !== null)
51
+ entries.push({ prefix: row.derivedPrefix, tagCount: row.derivedTagCount });
52
+ entries.push(...row.legacyEntries);
53
+ }
54
+ return {
55
+ prefixWidth: Math.max(0, ...entries.map((entry) => entry.prefix.length)),
56
+ countDigits: Math.max(1, ...entries.map((entry) => String(entry.tagCount).length)),
57
+ };
37
58
  }
38
59
  function renderMonorepo(preview, style) {
39
- const lines = ['Workspace tag prefixes:', ''];
60
+ const columns = measureColumns(preview);
61
+ const lines = ['Workspace tag prefixes'];
40
62
  for (const row of preview.workspaces) {
41
- lines.push(...renderWorkspaceRow(row, style));
63
+ lines.push('', ...renderTargetBlock(row, preview.collisions, columns, style));
42
64
  }
43
- if (preview.collisions.length > 0) {
44
- lines.push('', ...preview.collisions.map((collision) => formatStatusLine(style, 'failed', `tag prefix collision: '${collision.tagPrefix}' used by ${collision.workspacePaths.join(', ')}`)));
65
+ if (preview.retiredPackages.length > 0) {
66
+ lines.push('', 'Retired packages', '', ...renderRetiredLines(preview.retiredPackages, columns, style));
45
67
  }
46
- if (preview.undeclaredCandidates.length > 0) {
47
- lines.push('', 'Undeclared tag prefixes:', '', ...preview.undeclaredCandidates.map((candidate) => ` '${candidate.prefix}': ${candidate.tagCount} tags (e.g., ${candidate.exampleTags.join(', ')})`), '', 'Suggested config snippet (adjust `dir` to match the workspace if the guess is wrong, and replace the `name` placeholder with the legacy npm name):', '', renderSuggestedSnippet(preview.undeclaredCandidates), '', "If the suggested `dir` does not match the workspace, adjust it before pasting. Each legacy identity requires a `name`: Replace the `TODO-fill-in-legacy-npm-name` placeholder with the package's prior npm name.");
68
+ lines.push('', 'Undeclared tag prefixes', '', ...renderUndeclaredSection(preview.undeclaredCandidates, style));
69
+ if (preview.workspaces.some((row) => row.derivedPrefix !== null && row.derivedTagCount === 0)) {
70
+ lines.push('', formatStatusLine(style, 'info', FIRST_TAG_FOOTER));
48
71
  }
49
72
  lines.push('');
50
73
  return lines.join('\n');
51
74
  }
52
- function renderWorkspaceRow(row, style) {
53
- const lines = [];
75
+ function renderRetiredLines(retiredPackages, columns, style) {
76
+ return retiredPackages.map((entry) => {
77
+ const identity = entry.successor === undefined ? entry.name : `${entry.name}, successor: ${entry.successor}`;
78
+ const status = entry.tagCount > 0 ? 'passed' : 'warning';
79
+ return ` ${formatStatusLine(style, status, formatPrefixColumns(columns, entry.tagPrefix, entry.tagCount, identity))}`;
80
+ });
81
+ }
82
+ function renderSinglePackage() {
83
+ const lines = [
84
+ 'Workspace tag prefixes',
85
+ '',
86
+ '. (single-package mode)',
87
+ formatRow('derived prefix', 'v'),
88
+ '',
89
+ ];
90
+ return lines.join('\n');
91
+ }
92
+ function renderSuggestedSnippet(candidates) {
93
+ const entries = candidates
94
+ .map((candidate) => ` { dir: '${candidate.suggestedDir}', legacyIdentities: [{ name: 'TODO-fill-in-legacy-npm-name', tagPrefix: '${candidate.prefix}' }] },`)
95
+ .join('\n');
96
+ return ` workspaces: [\n${entries}\n ],`;
97
+ }
98
+ function renderTargetBlock(row, collisions, columns, style) {
99
+ const lines = [row.name === null ? row.workspacePath : `${row.workspacePath} (${row.name})`];
54
100
  if (row.derivedPrefix === null) {
55
101
  const failure = formatStatusLine(style, 'failed', `derivation failed: ${row.derivationError ?? 'unknown error'}`);
56
- lines.push(` ${row.workspacePath}: ${failure}`);
102
+ lines.push(formatRow('derived prefix', failure));
57
103
  return lines;
58
104
  }
59
- const statusMarker = row.derivedTagCount > 0
60
- ? formatStatusLine(style, 'passed', `${row.derivedTagCount} tags`)
61
- : formatStatusLine(style, 'warning', 'no existing tags');
62
- lines.push(` ${row.workspacePath}: derived prefix '${row.derivedPrefix}', ${statusMarker}`);
105
+ const collidingPaths = collisions
106
+ .filter((collision) => collision.tagPrefix === row.derivedPrefix)
107
+ .flatMap((collision) => collision.workspacePaths)
108
+ .filter((path) => path !== row.workspacePath);
109
+ let derivedStatus = 'passed';
110
+ let derivedNote;
111
+ if (collidingPaths.length > 0) {
112
+ derivedStatus = 'failed';
113
+ derivedNote = `collides with ${collidingPaths.join(', ')}`;
114
+ }
115
+ else if (row.derivedTagCount === 0) {
116
+ derivedStatus = 'warning';
117
+ derivedNote = 'no derived tags yet';
118
+ }
119
+ const derivedCells = formatPrefixColumns(columns, row.derivedPrefix, row.derivedTagCount, derivedNote);
120
+ lines.push(formatRow('derived prefix', formatStatusLine(style, derivedStatus, derivedCells)));
63
121
  for (const entry of row.legacyEntries) {
64
- if (entry.tagCount > 0) {
65
- const recognizedMessage = `${entry.tagCount} legacy tags with '${entry.prefix}' prefix (recognized)`;
66
- lines.push(` ${formatStatusLine(style, 'passed', recognizedMessage)}`);
67
- }
68
- else {
69
- const taglessMessage = `recorded legacy prefix '${entry.prefix}' does not have any tags`;
70
- lines.push(` ${formatStatusLine(style, 'warning', taglessMessage)}`);
71
- }
122
+ const status = entry.tagCount > 0 ? 'passed' : 'warning';
123
+ const legacyCells = formatPrefixColumns(columns, entry.prefix, entry.tagCount, entry.name);
124
+ lines.push(formatRow('legacy identity', formatStatusLine(style, status, legacyCells)));
72
125
  }
73
126
  return lines;
74
127
  }
75
- function renderSuggestedSnippet(candidates) {
76
- const entries = candidates
77
- .map((candidate) => ` { dir: '${candidate.suggestedDir}', legacyIdentities: [{ name: 'TODO-fill-in-legacy-npm-name', tagPrefix: '${candidate.prefix}' }] },`)
78
- .join('\n');
79
- return ` workspaces: [\n${entries}\n ],`;
80
- }
81
- function computeExitCode(preview) {
82
- const hasDerivationFailure = preview.workspaces.some((row) => row.derivedPrefix === null);
83
- const hasCollision = preview.collisions.length > 0;
84
- return hasDerivationFailure || hasCollision ? 1 : 0;
128
+ function renderUndeclaredSection(candidates, style) {
129
+ if (candidates.length === 0)
130
+ return [' (none)'];
131
+ return [
132
+ ...candidates.map((candidate) => ` ${formatStatusLine(style, 'warning', `'${candidate.prefix}': ${candidate.tagCount} tags (e.g., ${candidate.exampleTags.join(', ')})`)}`),
133
+ '',
134
+ 'Suggested config snippet (adjust `dir` to match the workspace if the guess is wrong, and replace the `name` placeholder with the legacy npm name):',
135
+ '',
136
+ renderSuggestedSnippet(candidates),
137
+ '',
138
+ "If the suggested `dir` does not match the workspace, adjust it before pasting. Each legacy identity requires a `name`: Replace the `TODO-fill-in-legacy-npm-name` placeholder with the package's prior npm name.",
139
+ ];
85
140
  }
@@ -8,7 +8,6 @@ import { CONFIG_FILE_PATH } from "../loadConfig.js";
8
8
  import { loadValidatedConfig, reportConfigProblem } from "../loadValidatedConfig.js";
9
9
  import { hashPreset } from "./presets.js";
10
10
  import { resolveLabels } from "./resolveLabels.js";
11
- import { checkRetiredSyncLabelsConfig } from "./retiredConfig.js";
12
11
  export const LABELS_OUTPUT_PATH = '.github/labels.yaml';
13
12
  export function formatLabelsYaml(labels, presetHashes, configFilePath = CONFIG_FILE_PATH) {
14
13
  const headerLines = ['# Generated by release-kit sync-labels — do not edit.', `# Source: ${configFilePath}`];
@@ -43,9 +42,6 @@ export async function loadRepoLabelsConfig(stderrStyle, configPath) {
43
42
  return result.config.repoLabels;
44
43
  }
45
44
  export async function generateCommand({ shouldCheck = false, configPath, styles }) {
46
- if (checkRetiredSyncLabelsConfig(configPath)) {
47
- return 1;
48
- }
49
45
  const repoLabels = await loadRepoLabelsConfig(styles.stderr, configPath);
50
46
  if (repoLabels === undefined) {
51
47
  return 1;
@@ -5,14 +5,10 @@ import { describeEmptyWorkspace, discoverWorkspaces } from "../discoverWorkspace
5
5
  import { CONFIG_FILE_PATH } from "../loadConfig.js";
6
6
  import { loadValidatedConfig, reportConfigProblem } from "../loadValidatedConfig.js";
7
7
  import { generateCommand, LABELS_OUTPUT_PATH } from "./generateCommand.js";
8
- import { checkRetiredSyncLabelsConfig } from "./retiredConfig.js";
9
8
  import { buildScopeLabels, readSyncLabelsWorkflowTemplate, renderRepoLabelsBlock, renderRepoLabelsConfigScript, } from "./templates.js";
10
9
  const WORKFLOW_PATH = '.github/workflows/sync-labels.yaml';
11
10
  export async function syncLabelsInitCommand({ configPath, isDryRun, shouldForce, styles, }) {
12
11
  const configFilePath = configPath ?? CONFIG_FILE_PATH;
13
- if (checkRetiredSyncLabelsConfig(configPath)) {
14
- return 1;
15
- }
16
12
  if (isDryRun) {
17
13
  console.info('[dry-run mode]');
18
14
  }
@@ -3,7 +3,6 @@ import { existsSync } from 'node:fs';
3
3
  import { GIT_OUTPUT_LIMIT_BYTES, reportError } from '@williamthorsen/nmr-core';
4
4
  import { describeError } from '@williamthorsen/toolbelt.errors';
5
5
  import { BIN } from "../bin.js";
6
- import { checkRetiredSyncLabelsConfig } from "./retiredConfig.js";
7
6
  const WORKFLOW_FILE = '.github/workflows/sync-labels.yaml';
8
7
  function checkGhAvailable() {
9
8
  try {
@@ -15,9 +14,6 @@ function checkGhAvailable() {
15
14
  }
16
15
  }
17
16
  export function syncLabelsCommand() {
18
- if (checkRetiredSyncLabelsConfig()) {
19
- return 1;
20
- }
21
17
  if (!checkGhAvailable()) {
22
18
  reportError(BIN, 'The `gh` CLI is not installed or not in PATH. Install it from https://cli.github.com/');
23
19
  return 1;