@williamthorsen/release-kit 16.0.0 → 16.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,59 @@
1
1
  [
2
+ {
3
+ "version": "16.1.1",
4
+ "date": "2026-11-05",
5
+ "sections": [
6
+ {
7
+ "title": "🐛 Bug fixes",
8
+ "audience": "all",
9
+ "items": [
10
+ {
11
+ "description": "Fixes the issue that the bin scripts of `nmr`, `release-kit`, and `v11y-check` reported every error other than missing build output as `<bin>: failed to load: <message>` without a stack, which hid where the fault occurred. (#1157)",
12
+ "hash": "ef1ed5f50bf0b01f19b6d6fe82a40322a8286002",
13
+ "entry": 2
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "title": "♻️ Refactoring",
19
+ "audience": "dev",
20
+ "items": [
21
+ {
22
+ "description": "Moves the CLI entries of `release-kit` and `v11y-check` from `src/bin/` to `src/cli.ts`, matching the layout of `nmr`, and renames the command module previously at `v11y-check`'s `src/cli.ts` to `src/commands.ts`. (#1154)",
23
+ "hash": "8ec4abd71b8952d9bee798031bd336c386fa7cf1",
24
+ "entry": 1
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ },
30
+ {
31
+ "version": "16.1.0",
32
+ "date": "2026-11-03",
33
+ "sections": [
34
+ {
35
+ "title": "🎉 Features",
36
+ "audience": "all",
37
+ "items": [
38
+ {
39
+ "description": "Makes `prepare` and `publish` refuse a release, dry runs included, when a tarball packed from a clean checkout of HEAD lacks a file named by the package's `main`, `types`, `bin`, or `exports`, which keeps a package with missing build output from reaching the registry. (#1147)",
40
+ "hash": "c730654140e89d52ac7430987497d8b954fa7f00",
41
+ "entry": 1
42
+ },
43
+ {
44
+ "description": "Adds the `devExportConditions` config key, which lists the export conditions that resolve only inside the repo and whose targets the tarball check does not require, in addition to `source`. (#1147)",
45
+ "hash": "c730654140e89d52ac7430987497d8b954fa7f00",
46
+ "entry": 2
47
+ },
48
+ {
49
+ "description": "Adds a `content` label to the `common` label preset, for consumer-facing material such as copy, images, or displayed data rather than functionality. (#1150)",
50
+ "hash": "8c7b990041e5744c26547fe0ce8c9305f0f7c6f4",
51
+ "entry": 1
52
+ }
53
+ ]
54
+ }
55
+ ]
56
+ },
2
57
  {
3
58
  "version": "16.0.0",
4
59
  "date": "2026-10-28",
@@ -1,6 +1,6 @@
1
1
  /** @noformat -- @generated. Do not edit. Compiled by rdy. */
2
2
  /* eslint-disable */
3
- export const __readyupVersion = "0.44.0";
3
+ export const __readyupVersion = "0.45.0";
4
4
 
5
5
 
6
6
  // .readyup/kits/default.ts
@@ -223,7 +223,7 @@ function stripYamlQuotes(value) {
223
223
  }
224
224
 
225
225
  // .readyup/lib/workflow-hashes.ts
226
- var COMMON_PRESET_HASH = "1d3e2df1ccd7deea4707fa07a4ae171d761344b65c1a311467fc0f66b70ee646";
226
+ var COMMON_PRESET_HASH = "5c6d7efcc8744d9d30720d24e345b04e03f2b28677ad0c6782020d91731b8aa4";
227
227
  var SYNC_LABELS_WORKFLOW_HASH = "0a88829d154c0de7cdd4246fede99e9311a89ab4199034dbe601663d641ab1e4";
228
228
  var RELEASE_WORKFLOW_HASH_MONOREPO = "f63f4cabb57f94ee6550f17a9874c7a346d46554595ddbcd46fdd3208d613437";
229
229
  var RELEASE_WORKFLOW_HASH_SINGLE = "d26dd4ca186632b388ea46bd885721124c03d19918e511baa40821d2d760da69";
@@ -234,7 +234,7 @@ var CREATE_GITHUB_RELEASE_WORKFLOW_HASH_SINGLE = "d6de961de913c67b08dfa7e07f7349
234
234
 
235
235
  // .readyup/kits/default.ts
236
236
  function getMinVersion() {
237
- const picked = { "version": "16.0.0" };
237
+ const picked = { "version": "16.1.1" };
238
238
  if (typeof picked["version"] !== "string") {
239
239
  throw new TypeError("release-kit/package.json: 'version' must be a string");
240
240
  }
@@ -1,6 +1,6 @@
1
1
  /** @noformat -- @generated. Do not edit. Compiled by rdy. */
2
2
  /* eslint-disable */
3
- export const __readyupVersion = "0.44.0";
3
+ export const __readyupVersion = "0.45.0";
4
4
 
5
5
 
6
6
  // .readyup/kits/npm-auto-publish.ts
@@ -7,7 +7,7 @@
7
7
  "checklist": "release-kit",
8
8
  "checks": [
9
9
  "@williamthorsen/release-kit in devDependencies",
10
- "@williamthorsen/release-kit >= 16.0.0",
10
+ "@williamthorsen/release-kit >= 16.1.1",
11
11
  "release.yaml workflow exists",
12
12
  "release.yaml matches template",
13
13
  "release.yaml does not reference deprecated tag ref",
@@ -58,12 +58,12 @@
58
58
  "path": "lib/config-checks.ts"
59
59
  },
60
60
  {
61
- "hash": "13f15dfe",
61
+ "hash": "f652dae3",
62
62
  "kind": "module",
63
63
  "path": "lib/workflow-hashes.ts"
64
64
  },
65
65
  {
66
- "hash": "de7d08de",
66
+ "hash": "1c9904c3",
67
67
  "kind": "inline",
68
68
  "path": "../package.json",
69
69
  "paths": [
@@ -98,10 +98,10 @@
98
98
  ],
99
99
  "name": "default",
100
100
  "path": "kits/default.js",
101
- "readyupVersion": "0.44.0",
101
+ "readyupVersion": "0.45.0",
102
102
  "source": "kits/default.ts",
103
103
  "sourceHash": "56566d0b",
104
- "targetHash": "43d5913f"
104
+ "targetHash": "070a29f8"
105
105
  },
106
106
  {
107
107
  "checklistChecks": [
@@ -157,10 +157,10 @@
157
157
  ],
158
158
  "name": "npm-auto-publish",
159
159
  "path": "kits/npm-auto-publish.js",
160
- "readyupVersion": "0.44.0",
160
+ "readyupVersion": "0.45.0",
161
161
  "source": "kits/npm-auto-publish.ts",
162
162
  "sourceHash": "682fdd4a",
163
- "targetHash": "84f31b6d"
163
+ "targetHash": "52d15648"
164
164
  }
165
165
  ]
166
166
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 16.1.1 — 2026-11-05
6
+
7
+ ### 🐛 Bug fixes
8
+
9
+ - Fixes the issue that the bin scripts of `nmr`, `release-kit`, and `v11y-check` reported every error other than missing build output as `<bin>: failed to load: <message>` without a stack, which hid where the fault occurred. (#1157)
10
+
11
+ ### ♻️ Refactoring
12
+
13
+ - Moves the CLI entries of `release-kit` and `v11y-check` from `src/bin/` to `src/cli.ts`, matching the layout of `nmr`, and renames the command module previously at `v11y-check`'s `src/cli.ts` to `src/commands.ts`. (#1154)
14
+
15
+ ## 16.1.0 — 2026-11-03
16
+
17
+ ### 🎉 Features
18
+
19
+ - Makes `prepare` and `publish` refuse a release, dry runs included, when a tarball packed from a clean checkout of HEAD lacks a file named by the package's `main`, `types`, `bin`, or `exports`, which keeps a package with missing build output from reaching the registry. (#1147)
20
+ - Adds the `devExportConditions` config key, which lists the export conditions that resolve only inside the repo and whose targets the tarball check does not require, in addition to `source`. (#1147)
21
+ - Adds a `content` label to the `common` label preset, for consumer-facing material such as copy, images, or displayed data rather than functionality. (#1150)
22
+
5
23
  ## 16.0.0 — 2026-10-28
6
24
 
7
25
  ### 🎉 Features
package/README.md CHANGED
@@ -7,11 +7,11 @@ Version-bumping and changelog-generation toolkit for release workflows.
7
7
  Provides a self-contained CLI that auto-discovers workspaces from `pnpm-workspace.yaml`, parses conventional commits, determines version bumps, updates `package.json` files, and generates changelogs from the same commit history that decides each bump (with optional [editorial overrides](docs/editorial-overrides.md)).
8
8
 
9
9
  <!-- section:release-notes -->
10
- ## Release notes — v16.0.0 (2026-10-28)
10
+ ## Release notes — v16.1.1 (2026-11-05)
11
11
 
12
- ### 🎉 Features
12
+ ### 🐛 Bug fixes
13
13
 
14
- - 🚨 **Breaking:** Renames the `changelogMarkdown.keepVersions` config key to `changelogMarkdown.keptVersionCount` and makes config validation reject the old key with an error that names the replacement. (#1134)
14
+ - Fixes the issue that the bin scripts of `nmr`, `release-kit`, and `v11y-check` reported every error other than missing build output as `<bin>: failed to load: <message>` without a stack, which hid where the fault occurred. (#1157)
15
15
  <!-- /section:release-notes -->
16
16
 
17
17
  ## Installation
@@ -71,8 +71,9 @@ Every command other than `--version` and `--help` first locates the repo root an
71
71
  1. **Workspace discovery**: Reads `pnpm-workspace.yaml` and resolves its `packages` patterns to find workspace directories, applying pnpm's semantics: A `!`-prefixed entry excludes what it matches, wherever it appears in the list. Each directory containing a `package.json` becomes a workspace. The repo is treated as a single-package project when it doesn't contain a workspace file, and when the workspace file doesn't declare a `packages` list: a file kept for `catalog:` or `overrides:` alone, which pnpm likewise resolves to the root package. A workspace file that declares patterns that don't resolve to any package is an error naming the condition that emptied it.
72
72
  2. **Config loading**: Loads `.config/release-kit.config.ts` (if present) from the repo root, or the file named by `--config` relative to the invocation directory, and merges it with discovered defaults.
73
73
  3. **Commit analysis**: For each workspace, reads the history once, builds the changelog items of the commits since the last version tag, and takes the bump as the highest level that those items call for.
74
- 4. **Version bump + changelog**: Bumps `package.json` versions, builds structured `ChangelogEntry[]` from the commits between each pair of release tags, applies any [editorial overrides](docs/editorial-overrides.md) from per-scope `.meta/changelog-overrides.json` files, and renders both `CHANGELOG.md` and `.meta/changelog.json` from that single source, so the two always agree.
75
- 5. **Release tags file**: Writes computed tags to `tmp/.release-tags` for the release workflow to read when tagging and pushing.
74
+ 4. **Tarball check**: Packs each publishable package that the release includes from a clean checkout of HEAD, and refuses the release when a tarball lacks a file that the package's manifest names; see [Packages whose tarball would lack a manifest file](docs/releasing.md#packages-whose-tarball-would-lack-a-manifest-file).
75
+ 5. **Version bump + changelog**: Bumps `package.json` versions, builds structured `ChangelogEntry[]` from the commits between each pair of release tags, applies any [editorial overrides](docs/editorial-overrides.md) from per-scope `.meta/changelog-overrides.json` files, and renders both `CHANGELOG.md` and `.meta/changelog.json` from that single source, so the two always agree.
76
+ 6. **Release tags file**: Writes computed tags to `tmp/.release-tags` for the release workflow to read when tagging and pushing.
76
77
 
77
78
  [Changelogs](docs/changelogs.md) covers what a published tarball includes, the `changelog.json` item schema, and which commits appear in a changelog.
78
79
 
@@ -1,11 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  try {
3
- await import('../dist/esm/bin/release-kit.js');
3
+ await import('../dist/esm/cli.js');
4
4
  } catch (error) {
5
- if (error.code === 'ERR_MODULE_NOT_FOUND') {
5
+ if (error?.code === 'ERR_MODULE_NOT_FOUND') {
6
6
  process.stderr.write('release-kit: build output not found; run `nmr build` first\n');
7
7
  } else {
8
- process.stderr.write(`release-kit: failed to load: ${error.message}\n`);
8
+ const message = typeof error?.message === 'string' && error.message !== '' ? error.message : String(error);
9
+ const lines = typeof error?.stack === 'string' ? error.stack.split('\n') : [];
10
+ const firstFrame = lines.findIndex((line) => /^\s+at /.test(line));
11
+ const frames = firstFrame === -1 ? '' : `${lines.slice(firstFrame).join('\n')}\n`;
12
+ process.stderr.write(`release-kit: internal error: ${message}\n${frames}`);
9
13
  }
10
14
  process.exit(1);
11
15
  }
@@ -0,0 +1,9 @@
1
+ import { packFromCleanCheckout } from './packFromCleanCheckout.js';
2
+ /**
3
+ * Throws when the tarball of a publishable package among the given workspaces would lack a file that its manifest
4
+ * names, or when the packages cannot be packed from a clean checkout of HEAD.
5
+ *
6
+ * Each workspace path is repo-relative and is read from the current directory. A private package is not packed, and
7
+ * an empty list doesn't clone anything.
8
+ */
9
+ export declare function assertTarballsComplete(workspacePaths: readonly string[], devExportConditions: readonly string[], pack?: typeof packFromCleanCheckout): void;
@@ -0,0 +1,52 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { describeError } from '@williamthorsen/toolbelt.errors';
4
+ import { detectPackageManager } from "./detectPackageManager.js";
5
+ import { findMissingManifestPaths } from "./findMissingManifestPaths.js";
6
+ import { isPublishableManifest } from "./isPublishableManifest.js";
7
+ import { packFromCleanCheckout } from "./packFromCleanCheckout.js";
8
+ import { isRecord } from "./typeGuards.js";
9
+ export function assertTarballsComplete(workspacePaths, devExportConditions, pack = packFromCleanCheckout) {
10
+ const candidates = workspacePaths
11
+ .map((workspacePath) => ({ workspacePath, manifest: readManifest(workspacePath) }))
12
+ .filter(({ manifest }) => isPublishableManifest(manifest));
13
+ if (candidates.length === 0) {
14
+ return;
15
+ }
16
+ process.stderr.write(`Checking the tarballs of ${candidates.length} ${candidates.length === 1 ? 'package' : 'packages'} from a clean checkout of HEAD...\n`);
17
+ let filesByWorkspace;
18
+ try {
19
+ filesByWorkspace = pack(candidates.map(({ workspacePath }) => workspacePath), detectPackageManager());
20
+ }
21
+ catch (error) {
22
+ throw new Error(`Cannot release: The packages could not be packed from a clean checkout of HEAD.\n${describeError(error)}`, { cause: error });
23
+ }
24
+ const incompletePackages = candidates
25
+ .map(({ workspacePath, manifest }) => ({
26
+ workspacePath,
27
+ manifest,
28
+ missingPaths: findMissingManifestPaths(manifest, filesByWorkspace.get(workspacePath) ?? [], devExportConditions),
29
+ }))
30
+ .filter(({ missingPaths }) => missingPaths.length > 0);
31
+ if (incompletePackages.length === 0) {
32
+ return;
33
+ }
34
+ throw new Error(describeRefusal(incompletePackages));
35
+ }
36
+ function describeRefusal(incompletePackages) {
37
+ const sections = ['Cannot release: The tarball of every package must contain each file that its manifest names.'];
38
+ for (const { workspacePath, manifest, missingPaths } of incompletePackages) {
39
+ const name = typeof manifest['name'] === 'string' ? manifest['name'] : workspacePath;
40
+ sections.push([
41
+ `${name} (${workspacePath}) lacks:`,
42
+ ...missingPaths.map(({ location, path }) => ` - ${path} (${location})`),
43
+ ].join('\n'));
44
+ }
45
+ sections.push('Build these files in the package\'s publish lifecycle, such as in a "prepack" script. ' +
46
+ 'If an export condition is for development only, list it in the "devExportConditions" config key.');
47
+ return sections.join('\n\n');
48
+ }
49
+ function readManifest(workspacePath) {
50
+ const manifest = JSON.parse(readFileSync(join(workspacePath, 'package.json'), 'utf8'));
51
+ return isRecord(manifest) ? manifest : {};
52
+ }
@@ -75,7 +75,7 @@ function buildSections(sectionMap, devOnlySections) {
75
75
  return sections.toSorted((a, b) => getCanonicalSectionPriority(a.title) - getCanonicalSectionPriority(b.title));
76
76
  }
77
77
  function formatDate(timestampSec) {
78
- return new Date(timestampSec * 1_000).toISOString().slice(0, 10);
78
+ return new Date(timestampSec * 1000).toISOString().slice(0, 10);
79
79
  }
80
80
  function getCanonicalSectionPriority(title) {
81
81
  const index = CANONICAL_SECTION_ORDER.get(stripGroupDecorations(title));
@@ -170,7 +170,7 @@ function transformReleases(windows, context) {
170
170
  unreleasedReading: {
171
171
  bump: determineBumpType(signals, context.workTypes, context.versionPatterns),
172
172
  commits: unreleasedCommits.filter((commit) => !isReleaseSubject(commit.subject)).toReversed(),
173
- date: formatDate(unreleasedWindow?.timestampSec ?? Math.floor(Date.now() / 1_000)),
173
+ date: formatDate(unreleasedWindow?.timestampSec ?? Math.floor(Date.now() / 1000)),
174
174
  diagnostics,
175
175
  parsedCommitCount,
176
176
  sections: buildSections(sectionMap, context.devOnlySections),
@@ -1,29 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgsOrExit, reportError, resolveSelfVersion } from '@williamthorsen/nmr-core';
3
3
  import { describeError } from '@williamthorsen/toolbelt.errors';
4
- import { commitCommand } from "../commitCommand.js";
5
- import { configFlagSchema } from "../configFlagSchema.js";
6
- import { createGithubReleaseCommand } from "../createGithubReleaseCommand.js";
7
- import { enterRepoRoot } from "../enterRepoRoot.js";
8
- import { showInitHelp, showUpdateTemplatesHelp } from "../help/initHelp.js";
9
- import { showOverridesHelp, showOverridesValidateHelp } from "../help/overridesHelp.js";
10
- import { showPrepareHelp } from "../help/prepareHelp.js";
11
- import { showCommitHelp, showCreateGithubReleaseHelp, showPublishHelp, showPushHelp, showShowTagPrefixesHelp, showTagHelp, } from "../help/releaseStepsHelp.js";
12
- import { showSyncLabelsGenerateHelp, showSyncLabelsHelp, showSyncLabelsInitHelp, showSyncLabelsSyncHelp, } from "../help/syncLabelsHelp.js";
13
- import { showUsage } from "../help/usage.js";
14
- import { initCommand } from "../init/initCommand.js";
15
- import { updateTemplatesCommand } from "../init/updateTemplatesCommand.js";
16
- import { prepareCommand } from "../prepareCommand.js";
17
- import { publishCommand } from "../publishCommand.js";
18
- import { pushCommand } from "../pushCommand.js";
19
- import { resolveConfigFlag } from "../resolveConfigFlag.js";
20
- import { resolveStylesOrExit } from "../resolveStylesOrExit.js";
21
- import { showTagPrefixesCommand } from "../showTagPrefixesCommand.js";
22
- import { generateCommand } from "../sync-labels/generateCommand.js";
23
- import { syncLabelsInitCommand } from "../sync-labels/initCommand.js";
24
- import { syncLabelsCommand } from "../sync-labels/syncCommand.js";
25
- import { tagCommand } from "../tagCommand.js";
26
- import { validateOverridesCommand } from "../validateOverridesCommand.js";
4
+ import { commitCommand } from "./commitCommand.js";
5
+ import { configFlagSchema } from "./configFlagSchema.js";
6
+ import { createGithubReleaseCommand } from "./createGithubReleaseCommand.js";
7
+ import { enterRepoRoot } from "./enterRepoRoot.js";
8
+ import { showInitHelp, showUpdateTemplatesHelp } from "./help/initHelp.js";
9
+ import { showOverridesHelp, showOverridesValidateHelp } from "./help/overridesHelp.js";
10
+ import { showPrepareHelp } from "./help/prepareHelp.js";
11
+ import { showCommitHelp, showCreateGithubReleaseHelp, showPublishHelp, showPushHelp, showShowTagPrefixesHelp, showTagHelp, } from "./help/releaseStepsHelp.js";
12
+ import { showSyncLabelsGenerateHelp, showSyncLabelsHelp, showSyncLabelsInitHelp, showSyncLabelsSyncHelp, } from "./help/syncLabelsHelp.js";
13
+ import { showUsage } from "./help/usage.js";
14
+ import { initCommand } from "./init/initCommand.js";
15
+ import { updateTemplatesCommand } from "./init/updateTemplatesCommand.js";
16
+ import { prepareCommand } from "./prepareCommand.js";
17
+ import { publishCommand } from "./publishCommand.js";
18
+ import { pushCommand } from "./pushCommand.js";
19
+ import { resolveConfigFlag } from "./resolveConfigFlag.js";
20
+ import { resolveStylesOrExit } from "./resolveStylesOrExit.js";
21
+ import { showTagPrefixesCommand } from "./showTagPrefixesCommand.js";
22
+ import { generateCommand } from "./sync-labels/generateCommand.js";
23
+ import { syncLabelsInitCommand } from "./sync-labels/initCommand.js";
24
+ import { syncLabelsCommand } from "./sync-labels/syncCommand.js";
25
+ import { tagCommand } from "./tagCommand.js";
26
+ import { validateOverridesCommand } from "./validateOverridesCommand.js";
27
27
  const VERSION = resolveSelfVersion(import.meta.url);
28
28
  function enterRepoRootOrExit() {
29
29
  try {
@@ -166,6 +166,7 @@ export declare const releaseKitConfigSchema: z.ZodObject<{
166
166
  changelogMarkdown: z.ZodOptional<z.ZodObject<{
167
167
  keptVersionCount: z.ZodOptional<z.ZodNumber>;
168
168
  }, z.core.$strict>>;
169
+ devExportConditions: z.ZodOptional<z.ZodArray<z.ZodString>>;
169
170
  formatCommand: z.ZodOptional<z.ZodString>;
170
171
  project: z.ZodOptional<z.ZodObject<{
171
172
  paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -89,6 +89,7 @@ export const releaseKitConfigSchema = z
89
89
  breakingPolicies: z.record(z.string(), breakingPolicyValueSchema).optional(),
90
90
  changelogJson: changelogJsonInputSchema.optional(),
91
91
  changelogMarkdown: changelogMarkdownInputSchema.optional(),
92
+ devExportConditions: z.array(z.string().min(1)).optional(),
92
93
  formatCommand: z.string().min(1).optional(),
93
94
  project: projectConfigSchema.optional(),
94
95
  releaseNotes: releaseNotesInputSchema.optional(),
@@ -4,3 +4,5 @@ export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'yarn-berry';
4
4
  * and otherwise returns `npm`. Yarn v2+ is `'yarn-berry'`, to distinguish it from Yarn Classic.
5
5
  */
6
6
  export declare function detectPackageManager(): PackageManager;
7
+ /** Maps a `PackageManager` value to its CLI executable name. */
8
+ export declare function resolveExecutable(packageManager: PackageManager): string;
@@ -20,6 +20,9 @@ export function detectPackageManager() {
20
20
  }
21
21
  return detectFromLockfile();
22
22
  }
23
+ export function resolveExecutable(packageManager) {
24
+ return packageManager === 'yarn-berry' ? 'yarn' : packageManager;
25
+ }
23
26
  function isYarnBerry(version) {
24
27
  if (version === undefined) {
25
28
  return false;
@@ -11,7 +11,7 @@ export function enumerateReleaseWindows(options) {
11
11
  }
12
12
  const unreleasedWindow = {
13
13
  version: unreleasedTag,
14
- timestampSec: Math.floor(now() / 1_000),
14
+ timestampSec: Math.floor(now() / 1000),
15
15
  commits: [],
16
16
  };
17
17
  const ancestry = readAncestry();
@@ -0,0 +1,14 @@
1
+ /** A path that a manifest names but the tarball lacks, with the manifest location that names it. */
2
+ export interface MissingManifestPath {
3
+ location: string;
4
+ path: string;
5
+ }
6
+ /**
7
+ * Lists each path named by the manifest's `main`, `types`, `bin`, and `exports` that `packedFiles` lacks.
8
+ *
9
+ * `packedFiles` holds tarball entries relative to the package root. `main` resolves as Node resolves it: the path
10
+ * itself, then with `.js` or `.json` appended, then as a directory containing `index.js`. An `exports` target
11
+ * containing `*` passes when any entry matches it, with `*` spanning `/` as in Node's subpath patterns. A target under
12
+ * `source` or under one of `devExportConditions` is exempt, at any depth.
13
+ */
14
+ export declare function findMissingManifestPaths(manifest: Record<string, unknown>, packedFiles: readonly string[], devExportConditions: readonly string[]): MissingManifestPath[];
@@ -0,0 +1,90 @@
1
+ import { isRecord } from "./typeGuards.js";
2
+ const BUILT_IN_DEV_CONDITION = 'source';
3
+ export function findMissingManifestPaths(manifest, packedFiles, devExportConditions) {
4
+ const packedSet = new Set(packedFiles);
5
+ const exemptConditions = new Set([BUILT_IN_DEV_CONDITION, ...devExportConditions]);
6
+ const missingPaths = [];
7
+ const main = manifest['main'];
8
+ if (typeof main === 'string' && listMainCandidates(main).every((candidate) => !packedSet.has(candidate))) {
9
+ missingPaths.push({ location: 'main', path: main });
10
+ }
11
+ const types = manifest['types'];
12
+ if (typeof types === 'string' && !packedSet.has(normalizePath(types))) {
13
+ missingPaths.push({ location: 'types', path: types });
14
+ }
15
+ const binTargets = listBinTargets(manifest['bin']);
16
+ for (const { location, path } of binTargets) {
17
+ if (!packedSet.has(normalizePath(path))) {
18
+ missingPaths.push({ location, path });
19
+ }
20
+ }
21
+ const exportTargets = [];
22
+ collectExportTargets(manifest['exports'], 'exports', exemptConditions, exportTargets);
23
+ for (const target of exportTargets) {
24
+ if (!isTargetPacked(target.path, packedFiles, packedSet)) {
25
+ missingPaths.push(target);
26
+ }
27
+ }
28
+ return missingPaths;
29
+ }
30
+ function collectExportTargets(value, location, exemptConditions, targets) {
31
+ if (typeof value === 'string') {
32
+ targets.push({ location, path: value });
33
+ return;
34
+ }
35
+ if (Array.isArray(value)) {
36
+ for (const [index, element] of value.entries()) {
37
+ collectExportTargets(element, `${location}[${index}]`, exemptConditions, targets);
38
+ }
39
+ return;
40
+ }
41
+ if (!isRecord(value))
42
+ return;
43
+ for (const [key, nestedValue] of Object.entries(value)) {
44
+ if (key.startsWith('.')) {
45
+ collectExportTargets(nestedValue, `${location}[${JSON.stringify(key)}]`, exemptConditions, targets);
46
+ }
47
+ else if (!exemptConditions.has(key)) {
48
+ collectExportTargets(nestedValue, `${location}.${key}`, exemptConditions, targets);
49
+ }
50
+ }
51
+ }
52
+ function isTargetPacked(target, packedFiles, packedSet) {
53
+ const normalizedTarget = normalizePath(target);
54
+ if (!normalizedTarget.includes('*')) {
55
+ return packedSet.has(normalizedTarget);
56
+ }
57
+ const pattern = new RegExp(`^${normalizedTarget
58
+ .split('*')
59
+ .map((part) => RegExp.escape(part))
60
+ .join('.*')}$`);
61
+ return packedFiles.some((file) => pattern.test(file));
62
+ }
63
+ function listBinTargets(bin) {
64
+ if (typeof bin === 'string') {
65
+ return [{ location: 'bin', path: bin }];
66
+ }
67
+ if (!isRecord(bin))
68
+ return [];
69
+ const targets = [];
70
+ for (const [command, path] of Object.entries(bin)) {
71
+ if (typeof path === 'string') {
72
+ targets.push({ location: `bin.${command}`, path });
73
+ }
74
+ }
75
+ return targets;
76
+ }
77
+ function listMainCandidates(main) {
78
+ const normalizedMain = normalizePath(main);
79
+ const directoryPrefix = normalizedMain === '' ? '' : `${normalizedMain}/`;
80
+ const indexCandidates = [`${directoryPrefix}index.js`, `${directoryPrefix}index.json`];
81
+ if (normalizedMain === '')
82
+ return indexCandidates;
83
+ return [normalizedMain, `${normalizedMain}.js`, `${normalizedMain}.json`, ...indexCandidates];
84
+ }
85
+ function normalizePath(path) {
86
+ return path
87
+ .replace(/^\.?\/+/, '')
88
+ .replace(/^\.$/, '')
89
+ .replace(/\/+$/, '');
90
+ }
@@ -0,0 +1,11 @@
1
+ import { type PackageManager } from './detectPackageManager.js';
2
+ /**
3
+ * Packs each workspace as the publish environment would and returns each one's tarball entries, keyed by workspace
4
+ * path and relative to the package root.
5
+ *
6
+ * Clones HEAD of the repo at the current directory into a temporary directory, runs a frozen install there, then for
7
+ * each workspace runs its `prepublishOnly` when it declares one and packs it. Workspace paths are relative to the repo
8
+ * root. Throws an error that names the failing step and contains its output. Leaves the repo and its working tree
9
+ * untouched, and removes the temporary directory.
10
+ */
11
+ export declare function packFromCleanCheckout(workspacePaths: readonly string[], packageManager: PackageManager): Map<string, string[]>;
@@ -0,0 +1,81 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { resolveExecutable } from "./detectPackageManager.js";
6
+ import { isRecord } from "./typeGuards.js";
7
+ export function packFromCleanCheckout(workspacePaths, packageManager) {
8
+ const repoRoot = runStep('find the repository root', 'git', ['rev-parse', '--show-toplevel'], process.cwd()).trim();
9
+ const headSha = runStep('resolve HEAD', 'git', ['rev-parse', 'HEAD'], repoRoot).trim();
10
+ const scratchDir = mkdtempSync(join(tmpdir(), 'release-kit-pack-'));
11
+ try {
12
+ const cloneDir = join(scratchDir, 'checkout');
13
+ runStep('clone HEAD', 'git', ['clone', '--quiet', '--shared', '--no-checkout', repoRoot, cloneDir], scratchDir);
14
+ runStep('check out HEAD', 'git', ['checkout', '--quiet', '--detach', headSha], cloneDir);
15
+ const executable = resolveExecutable(packageManager);
16
+ const commands = PACKAGE_MANAGER_COMMANDS[packageManager];
17
+ runStep('install dependencies', executable, commands.installArgs, cloneDir);
18
+ const filesByWorkspace = new Map();
19
+ for (const [index, workspacePath] of workspacePaths.entries()) {
20
+ const packageDir = join(cloneDir, workspacePath);
21
+ if (declaresPrepublishOnly(packageDir)) {
22
+ runStep(`run prepublishOnly in ${workspacePath}`, executable, ['run', 'prepublishOnly'], packageDir);
23
+ }
24
+ const packDir = join(scratchDir, 'packs', String(index));
25
+ mkdirSync(packDir, { recursive: true });
26
+ runStep(`pack ${workspacePath}`, executable, commands.buildPackArgs(packDir), packageDir);
27
+ filesByWorkspace.set(workspacePath, listTarballEntries(findTarball(packDir, workspacePath)));
28
+ }
29
+ return filesByWorkspace;
30
+ }
31
+ finally {
32
+ rmSync(scratchDir, { force: true, recursive: true });
33
+ }
34
+ }
35
+ const PACKAGE_MANAGER_COMMANDS = {
36
+ npm: {
37
+ installArgs: ['ci', '--prefer-offline'],
38
+ buildPackArgs: (packDir) => ['pack', '--pack-destination', packDir],
39
+ },
40
+ pnpm: {
41
+ installArgs: ['install', '--frozen-lockfile', '--prefer-offline'],
42
+ buildPackArgs: (packDir) => ['pack', '--pack-destination', packDir],
43
+ },
44
+ yarn: {
45
+ installArgs: ['install', '--frozen-lockfile'],
46
+ buildPackArgs: (packDir) => ['pack', '--filename', join(packDir, 'package.tgz')],
47
+ },
48
+ 'yarn-berry': {
49
+ installArgs: ['install', '--immutable'],
50
+ buildPackArgs: (packDir) => ['pack', '--out', join(packDir, 'package.tgz')],
51
+ },
52
+ };
53
+ function declaresPrepublishOnly(packageDir) {
54
+ const manifest = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8'));
55
+ const scripts = isRecord(manifest) ? manifest['scripts'] : undefined;
56
+ return isRecord(scripts) && typeof scripts['prepublishOnly'] === 'string';
57
+ }
58
+ function findTarball(packDir, workspacePath) {
59
+ const tarballs = readdirSync(packDir).filter((name) => name.endsWith('.tgz'));
60
+ const [tarball] = tarballs;
61
+ if (tarballs.length !== 1 || tarball === undefined) {
62
+ throw new Error(`Could not pack ${workspacePath}: expected one tarball, found ${tarballs.length}.`);
63
+ }
64
+ return join(packDir, tarball);
65
+ }
66
+ function listTarballEntries(tarballPath) {
67
+ const listing = runStep('list the tarball', 'tar', ['--list', '--gzip', '--file', tarballPath], process.cwd());
68
+ return listing
69
+ .split('\n')
70
+ .filter((entry) => entry !== '' && !entry.endsWith('/'))
71
+ .map((entry) => entry.replace(/^package\//, ''));
72
+ }
73
+ function runStep(step, command, args, cwd) {
74
+ const result = spawnSync(command, args, { cwd, encoding: 'utf8', maxBuffer: 2 ** 26 });
75
+ if (result.error === undefined && result.status === 0) {
76
+ return result.stdout;
77
+ }
78
+ const outcome = result.error === undefined ? `exited with status ${result.status}` : result.error.message;
79
+ const output = [result.stdout, result.stderr].filter((text) => text !== '').join('\n');
80
+ throw new Error(`Could not ${step}: \`${[command, ...args].join(' ')}\` ${outcome}.${output === '' ? '' : `\n${output}`}`);
81
+ }
@@ -3,6 +3,7 @@ import { formatGlyphLine, reportError } from '@williamthorsen/nmr-core';
3
3
  import { describeError } from '@williamthorsen/toolbelt.errors';
4
4
  import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
5
5
  import { assertReleasesOnNpm } from "./assertReleasesOnNpm.js";
6
+ import { assertTarballsComplete } from "./assertTarballsComplete.js";
6
7
  import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
7
8
  import { dim } from "./format.js";
8
9
  import { RELEASE_GLYPHS } from "./glyphs.js";
@@ -63,7 +64,9 @@ export async function prepareCommand(argv, styles, invocationDir) {
63
64
  };
64
65
  runAndReport(() => {
65
66
  const plan = prepareReleaseTargets(config, targetOptions);
66
- assertReleasesOnNpm(findReleasedPackagePaths(plan, config.targets));
67
+ const releasedPackagePaths = findReleasedPackagePaths(plan, config.targets);
68
+ assertReleasesOnNpm(releasedPackagePaths);
69
+ assertTarballsComplete(releasedPackagePaths, userConfig?.devExportConditions ?? []);
67
70
  return plan;
68
71
  }, isDryRun, styles.stdout);
69
72
  }
@@ -1,4 +1,4 @@
1
- import type { PackageManager } from './detectPackageManager.js';
1
+ import { type PackageManager } from './detectPackageManager.js';
2
2
  import type { ResolvedTag } from './resolveReleaseTags.js';
3
3
  export interface PublishOptions {
4
4
  isDryRun: boolean;
@@ -1,4 +1,5 @@
1
1
  import { execFileSync } from 'node:child_process';
2
+ import { resolveExecutable } from "./detectPackageManager.js";
2
3
  export function publishPackage(resolvedTag, packageManager, options) {
3
4
  const { isDryRun, shouldGenerateProvenance } = options;
4
5
  const executable = resolveExecutable(packageManager);
@@ -6,12 +7,6 @@ export function publishPackage(resolvedTag, packageManager, options) {
6
7
  console.info(`\n${isDryRun ? '[dry-run] ' : ''}Running: ${executable} ${args.join(' ')} (cwd: ${resolvedTag.workspacePath})`);
7
8
  execFileSync(executable, args, { cwd: resolvedTag.workspacePath, stdio: 'inherit' });
8
9
  }
9
- function resolveExecutable(packageManager) {
10
- if (packageManager === 'yarn-berry') {
11
- return 'yarn';
12
- }
13
- return packageManager;
14
- }
15
10
  function buildPublishArgs(packageManager, options) {
16
11
  const args = packageManager === 'yarn-berry' ? ['npm', 'publish'] : ['publish'];
17
12
  if (options.isDryRun) {
@@ -1,7 +1,7 @@
1
1
  import { type StreamStyles } from '@williamthorsen/nmr-core';
2
2
  /**
3
- * Orchestrates the CLI `publish` command: checks for a clean working tree, resolves the tags on HEAD, and publishes
4
- * each publishable one, injecting release notes into its README for the duration of the publish.
5
- * A relative `--config` resolves against `invocationDir`.
3
+ * Orchestrates the CLI `publish` command: checks for a clean working tree, resolves the tags on HEAD, checks the
4
+ * tarballs of the publishable ones from a clean checkout, and publishes each one, injecting release notes into its
5
+ * README for the duration of the publish. A relative `--config` resolves against `invocationDir`.
6
6
  */
7
7
  export declare function publishCommand(argv: string[], styles: StreamStyles, invocationDir: string): Promise<void>;
@@ -3,6 +3,7 @@ import { join } from 'node:path';
3
3
  import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
4
4
  import { describeError } from '@williamthorsen/toolbelt.errors';
5
5
  import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
6
+ import { assertTarballsComplete } from "./assertTarballsComplete.js";
6
7
  import { configFlagSchema } from "./configFlagSchema.js";
7
8
  import { deriveReleaseNotesConfig } from "./deriveReleaseNotesConfig.js";
8
9
  import { detectPackageManager } from "./detectPackageManager.js";
@@ -46,6 +47,13 @@ export async function publishCommand(argv, styles, invocationDir) {
46
47
  console.info('Nothing to publish.');
47
48
  return;
48
49
  }
50
+ try {
51
+ assertTarballsComplete(publishableTags.map(({ workspacePath }) => workspacePath), userConfig?.devExportConditions ?? []);
52
+ }
53
+ catch (error) {
54
+ reportError(describeError(error));
55
+ process.exit(1);
56
+ }
49
57
  const packageManager = detectPackageManager();
50
58
  const { releaseNotes, changelogJsonOutputPath, sectionOrder } = deriveReleaseNotesConfig(userConfig);
51
59
  const shouldInject = releaseNotes.shouldInjectIntoReadme;
@@ -16,21 +16,22 @@ The two paths differ in what an absent file means. An absent default path means
16
16
 
17
17
  ## `ReleaseKitConfig` reference
18
18
 
19
- | Field | Type | Description |
20
- | ------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21
- | `releases` | `Record<string, ReleaseEntry>` | Names, excludes, or composes release targets; see [`releases`](#releases). Can't be combined with `workspaces` or `project` |
22
- | `workspaces` | `WorkspaceOverride[]` | Override or exclude discovered workspaces (matched by `dir`) |
23
- | `formatCommand` | `string` | Shell command to run after changelog generation; modified file paths are appended as arguments |
24
- | `versionPatterns` | `VersionPatterns` | Rules for which commit types trigger major/minor bumps |
25
- | `scopeAliases` | `Record<string, string>` | Maps shorthand scope names to canonical names in commit titles and in change-record entry `scopes`, which [route items to workspaces](changelogs.md#routing-to-workspaces). A renamed workspace directory or [release target](#releases) needs an entry from its old name to its new one |
26
- | `workTypes` | `Record<string, WorkTypeConfig>` | Work type definitions, merged with defaults by key |
27
- | `breakingPolicies` | `Record<string, 'forbidden' \| 'optional'>` | Per-type `!`-policy lookup. Defaults to `DEFAULT_BREAKING_POLICIES`. Replaces the default entirely when provided. Set to `{}` to disable enforcement |
28
- | `retiredPackages` | `RetiredPackage[]` | Packages that once lived in this repo but have been extracted or removed; suppresses undeclared-tag-prefix warnings |
29
- | `project` | `ProjectConfig` | Opt-in project-level release block. Declaring `project: {}` (even empty) enables a project-release stage in `prepare` |
30
- | `repoLabels` | `RepoLabelsConfig` | Declares the repository's label registry for `sync-labels`; see [Label configuration](labels.md#label-configuration) |
31
- | `changelogJson` | `ChangelogJsonConfig` | Controls `.meta/changelog.json`: `enabled` (default `true`), `outputPath` (default `'.meta/changelog.json'`), and `devOnlySections`; see [Custom work types](work-types.md#custom-work-types) |
32
- | `changelogMarkdown` | `ChangelogMarkdownConfig` | `keptVersionCount` (an integer, at least 1) bounds `CHANGELOG.md` to that many of the newest versions and moves the rest into per-major archive files; omitted, `CHANGELOG.md` renders every version. See [Archived versions](changelogs.md#archived-versions) |
33
- | `releaseNotes` | `ReleaseNotesConfig` | `shouldInjectIntoReadme` (default `false`) injects release notes into each package's README while it publishes; see [Release-notes injection](changelogs.md#release-notes-injection) |
19
+ | Field | Type | Description |
20
+ | --------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21
+ | `releases` | `Record<string, ReleaseEntry>` | Names, excludes, or composes release targets; see [`releases`](#releases). Can't be combined with `workspaces` or `project` |
22
+ | `workspaces` | `WorkspaceOverride[]` | Override or exclude discovered workspaces (matched by `dir`) |
23
+ | `devExportConditions` | `string[]` | Export conditions that resolve only inside the repo, such as one that points at unpublished source; the [tarball check](releasing.md#packages-whose-tarball-would-lack-a-manifest-file) does not require their targets in the tarball. `source` is exempt without being listed |
24
+ | `formatCommand` | `string` | Shell command to run after changelog generation; modified file paths are appended as arguments |
25
+ | `versionPatterns` | `VersionPatterns` | Rules for which commit types trigger major/minor bumps |
26
+ | `scopeAliases` | `Record<string, string>` | Maps shorthand scope names to canonical names in commit titles and in change-record entry `scopes`, which [route items to workspaces](changelogs.md#routing-to-workspaces). A renamed workspace directory or [release target](#releases) needs an entry from its old name to its new one |
27
+ | `workTypes` | `Record<string, WorkTypeConfig>` | Work type definitions, merged with defaults by key |
28
+ | `breakingPolicies` | `Record<string, 'forbidden' \| 'optional'>` | Per-type `!`-policy lookup. Defaults to `DEFAULT_BREAKING_POLICIES`. Replaces the default entirely when provided. Set to `{}` to disable enforcement |
29
+ | `retiredPackages` | `RetiredPackage[]` | Packages that once lived in this repo but have been extracted or removed; suppresses undeclared-tag-prefix warnings |
30
+ | `project` | `ProjectConfig` | Opt-in project-level release block. Declaring `project: {}` (even empty) enables a project-release stage in `prepare` |
31
+ | `repoLabels` | `RepoLabelsConfig` | Declares the repository's label registry for `sync-labels`; see [Label configuration](labels.md#label-configuration) |
32
+ | `changelogJson` | `ChangelogJsonConfig` | Controls `.meta/changelog.json`: `enabled` (default `true`), `outputPath` (default `'.meta/changelog.json'`), and `devOnlySections`; see [Custom work types](work-types.md#custom-work-types) |
33
+ | `changelogMarkdown` | `ChangelogMarkdownConfig` | `keptVersionCount` (an integer, at least 1) bounds `CHANGELOG.md` to that many of the newest versions and moves the rest into per-major archive files; omitted, `CHANGELOG.md` renders every version. See [Archived versions](changelogs.md#archived-versions) |
34
+ | `releaseNotes` | `ReleaseNotesConfig` | `shouldInjectIntoReadme` (default `false`) injects release notes into each package's README while it publishes; see [Release-notes injection](changelogs.md#release-notes-injection) |
34
35
 
35
36
  All fields are optional.
36
37
 
@@ -23,6 +23,10 @@ Example warning when an explicit tag is unpublishable:
23
23
  Skipping basic-v1.0.0 (packages/basic): package.json#private is true.
24
24
  ```
25
25
 
26
+ ### Tarball check
27
+
28
+ Before it publishes any package, and in a dry run as well, `publish` packs every publishable package from a clean checkout of HEAD and refuses when a tarball lacks a file that its manifest names. It is the same check that `prepare` runs; see [Packages whose tarball would lack a manifest file](releasing.md#packages-whose-tarball-would-lack-a-manifest-file).
29
+
26
30
  ## `release-kit create-github-release`
27
31
 
28
32
  Creates GitHub Releases from `changelog.json` for tags on HEAD. Independent of `npm publish`: Invoking this command creates Releases for publishable packages whether or not they were published to a registry. Private workspaces are skipped, and a project tag gets a Release (see [Publishability filter](#publishability-filter)).
package/docs/releasing.md CHANGED
@@ -63,6 +63,12 @@ release-kit prepare --bump=minor
63
63
 
64
64
  Before it writes any file, and in a dry run as well, `prepare` asks npm about each publishable package that the release includes, querying the registry that the package's `publishConfig.registry` names when it sets one. OIDC trusted publishing can't publish a package's first version, so `prepare` refuses to release a package that npm doesn't list, and names each one with its fix: Run `npm publish --access public` from the package's directory once, then register the package as a trusted publisher. The `npm-auto-publish` [readiness kit](readiness-checks.md) prints the `npm trust` command. `prepare` also refuses when it can't look a package up, such as when the registry is unreachable, and reports npm's error for that package.
65
65
 
66
+ ### Packages whose tarball would lack a manifest file
67
+
68
+ Before it writes any file, and in a dry run as well, `prepare` packs each publishable package that the release includes as the publish workflow would: It clones HEAD into a temporary directory, runs the package manager's frozen install there, runs the package's `prepublishOnly` script when it declares one, and packs the package. Build output in the working tree therefore does not count, and the check adds the time of one install and one build to every release. `prepare` refuses the release when a tarball lacks a file named by the package's `main`, `types`, `bin`, or `exports`, and names each package with each missing path and the manifest field that names it. The usual fix is to build the files in the package's publish lifecycle, such as in a `prepack` script. A failed install or build in the clean checkout also refuses the release, with the failing step's output.
69
+
70
+ A target under the `source` export condition is exempt, and so is a target under a condition listed in [`devExportConditions`](configuration.md#releasekitconfig-reference), which declares the conditions that resolve only inside the repo. [`publish`](publishing.md#tarball-check) runs the same check before it publishes any package.
71
+
66
72
  ### Previewing release notes with `--with-release-notes`
67
73
 
68
74
  `--with-release-notes` writes two versioned files per workspace after each workspace's `changelog.json` is produced:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/release-kit",
3
- "version": "16.0.0",
3
+ "version": "16.1.1",
4
4
  "description": "Version-bumping and changelog-generation toolkit for release workflows",
5
5
  "keywords": [
6
6
  "changelog",
@@ -21,7 +21,7 @@
21
21
  "license": "ISC",
22
22
  "author": "William Thorsen <william@thorsen.dev> (https://github.com/williamthorsen)",
23
23
  "sideEffects": [
24
- "./dist/esm/bin/*.js"
24
+ "./dist/esm/cli.js"
25
25
  ],
26
26
  "type": "module",
27
27
  "exports": {
@@ -52,10 +52,10 @@
52
52
  ],
53
53
  "dependencies": {
54
54
  "@williamthorsen/change-grammar": "0.3.0",
55
- "@williamthorsen/nmr-core": "0.14.0",
55
+ "@williamthorsen/nmr-core": "0.15.0",
56
56
  "@williamthorsen/toolbelt.errors": "0.9.0",
57
57
  "json-stringify-pretty-compact": "4.0.0",
58
- "semver": "7.8.5",
58
+ "semver": "7.8.6",
59
59
  "yaml": "2.9.1",
60
60
  "zod": "4.6.7"
61
61
  },
@@ -63,12 +63,12 @@
63
63
  "@types/semver": "7.8.0",
64
64
  "@williamthorsen/toolbelt.filesystem": "0.10.0",
65
65
  "@williamthorsen/toolbelt.testing": "0.8.0",
66
- "@williamthorsen/toolbelt.vitest": "0.13.0",
66
+ "@williamthorsen/toolbelt.vitest": "0.13.1",
67
67
  "actionlint": "2.0.6",
68
68
  "esbuild": "0.28.2",
69
- "readyup": "0.44.0",
69
+ "readyup": "0.45.0",
70
70
  "smol-toml": "1.9.1",
71
- "vitest": "5.0.5"
71
+ "vitest": "5.1.0"
72
72
  },
73
73
  "engines": {
74
74
  "node": ">=24.16.0"
@@ -9,6 +9,9 @@
9
9
  - name: bug
10
10
  color: d73a4a
11
11
  description: "Something isn't working"
12
+ - name: content
13
+ color: 0e8a16
14
+ description: Consumer-facing material, such as copy, images, or displayed data, rather than functionality
12
15
  - name: operational
13
16
  color: '000000'
14
17
  description: Actions outside the codebase
File without changes