@williamthorsen/release-kit 10.5.0 → 10.6.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.
@@ -50,9 +50,55 @@ function detectRepoType() {
50
50
  return "single-package";
51
51
  }
52
52
 
53
+ // src/packsPath.ts
54
+ function packsPath(filesField, path) {
55
+ if (!isStringArray(filesField)) return true;
56
+ const prefixes = listAncestorPrefixes(path);
57
+ return filesField.some((entry) => {
58
+ const pattern = compileEntry(entry);
59
+ return pattern !== void 0 && prefixes.some((prefix) => pattern.test(prefix));
60
+ });
61
+ }
62
+ var GLOB_TOKEN_PATTERN = /\*\*\/|\*\*|\*|\?|[^*?]+/g;
63
+ function compileEntry(entry) {
64
+ if (entry.startsWith("!")) return void 0;
65
+ const normalized = entry.replace(/^\.?\//, "").replace(/\/+$/, "");
66
+ if (normalized === "") return void 0;
67
+ return new RegExp(`^${translateGlob(normalized)}$`);
68
+ }
69
+ function isStringArray(value) {
70
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
71
+ }
72
+ function listAncestorPrefixes(path) {
73
+ const segments = path.split("/");
74
+ return segments.map((_segment, index) => segments.slice(0, index + 1).join("/"));
75
+ }
76
+ function translateGlob(pattern) {
77
+ let source = "";
78
+ for (const [token] of pattern.matchAll(GLOB_TOKEN_PATTERN)) {
79
+ switch (token) {
80
+ case "**/":
81
+ source += String.raw`(?:[^/]*\/)*`;
82
+ break;
83
+ case "**":
84
+ source += ".*";
85
+ break;
86
+ case "*":
87
+ source += "[^/]*";
88
+ break;
89
+ case "?":
90
+ source += "[^/]";
91
+ break;
92
+ default:
93
+ source += RegExp.escape(token);
94
+ }
95
+ }
96
+ return source;
97
+ }
98
+
53
99
  // .readyup/kits/default.ts
54
100
  function getMinVersion() {
55
- const picked = { "version": "10.5.0" };
101
+ const picked = { "version": "10.6.0" };
56
102
  if (typeof picked["version"] !== "string") {
57
103
  throw new TypeError("release-kit/package.json: 'version' must be a string");
58
104
  }
@@ -61,6 +107,7 @@ function getMinVersion() {
61
107
  function hasPublishablePackages() {
62
108
  return discoverWorkspaces({ filter: (w) => w.isPackage }).length > 0;
63
109
  }
110
+ var DEFAULT_CHANGELOG_JSON_PATH = ".meta/changelog.json";
64
111
  var CONFIG_EXPORT_PATTERNS = [
65
112
  /export\s+default\b/,
66
113
  /export\s+(?:const|let|var)\s+config\b/,
@@ -204,6 +251,32 @@ var default_default = defineRdyKit({
204
251
  }
205
252
  ]
206
253
  },
254
+ // Deliberately outside the config gate above: that check skips where the config file is absent, and a repo
255
+ // with no config file still defaults `changelogJson.enabled` to true and still publishes tarballs.
256
+ {
257
+ name: "published packages ship CHANGELOG.md",
258
+ severity: "warn",
259
+ skip: () => !hasPublishablePackages() ? "no publishable packages" : false,
260
+ check: () => packagesShipChangelog(),
261
+ fix: 'Add "CHANGELOG.md" to the files field of each affected package.json'
262
+ },
263
+ {
264
+ name: "changelog.json generation is enabled",
265
+ severity: "recommend",
266
+ skip: () => !hasPublishablePackages() ? "no publishable packages" : false,
267
+ check: () => changelogJsonIsEnabled(),
268
+ fix: "Remove changelogJson.enabled: false from .config/release-kit.config.ts so release-kit writes the machine-readable changelog that upgrade tooling reads before CHANGELOG.md",
269
+ checks: [
270
+ {
271
+ name: "published packages ship the changelog JSON",
272
+ severity: "warn",
273
+ check: () => packagesShipChangelogJson(),
274
+ get fix() {
275
+ return `Add "${resolveChangelogJsonOutputPath()}" to the files field of each affected package.json`;
276
+ }
277
+ }
278
+ ]
279
+ },
207
280
  {
208
281
  name: "config does not use removed releaseNotes.shouldCreateGithubRelease",
209
282
  severity: "error",
@@ -264,6 +337,11 @@ var default_default = defineRdyKit({
264
337
  }
265
338
  ]
266
339
  });
340
+ function changelogJsonIsEnabled() {
341
+ const content = readFile(".config/release-kit.config.ts");
342
+ if (content === void 0) return true;
343
+ return !/changelogJson\s*:\s*\{[^}]*enabled\s*:\s*false/.test(content);
344
+ }
267
345
  function configFileExportsConfig() {
268
346
  const content = readFile(".config/release-kit.config.ts");
269
347
  if (content === void 0) return false;
@@ -276,6 +354,12 @@ function labelsHaveCurrentPresetHash(presetName, expectedHash) {
276
354
  const match = pattern.exec(content);
277
355
  return match !== null && match[1] === expectedHash;
278
356
  }
357
+ function packagesShipChangelog() {
358
+ return reportWorkspacesOmitting("CHANGELOG.md");
359
+ }
360
+ function packagesShipChangelogJson() {
361
+ return reportWorkspacesOmitting(resolveChangelogJsonOutputPath());
362
+ }
279
363
  function readmeHasReleaseNotesMarkers(content) {
280
364
  return content.includes("<!-- section:release-notes -->") && content.includes("<!-- /section:release-notes -->");
281
365
  }
@@ -308,16 +392,41 @@ function releaseNotesInjectsIntoReadme() {
308
392
  if (content === void 0) return false;
309
393
  return /shouldInjectIntoReadme\s*:\s*true/.test(content);
310
394
  }
395
+ function reportWorkspacesOmitting(path) {
396
+ const failing = [];
397
+ const packageWorkspaces = discoverWorkspaces({ filter: (w) => w.isPackage });
398
+ for (const { dir, packageJson } of packageWorkspaces) {
399
+ if (!packsPath(packageJson["files"], path)) {
400
+ failing.push(dir === "." ? "package.json" : `${dir}/package.json`);
401
+ }
402
+ }
403
+ if (failing.length === 0) return true;
404
+ return {
405
+ ok: false,
406
+ detail: `files field omits ${path}: ${failing.join(", ")}`
407
+ };
408
+ }
409
+ function resolveChangelogJsonOutputPath() {
410
+ const content = readFile(".config/release-kit.config.ts");
411
+ if (content === void 0) return DEFAULT_CHANGELOG_JSON_PATH;
412
+ const match = /changelogJson\s*:\s*\{[^}]*outputPath\s*:\s*['"]([^'"]+)['"]/.exec(content);
413
+ return match?.[1] ?? DEFAULT_CHANGELOG_JSON_PATH;
414
+ }
311
415
  export {
312
416
  CLIFF_TEMPLATE_HASH,
313
417
  COMMON_PRESET_HASH,
418
+ DEFAULT_CHANGELOG_JSON_PATH,
314
419
  PUBLISH_WORKFLOW_HASH_MONOREPO,
315
420
  PUBLISH_WORKFLOW_HASH_SINGLE,
316
421
  RELEASE_WORKFLOW_HASH_MONOREPO,
317
422
  RELEASE_WORKFLOW_HASH_SINGLE,
318
423
  SYNC_LABELS_WORKFLOW_HASH,
424
+ changelogJsonIsEnabled,
319
425
  configFileExportsConfig,
320
426
  default_default as default,
427
+ packagesShipChangelog,
428
+ packagesShipChangelogJson,
321
429
  readmeHasReleaseNotesMarkers,
322
- readmesHaveReleaseNotesMarkers
430
+ readmesHaveReleaseNotesMarkers,
431
+ resolveChangelogJsonOutputPath
323
432
  };
@@ -8,12 +8,12 @@
8
8
  "esbuildVersion": "0.28.2",
9
9
  "inputs": [
10
10
  {
11
- "hash": "c948b179",
11
+ "hash": "4a359cc9",
12
12
  "kind": "module",
13
13
  "path": "kits/default.ts"
14
14
  },
15
15
  {
16
- "hash": "221d9444",
16
+ "hash": "bb217228",
17
17
  "kind": "inline",
18
18
  "path": "../package.json",
19
19
  "paths": [
@@ -30,6 +30,11 @@
30
30
  "kind": "module",
31
31
  "path": "../src/init/parseJsonRecord.ts"
32
32
  },
33
+ {
34
+ "hash": "b798d024",
35
+ "kind": "module",
36
+ "path": "../src/packsPath.ts"
37
+ },
33
38
  {
34
39
  "hash": "646fe51b",
35
40
  "kind": "module",
@@ -40,8 +45,8 @@
40
45
  "path": "kits/default.js",
41
46
  "readyupVersion": "0.35.0",
42
47
  "source": "kits/default.ts",
43
- "sourceHash": "c948b179",
44
- "targetHash": "08e4eb5a"
48
+ "sourceHash": "4a359cc9",
49
+ "targetHash": "5b3e6dcf"
45
50
  },
46
51
  {
47
52
  "checklists": [
package/CHANGELOG.md CHANGED
@@ -2,6 +2,43 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 10.6.0 — 2026-09-08
6
+
7
+ ### 🎉 Features
8
+
9
+ - Report a package whose tarball would omit its changelog (#768)
10
+
11
+ Adds ReadyUp checks to verify that every published package bundles its changelog files: the human-readable `CHANGELOG.md` and the machine-readable `changelog.json`.
12
+
13
+ - Add a migration field to changelog.json items (#770)
14
+
15
+ Adds an optional `migration` field to entries in the `.meta/changelog.json` that `release-kit` generates. If a commit body contains a labeled migration step, that content is copied to the `migration` field.
16
+
17
+ ### 🐛 Bug fixes
18
+
19
+ - Pin git-cliff and remove the per-run npx cache refresh (#776)
20
+
21
+ Fixes an issue where git-cliff's version was unpinned, resulting in a per-run cache refresh. An exact version is now pinned.
22
+
23
+ Removes the per-run cache refresh, which re-ran `npx` without `--prefer-offline` once per prepare and forced a registry round trip. An exact version leaves npm nothing to revalidate, so a prepare run no longer waits on registry latency.
24
+
25
+ Also silences git-cliff's INFO output, in particular the crates.io update notice.
26
+
27
+ ### ♻️ Refactoring
28
+
29
+ - Remove the unwired isChangelogItem type guard (#771)
30
+
31
+ Removes the unused `isChangelogItem` from `release-kit`'s `changelogJsonUtils.ts`, together with its tests.
32
+
33
+ Documentation is updated to record the boundary on `isChangelogEntry`: Entries parsed from `.meta/changelog.json` are shape-checked at the top level only, and strict validation applies only to the human-authored `.meta/changelog-overrides.json`.
34
+
35
+ ### 🧪 Tests
36
+
37
+ - Read console spy output with listConsoleLines rather than by hand (#781)
38
+
39
+ - Replaces the hand-rolled reads of a console spy's `mock.calls` in the `nmr`, `release-kit`, and `v11y-check` test suites with `listConsoleLines` from `@williamthorsen/toolbelt.vitest/candidate`.
40
+ - Clears the `toolbelt.vitest/no-console-calls-read` recommendation that `rdy run --packages` reported.
41
+
5
42
  ## 10.5.0 — 2026-09-01
6
43
 
7
44
  ### 🎉 Features
package/README.md CHANGED
@@ -5,13 +5,27 @@ Version-bumping and changelog-generation toolkit for release workflows.
5
5
  Provides a self-contained CLI that auto-discovers workspaces from `pnpm-workspace.yaml`, parses conventional commits, determines version bumps, updates `package.json` files, and generates changelogs from `git-cliff --context` output rendered in-process (with optional [editorial overrides](#editorial-overrides)).
6
6
 
7
7
  <!-- section:release-notes -->
8
- ## Release notes — v10.5.0 (2026-09-01)
8
+ ## Release notes — v10.6.0 (2026-09-08)
9
9
 
10
10
  ### 🎉 Features
11
11
 
12
- - Report a consuming repo that still declares @changesets/cli (#764)
12
+ - Report a package whose tarball would omit its changelog (#768)
13
13
 
14
- Adds a check to the default ReadyUp kit in `@williamthorsen/release-kit` that reports a repo still declaring `@changesets/cli`, the release tool superseded by release-kit. The check reports at `recommend` severity; its fix directs the consumer to remove the dependency together with the `.changeset/` directory and any `changeset:*` scripts.
14
+ Adds ReadyUp checks to verify that every published package bundles its changelog files: the human-readable `CHANGELOG.md` and the machine-readable `changelog.json`.
15
+
16
+ - Add a migration field to changelog.json items (#770)
17
+
18
+ Adds an optional `migration` field to entries in the `.meta/changelog.json` that `release-kit` generates. If a commit body contains a labeled migration step, that content is copied to the `migration` field.
19
+
20
+ ### 🐛 Bug fixes
21
+
22
+ - Pin git-cliff and remove the per-run npx cache refresh (#776)
23
+
24
+ Fixes an issue where git-cliff's version was unpinned, resulting in a per-run cache refresh. An exact version is now pinned.
25
+
26
+ Removes the per-run cache refresh, which re-ran `npx` without `--prefer-offline` once per prepare and forced a registry round trip. An exact version leaves npm nothing to revalidate, so a prepare run no longer waits on registry latency.
27
+
28
+ Also silences git-cliff's INFO output, in particular the crates.io update notice.
15
29
  <!-- /section:release-notes -->
16
30
 
17
31
  ## Installation
@@ -72,6 +86,8 @@ That's it for most repos. The CLI auto-discovers workspaces and applies sensible
72
86
  4. **Version bump + changelog**: bumps `package.json` versions, builds structured `ChangelogEntry[]` from `git-cliff --context`, applies any [editorial overrides](#editorial-overrides) from per-scope `.meta/changelog-overrides.json` files, and renders both `CHANGELOG.md` and `.meta/changelog.json` from that single source. `git-cliff` is invoked only for its `--context` JSON; markdown rendering happens in-process so `.meta/changelog.json` and `CHANGELOG.md` always agree.
73
87
  5. **Release tags file**: writes computed tags to `tmp/.release-tags` for the release workflow to read when tagging and pushing.
74
88
 
89
+ Neither changelog reaches a published tarball on its own. npm stopped including `CHANGELOG` automatically in npm 7, so a package that declares a `files` field ships a changelog only where that field names `CHANGELOG.md` and `.meta/changelog.json`. release-kit's readyup kit reports a publishable workspace whose `files` field omits either.
90
+
75
91
  ## Commit format
76
92
 
77
93
  release-kit parses commits in these formats:
@@ -400,6 +416,40 @@ Work types from your config are merged with these defaults by key — your entri
400
416
 
401
417
  The default `devOnlySections` (excluded from public release notes but still written to `CHANGELOG.md`) are derived from the `internal` and `process` tiers (excluding `fmt`). Override via `changelogJson.devOnlySections` in your config; matching is decorator-tolerant, so a bare-name override like `['Internal features']` keeps working against the emoji-prefixed and prefix-decorated default titles.
402
418
 
419
+ ## `changelog.json` item schema
420
+
421
+ Each item under a section in `.meta/changelog.json` carries one required field and four optional ones:
422
+
423
+ | Field | Type | Meaning |
424
+ | ------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------- |
425
+ | `description` | `string` | The bullet headline, taken from the commit subject with the ticket ID and type prefix stripped. |
426
+ | `body` | `string` | The commit body, with trailing trailer metadata stripped. |
427
+ | `breaking` | `boolean` | Present and `true` where the commit subject carried the `!` prefix. See [`!` (breaking change) policy](#-breaking-change-policy). |
428
+ | `migration` | `string` | The migration step for a consumer. See below. |
429
+ | `hash` | `string` | The full commit SHA, and the key on which an override entry matches. Absent on synthetic propagation entries. |
430
+
431
+ Every optional field is omitted rather than emitted as `null`, so a consumer tests for presence.
432
+
433
+ ### The `migration` field
434
+
435
+ A commit body states a migration step as a paragraph opening with the literal label `Migration:`. Where one is present, `migration` carries that paragraph with the label stripped:
436
+
437
+ ```jsonc
438
+ {
439
+ "description": "Rename the workspace field",
440
+ "body": "Renames `name` to `id`.\n\nMigration: Change any uses of `name` to `id`.",
441
+ "migration": "Change any uses of `name` to `id`.",
442
+ }
443
+ ```
444
+
445
+ Three properties are worth knowing:
446
+
447
+ - **It is independent of `breaking`.** A `fix` cannot carry `!` under the default breaking policy, and a `fix` that tightens validation still imposes a migration. Filter on `migration` to find every step; filter on `breaking` to find every breaking change.
448
+ - **`body` keeps the paragraph.** The field is an extraction, not a move, so `CHANGELOG.md` and `.meta/changelog.json` go on agreeing.
449
+ - **It is derived, not authored.** `migration` is not a field an override file can set (see [File shape](#file-shape)); it is re-derived from whatever `body` an override installs, and cleared where that body carries no labeled paragraph. To change the migration text, override `body`.
450
+
451
+ The label match is exact: `migration:` and `**Migration:**` are not recognized.
452
+
403
453
  ## Editorial overrides
404
454
 
405
455
  Generated changelogs occasionally need editorial correction — typos, redacted scope, reworded entries, or historical commits whose bodies carry verbatim PR-template scaffolding (`## What`, `## Why`, etc.) that renders as literal text in user-facing release notes. Rewriting git history is not viable, and any in-place edit to `CHANGELOG.md` or `.meta/changelog.json` is overwritten on the next release because release-kit regenerates both artifacts from scratch.
@@ -461,6 +511,8 @@ Per-entry fields are all optional, but at least one must be present per entry:
461
511
  | `body` | `string` | Replaces the entry's body (the prose that renders below the bullet). Other fields are preserved. |
462
512
  | `breaking` | `boolean` | Toggles the `🚨 **Breaking:** ` marker on the bullet. |
463
513
 
514
+ There is no `migration` key: the field is derived from `body`, so an override that replaces `body` re-derives it. See [The `migration` field](#the-migration-field).
515
+
464
516
  ### Hash-prefix matching
465
517
 
466
518
  Keys can be either the full 40-character commit SHA or a non-ambiguous prefix. The matcher walks every `ChangelogItem.hash` value present in the entry tree and resolves each override key to its set of matching hashes:
@@ -916,7 +968,7 @@ These kits are no longer reachable through `rdy run --from github:williamthorsen
916
968
  This package shells out to two external tools:
917
969
 
918
970
  - **`git`** — must be available on `PATH`. Used to find tags and retrieve commit history.
919
- - **`git-cliff`** — automatically downloaded and cached via `npx` on first invocation. No need to install it as a dev dependency.
971
+ - **`git-cliff`** — pinned to an exact version and fetched via `npx` on first invocation, then served from npx's cache. No need to install it as a dev dependency. The pin moves only when a new release-kit version names a newer git-cliff, so two releases cut months apart run the same changelog generator.
920
972
 
921
973
  ## Upgrading from v4 to v5
922
974
 
@@ -1070,7 +1122,7 @@ If your format command does not accept file arguments, update it to one that doe
1070
1122
 
1071
1123
  ### v1.1.0: `git-cliff` is no longer a required dev dependency
1072
1124
 
1073
- `git-cliff` is now invoked via `npx --yes git-cliff` instead of requiring it as a dev dependency. You can remove it from your `devDependencies`. The version is not pinned, so `npx` downloads and caches the latest version on first invocation. To pin a specific version, use `npx --yes git-cliff@2.12.0` by wrapping the call in a custom script.
1125
+ `git-cliff` is now invoked via `npx` instead of requiring it as a dev dependency. You can remove it from your `devDependencies`. release-kit names an exact version in the invocation, so the version you get is decided by the release-kit version you install rather than by whatever the registry serves that day.
1074
1126
 
1075
1127
  ## Migration from changesets
1076
1128
 
@@ -1,6 +1,7 @@
1
1
  import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
2
2
  import { extractVersion } from "./changelogJsonUtils.js";
3
3
  import { DEFAULT_WORK_TYPES } from "./defaults.js";
4
+ import { extractMigration } from "./extractMigration.js";
4
5
  import { COMMIT_PREPROCESSOR_PATTERNS, PIPE_SCOPE_SOURCE } from "./parseCommitMessage.js";
5
6
  import { resolveCliffConfigPath } from "./resolveCliffConfigPath.js";
6
7
  import { runGitCliff } from "./runGitCliff.js";
@@ -101,6 +102,10 @@ function transformReleases(releases, devOnlySections) {
101
102
  if (breaking) {
102
103
  item.breaking = true;
103
104
  }
105
+ const migration = extractMigration(body);
106
+ if (migration !== undefined) {
107
+ item.migration = migration;
108
+ }
104
109
  if (commit.id !== undefined && commit.id !== '') {
105
110
  item.hash = commit.id;
106
111
  }
@@ -1,13 +1,11 @@
1
- import type { ChangelogEntry, ChangelogItem } from './types.js';
1
+ import type { ChangelogEntry } from './types.js';
2
2
  /**
3
- * Type guard for `ChangelogItem` values parsed from JSON.
3
+ * Type guard for `ChangelogEntry` values parsed from JSON.
4
4
  *
5
- * Accepts the required `description` plus optional `body`, `breaking`, and `hash` fields. The
6
- * `hash` field is shape-checked but not required — synthetic propagation entries omit it, and
7
- * pre-hash `changelog.json` files are still considered valid.
5
+ * Checks the entry's top level only: `sections` is verified to be an array, and its contents pass
6
+ * through unvalidated. `.meta/changelog.json` is written by release-kit and read tolerantly; strict
7
+ * validation applies to the human-authored `.meta/changelog-overrides.json`, in `changelogOverrides.ts`.
8
8
  */
9
- export declare function isChangelogItem(value: unknown): value is ChangelogItem;
10
- /** Type guard for `ChangelogEntry` values parsed from JSON. */
11
9
  export declare function isChangelogEntry(value: unknown): value is ChangelogEntry;
12
10
  /** Extract the version from a tag by stripping the prefix up to the first digit. */
13
11
  export declare function extractVersion(tag: string): string;
@@ -1,18 +1,5 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { isRecord, isUnknownArray } from "./typeGuards.js";
3
- export function isChangelogItem(value) {
4
- if (!isRecord(value))
5
- return false;
6
- if (typeof value['description'] !== 'string')
7
- return false;
8
- if (value['body'] !== undefined && typeof value['body'] !== 'string')
9
- return false;
10
- if (value['breaking'] !== undefined && typeof value['breaking'] !== 'boolean')
11
- return false;
12
- if (value['hash'] !== undefined && typeof value['hash'] !== 'string')
13
- return false;
14
- return true;
15
- }
16
3
  export function isChangelogEntry(value) {
17
4
  return (isRecord(value) &&
18
5
  typeof value['version'] === 'string' &&
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { describeError } from '@williamthorsen/toolbelt.errors';
4
+ import { extractMigration } from "./extractMigration.js";
4
5
  import { isRecord } from "./typeGuards.js";
5
6
  const OVERRIDES_FILENAME = '.meta/changelog-overrides.json';
6
7
  const VALID_AUDIENCE_VALUES = new Set(['all', 'dev', 'skip']);
@@ -206,6 +207,13 @@ function applyOverrideToItem(item, override) {
206
207
  }
207
208
  if (override.body !== undefined) {
208
209
  result.body = override.body;
210
+ const migration = extractMigration(override.body);
211
+ if (migration === undefined) {
212
+ delete result.migration;
213
+ }
214
+ else {
215
+ result.migration = migration;
216
+ }
209
217
  }
210
218
  if (override.breaking !== undefined) {
211
219
  result.breaking = override.breaking;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Extracts the instruction from a commit body's `Migration:` paragraph.
3
+ *
4
+ * The first paragraph opening with the label wins: the lede doctrine specifies one labeled
5
+ * paragraph per body, so a second is ignored rather than appended. The match is anchored and
6
+ * case-sensitive, so `migration:` and `**Migration:**` yield nothing; the doctrine calls the
7
+ * label literal, and tolerating variants would let the form drift.
8
+ *
9
+ * Newlines inside the paragraph are preserved. The first character is capitalized, which is
10
+ * inert for the imperative the doctrine requires and repairs the lowercase continuation of a
11
+ * paragraph written before it. Returns `undefined` when no paragraph carries the label, or when
12
+ * the label is followed by nothing.
13
+ */
14
+ export declare function extractMigration(body: string | undefined): string | undefined;
@@ -0,0 +1,18 @@
1
+ const PARAGRAPH_SEPARATOR = /\n(?:[ \t]*\n)+/;
2
+ const MIGRATION_LABEL = /^Migration:[ \t]*/;
3
+ export function extractMigration(body) {
4
+ if (body === undefined) {
5
+ return undefined;
6
+ }
7
+ for (const paragraph of body.split(PARAGRAPH_SEPARATOR)) {
8
+ if (!MIGRATION_LABEL.test(paragraph)) {
9
+ continue;
10
+ }
11
+ const instruction = paragraph.replace(MIGRATION_LABEL, '').trim();
12
+ if (instruction.length === 0) {
13
+ return undefined;
14
+ }
15
+ return instruction.charAt(0).toUpperCase() + instruction.slice(1);
16
+ }
17
+ return undefined;
18
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Reports whether npm would place `path` in the published tarball, given a package.json `files` value.
3
+ *
4
+ * An absent or malformed `files` value packs everything, so it reports `true`; an empty array packs nothing
5
+ * beyond the files npm always includes, so it reports `false`. An entry naming an ancestor directory covers
6
+ * everything beneath it, which is how `files: ["dist/*"]` ships `dist/esm/index.js`, so every ancestor prefix
7
+ * of `path` is tested rather than `path` alone.
8
+ *
9
+ * Two approximations of npm's packing rules remain, both failing toward a silent pass: a negation entry
10
+ * (`!dist/*.map`) never counts toward inclusion, because gitignore-style ordered negation is not modeled; and a
11
+ * repo controlling its tarball through `.npmignore` alone declares no `files` field, so it reports `true`
12
+ * without the ignore file being read. For a check that warns about an omission, a missed report costs less than
13
+ * a false one.
14
+ */
15
+ export declare function packsPath(filesField: unknown, path: string): boolean;
@@ -0,0 +1,47 @@
1
+ export function packsPath(filesField, path) {
2
+ if (!isStringArray(filesField))
3
+ return true;
4
+ const prefixes = listAncestorPrefixes(path);
5
+ return filesField.some((entry) => {
6
+ const pattern = compileEntry(entry);
7
+ return pattern !== undefined && prefixes.some((prefix) => pattern.test(prefix));
8
+ });
9
+ }
10
+ const GLOB_TOKEN_PATTERN = /\*\*\/|\*\*|\*|\?|[^*?]+/g;
11
+ function compileEntry(entry) {
12
+ if (entry.startsWith('!'))
13
+ return undefined;
14
+ const normalized = entry.replace(/^\.?\//, '').replace(/\/+$/, '');
15
+ if (normalized === '')
16
+ return undefined;
17
+ return new RegExp(`^${translateGlob(normalized)}$`);
18
+ }
19
+ function isStringArray(value) {
20
+ return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
21
+ }
22
+ function listAncestorPrefixes(path) {
23
+ const segments = path.split('/');
24
+ return segments.map((_segment, index) => segments.slice(0, index + 1).join('/'));
25
+ }
26
+ function translateGlob(pattern) {
27
+ let source = '';
28
+ for (const [token] of pattern.matchAll(GLOB_TOKEN_PATTERN)) {
29
+ switch (token) {
30
+ case '**/':
31
+ source += String.raw `(?:[^/]*\/)*`;
32
+ break;
33
+ case '**':
34
+ source += '.*';
35
+ break;
36
+ case '*':
37
+ source += '[^/]*';
38
+ break;
39
+ case '?':
40
+ source += '[^/]';
41
+ break;
42
+ default:
43
+ source += RegExp.escape(token);
44
+ }
45
+ }
46
+ return source;
47
+ }
@@ -16,7 +16,6 @@ import { planVersionBump, planVersionSet } from "./planVersionBump.js";
16
16
  import { readCurrentVersion } from "./readCurrentVersion.js";
17
17
  import { renderChangelogMarkdown } from "./renderChangelogMarkdown.js";
18
18
  import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
19
- import { refreshGitCliffCache } from "./runGitCliff.js";
20
19
  export function releasePrepare(config, options) {
21
20
  const { bumpOverride, setVersion, withReleaseNotes } = options;
22
21
  const writes = [];
@@ -46,7 +45,6 @@ export function releasePrepare(config, options) {
46
45
  if (!isForwardVersion(currentVersion, setVersion)) {
47
46
  throw new Error(`--set-version ${setVersion} is not greater than current version ${currentVersion}`);
48
47
  }
49
- refreshGitCliffCache();
50
48
  bump = planVersionSet(config.packageFiles, setVersion);
51
49
  }
52
50
  else {
@@ -78,7 +76,6 @@ export function releasePrepare(config, options) {
78
76
  formatCommand: undefined,
79
77
  };
80
78
  }
81
- refreshGitCliffCache();
82
79
  bump = planVersionBump(config.packageFiles, releaseType);
83
80
  }
84
81
  writes.push(...bump.writes);
@@ -23,7 +23,6 @@ import { readCurrentVersion } from "./readCurrentVersion.js";
23
23
  import { releasePrepareProject } from "./releasePrepareProject.js";
24
24
  import { renderChangelogMarkdown } from "./renderChangelogMarkdown.js";
25
25
  import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
26
- import { refreshGitCliffCache } from "./runGitCliff.js";
27
26
  export function releasePrepareMono(config, options) {
28
27
  const { only, withReleaseNotes } = options;
29
28
  const writes = [];
@@ -53,9 +52,6 @@ export function releasePrepareMono(config, options) {
53
52
  enabled: withReleaseNotes === true && config.changelogJson.enabled,
54
53
  sectionOrder,
55
54
  };
56
- if (fullReleaseSet.size > 0) {
57
- refreshGitCliffCache();
58
- }
59
55
  const { tags, modifiedFiles } = executeReleaseSet({
60
56
  sortedDirs,
61
57
  fullReleaseSet,
@@ -1,4 +1,6 @@
1
1
  import { bold, dim, sectionHeader } from "./format.js";
2
+ import { GIT_CLIFF_NPX_ARGS } from "./runGitCliff.js";
3
+ const CLIFF_DRY_RUN_COMMAND = `npx ${GIT_CLIFF_NPX_ARGS.join(' ')} ...`;
2
4
  export function reportPrepare(plan, options) {
3
5
  const isMultiWorkspace = plan.workspaces.some((w) => w.name !== undefined) || plan.project !== undefined;
4
6
  if (isMultiWorkspace) {
@@ -103,7 +105,7 @@ function formatProjectSection(lines, project, dryRun) {
103
105
  lines.push(dim(' Generating changelogs...'));
104
106
  for (const file of project.changelogFiles) {
105
107
  if (dryRun) {
106
- lines.push(dim(` [dry-run] Would run: npx --yes git-cliff ... --output ${file}`));
108
+ lines.push(dim(` [dry-run] Would run: ${CLIFF_DRY_RUN_COMMAND} --output ${file}`));
107
109
  }
108
110
  else {
109
111
  lines.push(dim(` Generating changelog: ${file}`));
@@ -195,7 +197,7 @@ function formatBumpFiles(lines, workspace, dryRun, indent = '') {
195
197
  function formatChangelogFiles(lines, workspace, dryRun, indent = '') {
196
198
  for (const file of workspace.changelogFiles) {
197
199
  if (dryRun) {
198
- lines.push(dim(`${indent} [dry-run] Would run: npx --yes git-cliff ... --output ${file}`));
200
+ lines.push(dim(`${indent} [dry-run] Would run: ${CLIFF_DRY_RUN_COMMAND} --output ${file}`));
199
201
  }
200
202
  else {
201
203
  lines.push(dim(`${indent} Generating changelog: ${file}`));