@williamthorsen/release-kit 10.2.1 → 10.3.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 (63) hide show
  1. package/.readyup/kits/default.js +78 -55
  2. package/.readyup/kits/npm-auto-publish.js +169 -57
  3. package/.readyup/manifest.json +6 -6
  4. package/CHANGELOG.md +54 -0
  5. package/README.md +18 -6
  6. package/bin/release-kit.js +1 -1
  7. package/dist/esm/bin/release-kit.js +4 -4
  8. package/dist/esm/buildChangelogEntries.js +17 -15
  9. package/dist/esm/buildDependencyGraph.js +6 -2
  10. package/dist/esm/buildReleaseSummary.d.ts +1 -1
  11. package/dist/esm/changelogJsonFile.d.ts +1 -3
  12. package/dist/esm/changelogJsonFile.js +4 -18
  13. package/dist/esm/changelogJsonUtils.js +7 -7
  14. package/dist/esm/changelogOverrides.js +13 -12
  15. package/dist/esm/checkWorkTypesDrift.js +1 -1
  16. package/dist/esm/commitCommand.js +3 -13
  17. package/dist/esm/createTags.js +3 -14
  18. package/dist/esm/deriveWorkspaceConfig.js +3 -3
  19. package/dist/esm/detectPackageManager.js +2 -2
  20. package/dist/esm/detectUndeclaredTagPrefixes.js +1 -1
  21. package/dist/esm/discoverWorkspaces.js +1 -1
  22. package/dist/esm/getCommitsSinceTarget.js +2 -2
  23. package/dist/esm/help/prepareHelp.d.ts +1 -1
  24. package/dist/esm/help/prepareHelp.js +2 -2
  25. package/dist/esm/init/checks.js +1 -1
  26. package/dist/esm/init/detectRepoType.js +1 -1
  27. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +2 -0
  28. package/dist/esm/injectReleaseNotesIntoReadme.js +13 -10
  29. package/dist/esm/loadConfig.js +8 -6
  30. package/dist/esm/planReleaseNotesPreviews.d.ts +13 -0
  31. package/dist/esm/planReleaseNotesPreviews.js +43 -0
  32. package/dist/esm/planVersionBump.d.ts +9 -0
  33. package/dist/esm/planVersionBump.js +54 -0
  34. package/dist/esm/prepareCommand.d.ts +0 -4
  35. package/dist/esm/prepareCommand.js +51 -45
  36. package/dist/esm/propagateBumps.js +1 -1
  37. package/dist/esm/releaseFiles.d.ts +4 -0
  38. package/dist/esm/releaseFiles.js +34 -0
  39. package/dist/esm/releasePlan.d.ts +10 -0
  40. package/dist/esm/releasePlan.js +53 -0
  41. package/dist/esm/releasePrepare.d.ts +3 -3
  42. package/dist/esm/releasePrepare.js +53 -54
  43. package/dist/esm/releasePrepareMono.d.ts +3 -2
  44. package/dist/esm/releasePrepareMono.js +50 -53
  45. package/dist/esm/releasePrepareProject.d.ts +3 -0
  46. package/dist/esm/releasePrepareProject.js +33 -37
  47. package/dist/esm/renderChangelogMarkdown.d.ts +0 -7
  48. package/dist/esm/renderChangelogMarkdown.js +0 -13
  49. package/dist/esm/reportPrepare.d.ts +5 -1
  50. package/dist/esm/reportPrepare.js +37 -17
  51. package/dist/esm/sync-labels/generateCommand.js +4 -2
  52. package/dist/esm/sync-labels/presets.js +5 -5
  53. package/dist/esm/sync-labels/resolveLabels.js +5 -3
  54. package/dist/esm/syncWorkTypes.js +1 -1
  55. package/dist/esm/types.d.ts +4 -9
  56. package/dist/esm/validateConfig.js +11 -11
  57. package/dist/esm/workTypesData.d.ts +1 -1
  58. package/dist/esm/workTypesUtils.js +1 -1
  59. package/package.json +3 -3
  60. package/dist/esm/bumpAllVersions.d.ts +0 -3
  61. package/dist/esm/bumpAllVersions.js +0 -61
  62. package/dist/esm/writeReleaseNotesPreviews.d.ts +0 -18
  63. package/dist/esm/writeReleaseNotesPreviews.js +0 -63
@@ -1,6 +1,6 @@
1
1
  /** @noformat — @generated. Do not edit. Compiled by rdy. */
2
2
  /* eslint-disable */
3
- export const __readyupVersion = "0.23.0";
3
+ export const __readyupVersion = "0.24.0";
4
4
 
5
5
 
6
6
  // .readyup/kits/default.ts
@@ -43,7 +43,7 @@ function detectRepoType() {
43
43
  if (existsSync("package.json")) {
44
44
  const raw = readFileSync("package.json", "utf8");
45
45
  const pkg = parseJsonRecord(raw);
46
- if (pkg !== void 0 && Array.isArray(pkg.workspaces)) {
46
+ if (pkg !== void 0 && Array.isArray(pkg["workspaces"])) {
47
47
  return "monorepo";
48
48
  }
49
49
  }
@@ -52,15 +52,21 @@ function detectRepoType() {
52
52
 
53
53
  // .readyup/kits/default.ts
54
54
  function getMinVersion() {
55
- const picked = { "version": "10.2.1" };
56
- if (typeof picked.version !== "string") {
55
+ const picked = { "version": "10.3.0" };
56
+ if (typeof picked["version"] !== "string") {
57
57
  throw new TypeError("release-kit/package.json: 'version' must be a string");
58
58
  }
59
- return picked.version;
59
+ return picked["version"];
60
60
  }
61
61
  function hasPublishablePackages() {
62
62
  return discoverWorkspaces({ filter: (w) => w.isPackage }).length > 0;
63
63
  }
64
+ var CONFIG_EXPORT_PATTERNS = [
65
+ /export\s+default\b/,
66
+ /export\s+(?:const|let|var)\s+config\b/,
67
+ /export\s*\{[^}]*\b(?:config|default)\b[^}]*\}/,
68
+ /export\s*\*/
69
+ ];
64
70
  var CLIFF_TEMPLATE_HASH = "93b72e0b1393cd6b1fe8e2a0e303cd326fd323435951b0493396b305af32d2ec";
65
71
  var COMMON_PRESET_HASH = "86f9e1db9000793a91168e8c6b5695311a422ee208121324549c068fe67fa184";
66
72
  var SYNC_LABELS_WORKFLOW_HASH = "d6e2403fb551d2d415f679125989c92760444eec887644565b2e05c9bf8f4c1e";
@@ -111,6 +117,7 @@ var default_default = defineRdyKit({
111
117
  {
112
118
  name: "release.yaml does not reference deprecated tag ref",
113
119
  severity: "error",
120
+ quiet: true,
114
121
  check: () => fileDoesNotContain(".github/workflows/release.yaml", /@(release|publish)-workflow-v[0-9]/),
115
122
  fix: "Update release.yaml to use @workflow/release-v1 (run `release-kit init --force` to regenerate, or replace the ref manually)"
116
123
  }
@@ -135,50 +142,79 @@ var default_default = defineRdyKit({
135
142
  {
136
143
  name: "publish.yaml does not reference deprecated tag ref",
137
144
  severity: "error",
145
+ quiet: true,
138
146
  check: () => fileDoesNotContain(".github/workflows/publish.yaml", /@(release|publish)-workflow-v[0-9]/),
139
147
  fix: "Update publish.yaml to use @workflow/publish-v1 (run `release-kit init --force` to regenerate, or replace the ref manually)"
140
148
  }
141
149
  ]
142
150
  },
143
151
  {
144
- name: "releaseNotes config is consistent with changelogJson",
145
- severity: "warn",
146
- skip: () => !fileExists(".config/release-kit.config.ts") ? "no release-kit config file" : false,
147
- check: () => releaseNotesConfigIsConsistent(),
148
- fix: "Either enable changelogJson.enabled or disable releaseNotes.shouldInjectIntoReadme"
149
- },
150
- {
151
- name: "config does not use removed releaseNotes.shouldCreateGithubRelease",
152
+ name: ".config/release-kit.config.ts exports a config",
152
153
  severity: "error",
153
154
  skip: () => !fileExists(".config/release-kit.config.ts") ? "no release-kit config file" : false,
154
- check: () => fileDoesNotContain(".config/release-kit.config.ts", /shouldCreateGithubRelease/),
155
- fix: "Remove 'shouldCreateGithubRelease' from .config/release-kit.config.ts. Adoption of GitHub Releases is now signaled by installing the create-github-release workflow (see release-kit README for setup)."
156
- },
157
- {
158
- name: ".config/release-kit.config.ts uses defineConfig",
159
- severity: "recommend",
160
- skip: () => !fileExists(".config/release-kit.config.ts") ? "no release-kit config file" : false,
161
- check: () => fileContains(".config/release-kit.config.ts", /defineConfig/),
162
- fix: "Wrap your config export with defineConfig() from @williamthorsen/release-kit/config for type safety"
163
- },
164
- {
165
- name: "releaseNotes.shouldInjectIntoReadme is true",
166
- severity: "warn",
167
- skip: () => !fileExists(".config/release-kit.config.ts") ? "no release-kit config file" : false,
168
- check: () => releaseNotesInjectsIntoReadme(),
169
- fix: "Set releaseNotes.shouldInjectIntoReadme to true in .config/release-kit.config.ts",
155
+ check: () => configFileExportsConfig(),
156
+ fix: "Export the config from .config/release-kit.config.ts as a default export or as a named `config` export; release-kit resolves no other export",
170
157
  checks: [
171
158
  {
172
- name: "README contains release-notes section markers",
159
+ name: "releaseNotes config is consistent with changelogJson",
160
+ severity: "warn",
161
+ check: () => releaseNotesConfigIsConsistent(),
162
+ fix: "Either enable changelogJson.enabled or disable releaseNotes.shouldInjectIntoReadme"
163
+ },
164
+ {
165
+ name: ".config/release-kit.config.ts uses defineConfig",
166
+ severity: "recommend",
167
+ check: () => fileContains(".config/release-kit.config.ts", /defineConfig/),
168
+ fix: "Wrap your config export with defineConfig() from @williamthorsen/release-kit/config for type safety"
169
+ },
170
+ {
171
+ name: "releaseNotes.shouldInjectIntoReadme is true",
172
+ severity: "warn",
173
+ check: () => releaseNotesInjectsIntoReadme(),
174
+ fix: "Set releaseNotes.shouldInjectIntoReadme to true in .config/release-kit.config.ts",
175
+ checks: [
176
+ {
177
+ name: "README contains release-notes section markers",
178
+ severity: "warn",
179
+ check: readmesHaveReleaseNotesMarkers,
180
+ fix: "Add `<!-- section:release-notes -->` and `<!-- /section:release-notes -->` markers to each affected README"
181
+ }
182
+ ]
183
+ },
184
+ {
185
+ name: "repoLabels block declared in .config/release-kit.config.ts",
186
+ severity: "recommend",
187
+ check: () => fileContains(".config/release-kit.config.ts", /repoLabels/),
188
+ fix: "Run `release-kit sync-labels init` to seed a repoLabels block, then customize labels"
189
+ },
190
+ {
191
+ name: ".github/labels.yaml exists",
173
192
  severity: "warn",
174
- check: readmesHaveReleaseNotesMarkers,
175
- fix: "Add `<!-- section:release-notes -->` and `<!-- /section:release-notes -->` markers to each affected README"
193
+ skip: () => !fileContains(".config/release-kit.config.ts", /repoLabels/) ? "no repoLabels config" : false,
194
+ check: () => fileExists(".github/labels.yaml"),
195
+ fix: "Run `release-kit sync-labels generate` to produce the labels file",
196
+ checks: [
197
+ {
198
+ name: "labels.yaml has current common preset",
199
+ severity: "warn",
200
+ check: () => labelsHaveCurrentPresetHash("common", COMMON_PRESET_HASH),
201
+ fix: "Run `release-kit sync-labels generate` to incorporate updated common labels"
202
+ }
203
+ ]
176
204
  }
177
205
  ]
178
206
  },
207
+ {
208
+ name: "config does not use removed releaseNotes.shouldCreateGithubRelease",
209
+ severity: "error",
210
+ quiet: true,
211
+ check: () => fileDoesNotContain(".config/release-kit.config.ts", /shouldCreateGithubRelease/),
212
+ fix: "Remove 'shouldCreateGithubRelease' from .config/release-kit.config.ts. Adoption of GitHub Releases is now signaled by installing the create-github-release workflow (see release-kit README for setup)."
213
+ },
179
214
  {
180
215
  name: "git-cliff not in devDependencies",
181
216
  severity: "recommend",
217
+ quiet: true,
182
218
  check: () => !hasDevDependency("git-cliff"),
183
219
  fix: "pnpm remove git-cliff \u2014 release-kit handles changelog generation directly"
184
220
  },
@@ -206,41 +242,26 @@ var default_default = defineRdyKit({
206
242
  {
207
243
  name: "sync-labels.yaml does not reference deprecated tag ref",
208
244
  severity: "error",
245
+ quiet: true,
209
246
  check: () => fileDoesNotContain(".github/workflows/sync-labels.yaml", /@sync-labels-workflow-v[0-9]/),
210
247
  fix: "Update sync-labels.yaml to use @workflow/sync-labels-v1 (run `release-kit sync-labels init --force` to regenerate, or replace the ref manually)"
211
248
  },
212
249
  {
213
250
  name: "retired .config/sync-labels.config.ts is absent",
214
251
  severity: "error",
252
+ quiet: true,
215
253
  check: () => !fileExists(".config/sync-labels.config.ts"),
216
254
  fix: "Move the labels into the repoLabels block of .config/release-kit.config.ts, then delete .config/sync-labels.config.ts"
217
- },
218
- {
219
- name: "repoLabels block declared in .config/release-kit.config.ts",
220
- severity: "recommend",
221
- skip: () => !fileExists(".config/release-kit.config.ts") ? "no release-kit config file" : false,
222
- check: () => fileContains(".config/release-kit.config.ts", /repoLabels/),
223
- fix: "Run `release-kit sync-labels init` to seed a repoLabels block, then customize labels"
224
- },
225
- {
226
- name: ".github/labels.yaml exists",
227
- severity: "warn",
228
- skip: () => !fileContains(".config/release-kit.config.ts", /repoLabels/) ? "no repoLabels config" : false,
229
- check: () => fileExists(".github/labels.yaml"),
230
- fix: "Run `release-kit sync-labels generate` to produce the labels file",
231
- checks: [
232
- {
233
- name: "labels.yaml has current common preset",
234
- severity: "warn",
235
- check: () => labelsHaveCurrentPresetHash("common", COMMON_PRESET_HASH),
236
- fix: "Run `release-kit sync-labels generate` to incorporate updated common labels"
237
- }
238
- ]
239
255
  }
240
256
  ]
241
257
  }
242
258
  ]
243
259
  });
260
+ function configFileExportsConfig() {
261
+ const content = readFile(".config/release-kit.config.ts");
262
+ if (content === void 0) return false;
263
+ return CONFIG_EXPORT_PATTERNS.some((pattern) => pattern.test(content));
264
+ }
244
265
  function labelsHaveCurrentPresetHash(presetName, expectedHash) {
245
266
  const content = readFile(".github/labels.yaml");
246
267
  if (content === void 0) return false;
@@ -253,7 +274,8 @@ function readmeHasReleaseNotesMarkers(content) {
253
274
  }
254
275
  function readmesHaveReleaseNotesMarkers() {
255
276
  const failing = [];
256
- for (const { dir } of discoverWorkspaces({ filter: (w) => w.isPackage })) {
277
+ const packageWorkspaces = discoverWorkspaces({ filter: (w) => w.isPackage });
278
+ for (const { dir } of packageWorkspaces) {
257
279
  const readmePath = dir === "." ? "README.md" : `${dir}/README.md`;
258
280
  const content = readFile(readmePath);
259
281
  if (content === void 0 || !readmeHasReleaseNotesMarkers(content)) {
@@ -287,6 +309,7 @@ export {
287
309
  RELEASE_WORKFLOW_HASH_MONOREPO,
288
310
  RELEASE_WORKFLOW_HASH_SINGLE,
289
311
  SYNC_LABELS_WORKFLOW_HASH,
312
+ configFileExportsConfig,
290
313
  default_default as default,
291
314
  readmeHasReleaseNotesMarkers,
292
315
  readmesHaveReleaseNotesMarkers
@@ -1,13 +1,13 @@
1
1
  /** @noformat — @generated. Do not edit. Compiled by rdy. */
2
2
  /* eslint-disable */
3
- export const __readyupVersion = "0.23.0";
3
+ export const __readyupVersion = "0.24.0";
4
4
 
5
5
 
6
6
  // .readyup/kits/npm-auto-publish.ts
7
7
  import { execSync } from "node:child_process";
8
8
  import { existsSync, readdirSync, readFileSync } from "node:fs";
9
9
  import path from "node:path";
10
- import { defineRdyChecklist, defineRdyKit, defineRdyStagedChecklist } from "readyup";
10
+ import { defineRdyChecklist, defineRdyKit } from "readyup";
11
11
  import {
12
12
  discoverWorkspaces,
13
13
  fileContains,
@@ -17,33 +17,43 @@ import {
17
17
  readFile,
18
18
  readJsonFile
19
19
  } from "readyup/check-utils";
20
+ var AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["E401", "ENEEDAUTH"]);
20
21
  var PUBLISH_WORKFLOW_FILE = "publish.yaml";
21
- var repoChecklist = defineRdyStagedChecklist({
22
+ var UNREACHABLE_ERROR_CODES = /* @__PURE__ */ new Set([
23
+ "EAI_AGAIN",
24
+ "ECONNREFUSED",
25
+ "ECONNRESET",
26
+ "ENETUNREACH",
27
+ "ENOTFOUND",
28
+ "ERR_SOCKET_TIMEOUT",
29
+ "ETIMEDOUT"
30
+ ]);
31
+ var repoChecklist = defineRdyChecklist({
22
32
  name: "repo",
23
- preconditions: [
33
+ checks: [
24
34
  {
25
35
  name: "publish.yaml exists",
36
+ skip: () => skipIfNothingPublishable(),
26
37
  check: () => fileExists(".github/workflows/publish.yaml"),
27
- fix: 'Run "release-kit init" to scaffold the publish workflow, or create .github/workflows/publish.yaml manually'
38
+ fix: 'Run "release-kit init" to scaffold the publish workflow, or create .github/workflows/publish.yaml manually',
39
+ checks: [
40
+ {
41
+ name: "id-token: write permission declared",
42
+ check: () => fileContains(".github/workflows/publish.yaml", /id-token:\s*write/),
43
+ fix: 'Add "permissions: { id-token: write, contents: read }" to .github/workflows/publish.yaml \u2014 required for OIDC-based npm authentication'
44
+ },
45
+ {
46
+ name: "No legacy token references in workflow files",
47
+ quiet: true,
48
+ check: () => !hasTokenReferences(),
49
+ fix: "Remove NPM_TOKEN/NODE_AUTH_TOKEN references from workflow files; OIDC auth replaces token-based auth"
50
+ },
51
+ {
52
+ name: "Provenance setting matches repo visibility",
53
+ check: checkProvenanceMatchesVisibility
54
+ }
55
+ ]
28
56
  }
29
- ],
30
- groups: [
31
- [
32
- {
33
- name: "id-token: write permission declared",
34
- check: () => fileContains(".github/workflows/publish.yaml", /id-token:\s*write/),
35
- fix: 'Add "permissions: { id-token: write, contents: read }" to .github/workflows/publish.yaml \u2014 required for OIDC-based npm authentication'
36
- },
37
- {
38
- name: "No legacy token references in workflow files",
39
- check: () => !hasTokenReferences(),
40
- fix: "Remove NPM_TOKEN/NODE_AUTH_TOKEN references from workflow files; OIDC auth replaces token-based auth"
41
- },
42
- {
43
- name: "Provenance setting matches repo visibility",
44
- check: checkProvenanceMatchesVisibility
45
- }
46
- ]
47
57
  ]
48
58
  });
49
59
  var packagesChecklist = defineRdyChecklist({
@@ -53,7 +63,7 @@ var packagesChecklist = defineRdyChecklist({
53
63
  name: 'packageManager field starts with "pnpm"',
54
64
  check: () => {
55
65
  const rootPkg = readJsonFile("package.json");
56
- const pm = typeof rootPkg?.packageManager === "string" ? rootPkg.packageManager : "";
66
+ const pm = typeof rootPkg?.["packageManager"] === "string" ? rootPkg["packageManager"] : "";
57
67
  return pm.startsWith("pnpm");
58
68
  },
59
69
  fix: 'Set "packageManager": "pnpm@..." in root package.json'
@@ -64,9 +74,22 @@ var packagesChecklist = defineRdyChecklist({
64
74
  fix: "Ensure pnpm-workspace.yaml lists package globs, or that a root package.json exists"
65
75
  }
66
76
  ],
67
- get checks() {
68
- return discoverWorkspaces().map((workspace) => buildWorkspaceCheck(workspace));
69
- }
77
+ checks: [
78
+ {
79
+ name: "npm session is usable",
80
+ skip: () => skipIfNothingPublishable(),
81
+ check: () => {
82
+ const auth = getCachedNpmAuthStatus();
83
+ return auth.status === "authenticated" ? { ok: true } : { ok: false, detail: auth.detail };
84
+ },
85
+ // readyup reads `fix` before it consults `skip`, and again when it validates the kit, so a getter here would
86
+ // reach the registry on every run regardless of the skip. Per-outcome wording goes in the check's detail.
87
+ fix: 'Restore a usable npm session: log in with "npm login", or restore access to the registry, which the trusted-publisher check queries directly',
88
+ get checks() {
89
+ return discoverWorkspaces().map((workspace) => buildWorkspaceCheck(workspace));
90
+ }
91
+ }
92
+ ]
70
93
  });
71
94
  var npm_auto_publish_default = defineRdyKit({
72
95
  fixLocation: "inline",
@@ -78,7 +101,7 @@ function buildWorkspaceCheck(workspace) {
78
101
  const children = [
79
102
  {
80
103
  name: "repository field exists",
81
- check: () => workspace.packageJson.repository !== void 0 && workspace.packageJson.repository !== null,
104
+ check: () => workspace.packageJson["repository"] !== void 0 && workspace.packageJson["repository"] !== null,
82
105
  fix: `Add a "repository" field to ${pkgJsonPath} pointing to the GitHub repo`
83
106
  }
84
107
  ];
@@ -100,7 +123,7 @@ function buildWorkspaceCheck(workspace) {
100
123
  checks: [
101
124
  {
102
125
  name: "trusted publisher configured",
103
- check: () => hasTrustedPublisher(displayName, getCachedOwnerRepo(), PUBLISH_WORKFLOW_FILE),
126
+ check: () => checkTrustedPublisher(displayName),
104
127
  get fix() {
105
128
  return `Run: npm trust github ${displayName} --repo ${getCachedOwnerRepo()} --file ${PUBLISH_WORKFLOW_FILE}`;
106
129
  }
@@ -110,7 +133,7 @@ function buildWorkspaceCheck(workspace) {
110
133
  {
111
134
  name: "files field exists",
112
135
  severity: "warn",
113
- check: () => workspace.packageJson.files !== void 0,
136
+ check: () => workspace.packageJson["files"] !== void 0,
114
137
  fix: `Add a "files" field to ${pkgJsonPath} to control which files are included in the published tarball`
115
138
  }
116
139
  );
@@ -148,13 +171,79 @@ function checkProvenanceMatchesVisibility() {
148
171
  }
149
172
  return { ok: true };
150
173
  }
151
- var cachedOwnerRepo;
152
- function getCachedOwnerRepo() {
153
- if (cachedOwnerRepo === void 0) {
154
- cachedOwnerRepo = getOwnerRepo();
174
+ function checkTrustedPublisher(packageName) {
175
+ const result = classifyTrustQuery(
176
+ runNpmJson(`npm trust list ${packageName} --json`),
177
+ getCachedOwnerRepo(),
178
+ PUBLISH_WORKFLOW_FILE
179
+ );
180
+ switch (result.status) {
181
+ case "configured":
182
+ return { ok: true };
183
+ case "not-configured":
184
+ return { ok: false, detail: "No trusted publisher is configured for this package" };
185
+ default:
186
+ return { ok: false, detail: result.detail };
155
187
  }
156
- return cachedOwnerRepo;
157
188
  }
189
+ function classifyNpmAuth(result) {
190
+ if (result.exitOk) {
191
+ return { status: "authenticated" };
192
+ }
193
+ const error = readNpmError(result.stdout);
194
+ if (error === void 0) {
195
+ return { status: "unreachable", detail: "The npm registry query failed without a readable error payload" };
196
+ }
197
+ if (AUTH_ERROR_CODES.has(error.code)) {
198
+ return { status: "unauthenticated", detail: `The npm registry rejected the session (${error.code})` };
199
+ }
200
+ if (UNREACHABLE_ERROR_CODES.has(error.code)) {
201
+ return { status: "unreachable", detail: `Cannot reach the npm registry (${error.code})` };
202
+ }
203
+ return { status: "unreachable", detail: `The npm registry query failed (${error.code}): ${error.summary}` };
204
+ }
205
+ function classifyTrustQuery(result, expectedRepo, expectedFile) {
206
+ if (!result.exitOk) {
207
+ const error = readNpmError(result.stdout);
208
+ if (error === void 0) {
209
+ return { status: "error", detail: "The npm trust query failed without a readable error payload" };
210
+ }
211
+ if (error.code === "E404") {
212
+ return { status: "not-configured" };
213
+ }
214
+ return { status: "error", detail: `The npm trust query failed (${error.code}): ${error.summary}` };
215
+ }
216
+ const relationships = readTrustRelationships(result.stdout);
217
+ if (relationships === void 0) {
218
+ return { status: "error", detail: "The npm trust query returned a payload this check cannot read" };
219
+ }
220
+ if (relationships.length === 0) {
221
+ return { status: "not-configured" };
222
+ }
223
+ const isMatch = relationships.some(
224
+ (relationship) => relationship.type === "github" && relationship.repository === expectedRepo && relationship.file === expectedFile
225
+ );
226
+ return isMatch ? { status: "configured" } : {
227
+ status: "mismatched",
228
+ detail: `Expected github ${expectedRepo} (${expectedFile}); found ${describeTrustRelationships(relationships)}`
229
+ };
230
+ }
231
+ function describeTrustRelationships(relationships) {
232
+ return relationships.map((relationship) => {
233
+ const type = typeof relationship.type === "string" ? relationship.type : "(unknown)";
234
+ const repository = typeof relationship.repository === "string" ? relationship.repository : "(unknown)";
235
+ const file = typeof relationship.file === "string" ? relationship.file : "(unknown)";
236
+ return `${type} ${repository} (${file})`;
237
+ }).join(", ");
238
+ }
239
+ var getCachedNpmAuthStatus = /* @__PURE__ */ (() => {
240
+ let cached;
241
+ return () => cached ??= classifyNpmAuth(runNpmJson("npm whoami --json"));
242
+ })();
243
+ var getCachedOwnerRepo = /* @__PURE__ */ (() => {
244
+ let cached;
245
+ return () => cached ??= getOwnerRepo();
246
+ })();
158
247
  function getOwnerRepo() {
159
248
  const url = execSync("git remote get-url origin", {
160
249
  encoding: "utf8"
@@ -183,27 +272,6 @@ function hasTokenReferences() {
183
272
  }
184
273
  return false;
185
274
  }
186
- function hasTrustedPublisher(packageName, expectedRepo, expectedFile) {
187
- let output;
188
- try {
189
- output = execSync(`npm trust list ${packageName} --json`, {
190
- encoding: "utf8",
191
- stdio: "pipe"
192
- });
193
- } catch {
194
- return false;
195
- }
196
- let parsed;
197
- try {
198
- parsed = JSON.parse(output);
199
- } catch {
200
- return false;
201
- }
202
- if (!isRecord(parsed)) {
203
- return false;
204
- }
205
- return parsed.type === "github" && parsed.repository === expectedRepo && parsed.file === expectedFile;
206
- }
207
275
  function isPublishedToNpm(packageName) {
208
276
  try {
209
277
  execSync(`npm view ${packageName} version`, {
@@ -225,11 +293,55 @@ function isRepoPrivate() {
225
293
  function parseProvenanceSetting(workflowContent) {
226
294
  return /^[^#]*provenance:\s*['"]?true['"]?/im.test(workflowContent);
227
295
  }
296
+ function readNpmError(stdout) {
297
+ let parsed;
298
+ try {
299
+ parsed = JSON.parse(stdout);
300
+ } catch {
301
+ return void 0;
302
+ }
303
+ if (!isRecord(parsed) || !isRecord(parsed["error"]) || typeof parsed["error"]["code"] !== "string") {
304
+ return void 0;
305
+ }
306
+ const { code, summary } = parsed["error"];
307
+ return { code, summary: typeof summary === "string" ? summary : "" };
308
+ }
309
+ function readTrustRelationships(stdout) {
310
+ let parsed;
311
+ try {
312
+ parsed = JSON.parse(stdout);
313
+ } catch {
314
+ return void 0;
315
+ }
316
+ if (Array.isArray(parsed)) {
317
+ return parsed.filter((entry) => isRecord(entry) && "type" in entry);
318
+ }
319
+ if (!isRecord(parsed)) {
320
+ return void 0;
321
+ }
322
+ return "type" in parsed ? [parsed] : [];
323
+ }
324
+ function runNpmJson(command) {
325
+ try {
326
+ return { exitOk: true, stdout: execSync(command, { encoding: "utf8", stdio: "pipe" }) };
327
+ } catch (error) {
328
+ const stdout = isRecord(error) && typeof error["stdout"] === "string" ? error["stdout"] : "";
329
+ return { exitOk: false, stdout };
330
+ }
331
+ }
332
+ function skipIfNothingPublishable() {
333
+ const publishable = discoverWorkspaces({ filter: (workspace) => workspace.isPackage });
334
+ return publishable.length > 0 ? false : "no publishable packages";
335
+ }
228
336
  function skipIfNotPublishable(workspace) {
229
337
  return workspace.isPackage ? false : "package.json#private is true";
230
338
  }
231
339
  export {
232
340
  buildWorkspaceCheck,
341
+ classifyNpmAuth,
342
+ classifyTrustQuery,
233
343
  npm_auto_publish_default as default,
234
- skipIfNotPublishable
344
+ packagesChecklist,
345
+ skipIfNotPublishable,
346
+ skipIfNothingPublishable
235
347
  };
@@ -7,10 +7,10 @@
7
7
  ],
8
8
  "name": "default",
9
9
  "path": "kits/default.js",
10
- "readyupVersion": "0.23.0",
10
+ "readyupVersion": "0.24.0",
11
11
  "source": "kits/default.ts",
12
- "sourceHash": "afe26920",
13
- "targetHash": "5ac6d830"
12
+ "sourceHash": "fe239d01",
13
+ "targetHash": "9bd8fb5d"
14
14
  },
15
15
  {
16
16
  "checklists": [
@@ -19,10 +19,10 @@
19
19
  ],
20
20
  "name": "npm-auto-publish",
21
21
  "path": "kits/npm-auto-publish.js",
22
- "readyupVersion": "0.23.0",
22
+ "readyupVersion": "0.24.0",
23
23
  "source": "kits/npm-auto-publish.ts",
24
- "sourceHash": "71e894e6",
25
- "targetHash": "c1c05255"
24
+ "sourceHash": "ac484613",
25
+ "targetHash": "f790e7d5"
26
26
  }
27
27
  ]
28
28
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,60 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 10.3.0 — 2026-08-08
6
+
7
+ ### 🎉 Features
8
+
9
+ - Stop reporting absence checks that pass (#610)
10
+
11
+ A ReadyUp check that checks for a problem (such as an unwanted file or a stale config reference) now stays silent unless it finds one. The `nmr`, `release-kit`, and `v11y-check` ReadyUp kits previously printed a line for every such check on every run.
12
+
13
+ - Stop reporting non-issues in the readyup kits (#613)
14
+
15
+ Modifies the `release-kit` and `nmr` ReadyUp kits to avoid verbose reporting when an initial check determines that further checks aren't applicable. In particular, a repo with no `release-kit` config file gets a single line in the `release-kit` report, and a repo that publishes nothing now bypasses the `npm-auto-publish` kit instead of failing it.
16
+
17
+ ### 🐛 Bug fixes
18
+
19
+ - Publish build output atomically so a failed build cannot destroy it (#631)
20
+
21
+ Fixes an issue where a failed or still-running `nmr build` could leave a package with no build output at all, breaking the tooling that a subsequent build depends on. Separately, the error reported when a package's build output is missing now names the correct recovery command.
22
+
23
+ ### ♻️ Refactoring
24
+
25
+ - Retire the deferred-lint-rule list and enforce its rules (#628)
26
+
27
+ Errors that `release-kit` rethrows now carry the original error as their cause, previously available only as message text. Help text and documentation for `--with-release-notes` now use the same placeholder notation for its output paths.
28
+
29
+ All violations of lint rules in the `release-kit` project are now cleared, and the list of deferred lint rules is retired, restoring associated rules to a max severity of "error" during strict linting.
30
+
31
+ ### ⚙️ Tooling
32
+
33
+ - Migrate to the shared tsconfig baseline (#626)
34
+
35
+ Adopts `@williamthorsen/tsconfig` as the standard TypeScript configuration for this repo, replacing the previous hand-maintained copy. Reading a property through an index signature now requires bracket notation or a type that declares the property. The `nmr` build now fails on a base TypeScript config it cannot resolve, rather than building without it, and it now resolves a base config named by package name alone.
36
+
37
+ ## 10.2.2 — 2026-08-05
38
+
39
+ ### 🐛 Bug fixes
40
+
41
+ - Report the missing npm login once, not per package (#598)
42
+
43
+ Fixes an issue where running release-kit's publish-readiness checks without an npm login reported a trusted-publisher failure for every published package. The missing login is now reported once, with `npm login` as the fix. An unreachable registry now has its own check. The trusted publisher check produces more informative failure messages.
44
+
45
+ - Make prepare all-or-nothing and report the tags file by path (#601)
46
+
47
+ Fixes an issue where a failed `prepare` could leave the release flow in a broken intermediate state. A release now lands completely or not at all, and a formatting failure no longer discards one that already landed. When a write fails partway, the error names every file that reached disk, every file that did not, and the commands that undo them.
48
+
49
+ `commit` and `tag` now report the full path of the tags file they looked for, and distinguish a missing file from one that could not be read, so that running from the wrong directory or worktree is no longer reported as a release that never happened.
50
+
51
+ Runs with `--with-release-notes` now list the preview files they write, and `--dry-run` names the ones a real run would write.
52
+
53
+ ### 🧪 Tests
54
+
55
+ - Clear the deferred vitest test rules and enforce them (#602)
56
+
57
+ Fixes a subset of previously deferred lint violations and restores the severity of Vitest lint rules to "error" for purposes strict linting.
58
+
5
59
  ## 10.2.1 — 2026-08-04
6
60
 
7
61
  ### 🐛 Bug fixes