@williamthorsen/release-kit 14.0.0 → 14.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.
Files changed (110) hide show
  1. package/.meta/changelog.json +173 -0
  2. package/.readyup/kits/default.js +64 -7
  3. package/.readyup/kits/npm-auto-publish.js +127 -75
  4. package/.readyup/manifest.json +22 -7
  5. package/CHANGELOG.md +47 -0
  6. package/README.md +18 -4
  7. package/dist/esm/assertReleasesOnNpm.d.ts +7 -0
  8. package/dist/esm/assertReleasesOnNpm.js +56 -0
  9. package/dist/esm/assertTaggedBaseline.d.ts +2 -2
  10. package/dist/esm/attachChangelogDiagnostics.d.ts +2 -2
  11. package/dist/esm/bin/release-kit.js +10 -6
  12. package/dist/esm/buildChangelogEntries.d.ts +3 -3
  13. package/dist/esm/buildChangelogEntries.js +3 -3
  14. package/dist/esm/buildDependencyGraph.d.ts +11 -11
  15. package/dist/esm/buildDependencyGraph.js +13 -13
  16. package/dist/esm/buildReleaseSummary.d.ts +3 -3
  17. package/dist/esm/buildReleaseSummary.js +4 -12
  18. package/dist/esm/changelogArchive.d.ts +14 -0
  19. package/dist/esm/changelogArchive.js +21 -0
  20. package/dist/esm/changelogJsonFile.d.ts +2 -2
  21. package/dist/esm/changelogOverrides.d.ts +9 -8
  22. package/dist/esm/changelogOverrides.js +3 -6
  23. package/dist/esm/createGithubReleaseCommand.d.ts +2 -1
  24. package/dist/esm/createGithubReleaseCommand.js +7 -4
  25. package/dist/esm/deriveDirectoryConfig.d.ts +13 -0
  26. package/dist/esm/deriveDirectoryConfig.js +59 -0
  27. package/dist/esm/deriveWorkspaceConfig.js +2 -3
  28. package/dist/esm/discoverWorkspaces.d.ts +3 -3
  29. package/dist/esm/enumerateReleaseWindows.d.ts +1 -1
  30. package/dist/esm/enumerateReleaseWindows.js +1 -3
  31. package/dist/esm/findTransitiveDependents.js +3 -3
  32. package/dist/esm/findUnroutedEntryScopes.d.ts +2 -2
  33. package/dist/esm/formatDirectoryPublishSkip.d.ts +3 -0
  34. package/dist/esm/formatDirectoryPublishSkip.js +3 -0
  35. package/dist/esm/formatExcludedSkip.d.ts +5 -2
  36. package/dist/esm/formatExcludedSkip.js +2 -2
  37. package/dist/esm/formatProjectPublishSkip.d.ts +3 -0
  38. package/dist/esm/formatProjectPublishSkip.js +3 -0
  39. package/dist/esm/generateChangelogs.d.ts +7 -7
  40. package/dist/esm/generateChangelogs.js +2 -2
  41. package/dist/esm/help/prepareHelp.d.ts +1 -1
  42. package/dist/esm/help/prepareHelp.js +7 -5
  43. package/dist/esm/init/hasPublishablePackage.d.ts +10 -0
  44. package/dist/esm/init/hasPublishablePackage.js +28 -0
  45. package/dist/esm/init/initCommand.js +23 -13
  46. package/dist/esm/init/scaffold.d.ts +6 -1
  47. package/dist/esm/init/scaffold.js +10 -7
  48. package/dist/esm/init/templates.js +1 -1
  49. package/dist/esm/init/updateTemplatesCommand.d.ts +2 -1
  50. package/dist/esm/init/updateTemplatesCommand.js +12 -6
  51. package/dist/esm/isPublishableManifest.d.ts +2 -0
  52. package/dist/esm/isPublishableManifest.js +5 -0
  53. package/dist/esm/loadConfig.d.ts +12 -12
  54. package/dist/esm/loadConfig.js +79 -30
  55. package/dist/esm/matchesTagPrefix.d.ts +2 -0
  56. package/dist/esm/matchesTagPrefix.js +4 -0
  57. package/dist/esm/npmRegistry.d.ts +35 -0
  58. package/dist/esm/npmRegistry.js +58 -0
  59. package/dist/esm/prepareCommand.d.ts +1 -1
  60. package/dist/esm/prepareCommand.js +50 -36
  61. package/dist/esm/prepareTarget.d.ts +103 -0
  62. package/dist/esm/prepareTarget.js +274 -0
  63. package/dist/esm/previewTagPrefixes.d.ts +5 -3
  64. package/dist/esm/previewTagPrefixes.js +60 -14
  65. package/dist/esm/propagateBumps.js +1 -1
  66. package/dist/esm/publishCommand.js +13 -1
  67. package/dist/esm/readChangelogSections.d.ts +6 -4
  68. package/dist/esm/readChangelogSections.js +18 -4
  69. package/dist/esm/releasePlan.d.ts +4 -2
  70. package/dist/esm/releasePlan.js +34 -12
  71. package/dist/esm/releasePrepareTargets.d.ts +47 -0
  72. package/dist/esm/releasePrepareTargets.js +335 -0
  73. package/dist/esm/renderChangelogMarkdown.d.ts +23 -0
  74. package/dist/esm/renderChangelogMarkdown.js +47 -11
  75. package/dist/esm/reportPrepare.d.ts +2 -3
  76. package/dist/esm/reportPrepare.js +24 -12
  77. package/dist/esm/resolveCommandTags.d.ts +3 -1
  78. package/dist/esm/resolveCommandTags.js +15 -22
  79. package/dist/esm/resolveReleaseDeclaration.d.ts +45 -0
  80. package/dist/esm/resolveReleaseDeclaration.js +153 -0
  81. package/dist/esm/resolveReleaseTags.d.ts +14 -3
  82. package/dist/esm/resolveReleaseTags.js +36 -15
  83. package/dist/esm/resolveReleaseTargets.d.ts +29 -0
  84. package/dist/esm/resolveReleaseTargets.js +131 -0
  85. package/dist/esm/sync-labels/generateCommand.js +2 -1
  86. package/dist/esm/sync-labels/resolveLabels.d.ts +4 -3
  87. package/dist/esm/sync-labels/resolveLabels.js +30 -1
  88. package/dist/esm/sync-labels/templates.js +1 -1
  89. package/dist/esm/sync-labels/types.d.ts +2 -0
  90. package/dist/esm/types.d.ts +251 -167
  91. package/dist/esm/types.js +22 -0
  92. package/dist/esm/validateConfig.js +80 -21
  93. package/dist/esm/validateOverridesCommand.d.ts +1 -1
  94. package/dist/esm/validateOverridesCommand.js +17 -28
  95. package/docs/api.md +25 -58
  96. package/docs/changelogs.md +26 -6
  97. package/docs/configuration.md +80 -17
  98. package/docs/labels.md +30 -5
  99. package/docs/project-releases.md +42 -2
  100. package/docs/publishing.md +3 -3
  101. package/docs/releasing.md +12 -4
  102. package/docs/work-types.md +1 -1
  103. package/package.json +6 -6
  104. package/presets/labels/common.yaml +11 -0
  105. package/dist/esm/releasePrepare.d.ts +0 -36
  106. package/dist/esm/releasePrepare.js +0 -230
  107. package/dist/esm/releasePrepareMono.d.ts +0 -29
  108. package/dist/esm/releasePrepareMono.js +0 -503
  109. package/dist/esm/releasePrepareProject.d.ts +0 -47
  110. package/dist/esm/releasePrepareProject.js +0 -164
@@ -1,4 +1,177 @@
1
1
  [
2
+ {
3
+ "version": "14.2.0",
4
+ "date": "2026-10-12",
5
+ "sections": [
6
+ {
7
+ "title": "🎉 Features",
8
+ "audience": "all",
9
+ "items": [
10
+ {
11
+ "description": "Adds an optional `aliases` array to a `repoLabels.labels` entry, which `sync-labels` writes to `.github/labels.yaml` so that the sync renames each existing label named by an alias to the entry's name and keeps it on the issues and pull requests that have it. (#1015)",
12
+ "hash": "29e43c8066d15d956e60964e616a1bb51c3cd6bd",
13
+ "entry": 1
14
+ },
15
+ {
16
+ "description": "Makes `release-kit sync-labels` reject a config whose alias names a label that the config resolves or appears twice, because the sync would otherwise merge one declared label into another and delete it. (#1015)",
17
+ "hash": "29e43c8066d15d956e60964e616a1bb51c3cd6bd",
18
+ "entry": 2
19
+ },
20
+ {
21
+ "description": "Adds the `confidence:high` (\"Clearly worth doing\"), `confidence:medium` (\"Worth doing, but arguable\"), and `confidence:low` (\"Not worth a ticket now\") labels to the `common` preset of `release-kit sync-labels`, and documents in `docs/labels.md` that the family grades how advisable a recommendation is. (#1019)",
22
+ "hash": "a92f374228e4a2ccc1f7053b1565cbf081c241b8",
23
+ "entry": 1
24
+ },
25
+ {
26
+ "description": "Adds a warning to the `release-kit` readyup kit that names each `scope:` label in `.github/labels.yaml` with a non-empty description, archived labels included, because GitHub's label list groups scope labels compactly only when they have no description. (#1022)",
27
+ "hash": "b24ced5a34b7f3baacbbf2bbe2aca5e0eb08d548",
28
+ "entry": 1
29
+ },
30
+ {
31
+ "description": "Adds the `releases` block to `.config/release-kit.config.ts`, in which an entry renames a discovered workspace's release target, excludes the workspace with `false`, or declares a composite of named members that the root `package.json` versions, as an alternative to the `workspaces` and `project` blocks, which keep working unchanged. (#1028)",
32
+ "hash": "6427c9642c3517bc4a0a6c8b8f6ff1012bd0adcf",
33
+ "entry": 1
34
+ },
35
+ {
36
+ "description": "Lets `release-kit prepare --only` select a `releases` composite by its target name, and makes a narrowed run that leaves the composite out skip it with a warning that names it. (#1028)",
37
+ "hash": "6427c9642c3517bc4a0a6c8b8f6ff1012bd0adcf",
38
+ "entry": 2
39
+ },
40
+ {
41
+ "description": "Adds directory release targets to `release-kit`: A `releases` entry whose `path` is not a workspace now declares a target that `prepare` versions from the directory's `package.json` or `version.json`, whose `CHANGELOG.md` `prepare` writes, and whose tags `push` and `create-github-release` handle, while `publish` skips them because a directory does not have any package to publish to npm. (#1029)",
42
+ "hash": "06d7ada2cd352d9ba9cfbf1cc30195b2273488d9",
43
+ "entry": 1
44
+ },
45
+ {
46
+ "description": "Adds the repo-root target `{ path: '.' }`, which versions the root `package.json`, tags `v{version}`, and writes every commit in the repo to `./CHANGELOG.md`, replacing a `project` block whose window covered the whole repo only while every workspace was excluded. (#1029)",
47
+ "hash": "06d7ada2cd352d9ba9cfbf1cc30195b2273488d9",
48
+ "entry": 2
49
+ },
50
+ {
51
+ "description": "Adds the `changelogMarkdown.keepVersions` config option, which makes `prepare` write only the newest `keepVersions` sections to `CHANGELOG.md`, with links to `changelogs/CHANGELOG-v{major}.md` archive files that contain the older sections, and delete any archive file that is no longer needed after the option is raised or removed. (#1031)",
52
+ "hash": "0368850a5719d9a9ee21db6a82063adc44b4ea70",
53
+ "entry": 1
54
+ },
55
+ {
56
+ "description": "Extends `prepare` to read hand-written sections from the `changelogs/` archive files as well as from `CHANGELOG.md`, so that an archived section written by hand is kept when the changelog is regenerated. (#1031)",
57
+ "hash": "0368850a5719d9a9ee21db6a82063adc44b4ea70",
58
+ "entry": 2
59
+ },
60
+ {
61
+ "description": "Adds the warning \"published packages ship the changelog archives\" to the release-kit readyup kit, which reports each publishable package whose `files` field omits `changelogs/` while `keepVersions` is set, because the links in `CHANGELOG.md` to the archive files would otherwise be broken in the published package. (#1031)",
62
+ "hash": "0368850a5719d9a9ee21db6a82063adc44b4ea70",
63
+ "entry": 3
64
+ }
65
+ ]
66
+ },
67
+ {
68
+ "title": "🐛 Bug fixes",
69
+ "audience": "all",
70
+ "items": [
71
+ {
72
+ "description": "Fixes the issue that the `npm-auto-publish` kit crashed without reporting any results in a repo whose `origin` remote is missing or does not point to GitHub, and makes its trusted-publisher and provenance checks name that cause and advise adding the remote. (#1018)",
73
+ "hash": "7faff3726a51003c80060ba5fd98235338997145",
74
+ "entry": 1
75
+ },
76
+ {
77
+ "description": "Fixes the issue that `release-kit prepare` accepted two workspaces whose directories share a name, which `--only` and the dependency graph use to identify a workspace, and makes `prepare` and `release-kit overrides validate` stop with an error that lists the path of each. (#1026)",
78
+ "hash": "7211951adb8e3e7b05a35196096d67bc33890801",
79
+ "entry": 2
80
+ },
81
+ {
82
+ "description": "Fixes the issue that `release-kit push` never pushed the project tag on HEAD, and failed with `HEAD does not have any release tags.` when the project tag was the only release tag. (#1027)",
83
+ "hash": "ffab419c87725051d04a1d817fc4be8b7222694b",
84
+ "entry": 1
85
+ },
86
+ {
87
+ "description": "Fixes the issue that `release-kit create-github-release` did not create a GitHub Release for the project tag; a private workspace's tag is still skipped. (#1027)",
88
+ "hash": "ffab419c87725051d04a1d817fc4be8b7222694b",
89
+ "entry": 2
90
+ },
91
+ {
92
+ "description": "Makes `release-kit publish` skip a project tag named by `--tags` with the warning `Skipping v0.13.0: a project tag is not an npm package.`, since the project does not have an npm package to publish. (#1027)",
93
+ "hash": "ffab419c87725051d04a1d817fc4be8b7222694b",
94
+ "entry": 3
95
+ }
96
+ ]
97
+ },
98
+ {
99
+ "title": "♻️ Refactoring",
100
+ "audience": "dev",
101
+ "items": [
102
+ {
103
+ "description": "Replaces `releasePrepare`, `releasePrepareMono`, and `releasePrepareProject` with `releasePrepareTargets`, which prepares a release from one list of package and project targets resolved from any config shape. (#1026)",
104
+ "hash": "7211951adb8e3e7b05a35196096d67bc33890801",
105
+ "entry": 1
106
+ }
107
+ ]
108
+ },
109
+ {
110
+ "title": "📚 Documentation",
111
+ "audience": "dev",
112
+ "items": [
113
+ {
114
+ "description": "Rewrites `docs/api.md` to describe what the `.` and `./config` entries export, and drops its sections on `deriveWorkspaceConfig`, the script-based approach, and `resolveReleaseTags`, none of which the package exports. (#1026)",
115
+ "hash": "7211951adb8e3e7b05a35196096d67bc33890801",
116
+ "entry": 3
117
+ },
118
+ {
119
+ "description": "Documents in `docs/project-releases.md` the tag pattern that a scaffolded `create-github-release.yaml` needs in order to trigger on a project tag, because its default `'*-v[0-9]*.[0-9]*.[0-9]*'` filter does not match a `v<version>` tag. (#1027)",
120
+ "hash": "ffab419c87725051d04a1d817fc4be8b7222694b",
121
+ "entry": 4
122
+ }
123
+ ]
124
+ }
125
+ ]
126
+ },
127
+ {
128
+ "version": "14.1.0",
129
+ "date": "2026-10-11",
130
+ "sections": [
131
+ {
132
+ "title": "🎉 Features",
133
+ "audience": "all",
134
+ "items": [
135
+ {
136
+ "description": "Stops `release-kit init` from scaffolding `publish.yaml` and `create-github-release.yaml` when every package is private, even with `--force`, and removes the provenance and trusted-publisher steps from the next steps that it prints. (#995)",
137
+ "hash": "93355490219535fac282ab2256179094ca920e47",
138
+ "entry": 1
139
+ },
140
+ {
141
+ "description": "Stops `release-kit update-templates` from re-creating a deleted `publish.yaml` or `create-github-release.yaml` when every package is private, while it still refreshes either workflow that exists. (#995)",
142
+ "hash": "93355490219535fac282ab2256179094ca920e47",
143
+ "entry": 2
144
+ },
145
+ {
146
+ "description": "Makes `release-kit prepare` refuse, before it writes any file and in a dry run too, to release a publishable package that npm does not list or that it cannot look up, and prints `npm publish --access public` as the fix for each unlisted package. (#996)",
147
+ "hash": "7b8b6d67b264af56dff761d0bf987eca8bc2f934",
148
+ "entry": 1
149
+ }
150
+ ]
151
+ },
152
+ {
153
+ "title": "🐛 Bug fixes",
154
+ "audience": "all",
155
+ "items": [
156
+ {
157
+ "description": "Fixes the issue that the `npm-auto-publish` kit's \"published to npm\" check reported a failed lookup, such as an unreachable registry or an authentication error, as an unpublished package and ignored `publishConfig.registry`. (#996)",
158
+ "hash": "7b8b6d67b264af56dff761d0bf987eca8bc2f934",
159
+ "entry": 2
160
+ },
161
+ {
162
+ "description": "Fixes the issue that release-kit's changelog generation treated a floating major tag such as `v1` as a release and added a spurious release to the history, by counting a tag only when the rest of its name after the prefix is a complete SemVer version such as `1.0.0` or `1.0.0-beta.1`. (#1000)",
163
+ "hash": "712e7500109b4bb3db579b60c72e4bf85b308098",
164
+ "entry": 1
165
+ },
166
+ {
167
+ "description": "Fixes the issue that `release-kit show-tag-prefixes` counted every tag that starts with a workspace's prefix, including floating pointers such as `core-v1` and non-release tags such as `core-vnext`, rather than only the tags that release-kit counts as releases. (#1000)",
168
+ "hash": "712e7500109b4bb3db579b60c72e4bf85b308098",
169
+ "entry": 2
170
+ }
171
+ ]
172
+ }
173
+ ]
174
+ },
2
175
  {
3
176
  "version": "14.0.0",
4
177
  "date": "2026-10-08",
@@ -16,6 +16,9 @@ import {
16
16
  readFile
17
17
  } from "readyup/check-utils";
18
18
 
19
+ // src/changelogArchive.ts
20
+ var CHANGELOG_ARCHIVE_DIRECTORY = "changelogs";
21
+
19
22
  // src/init/detectRepoType.ts
20
23
  import { existsSync, readFileSync } from "node:fs";
21
24
 
@@ -98,7 +101,7 @@ function translateGlob(pattern) {
98
101
 
99
102
  // .readyup/kits/default.ts
100
103
  function getMinVersion() {
101
- const picked = { "version": "14.0.0" };
104
+ const picked = { "version": "14.2.0" };
102
105
  if (typeof picked["version"] !== "string") {
103
106
  throw new TypeError("release-kit/package.json: 'version' must be a string");
104
107
  }
@@ -114,9 +117,9 @@ var CONFIG_EXPORT_PATTERNS = [
114
117
  /export\s*\{[^}]*\b(?:config|default)\b[^}]*\}/,
115
118
  /export\s*\*/
116
119
  ];
117
- var COMMON_PRESET_HASH = "618bb5cbcbdd4f4f0c2a777add7e14d992d48483350dd9651bf8cf4c1fa9d418";
118
- var SYNC_LABELS_WORKFLOW_HASH = "6b50015e2919ffa4b16d0b4a1f340e585ff3e5b779a2256010d3c62fa07ff8f2";
119
- var RELEASE_WORKFLOW_HASH_MONOREPO = "4a20ff3315155500f64a27133c54eb1bc1f07a4acc370ec0d83176109041c60d";
120
+ var COMMON_PRESET_HASH = "1d3e2df1ccd7deea4707fa07a4ae171d761344b65c1a311467fc0f66b70ee646";
121
+ var SYNC_LABELS_WORKFLOW_HASH = "0a88829d154c0de7cdd4246fede99e9311a89ab4199034dbe601663d641ab1e4";
122
+ var RELEASE_WORKFLOW_HASH_MONOREPO = "f63f4cabb57f94ee6550f17a9874c7a346d46554595ddbcd46fdd3208d613437";
120
123
  var RELEASE_WORKFLOW_HASH_SINGLE = "d26dd4ca186632b388ea46bd885721124c03d19918e511baa40821d2d760da69";
121
124
  var PUBLISH_WORKFLOW_HASH_MONOREPO = "0afa9ffe914f3dc8f043e68252ebc604c8cc1a953422fcea37a909a4def370ee";
122
125
  var PUBLISH_WORKFLOW_HASH_SINGLE = "6f31183e0a1e66be791a19266c3b028dadbd9fe010f7fc4452f3f8970c937b43";
@@ -257,6 +260,13 @@ var default_default = defineRdyKit({
257
260
  severity: "warn",
258
261
  check: () => labelsHaveCurrentPresetHash("common", COMMON_PRESET_HASH),
259
262
  fix: "Run `release-kit sync-labels generate` to incorporate updated common labels"
263
+ },
264
+ {
265
+ name: "scope labels in .github/labels.yaml have no description",
266
+ severity: "warn",
267
+ quiet: true,
268
+ check: () => scopeLabelsHaveNoDescriptions(),
269
+ fix: "Remove `description` from these scope labels in the repoLabels block of .config/release-kit.config.ts, so that GitHub groups them compactly, then run `release-kit sync-labels generate`"
260
270
  }
261
271
  ]
262
272
  }
@@ -272,6 +282,16 @@ var default_default = defineRdyKit({
272
282
  check: () => packagesShipChangelog(),
273
283
  fix: 'Add "CHANGELOG.md" to the files field of each affected package.json'
274
284
  },
285
+ {
286
+ name: "published packages ship the changelog archives",
287
+ severity: "warn",
288
+ skip: () => {
289
+ if (!hasPublishablePackages()) return "no publishable packages";
290
+ return changelogArchivesAreEnabled() ? false : "changelogMarkdown.keepVersions is not set";
291
+ },
292
+ check: () => packagesShipChangelogArchives(),
293
+ fix: 'Add "changelogs/" to the files field of each affected package.json, so that the links from CHANGELOG.md to its archive files resolve in the published package'
294
+ },
275
295
  {
276
296
  name: "changelog.json generation is enabled",
277
297
  severity: "recommend",
@@ -335,6 +355,11 @@ var default_default = defineRdyKit({
335
355
  }
336
356
  ]
337
357
  });
358
+ function changelogArchivesAreEnabled() {
359
+ const content = readFile(".config/release-kit.config.ts");
360
+ if (content === void 0) return false;
361
+ return /changelogMarkdown\s*:\s*\{[^}]*keepVersions\s*:/.test(content);
362
+ }
338
363
  function changelogJsonIsEnabled() {
339
364
  const content = readFile(".config/release-kit.config.ts");
340
365
  if (content === void 0) return true;
@@ -355,6 +380,9 @@ function labelsHaveCurrentPresetHash(presetName, expectedHash) {
355
380
  function packagesShipChangelog() {
356
381
  return reportWorkspacesOmitting("CHANGELOG.md");
357
382
  }
383
+ function packagesShipChangelogArchives() {
384
+ return reportWorkspacesOmitting(`${CHANGELOG_ARCHIVE_DIRECTORY}/CHANGELOG-v1.md`, `${CHANGELOG_ARCHIVE_DIRECTORY}/`);
385
+ }
358
386
  function packagesShipChangelogJson() {
359
387
  return reportWorkspacesOmitting(resolveChangelogJsonOutputPath());
360
388
  }
@@ -390,7 +418,7 @@ function releaseNotesInjectsIntoReadme() {
390
418
  if (content === void 0) return false;
391
419
  return /shouldInjectIntoReadme\s*:\s*true/.test(content);
392
420
  }
393
- function reportWorkspacesOmitting(path) {
421
+ function reportWorkspacesOmitting(path, label = path) {
394
422
  const failing = [];
395
423
  const packageWorkspaces = discoverWorkspaces({ filter: (w) => w.isPackage });
396
424
  for (const { dir, packageJson } of packageWorkspaces) {
@@ -401,7 +429,7 @@ function reportWorkspacesOmitting(path) {
401
429
  if (failing.length === 0) return true;
402
430
  return {
403
431
  ok: false,
404
- detail: `files field omits ${path}: ${failing.join(", ")}`
432
+ detail: `files field omits ${label}: ${failing.join(", ")}`
405
433
  };
406
434
  }
407
435
  function resolveChangelogJsonOutputPath() {
@@ -410,6 +438,32 @@ function resolveChangelogJsonOutputPath() {
410
438
  const match = /changelogJson\s*:\s*\{[^}]*outputPath\s*:\s*['"]([^'"]+)['"]/.exec(content);
411
439
  return match?.[1] ?? DEFAULT_CHANGELOG_JSON_PATH;
412
440
  }
441
+ function scopeLabelsHaveNoDescriptions() {
442
+ const content = readFile(".github/labels.yaml");
443
+ if (content === void 0) return true;
444
+ const described = [];
445
+ let currentName;
446
+ for (const line of content.split("\n")) {
447
+ const nameMatch = /^- name:\s*(.*?)\s*$/.exec(line);
448
+ if (nameMatch) {
449
+ currentName = stripYamlQuotes(nameMatch[1] ?? "");
450
+ continue;
451
+ }
452
+ const descriptionMatch = /^\s+description:\s*(.*?)\s*$/.exec(line);
453
+ if (descriptionMatch && currentName?.startsWith("scope:")) {
454
+ if (stripYamlQuotes(descriptionMatch[1] ?? "") !== "") described.push(currentName);
455
+ }
456
+ }
457
+ if (described.length === 0) return true;
458
+ return {
459
+ ok: false,
460
+ detail: `scope labels with a description: ${described.join(", ")}`
461
+ };
462
+ }
463
+ function stripYamlQuotes(value) {
464
+ const match = /^(['"])(.*)\1$/.exec(value);
465
+ return match?.[2] ?? value;
466
+ }
413
467
  export {
414
468
  COMMON_PRESET_HASH,
415
469
  CREATE_GITHUB_RELEASE_WORKFLOW_HASH_MONOREPO,
@@ -420,12 +474,15 @@ export {
420
474
  RELEASE_WORKFLOW_HASH_MONOREPO,
421
475
  RELEASE_WORKFLOW_HASH_SINGLE,
422
476
  SYNC_LABELS_WORKFLOW_HASH,
477
+ changelogArchivesAreEnabled,
423
478
  changelogJsonIsEnabled,
424
479
  configFileExportsConfig,
425
480
  default_default as default,
426
481
  packagesShipChangelog,
482
+ packagesShipChangelogArchives,
427
483
  packagesShipChangelogJson,
428
484
  readmeHasReleaseNotesMarkers,
429
485
  readmesHaveReleaseNotesMarkers,
430
- resolveChangelogJsonOutputPath
486
+ resolveChangelogJsonOutputPath,
487
+ scopeLabelsHaveNoDescriptions
431
488
  };
@@ -13,7 +13,7 @@ import {
13
13
  fileContains,
14
14
  fileExists,
15
15
  getJsonValue,
16
- isRecord,
16
+ isRecord as isRecord2,
17
17
  readFile,
18
18
  readJsonFile
19
19
  } from "readyup/check-utils";
@@ -23,10 +23,16 @@ function hasProvenance(workflowContent) {
23
23
  return /^[^#]*provenance:\s*['"]?true['"]?/im.test(workflowContent);
24
24
  }
25
25
 
26
- // .readyup/kits/npm-auto-publish.ts
26
+ // src/npmRegistry.ts
27
+ import { execFileSync } from "node:child_process";
28
+
29
+ // src/typeGuards.ts
30
+ function isRecord(value) {
31
+ return typeof value === "object" && value !== null && !Array.isArray(value);
32
+ }
33
+
34
+ // src/npmRegistry.ts
27
35
  var AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["E401", "ENEEDAUTH"]);
28
- var OTP_ERROR_CODE = "EOTP";
29
- var PUBLISH_WORKFLOW_FILE = "publish.yaml";
30
36
  var UNREACHABLE_ERROR_CODES = /* @__PURE__ */ new Set([
31
37
  "EAI_AGAIN",
32
38
  "ECONNREFUSED",
@@ -36,6 +42,55 @@ var UNREACHABLE_ERROR_CODES = /* @__PURE__ */ new Set([
36
42
  "ERR_SOCKET_TIMEOUT",
37
43
  "ETIMEDOUT"
38
44
  ]);
45
+ function classifyNpmPackageLookup(result) {
46
+ if (result.exitOk) {
47
+ return { status: "published" };
48
+ }
49
+ const error = readNpmError(result.stdout);
50
+ if (error === void 0) {
51
+ return { status: "unverifiable", detail: "The npm registry query failed without a readable error payload" };
52
+ }
53
+ if (error.code === "E404") {
54
+ return { status: "unpublished" };
55
+ }
56
+ if (AUTH_ERROR_CODES.has(error.code)) {
57
+ return { status: "unverifiable", detail: `The npm registry rejected the session (${error.code})` };
58
+ }
59
+ if (UNREACHABLE_ERROR_CODES.has(error.code)) {
60
+ return { status: "unverifiable", detail: `Cannot reach the npm registry (${error.code})` };
61
+ }
62
+ return { status: "unverifiable", detail: `The npm registry query failed (${error.code}): ${error.summary}` };
63
+ }
64
+ function lookUpNpmPackage(packageName, registry) {
65
+ const args = ["view", packageName, "version", "--json", ...registry === void 0 ? [] : ["--registry", registry]];
66
+ return classifyNpmPackageLookup(runNpmJson(args));
67
+ }
68
+ function readNpmError(stdout) {
69
+ let parsed;
70
+ try {
71
+ parsed = JSON.parse(stdout);
72
+ } catch {
73
+ return void 0;
74
+ }
75
+ if (!isRecord(parsed) || !isRecord(parsed["error"]) || typeof parsed["error"]["code"] !== "string") {
76
+ return void 0;
77
+ }
78
+ const { code, summary } = parsed["error"];
79
+ return { code, summary: typeof summary === "string" ? summary : "" };
80
+ }
81
+ function runNpmJson(args) {
82
+ try {
83
+ return { exitOk: true, stdout: execFileSync("npm", args, { encoding: "utf8", stdio: "pipe" }) };
84
+ } catch (error) {
85
+ const stdout = isRecord(error) && typeof error["stdout"] === "string" ? error["stdout"] : "";
86
+ return { exitOk: false, stdout };
87
+ }
88
+ }
89
+
90
+ // .readyup/kits/npm-auto-publish.ts
91
+ var OTP_ERROR_CODE = "EOTP";
92
+ var PUBLISH_WORKFLOW_FILE = "publish.yaml";
93
+ var NO_GITHUB_REMOTE_FIX = "Add an origin remote that points to the GitHub repo, then run this check again";
39
94
  var repoChecklist = defineRdyChecklist({
40
95
  name: "repo",
41
96
  checks: [
@@ -149,7 +204,7 @@ function buildWorkspaceCheck(workspace) {
149
204
  const auth = getCachedNpmAuthStatus();
150
205
  return auth.status === "unreachable" ? auth.detail : false;
151
206
  },
152
- check: () => isPublishedToNpm(displayName),
207
+ check: () => describeNpmPackageLookup(lookUpNpmPackage(displayName, readPublishRegistry(workspace))),
153
208
  fix: `Run "npm publish --access public" from ${workspace.dir} to bootstrap the package on npm`,
154
209
  checks: [
155
210
  {
@@ -158,10 +213,8 @@ function buildWorkspaceCheck(workspace) {
158
213
  const capability = getCachedTrustCapability();
159
214
  return capability.ok ? false : capability.detail;
160
215
  },
161
- check: () => checkTrustedPublisher(displayName),
162
- get fix() {
163
- return `Run: npm trust github ${displayName} --repo ${getCachedOwnerRepo()} --file ${PUBLISH_WORKFLOW_FILE}`;
164
- }
216
+ check: () => describeTrustedPublisher(getTrustQueryResult(displayName), displayName, getCachedOwnerRepo()),
217
+ fix: formatTrustCommand(displayName, "<owner>/<repo>")
165
218
  }
166
219
  ]
167
220
  },
@@ -182,25 +235,18 @@ function buildWorkspaceCheck(workspace) {
182
235
  function checkProvenanceMatchesVisibility() {
183
236
  const workflowPath = ".github/workflows/publish.yaml";
184
237
  const content = readFile(workflowPath);
238
+ const ownerRepo = getCachedOwnerRepo();
239
+ if (!ownerRepo.ok) {
240
+ return classifyProvenanceSetting(content, ownerRepo);
241
+ }
185
242
  let visibility;
186
243
  try {
187
- visibility = isRepoPrivate() ? "private" : "public";
244
+ visibility = isRepoPrivate(ownerRepo.value) ? "private" : "public";
188
245
  } catch {
189
246
  visibility = "unknown";
190
247
  }
191
248
  return classifyProvenanceSetting(content, visibility);
192
249
  }
193
- function checkTrustedPublisher(packageName) {
194
- const result = getTrustQueryResult(packageName);
195
- switch (result.status) {
196
- case "configured":
197
- return { ok: true };
198
- case "not-configured":
199
- return { ok: false, detail: "The npm registry does not list any trusted publisher for this package" };
200
- default:
201
- return { ok: false, detail: result.detail };
202
- }
203
- }
204
250
  function classifyNpmAuth(result) {
205
251
  if (result.exitOk) {
206
252
  return { status: "authenticated" };
@@ -225,6 +271,9 @@ function classifyProvenanceSetting(workflowContent, visibility) {
225
271
  fix: "Check the file's permissions"
226
272
  };
227
273
  }
274
+ if (typeof visibility !== "string") {
275
+ return { ok: false, detail: visibility.detail, fix: NO_GITHUB_REMOTE_FIX };
276
+ }
228
277
  if (visibility === "unknown") {
229
278
  return {
230
279
  ok: false,
@@ -282,14 +331,40 @@ function classifyTrustQuery(result, expectedRepo, expectedFile) {
282
331
  if (relationships.length === 0) {
283
332
  return { status: "not-configured" };
284
333
  }
334
+ if (!expectedRepo.ok) {
335
+ return {
336
+ status: "error",
337
+ detail: `Cannot compare the trusted publisher with this repo; found ${describeTrustRelationships(relationships)}`
338
+ };
339
+ }
285
340
  const isMatch = relationships.some(
286
- (relationship) => relationship.type === "github" && relationship.repository === expectedRepo && relationship.file === expectedFile
341
+ (relationship) => relationship.type === "github" && relationship.repository === expectedRepo.value && relationship.file === expectedFile
287
342
  );
288
343
  return isMatch ? { status: "configured" } : {
289
344
  status: "mismatched",
290
- detail: `Expected github ${expectedRepo} (${expectedFile}); found ${describeTrustRelationships(relationships)}`
345
+ detail: `Expected github ${expectedRepo.value} (${expectedFile}); found ${describeTrustRelationships(relationships)}`
346
+ };
347
+ }
348
+ function describeNpmPackageLookup(lookup) {
349
+ if (lookup.status === "published") {
350
+ return { ok: true };
351
+ }
352
+ if (lookup.status === "unpublished") {
353
+ return { ok: false, detail: "The npm registry does not list this package" };
354
+ }
355
+ return {
356
+ ok: false,
357
+ detail: lookup.detail,
358
+ fix: "Restore access to the npm registry, then run this check again"
291
359
  };
292
360
  }
361
+ function describeTrustedPublisher(result, packageName, ownerRepo) {
362
+ if (result.status === "configured") {
363
+ return { ok: true };
364
+ }
365
+ const detail = result.status === "not-configured" ? "The npm registry does not list any trusted publisher for this package" : result.detail;
366
+ return ownerRepo.ok ? { ok: false, detail, fix: formatTrustCommand(packageName, ownerRepo.value) } : { ok: false, detail: `${detail}. ${ownerRepo.detail}`, fix: NO_GITHUB_REMOTE_FIX };
367
+ }
293
368
  function describeTrustRelationships(relationships) {
294
369
  return relationships.map((relationship) => {
295
370
  const type = typeof relationship.type === "string" ? relationship.type : "(unknown)";
@@ -298,32 +373,21 @@ function describeTrustRelationships(relationships) {
298
373
  return `${type} ${repository} (${file})`;
299
374
  }).join(", ");
300
375
  }
376
+ function formatTrustCommand(packageName, ownerRepo) {
377
+ return `Run: npm trust github ${packageName} --repo ${ownerRepo} --file ${PUBLISH_WORKFLOW_FILE}`;
378
+ }
301
379
  var getCachedNpmAuthStatus = /* @__PURE__ */ (() => {
302
380
  let cached;
303
- return () => cached ??= classifyNpmAuth(runNpmJson("npm whoami --json"));
381
+ return () => cached ??= classifyNpmAuth(runNpmJson(["whoami", "--json"]));
304
382
  })();
305
383
  var getCachedOwnerRepo = /* @__PURE__ */ (() => {
306
384
  let cached;
307
- return () => cached ??= getOwnerRepo();
385
+ return () => cached ??= resolveOwnerRepo();
308
386
  })();
309
387
  var getCachedTrustCapability = /* @__PURE__ */ (() => {
310
388
  let cached;
311
389
  return () => cached ??= resolveTrustCapability();
312
390
  })();
313
- function getOwnerRepo() {
314
- const url = execSync("git remote get-url origin", {
315
- encoding: "utf8"
316
- }).trim();
317
- const sshMatch = url.match(/git@github\.com:(.+?)(?:\.git)?$/);
318
- if (sshMatch?.[1]) {
319
- return sshMatch[1];
320
- }
321
- const httpsMatch = url.match(/github\.com\/(.+?)(?:\.git)?$/);
322
- if (httpsMatch?.[1]) {
323
- return httpsMatch[1];
324
- }
325
- throw new Error(`Cannot parse GitHub owner/repo from remote URL: ${url}`);
326
- }
327
391
  var trustQueryResults = /* @__PURE__ */ new Map();
328
392
  function getTrustQueryResult(packageName) {
329
393
  const cached = trustQueryResults.get(packageName);
@@ -331,7 +395,7 @@ function getTrustQueryResult(packageName) {
331
395
  return cached;
332
396
  }
333
397
  const result = classifyTrustQuery(
334
- runNpmJson(`npm trust list ${packageName} --json`),
398
+ runNpmJson(["trust", "list", packageName, "--json"]),
335
399
  getCachedOwnerRepo(),
336
400
  PUBLISH_WORKFLOW_FILE
337
401
  );
@@ -352,40 +416,24 @@ function hasTokenReferences() {
352
416
  }
353
417
  return false;
354
418
  }
355
- function isPublishedToNpm(packageName) {
356
- try {
357
- execSync(`npm view ${packageName} version`, {
358
- encoding: "utf8",
359
- stdio: "pipe"
360
- });
361
- return true;
362
- } catch {
363
- return false;
364
- }
365
- }
366
- function isRepoPrivate() {
367
- const ownerRepo = getOwnerRepo();
419
+ function isRepoPrivate(ownerRepo) {
368
420
  const result = execSync(`gh api repos/${ownerRepo} --jq .private`, {
369
421
  encoding: "utf8"
370
422
  }).trim();
371
423
  return result === "true";
372
424
  }
425
+ function parseGitHubOwnerRepo(url) {
426
+ const match = /git@github\.com:(.+?)(?:\.git)?$/.exec(url) ?? /github\.com\/(.+?)(?:\.git)?$/.exec(url);
427
+ const ownerRepo = match?.[1];
428
+ return ownerRepo === void 0 ? { ok: false, detail: `The origin remote does not point to GitHub: ${url}` } : { ok: true, value: ownerRepo };
429
+ }
373
430
  function probeTrustQuery() {
374
431
  const probeName = selectProbeName();
375
432
  return probeName === void 0 ? void 0 : getTrustQueryResult(probeName);
376
433
  }
377
- function readNpmError(stdout) {
378
- let parsed;
379
- try {
380
- parsed = JSON.parse(stdout);
381
- } catch {
382
- return void 0;
383
- }
384
- if (!isRecord(parsed) || !isRecord(parsed["error"]) || typeof parsed["error"]["code"] !== "string") {
385
- return void 0;
386
- }
387
- const { code, summary } = parsed["error"];
388
- return { code, summary: typeof summary === "string" ? summary : "" };
434
+ function readPublishRegistry(workspace) {
435
+ const registry = getJsonValue(workspace.packageJson, "publishConfig", "registry");
436
+ return typeof registry === "string" ? registry : void 0;
389
437
  }
390
438
  function readTrustRelationships(stdout) {
391
439
  let parsed;
@@ -395,25 +443,26 @@ function readTrustRelationships(stdout) {
395
443
  return void 0;
396
444
  }
397
445
  if (Array.isArray(parsed)) {
398
- return parsed.filter((entry) => isRecord(entry) && "type" in entry);
446
+ return parsed.filter((entry) => isRecord2(entry) && "type" in entry);
399
447
  }
400
- if (!isRecord(parsed)) {
448
+ if (!isRecord2(parsed)) {
401
449
  return void 0;
402
450
  }
403
451
  return "type" in parsed ? [parsed] : [];
404
452
  }
453
+ function resolveOwnerRepo() {
454
+ let url;
455
+ try {
456
+ url = execSync("git remote get-url origin", { encoding: "utf8", stdio: "pipe" }).trim();
457
+ } catch {
458
+ return { ok: false, detail: "The repo does not have a readable origin remote" };
459
+ }
460
+ return parseGitHubOwnerRepo(url);
461
+ }
405
462
  function resolveTrustCapability() {
406
463
  const auth = getCachedNpmAuthStatus();
407
464
  return classifyTrustCapability(auth, auth.status === "authenticated" ? probeTrustQuery() : void 0);
408
465
  }
409
- function runNpmJson(command) {
410
- try {
411
- return { exitOk: true, stdout: execSync(command, { encoding: "utf8", stdio: "pipe" }) };
412
- } catch (error) {
413
- const stdout = isRecord(error) && typeof error["stdout"] === "string" ? error["stdout"] : "";
414
- return { exitOk: false, stdout };
415
- }
416
- }
417
466
  function selectProbeName() {
418
467
  return discoverWorkspaces({ filter: (workspace) => workspace.isPackage }).find(
419
468
  (workspace) => workspace.name !== void 0
@@ -433,7 +482,10 @@ export {
433
482
  classifyTrustCapability,
434
483
  classifyTrustQuery,
435
484
  npm_auto_publish_default as default,
485
+ describeNpmPackageLookup,
486
+ describeTrustedPublisher,
436
487
  packagesChecklist,
488
+ parseGitHubOwnerRepo,
437
489
  selectProbeName,
438
490
  skipIfNotPublishable,
439
491
  skipIfNothingPublishable