@williamthorsen/release-kit 8.0.0 → 8.1.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.
- package/CHANGELOG.md +22 -0
- package/README.md +47 -11
- package/dist/esm/bin/release-kit.js +4 -2
- package/dist/esm/buildChangelogEntries.js +2 -2
- package/dist/esm/changelogOverrides.js +4 -3
- package/dist/esm/createGithubRelease.js +2 -2
- package/dist/esm/createGithubReleaseCommand.js +11 -1
- package/dist/esm/detectPackageManager.js +1 -1
- package/dist/esm/format.js +2 -2
- package/dist/esm/formatPrivateSkip.d.ts +2 -0
- package/dist/esm/formatPrivateSkip.js +3 -0
- package/dist/esm/getCommitsSinceTarget.js +1 -1
- package/dist/esm/injectReleaseNotesIntoReadme.js +1 -1
- package/dist/esm/prepareCommand.js +7 -6
- package/dist/esm/previewTagPrefixes.js +2 -5
- package/dist/esm/publishCommand.js +4 -4
- package/dist/esm/releasePrepareMono.js +6 -5
- package/dist/esm/renderReleaseNotes.js +2 -2
- package/dist/esm/resolveCommandTags.js +4 -3
- package/dist/esm/sync-labels/generateCommand.js +1 -1
- package/dist/esm/sync-labels/templates.js +27 -3
- package/dist/esm/writeReleaseNotesPreviews.js +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 8.1.0 — 2026-07-22
|
|
6
|
+
|
|
7
|
+
### 🎉 Features
|
|
8
|
+
|
|
9
|
+
- Make label deletions reviewable and apply labels on merge (#493)
|
|
10
|
+
|
|
11
|
+
Pull requests that touch a repository's label file now get a check listing every label change the sync would make, including deletions of labels the file never mentions, which a diff of the file cannot show. Merging the pull request now applies those labels. The sync's deletion and rename handling now changes only through a reviewed upgrade to a verified release. Repositories that already scaffolded this workflow adopt the new triggers by re-running `release-kit sync-labels init --force`.
|
|
12
|
+
|
|
13
|
+
### 📦 Dependencies
|
|
14
|
+
|
|
15
|
+
- Upgrade dependencies and align the Node support policy (#483)
|
|
16
|
+
|
|
17
|
+
All four published packages (`nmr`, `nmr-core`, `release-kit`, and `v11y-check`) now require Node.js 24 or later, up from Node 18.17. Separately, `nmr-compile` now rebuilds when the TypeScript version changes.
|
|
18
|
+
|
|
19
|
+
## 8.0.1 — 2026-07-18
|
|
20
|
+
|
|
21
|
+
### 🐛 Bug fixes
|
|
22
|
+
|
|
23
|
+
- Skip private packages cleanly in publish and create-github-release (#477)
|
|
24
|
+
|
|
25
|
+
Fixes an issue where a workspace marked private in its `package.json` could break a release: `release-kit publish` failed on it, and `release-kit create-github-release` created an unwanted public GitHub Release for it. Both commands now skip private workspaces cleanly: publish reports the skip and continues instead of failing the run, and create-github-release produces no Release for a private package. Any publishable packages released alongside it still publish and still get their Releases. Marking a workspace private is now all it takes to exclude it from both publishing and GitHub Releases.
|
|
26
|
+
|
|
5
27
|
## 8.0.0 — 2026-07-13
|
|
6
28
|
|
|
7
29
|
### 🎉 Features
|
package/README.md
CHANGED
|
@@ -5,19 +5,19 @@ 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 — v8.
|
|
8
|
+
## Release notes — v8.1.0 (2026-07-22)
|
|
9
9
|
|
|
10
10
|
### 🎉 Features
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- Make label deletions reviewable and apply labels on merge (#493)
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
A package that ships working declarations found only by sitting beside its JavaScript entry point escapes the check, since it declares nothing. Add a `types` entry to bring it under the check.
|
|
14
|
+
Pull requests that touch a repository's label file now get a check listing every label change the sync would make, including deletions of labels the file never mentions, which a diff of the file cannot show. Merging the pull request now applies those labels. The sync's deletion and rename handling now changes only through a reviewed upgrade to a verified release. Repositories that already scaffolded this workflow adopt the new triggers by re-running `release-kit sync-labels init --force`.
|
|
17
15
|
<!-- /section:release-notes -->
|
|
18
16
|
|
|
19
17
|
## Installation
|
|
20
18
|
|
|
19
|
+
Requires Node.js 24 or later.
|
|
20
|
+
|
|
21
21
|
```bash
|
|
22
22
|
pnpm add -D @williamthorsen/release-kit
|
|
23
23
|
```
|
|
@@ -626,22 +626,24 @@ Publish packages that have release tags on HEAD. The publish workflow's reusable
|
|
|
626
626
|
|
|
627
627
|
#### Publishability filter
|
|
628
628
|
|
|
629
|
-
`publish`
|
|
629
|
+
`publish` and `create-github-release` operate only on workspaces where `package.json#private` is absent or `false`. A workspace marked `private: true` is "versioned but not published": it can still be tagged by `release-kit tag` and get a `CHANGELOG.md` entry, but it is skipped by both `release-kit publish` (no registry publish) and `release-kit create-github-release` (no GitHub Release). `private: true` alone routes a workspace out of publish and announce — no extra release config is required. Other commands ignore this filter and operate on private workspaces unchanged.
|
|
630
630
|
|
|
631
|
-
|
|
631
|
+
An unpublishable tag is always skipped cleanly, never fatal. For `release-kit publish`, whether the skip is announced depends on how the tag was resolved:
|
|
632
632
|
|
|
633
633
|
- **Without `--tags`** (implicit resolution): unpublishable tags on HEAD are silently filtered. The pre-publish listing shows only the publishable subset. If the filter empties the set, `release-kit publish` prints `Nothing to publish.` and exits 0.
|
|
634
|
-
- **With `--tags`** (explicit naming):
|
|
634
|
+
- **With `--tags`** (explicit naming): each named tag pointing at an unpublishable workspace is skipped with a warning, and any publishable tags named in the same command still publish.
|
|
635
|
+
|
|
636
|
+
`release-kit create-github-release` skips unpublishable workspaces with a warning regardless of `--tags`; an all-private tag set is a clean no-op.
|
|
635
637
|
|
|
636
|
-
Example
|
|
638
|
+
Example warning when an explicit tag is unpublishable:
|
|
637
639
|
|
|
638
640
|
```
|
|
639
|
-
|
|
641
|
+
Skipping basic-v1.0.0 (packages/basic): package.json#private is true.
|
|
640
642
|
```
|
|
641
643
|
|
|
642
644
|
### `release-kit create-github-release`
|
|
643
645
|
|
|
644
|
-
Create GitHub Releases from `changelog.json` for tags on HEAD. Independent of `npm publish`: invoking this command creates Releases
|
|
646
|
+
Create 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 (see [Publishability filter](#publishability-filter)).
|
|
645
647
|
|
|
646
648
|
| Flag | Description |
|
|
647
649
|
| ---------------------- | ------------------------------------------------------------------------- |
|
|
@@ -728,6 +730,40 @@ Manage GitHub label definitions via config-driven YAML files.
|
|
|
728
730
|
|
|
729
731
|
`init` scaffolds `.config/sync-labels.config.ts` with auto-detected workspace scope labels and a `.github/workflows/sync-labels.yaml` caller workflow, then generates `.github/labels.yaml`. `generate` reads the config and writes `.github/labels.yaml`. `sync` triggers the workflow remotely — it requires the `gh` CLI and an existing workflow file.
|
|
730
732
|
|
|
733
|
+
#### When labels are applied
|
|
734
|
+
|
|
735
|
+
The scaffolded workflow carries three triggers:
|
|
736
|
+
|
|
737
|
+
| Trigger | Job | Token | Effect |
|
|
738
|
+
| --------------------------------------------------------- | ------- | --------------- | --------------------------------- |
|
|
739
|
+
| Push to the default branch touching `.github/labels.yaml` | `sync` | `issues: write` | Applies the labels |
|
|
740
|
+
| Manual dispatch (`release-kit sync-labels sync`) | `sync` | `issues: write` | Applies the labels |
|
|
741
|
+
| Pull request touching `.github/labels.yaml` | `check` | `issues: read` | Logs the diff without applying it |
|
|
742
|
+
|
|
743
|
+
Applying on merge closes the window in which a regenerated `.github/labels.yaml` sits unapplied — a window in which a later manual dispatch would apply a label set nobody reviewed.
|
|
744
|
+
|
|
745
|
+
The `check` job runs the same sync in dry-run. Its **Label diff** log group lists every create, edit, rename, and deletion the sync would perform, including deletions of labels the file does not declare — the destructive edits a diff of `.github/labels.yaml` alone cannot show, because a label created by hand or by another workflow is absent from the file both before and after. Read that log; the check reports success whether or not the diff is destructive, so a green check is not evidence that nothing will be deleted.
|
|
746
|
+
|
|
747
|
+
The two jobs are split because a job's permissions are fixed when the run is created and cannot vary by trigger. The split is what keeps a write-scoped token out of pull-request runs.
|
|
748
|
+
|
|
749
|
+
The push trigger filters on the path alone; the `sync` job compares `github.ref_name` against the repository's default branch. The workflow therefore needs no per-repo edit whatever that branch is named — and a push touching `.github/labels.yaml` on any other branch produces a run whose jobs all skip.
|
|
750
|
+
|
|
751
|
+
Manual dispatch is not a preview. It matches the `sync` job, so it applies the labels, deletions included; only the pull-request path runs in dry-run.
|
|
752
|
+
|
|
753
|
+
#### Dry-run checks on fork pull requests
|
|
754
|
+
|
|
755
|
+
GitHub issues a read-only `GITHUB_TOKEN` to pull requests from forks, and by default holds runs from first-time contributors until a maintainer approves them. The dry-run reads labels and writes nothing, so the check normally runs once approved. A repo that disables workflows on fork pull requests gets no check at all; to preview such a change, push the branch to the base repo and open the pull request from there.
|
|
756
|
+
|
|
757
|
+
#### Migrating an existing repo
|
|
758
|
+
|
|
759
|
+
The caller workflow gained the `push` and `pull_request` triggers after its first release. A repo scaffolded before that keeps working on manual dispatch alone — nothing breaks — but it applies labels only when someone remembers to. To adopt the new shape:
|
|
760
|
+
|
|
761
|
+
```sh
|
|
762
|
+
pnpm exec release-kit sync-labels init --force
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
`readyup` prompts for this: its `sync-labels.yaml matches template` check fails once the scaffolded workflow no longer matches the current template.
|
|
766
|
+
|
|
731
767
|
#### Published JSON Schema for `.meta/label-map.json`
|
|
732
768
|
|
|
733
769
|
release-kit publishes a JSON Schema for `.meta/label-map.json` — a separate, generic data file that maps commit-prefix scopes and types to GitHub label names. The schema lives at `packages/release-kit/schemas/label-map.json` in this repo and is reachable via the stable raw URL:
|
|
@@ -142,7 +142,8 @@ function showCreateGithubReleaseHelp() {
|
|
|
142
142
|
console.info(`
|
|
143
143
|
Usage: release-kit create-github-release [options]
|
|
144
144
|
|
|
145
|
-
Create GitHub Releases from changelog.json for tags on HEAD.
|
|
145
|
+
Create GitHub Releases from changelog.json for tags on HEAD. Private
|
|
146
|
+
(package.json#private) workspaces are skipped with a warning and get no Release.
|
|
146
147
|
|
|
147
148
|
Options:
|
|
148
149
|
--dry-run Preview without creating releases
|
|
@@ -241,7 +242,8 @@ Usage: release-kit publish [options]
|
|
|
241
242
|
|
|
242
243
|
Publish packages that have release tags on HEAD. Operates only on workspaces where
|
|
243
244
|
package.json#private is absent or false. Without --tags, unpublishable workspaces are
|
|
244
|
-
silently filtered out. With --tags,
|
|
245
|
+
silently filtered out. With --tags, an unpublishable tag is skipped with a warning while
|
|
246
|
+
any publishable tags still publish.
|
|
245
247
|
|
|
246
248
|
Options:
|
|
247
249
|
--dry-run Preview without publishing
|
|
@@ -9,7 +9,7 @@ const HTML_COMMENT_PREFIX_PATTERN = /^<!--[^>]*-->/;
|
|
|
9
9
|
const CANONICAL_SECTION_ORDER = new Map(Object.values(DEFAULT_WORK_TYPES).map((config, index) => [stripGroupDecorations(config.header), index]));
|
|
10
10
|
function canonicalSectionPriority(title) {
|
|
11
11
|
const index = CANONICAL_SECTION_ORDER.get(stripGroupDecorations(title));
|
|
12
|
-
return index ??
|
|
12
|
+
return index ?? Infinity;
|
|
13
13
|
}
|
|
14
14
|
export function stripGroupDecorations(group) {
|
|
15
15
|
return stripEmojiPrefix(group.replace(HTML_COMMENT_PREFIX_PATTERN, ''));
|
|
@@ -132,7 +132,7 @@ function subjectHasBreakingMarker(message) {
|
|
|
132
132
|
return /^(?:[^|]+\|)?\w+(?:\([^)]+\))?!:/.test(subject);
|
|
133
133
|
}
|
|
134
134
|
function extractDescription(message) {
|
|
135
|
-
const firstLine = message.split('\n')[0] ?? message;
|
|
135
|
+
const firstLine = message.split('\n', 1)[0] ?? message;
|
|
136
136
|
const afterColon = firstLine.split(': ').slice(1).join(': ');
|
|
137
137
|
if (afterColon.length > 0) {
|
|
138
138
|
return afterColon.charAt(0).toUpperCase() + afterColon.slice(1);
|
|
@@ -59,10 +59,11 @@ function validateSingleOverride(key, rawEntry, errors) {
|
|
|
59
59
|
}
|
|
60
60
|
let entryValid = true;
|
|
61
61
|
for (const fieldName of Object.keys(rawEntry)) {
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
entryValid = false;
|
|
62
|
+
if (KNOWN_OVERRIDE_FIELDS.has(fieldName)) {
|
|
63
|
+
continue;
|
|
65
64
|
}
|
|
65
|
+
errors.push(`overrides['${key}']: unknown field '${fieldName}'`);
|
|
66
|
+
entryValid = false;
|
|
66
67
|
}
|
|
67
68
|
const result = {};
|
|
68
69
|
if (rawEntry.audience !== undefined) {
|
|
@@ -26,7 +26,7 @@ export function createGithubRelease(options) {
|
|
|
26
26
|
const body = renderReleaseNotesSingle(entry, {
|
|
27
27
|
filter: matchesAudience('all'),
|
|
28
28
|
includeHeading: false,
|
|
29
|
-
...(sectionOrder
|
|
29
|
+
...(sectionOrder !== undefined && { sectionOrder }),
|
|
30
30
|
});
|
|
31
31
|
if (body.trim() === '') {
|
|
32
32
|
return { status: 'skipped', reason: 'empty-body' };
|
|
@@ -48,7 +48,7 @@ export function createGithubReleases(tags, changelogJsonOutputPath, dryRun, sect
|
|
|
48
48
|
tag,
|
|
49
49
|
changelogJsonPath,
|
|
50
50
|
dryRun,
|
|
51
|
-
...(sectionOrder
|
|
51
|
+
...(sectionOrder !== undefined && { sectionOrder }),
|
|
52
52
|
});
|
|
53
53
|
if (result.status === 'created') {
|
|
54
54
|
created.push(tag);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { createGithubReleases } from "./createGithubRelease.js";
|
|
3
|
+
import { formatPrivateSkip } from "./formatPrivateSkip.js";
|
|
3
4
|
import { parseRequestedTags } from "./parseRequestedTags.js";
|
|
4
5
|
import { resolveCommandTags } from "./resolveCommandTags.js";
|
|
5
6
|
import { resolveReleaseNotesConfig } from "./resolveReleaseNotesConfig.js";
|
|
@@ -12,10 +13,19 @@ export async function createGithubReleaseCommand(argv) {
|
|
|
12
13
|
const { dryRun } = parsed.flags;
|
|
13
14
|
const requestedTags = parseRequestedTags(parsed.flags.tags);
|
|
14
15
|
const resolvedTags = await resolveCommandTags(requestedTags);
|
|
16
|
+
const publishableTags = resolvedTags.filter((resolvedTag) => resolvedTag.isPublishable);
|
|
17
|
+
for (const resolvedTag of resolvedTags) {
|
|
18
|
+
if (!resolvedTag.isPublishable) {
|
|
19
|
+
console.warn(formatPrivateSkip(resolvedTag));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (publishableTags.length === 0) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
15
25
|
const { changelogJsonOutputPath, sectionOrder } = await resolveReleaseNotesConfig({ strictLoad: true });
|
|
16
26
|
let outcome;
|
|
17
27
|
try {
|
|
18
|
-
outcome = createGithubReleases(
|
|
28
|
+
outcome = createGithubReleases(publishableTags, changelogJsonOutputPath, dryRun, sectionOrder);
|
|
19
29
|
}
|
|
20
30
|
catch (error) {
|
|
21
31
|
reportError(`Failed to create GitHub Releases: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -7,7 +7,7 @@ export function detectPackageManager() {
|
|
|
7
7
|
const content = readFileSync(packageJsonPath, 'utf8');
|
|
8
8
|
const parsed = JSON.parse(content);
|
|
9
9
|
if (isRecord(parsed) && typeof parsed.packageManager === 'string') {
|
|
10
|
-
const [name, version] = parsed.packageManager.split('@');
|
|
10
|
+
const [name, version] = parsed.packageManager.split('@', 2);
|
|
11
11
|
if (name === 'pnpm' || name === 'npm') {
|
|
12
12
|
return name;
|
|
13
13
|
}
|
package/dist/esm/format.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export function bold(text) {
|
|
2
|
-
return `\
|
|
2
|
+
return `\u{1B}[1m${text}\u{1B}[0m`;
|
|
3
3
|
}
|
|
4
4
|
export function dim(text) {
|
|
5
|
-
return `\
|
|
5
|
+
return `\u{1B}[2m${text}\u{1B}[0m`;
|
|
6
6
|
}
|
|
7
7
|
export function sectionHeader(name) {
|
|
8
8
|
return `━━━ ${name} ━━━`;
|
|
@@ -22,7 +22,7 @@ export function renderInjectedReadme(readme, changelogJsonPath, tag, sectionOrde
|
|
|
22
22
|
const renderedSections = renderReleaseNotesSingle(entry, {
|
|
23
23
|
filter: matchesAudience('all'),
|
|
24
24
|
includeHeading: false,
|
|
25
|
-
...(sectionOrder
|
|
25
|
+
...(sectionOrder !== undefined && { sectionOrder }),
|
|
26
26
|
});
|
|
27
27
|
if (renderedSections.trimEnd().length === 0) {
|
|
28
28
|
console.warn(`Warning: no user-facing release notes for version ${version}; skipping README injection`);
|
|
@@ -85,9 +85,9 @@ export async function prepareCommand(argv) {
|
|
|
85
85
|
const options = {
|
|
86
86
|
dryRun,
|
|
87
87
|
force,
|
|
88
|
-
...(bumpOverride
|
|
89
|
-
...(setVersion
|
|
90
|
-
...(withReleaseNotes
|
|
88
|
+
...(bumpOverride !== undefined && { bumpOverride }),
|
|
89
|
+
...(setVersion !== undefined && { setVersion }),
|
|
90
|
+
...(withReleaseNotes && { withReleaseNotes: true }),
|
|
91
91
|
};
|
|
92
92
|
if (dryRun) {
|
|
93
93
|
console.info('\n🔍 DRY RUN — no files will be modified\n');
|
|
@@ -155,10 +155,11 @@ function runMonorepoMode(discoveredPaths, userConfig, options, only, setVersion,
|
|
|
155
155
|
if (only !== undefined) {
|
|
156
156
|
const knownNames = config.workspaces.map((w) => w.dir);
|
|
157
157
|
for (const name of only) {
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
process.exit(1);
|
|
158
|
+
if (knownNames.includes(name)) {
|
|
159
|
+
continue;
|
|
161
160
|
}
|
|
161
|
+
reportError(`Unknown workspace "${name}". Known workspaces: ${knownNames.join(', ')}`);
|
|
162
|
+
process.exit(1);
|
|
162
163
|
}
|
|
163
164
|
const graph = buildDependencyGraph(config.workspaces);
|
|
164
165
|
const violations = validateOnlyExcludesStrandedDependents(config.workspaces, only, graph, (workspace) => {
|
|
@@ -9,10 +9,7 @@ export async function previewTagPrefixes() {
|
|
|
9
9
|
const workspacePaths = (await discoverWorkspaces()) ?? [];
|
|
10
10
|
const userConfig = await loadUserConfig();
|
|
11
11
|
const overridesByDir = buildOverrideMap(userConfig);
|
|
12
|
-
const workspaces =
|
|
13
|
-
for (const workspacePath of workspacePaths) {
|
|
14
|
-
workspaces.push(buildPreviewRow(workspacePath, overridesByDir));
|
|
15
|
-
}
|
|
12
|
+
const workspaces = Array.from(workspacePaths, (workspacePath) => buildPreviewRow(workspacePath, overridesByDir));
|
|
16
13
|
const retiredPackages = buildRetiredPreviewEntries(userConfig?.retiredPackages ?? []);
|
|
17
14
|
const collisions = detectCollisions(workspaces);
|
|
18
15
|
const knownPrefixes = collectKnownPrefixes(workspaces, retiredPackages);
|
|
@@ -120,6 +117,6 @@ function buildRetiredPreviewEntries(retiredPackages) {
|
|
|
120
117
|
name: retired.name,
|
|
121
118
|
tagPrefix: retired.tagPrefix,
|
|
122
119
|
tagCount: countTagsMatching(retired.tagPrefix),
|
|
123
|
-
...(retired.successor !== undefined
|
|
120
|
+
...(retired.successor !== undefined && { successor: retired.successor }),
|
|
124
121
|
}));
|
|
125
122
|
}
|
|
@@ -3,6 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
5
5
|
import { detectPackageManager } from "./detectPackageManager.js";
|
|
6
|
+
import { formatPrivateSkip } from "./formatPrivateSkip.js";
|
|
6
7
|
import { injectReleaseNotesIntoReadme, resolveReadmePath } from "./injectReleaseNotesIntoReadme.js";
|
|
7
8
|
import { parseRequestedTags } from "./parseRequestedTags.js";
|
|
8
9
|
import { publishPackage } from "./publish.js";
|
|
@@ -82,11 +83,10 @@ function filterPublishableTags(resolvedTags, isExplicit) {
|
|
|
82
83
|
for (const tag of resolvedTags) {
|
|
83
84
|
(tag.isPublishable ? publishable : unpublishable).push(tag);
|
|
84
85
|
}
|
|
85
|
-
if (isExplicit
|
|
86
|
-
for (const
|
|
87
|
-
|
|
86
|
+
if (isExplicit) {
|
|
87
|
+
for (const resolvedTag of unpublishable) {
|
|
88
|
+
console.warn(formatPrivateSkip(resolvedTag));
|
|
88
89
|
}
|
|
89
|
-
process.exit(1);
|
|
90
90
|
}
|
|
91
91
|
return publishable;
|
|
92
92
|
}
|
|
@@ -95,8 +95,8 @@ export function releasePrepareMono(config, options) {
|
|
|
95
95
|
tags,
|
|
96
96
|
formatCommand,
|
|
97
97
|
dryRun,
|
|
98
|
-
...(allWarnings.length > 0
|
|
99
|
-
...(project
|
|
98
|
+
...(allWarnings.length > 0 && { warnings: allWarnings }),
|
|
99
|
+
...(project !== undefined && { project }),
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
function determineDirectBumps(config, options) {
|
|
@@ -487,10 +487,11 @@ function topologicalSort(releaseSet, graph) {
|
|
|
487
487
|
const sortedSet = new Set(sorted);
|
|
488
488
|
const cyclicDirs = [];
|
|
489
489
|
for (const dir of releaseDirs) {
|
|
490
|
-
if (
|
|
491
|
-
|
|
492
|
-
cyclicDirs.push(dir);
|
|
490
|
+
if (sortedSet.has(dir)) {
|
|
491
|
+
continue;
|
|
493
492
|
}
|
|
493
|
+
sorted.push(dir);
|
|
494
|
+
cyclicDirs.push(dir);
|
|
494
495
|
}
|
|
495
496
|
return { sorted, cyclicDirs };
|
|
496
497
|
}
|
|
@@ -50,8 +50,8 @@ function sortSectionsByOrder(sections, order) {
|
|
|
50
50
|
}
|
|
51
51
|
const indexed = sections.map((section, index) => ({ section, index }));
|
|
52
52
|
indexed.sort((a, b) => {
|
|
53
|
-
const priorityA = priority.get(a.section.title) ??
|
|
54
|
-
const priorityB = priority.get(b.section.title) ??
|
|
53
|
+
const priorityA = priority.get(a.section.title) ?? Infinity;
|
|
54
|
+
const priorityB = priority.get(b.section.title) ?? Infinity;
|
|
55
55
|
if (priorityA !== priorityB) {
|
|
56
56
|
return priorityA - priorityB;
|
|
57
57
|
}
|
|
@@ -42,10 +42,11 @@ export async function resolveCommandTags(tags) {
|
|
|
42
42
|
if (tags !== undefined) {
|
|
43
43
|
const availableTagNames = resolvedTags.map((t) => t.tag);
|
|
44
44
|
for (const name of tags) {
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
process.exit(1);
|
|
45
|
+
if (availableTagNames.includes(name)) {
|
|
46
|
+
continue;
|
|
48
47
|
}
|
|
48
|
+
reportError(`Unknown tag "${name}" in --tags. Available: ${availableTagNames.join(', ')}`);
|
|
49
|
+
process.exit(1);
|
|
49
50
|
}
|
|
50
51
|
resolvedTags = resolvedTags.filter((t) => tags.includes(t.tag));
|
|
51
52
|
}
|
|
@@ -8,7 +8,7 @@ import { resolveLabels } from "./resolveLabels.js";
|
|
|
8
8
|
export const LABELS_OUTPUT_PATH = '.github/labels.yaml';
|
|
9
9
|
export function formatLabelsYaml(labels, presetHashes) {
|
|
10
10
|
const headerLines = ['# Generated by release-kit sync-labels — do not edit.', `# Source: ${SYNC_LABELS_CONFIG_PATH}`];
|
|
11
|
-
for (const [name, hash] of [...presetHashes
|
|
11
|
+
for (const [name, hash] of [...presetHashes].sort(([a], [b]) => a.localeCompare(b))) {
|
|
12
12
|
headerLines.push(`# ${name} preset hash: ${hash}`);
|
|
13
13
|
}
|
|
14
14
|
const yamlBody = stringify(labels, { singleQuote: true, lineWidth: 0 });
|
|
@@ -6,13 +6,37 @@ name: Sync labels
|
|
|
6
6
|
on:
|
|
7
7
|
workflow_dispatch:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
# Apply on merge, so a regenerated labels file cannot sit unapplied.
|
|
10
|
+
push:
|
|
11
|
+
paths:
|
|
12
|
+
- .github/labels.yaml
|
|
12
13
|
|
|
14
|
+
# Preview on review: the check's log carries the full computed diff,
|
|
15
|
+
# including deletions of labels the file does not declare.
|
|
16
|
+
pull_request:
|
|
17
|
+
paths:
|
|
18
|
+
- .github/labels.yaml
|
|
19
|
+
|
|
20
|
+
# Permissions are fixed when a run is created and cannot vary by trigger within a job,
|
|
21
|
+
# so applying and previewing are separate jobs and the write token stays out of PR runs.
|
|
13
22
|
jobs:
|
|
23
|
+
# The push arm applies on the default branch alone. A \`branches:\` filter cannot name the
|
|
24
|
+
# default branch, so the gate lives on the job and this file stays identical across repos.
|
|
14
25
|
sync:
|
|
26
|
+
if: github.event_name != 'pull_request' && (github.event_name != 'push' || github.ref_name == github.event.repository.default_branch)
|
|
27
|
+
permissions:
|
|
28
|
+
contents: read
|
|
29
|
+
issues: write
|
|
30
|
+
uses: williamthorsen/node-monorepo-tools/.github/workflows/sync-labels.reusable.yaml@workflow/sync-labels-v1
|
|
31
|
+
|
|
32
|
+
check:
|
|
33
|
+
if: github.event_name == 'pull_request'
|
|
34
|
+
permissions:
|
|
35
|
+
contents: read
|
|
36
|
+
issues: read
|
|
15
37
|
uses: williamthorsen/node-monorepo-tools/.github/workflows/sync-labels.reusable.yaml@workflow/sync-labels-v1
|
|
38
|
+
with:
|
|
39
|
+
dry-run: true
|
|
16
40
|
`;
|
|
17
41
|
}
|
|
18
42
|
export function buildScopeLabels(workspacePaths) {
|
|
@@ -53,7 +53,7 @@ function writePreviewFile(filePath, content, dryRun) {
|
|
|
53
53
|
const result = writeFileWithCheck(filePath, content, { dryRun: false, overwrite: true });
|
|
54
54
|
if (result.outcome === 'failed') {
|
|
55
55
|
reportError(`Failed to write ${filePath}: ${result.error ?? 'unknown error'}`);
|
|
56
|
-
return { filePath, outcome: 'failed', ...(result.error
|
|
56
|
+
return { filePath, outcome: 'failed', ...(result.error !== undefined && { error: result.error }) };
|
|
57
57
|
}
|
|
58
58
|
if (result.outcome === 'created' || result.outcome === 'overwritten') {
|
|
59
59
|
console.info(dim(` Wrote ${filePath}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/release-kit",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.1.0",
|
|
4
4
|
"description": "Version-bumping and changelog-generation toolkit for release workflows",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/williamthorsen/node-monorepo-tools/tree/main/packages/release-kit#readme",
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
"semver": "7.8.5",
|
|
40
40
|
"yaml": "2.9.0",
|
|
41
41
|
"zod": "4.4.3",
|
|
42
|
-
"@williamthorsen/nmr-core": "0.7.
|
|
42
|
+
"@williamthorsen/nmr-core": "0.7.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/semver": "7.7.1",
|
|
46
46
|
"smol-toml": "1.7.0"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
|
-
"node": ">=
|
|
49
|
+
"node": ">=24"
|
|
50
50
|
},
|
|
51
51
|
"publishConfig": {
|
|
52
52
|
"access": "public",
|