@williamthorsen/release-kit 13.0.0 โ 14.0.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/.meta/changelog.json +87 -9
- package/.readyup/kits/default.js +3 -3
- package/.readyup/kits/npm-auto-publish.js +1 -1
- package/.readyup/manifest.json +7 -7
- package/CHANGELOG.md +30 -6
- package/README.md +7 -34
- package/dist/esm/findTransitiveDependents.d.ts +3 -0
- package/dist/esm/findTransitiveDependents.js +20 -0
- package/dist/esm/help/prepareHelp.d.ts +1 -1
- package/dist/esm/help/prepareHelp.js +4 -2
- package/dist/esm/init/templates.js +1 -1
- package/dist/esm/prepareCommand.d.ts +1 -0
- package/dist/esm/prepareCommand.js +39 -61
- package/dist/esm/releasePrepare.d.ts +2 -16
- package/dist/esm/releasePrepareMono.d.ts +17 -2
- package/dist/esm/releasePrepareMono.js +23 -11
- package/dist/esm/sync-labels/generateCommand.js +6 -1
- package/dist/esm/sync-labels/resolveLabels.js +1 -0
- package/dist/esm/sync-labels/types.d.ts +3 -1
- package/dist/esm/types.d.ts +7 -3
- package/dist/esm/types.js +1 -0
- package/docs/labels.md +16 -0
- package/docs/project-releases.md +2 -2
- package/docs/readiness-checks.md +2 -2
- package/docs/releasing.md +11 -9
- package/package.json +8 -8
- package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +0 -37
- package/dist/esm/validateOnlyExcludesStrandedDependents.js +0 -115
package/.meta/changelog.json
CHANGED
|
@@ -1,4 +1,75 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "14.0.0",
|
|
4
|
+
"date": "2026-10-08",
|
|
5
|
+
"sections": [
|
|
6
|
+
{
|
|
7
|
+
"title": "๐ Features",
|
|
8
|
+
"audience": "all",
|
|
9
|
+
"items": [
|
|
10
|
+
{
|
|
11
|
+
"description": "Changes monorepo `--set-version` to take comma-separated `<workspace>@N.N.N` entries, which `prepare` accepts without `--only` and together with `--bump` and `--force`, and which release each named workspace's dependents. (#970)",
|
|
12
|
+
"breaking": true,
|
|
13
|
+
"migration": "Replace `--only arrays --set-version 1.0.0` with `--set-version arrays@1.0.0 --only arrays`, and keep `--only` unless the release should cover every workspace, because a `--set-version` run without `--only` is a full run.",
|
|
14
|
+
"hash": "07e3e99cb03e6031619bd58dac6ee339ceac0e89",
|
|
15
|
+
"entry": 2
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"title": "๐ Bug fixes",
|
|
21
|
+
"audience": "all",
|
|
22
|
+
"items": [
|
|
23
|
+
{
|
|
24
|
+
"description": "Fixes the issue that `release-kit prepare --only` left each dependent of the named workspaces published against the old version range of its dependency, by also releasing every transitive dependent: a dependent with commits of its own at its own bump, and one without at a propagated patch bump. (#970)",
|
|
25
|
+
"breaking": true,
|
|
26
|
+
"migration": "Expect each `--only` run to release the dependents of the named workspaces as well, and remove any dependents that were added to `--only` only to get past the stranded-dependents error, which `prepare` no longer reports.",
|
|
27
|
+
"hash": "07e3e99cb03e6031619bd58dac6ee339ceac0e89",
|
|
28
|
+
"entry": 1
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"title": "๐งช Tests",
|
|
34
|
+
"audience": "dev",
|
|
35
|
+
"items": [
|
|
36
|
+
{
|
|
37
|
+
"description": "Replaces the hand-rolled `console.warn` spies in release-kit's config-warning and push tests with `silenceConsole`, which restores the method when its `using` scope exits. (#964)",
|
|
38
|
+
"hash": "38cc179b4116bcd5aba8371b17b0816150c9e9a3",
|
|
39
|
+
"entry": 4
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"title": "๐ Documentation",
|
|
45
|
+
"audience": "dev",
|
|
46
|
+
"items": [
|
|
47
|
+
{
|
|
48
|
+
"description": "Updates the readyup examples in the `nmr`, `release-kit`, and `v11y-check` READMEs and the `nmr` rulebook to use the `sources` key and `rdy run --sources`, and states that `--sources` needs readyup 0.40 or later. (#977)",
|
|
49
|
+
"hash": "41177f8f562d59a4cf0aff9d24199dbd370bb1a1",
|
|
50
|
+
"entry": 4
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"version": "13.1.0",
|
|
58
|
+
"date": "2026-10-02",
|
|
59
|
+
"sections": [
|
|
60
|
+
{
|
|
61
|
+
"title": "๐ Features",
|
|
62
|
+
"audience": "all",
|
|
63
|
+
"items": [
|
|
64
|
+
{
|
|
65
|
+
"description": "Adds an `archived: true` mark to `repoLabels.labels` entries, which `sync-labels generate` writes to `.github/labels.yaml` without changing the output for unmarked labels. (#955)",
|
|
66
|
+
"hash": "23c0e0eeaf3e59cd921632850c7771ccdd6effe1",
|
|
67
|
+
"entry": 1
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
},
|
|
2
73
|
{
|
|
3
74
|
"version": "13.0.0",
|
|
4
75
|
"date": "2026-10-01",
|
|
@@ -1906,10 +1977,22 @@
|
|
|
1906
1977
|
"audience": "all",
|
|
1907
1978
|
"items": [
|
|
1908
1979
|
{
|
|
1909
|
-
"description": "
|
|
1910
|
-
"body": "
|
|
1911
|
-
"hash": "
|
|
1912
|
-
}
|
|
1980
|
+
"description": "Allow git-cliff to be used without config (#31)",
|
|
1981
|
+
"body": "Adds a `resolveCliffConfigPath()` function that searches for a git-cliff config in a 4-step cascade (explicit path โ `.config/git-cliff.toml` โ `cliff.toml` โ bundled `cliff.toml.template`), eliminating the requirement for consuming repos to maintain a cliff config copy. Restructures the `init` command to scaffold only the workflow file by default, with new `--with-config` and `--force` flags. Moves `.release-tags` from `/tmp/release-kit/` to project-local `tmp/` for predictable behavior in local runs.",
|
|
1982
|
+
"hash": "0cbd2605d6f5f53d1efe3dda8dd0f6e4376b499f"
|
|
1983
|
+
}
|
|
1984
|
+
]
|
|
1985
|
+
}
|
|
1986
|
+
]
|
|
1987
|
+
},
|
|
1988
|
+
{
|
|
1989
|
+
"version": "2.0.0",
|
|
1990
|
+
"date": "2026-03-16",
|
|
1991
|
+
"sections": [
|
|
1992
|
+
{
|
|
1993
|
+
"title": "๐ Features",
|
|
1994
|
+
"audience": "all",
|
|
1995
|
+
"items": [
|
|
1913
1996
|
{
|
|
1914
1997
|
"description": "Slim down release workflow by removing unnecessary pnpm install (#21)",
|
|
1915
1998
|
"body": "Make release-kit self-contained by invoking git-cliff via `npx --yes` instead of requiring it on PATH, and by appending modified file paths to the format command so lightweight formatters like `npx prettier --write` work without a full `pnpm install`. Update init templates, README, and consuming repo config/workflow to reference workflow v3.",
|
|
@@ -1926,11 +2009,6 @@
|
|
|
1926
2009
|
"body": "Moves the reusable release workflow from `williamthorsen/.github` into this repo as `release-workflow.yaml`, stripping all pnpm-related steps since release-kit now runs git-cliff and prettier via `npx` internally. Updates this repo's caller workflow to use a relative path and update init templates to reference the new location. Establishes a naming convention (`{name}-workflow.yaml` for reusable, `{name}.yaml` for callers) and independent versioning strategy (`{name}-workflow-v{major}` tags), documented in `.github/workflows/README.md`.",
|
|
1927
2010
|
"breaking": true,
|
|
1928
2011
|
"hash": "f3416120cfe5bf0e7dbf51389e0c2647ccf41d30"
|
|
1929
|
-
},
|
|
1930
|
-
{
|
|
1931
|
-
"description": "Allow git-cliff to be used without config (#31)",
|
|
1932
|
-
"body": "Adds a `resolveCliffConfigPath()` function that searches for a git-cliff config in a 4-step cascade (explicit path โ `.config/git-cliff.toml` โ `cliff.toml` โ bundled `cliff.toml.template`), eliminating the requirement for consuming repos to maintain a cliff config copy. Restructures the `init` command to scaffold only the workflow file by default, with new `--with-config` and `--force` flags. Moves `.release-tags` from `/tmp/release-kit/` to project-local `tmp/` for predictable behavior in local runs.",
|
|
1933
|
-
"hash": "0cbd2605d6f5f53d1efe3dda8dd0f6e4376b499f"
|
|
1934
2012
|
}
|
|
1935
2013
|
]
|
|
1936
2014
|
},
|
package/.readyup/kits/default.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @noformat -- @generated. Do not edit. Compiled by rdy. */
|
|
2
2
|
/* eslint-disable */
|
|
3
|
-
export const __readyupVersion = "0.
|
|
3
|
+
export const __readyupVersion = "0.40.0";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
// .readyup/kits/default.ts
|
|
@@ -98,7 +98,7 @@ function translateGlob(pattern) {
|
|
|
98
98
|
|
|
99
99
|
// .readyup/kits/default.ts
|
|
100
100
|
function getMinVersion() {
|
|
101
|
-
const picked = { "version": "
|
|
101
|
+
const picked = { "version": "14.0.0" };
|
|
102
102
|
if (typeof picked["version"] !== "string") {
|
|
103
103
|
throw new TypeError("release-kit/package.json: 'version' must be a string");
|
|
104
104
|
}
|
|
@@ -116,7 +116,7 @@ var CONFIG_EXPORT_PATTERNS = [
|
|
|
116
116
|
];
|
|
117
117
|
var COMMON_PRESET_HASH = "618bb5cbcbdd4f4f0c2a777add7e14d992d48483350dd9651bf8cf4c1fa9d418";
|
|
118
118
|
var SYNC_LABELS_WORKFLOW_HASH = "6b50015e2919ffa4b16d0b4a1f340e585ff3e5b779a2256010d3c62fa07ff8f2";
|
|
119
|
-
var RELEASE_WORKFLOW_HASH_MONOREPO = "
|
|
119
|
+
var RELEASE_WORKFLOW_HASH_MONOREPO = "4a20ff3315155500f64a27133c54eb1bc1f07a4acc370ec0d83176109041c60d";
|
|
120
120
|
var RELEASE_WORKFLOW_HASH_SINGLE = "d26dd4ca186632b388ea46bd885721124c03d19918e511baa40821d2d760da69";
|
|
121
121
|
var PUBLISH_WORKFLOW_HASH_MONOREPO = "0afa9ffe914f3dc8f043e68252ebc604c8cc1a953422fcea37a909a4def370ee";
|
|
122
122
|
var PUBLISH_WORKFLOW_HASH_SINGLE = "6f31183e0a1e66be791a19266c3b028dadbd9fe010f7fc4452f3f8970c937b43";
|
package/.readyup/manifest.json
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
"esbuildVersion": "0.28.2",
|
|
9
9
|
"inputs": [
|
|
10
10
|
{
|
|
11
|
-
"hash": "
|
|
11
|
+
"hash": "7a8522cd",
|
|
12
12
|
"kind": "module",
|
|
13
13
|
"path": "kits/default.ts"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
|
-
"hash": "
|
|
16
|
+
"hash": "6eab98d1",
|
|
17
17
|
"kind": "inline",
|
|
18
18
|
"path": "../package.json",
|
|
19
19
|
"paths": [
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
],
|
|
44
44
|
"name": "default",
|
|
45
45
|
"path": "kits/default.js",
|
|
46
|
-
"readyupVersion": "0.
|
|
46
|
+
"readyupVersion": "0.40.0",
|
|
47
47
|
"source": "kits/default.ts",
|
|
48
|
-
"sourceHash": "
|
|
49
|
-
"targetHash": "
|
|
48
|
+
"sourceHash": "7a8522cd",
|
|
49
|
+
"targetHash": "4e21467f"
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
"checklists": [
|
|
@@ -68,10 +68,10 @@
|
|
|
68
68
|
],
|
|
69
69
|
"name": "npm-auto-publish",
|
|
70
70
|
"path": "kits/npm-auto-publish.js",
|
|
71
|
-
"readyupVersion": "0.
|
|
71
|
+
"readyupVersion": "0.40.0",
|
|
72
72
|
"source": "kits/npm-auto-publish.ts",
|
|
73
73
|
"sourceHash": "d9910d60",
|
|
74
|
-
"targetHash": "
|
|
74
|
+
"targetHash": "2a6931b6"
|
|
75
75
|
}
|
|
76
76
|
]
|
|
77
77
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 14.0.0 โ 2026-10-08
|
|
6
|
+
|
|
7
|
+
### ๐ Features
|
|
8
|
+
|
|
9
|
+
- ๐จ **Breaking:** Changes monorepo `--set-version` to take comma-separated `<workspace>@N.N.N` entries, which `prepare` accepts without `--only` and together with `--bump` and `--force`, and which release each named workspace's dependents. (#970)
|
|
10
|
+
|
|
11
|
+
### ๐ Bug fixes
|
|
12
|
+
|
|
13
|
+
- ๐จ **Breaking:** Fixes the issue that `release-kit prepare --only` left each dependent of the named workspaces published against the old version range of its dependency, by also releasing every transitive dependent: a dependent with commits of its own at its own bump, and one without at a propagated patch bump. (#970)
|
|
14
|
+
|
|
15
|
+
### ๐งช Tests
|
|
16
|
+
|
|
17
|
+
- Replaces the hand-rolled `console.warn` spies in release-kit's config-warning and push tests with `silenceConsole`, which restores the method when its `using` scope exits. (#964)
|
|
18
|
+
|
|
19
|
+
### ๐ Documentation
|
|
20
|
+
|
|
21
|
+
- Updates the readyup examples in the `nmr`, `release-kit`, and `v11y-check` READMEs and the `nmr` rulebook to use the `sources` key and `rdy run --sources`, and states that `--sources` needs readyup 0.40 or later. (#977)
|
|
22
|
+
|
|
23
|
+
## 13.1.0 โ 2026-10-02
|
|
24
|
+
|
|
25
|
+
### ๐ Features
|
|
26
|
+
|
|
27
|
+
- Adds an `archived: true` mark to `repoLabels.labels` entries, which `sync-labels generate` writes to `.github/labels.yaml` without changing the output for unmarked labels. (#955)
|
|
28
|
+
|
|
5
29
|
## 13.0.0 โ 2026-10-01
|
|
6
30
|
|
|
7
31
|
### ๐ Features
|
|
@@ -1105,9 +1129,13 @@ All notable changes to this project will be documented in this file.
|
|
|
1105
1129
|
|
|
1106
1130
|
### ๐ Features
|
|
1107
1131
|
|
|
1108
|
-
-
|
|
1132
|
+
- Allow git-cliff to be used without config (#31)
|
|
1109
1133
|
|
|
1110
|
-
|
|
1134
|
+
Adds a `resolveCliffConfigPath()` function that searches for a git-cliff config in a 4-step cascade (explicit path โ `.config/git-cliff.toml` โ `cliff.toml` โ bundled `cliff.toml.template`), eliminating the requirement for consuming repos to maintain a cliff config copy. Restructures the `init` command to scaffold only the workflow file by default, with new `--with-config` and `--force` flags. Moves `.release-tags` from `/tmp/release-kit/` to project-local `tmp/` for predictable behavior in local runs.
|
|
1135
|
+
|
|
1136
|
+
## 2.0.0 โ 2026-03-16
|
|
1137
|
+
|
|
1138
|
+
### ๐ Features
|
|
1111
1139
|
|
|
1112
1140
|
- ๐จ **Breaking:** Slim down release workflow by removing unnecessary pnpm install (#21)
|
|
1113
1141
|
|
|
@@ -1121,10 +1149,6 @@ All notable changes to this project will be documented in this file.
|
|
|
1121
1149
|
|
|
1122
1150
|
Moves the reusable release workflow from `williamthorsen/.github` into this repo as `release-workflow.yaml`, stripping all pnpm-related steps since release-kit now runs git-cliff and prettier via `npx` internally. Updates this repo's caller workflow to use a relative path and update init templates to reference the new location. Establishes a naming convention (`{name}-workflow.yaml` for reusable, `{name}.yaml` for callers) and independent versioning strategy (`{name}-workflow-v{major}` tags), documented in `.github/workflows/README.md`.
|
|
1123
1151
|
|
|
1124
|
-
- Allow git-cliff to be used without config (#31)
|
|
1125
|
-
|
|
1126
|
-
Adds a `resolveCliffConfigPath()` function that searches for a git-cliff config in a 4-step cascade (explicit path โ `.config/git-cliff.toml` โ `cliff.toml` โ bundled `cliff.toml.template`), eliminating the requirement for consuming repos to maintain a cliff config copy. Restructures the `init` command to scaffold only the workflow file by default, with new `--with-config` and `--force` flags. Moves `.release-tags` from `/tmp/release-kit/` to project-local `tmp/` for predictable behavior in local runs.
|
|
1127
|
-
|
|
1128
1152
|
### โป๏ธ Refactoring
|
|
1129
1153
|
|
|
1130
1154
|
- Clean up release-kit post-migration issues (#19)
|
package/README.md
CHANGED
|
@@ -7,42 +7,15 @@ 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 โ
|
|
10
|
+
## Release notes โ v14.0.0 (2026-10-08)
|
|
11
11
|
|
|
12
12
|
### ๐ Features
|
|
13
13
|
|
|
14
|
-
-
|
|
15
|
-
- Makes `release-kit` add an item derived from a change-record entry to a workspace's changelog and version bump only when the entry's `scopes`, after `scopeAliases` resolution, name the workspace's `dir`, are empty, or contain `*`, and extends that rule to the sections of earlier releases that `release-kit prepare` rebuilds. (#896)
|
|
16
|
-
- Adds a warning to the `release-kit prepare` report that lists each change-record entry scope matching no workspace in its commit's window, such as a misspelled scope or the old name of a renamed directory that has no `scopeAliases` entry. (#896)
|
|
17
|
-
- Makes `release-kit overrides validate` check a workspace's overrides against only the entries routed to that workspace, so that an override keyed to an entry routed elsewhere is reported as stale. (#896)
|
|
18
|
-
- Adds `release-kit update-templates`, which writes each scaffolded workflow that is missing or differs from the installed template, prints a diff of each updated file, refreshes `sync-labels.yaml` only when it exists, keeps `publish.yaml`'s provenance setting, and never reads or writes the config files. (#952)
|
|
19
|
-
- Changes the readyup kits' fix messages for a workflow that differs from its template, or that references a deprecated tag, to recommend `update-templates` rather than `init --force`. (#952)
|
|
20
|
-
|
|
21
|
-
### ๐ชฆ Removed
|
|
22
|
-
|
|
23
|
-
- ๐จ **Breaking:** Removes `required` as a `breakingPolicies` value, making `drop` default to `optional` so that an unmarked `drop:` commit no longer reports a policy violation. (#905)
|
|
24
|
-
- Stops `release-kit` from parsing a commit subject that combines a pipe scope with a parenthesized scope, such as `web|feat(other): ...`, or that lacks a space and a title after the colon, because subjects are now read through `@williamthorsen/change-grammar`. (#905)
|
|
25
|
-
- Stops publishing `schemas/label-map.json` from `release-kit`, whose identical schema `@williamthorsen/change-grammar` now publishes; URLs under existing `release-kit-v*` tags keep resolving. (#905)
|
|
14
|
+
- ๐จ **Breaking:** Changes monorepo `--set-version` to take comma-separated `<workspace>@N.N.N` entries, which `prepare` accepts without `--only` and together with `--bump` and `--force`, and which release each named workspace's dependents. (#970)
|
|
26
15
|
|
|
27
16
|
### ๐ Bug fixes
|
|
28
17
|
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
Makes `release-kit prepare` record the "Forced version bump." changelog entry for any release forced by `--force` or `--set-version` whose commits yield no changelog item, instead of only for a release with no commits at all. It also stops a monorepo's project release from dropping the forced-release entries that earlier releases recorded.
|
|
32
|
-
|
|
33
|
-
- Stops `release-kit prepare` from reporting `commitCount` 0 and dropping the unparseable commits and change-record and policy diagnostics of a workspace released only because a dependency was bumped, and makes `prepare` list that workspace's commits in the release commit summary under its tag. (#888)
|
|
34
|
-
- Stops `release-kit prepare` from labeling a direct release that has no commits as bumped via a dependency, by marking a propagation-only release with the new `propagatedOnly` result field, whose report line now reads `Bumped via dependency: โฆ` in place of `0 commits (bumped via dependency: โฆ)`. (#888)
|
|
35
|
-
- Stops a `release-kit` command run from inside a package from releasing that package as a single-package repo with its own tag and changelog, by running every command from the repo root: the nearest directory that contains `pnpm-workspace.yaml`, or else the working directory when it contains a `package.json`. (#893)
|
|
36
|
-
- Stops `sync-labels generate` from writing the `--config` path into the `.github/labels.yaml` header as typed, which made the header and the result of `generate --check` depend on the directory from which the command ran; the header now names the config by its path relative to the repo root. (#893)
|
|
37
|
-
- Stops `push`, `publish`, and `create-github-release` from pushing, publishing, or creating a GitHub Release for a tag on HEAD whose workspace the config excludes with `shouldExclude: true`, which each command now skips with a warning, including when `--tags` names the tag or when every tag on HEAD is excluded. (#895)
|
|
38
|
-
- Stops `release-kit prepare` from silently deleting a `CHANGELOG.md` section whose version is absent from both the release windows and `changelog.json`, such as history written by hand before release-kit was adopted; `prepare` now keeps that section verbatim in version order, lists the kept versions in its report, and warns about each `##` section that names no version and is dropped. (#897)
|
|
39
|
-
- Stops `release-kit prepare` from folding every earlier commit into the new version, and deriving the bump from all of them, when the current version was released and recorded in the changelog but never tagged; `prepare` now fails before any write, under `--force` and `--set-version` too, and names each target and the tag to create. (#897)
|
|
40
|
-
- Adds the missing `removal` and `formatting` labels to the `common` preset by defining a label for every work type in change-grammar's taxonomy, and keeps the names, colors, and descriptions of the existing type labels. (#906)
|
|
41
|
-
- Fixes the `release.yaml` generated by `release-kit init`, which `actionlint` rejected because its `bump` choice input listed an empty string as the auto-detect option, by offering `auto` as the default option and passing it to the reusable release workflow as an empty `bump`. (#936)
|
|
42
|
-
- Rewords the error messages, `prepare` report lines, command help, and readyup kit fix strings of `release-kit` that addressed the reader as "your", separated a detail from its subject with an em-dash, or stated an absence with "no", such as "No workspaces had release-worthy changes." (#937)
|
|
43
|
-
- Fixes the issue that the `release.yaml` and `sync-labels.yaml` workflows generated by `release-kit init` and `release-kit sync-labels init` contained unclear wording in two comments and in the `force` input description, which the change rewrites without altering any job, trigger, or input; the release-kit kit check reports a copy generated before this change as stale until `release-kit init --force` or `release-kit sync-labels init --force` regenerates it. (#946)
|
|
44
|
-
- Fixes the issue that `release-kit` titled a propagation-only bump's changelog section `Dependency updates` rather than with the resolved `deps` work type's header, `๐ฆ Dependencies` by default or a consumer's `workTypes.deps.header` override, which broke the heading convention and left the section outside the section order. (#950)
|
|
45
|
-
- Fixes the issue that `release-kit init`'s next steps told a public repo to set `provenance: true` in `publish.yaml`, which the template already sets, and now tells a private repo to remove it instead. (#952)
|
|
18
|
+
- ๐จ **Breaking:** Fixes the issue that `release-kit prepare --only` left each dependent of the named workspaces published against the old version range of its dependency, by also releasing every transitive dependent: a dependent with commits of its own at its own bump, and one without at a propagated patch bump. (#970)
|
|
46
19
|
<!-- /section:release-notes -->
|
|
47
20
|
|
|
48
21
|
## Installation
|
|
@@ -88,7 +61,7 @@ Commit the scaffolded workflows, then start a release from the `release` workflo
|
|
|
88
61
|
|
|
89
62
|
```sh
|
|
90
63
|
gh workflow run release.yaml # every workspace with release-worthy changes
|
|
91
|
-
gh workflow run release.yaml -f only=arrays # the named workspaces
|
|
64
|
+
gh workflow run release.yaml -f only=arrays # the named workspaces and their dependents
|
|
92
65
|
```
|
|
93
66
|
|
|
94
67
|
After upgrading release-kit, run `release-kit update-templates` to update the scaffolded workflows to its templates; it leaves the config files alone.
|
|
@@ -167,15 +140,15 @@ release-kit publishes two [readyup](https://www.npmjs.com/package/readyup) kits:
|
|
|
167
140
|
import { defineRdyConfig } from 'readyup';
|
|
168
141
|
|
|
169
142
|
export default defineRdyConfig({
|
|
170
|
-
|
|
143
|
+
sources: ['npm:@williamthorsen/release-kit'],
|
|
171
144
|
});
|
|
172
145
|
```
|
|
173
146
|
|
|
174
147
|
```bash
|
|
175
|
-
rdy run --
|
|
148
|
+
rdy run --sources
|
|
176
149
|
```
|
|
177
150
|
|
|
178
|
-
`rdy run` needs `readyup` 0.
|
|
151
|
+
`rdy run --sources` needs `readyup` 0.40 or later, and `@williamthorsen/release-kit` as a _direct_ devDependency: A strict pnpm layout links nothing else into the project; therefore, a transitive copy is unreachable.
|
|
179
152
|
|
|
180
153
|
[Readiness checks](docs/readiness-checks.md) covers running each kit alone and what `npm-auto-publish` needs from the npm session.
|
|
181
154
|
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { DependencyGraph } from './buildDependencyGraph.js';
|
|
2
|
+
/** Returns the start workspace `dir`s together with the `dir` of every workspace that depends on one, transitively. */
|
|
3
|
+
export declare function findTransitiveDependents(graph: DependencyGraph, startDirs: Iterable<string>): Set<string>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function findTransitiveDependents(graph, startDirs) {
|
|
2
|
+
const found = new Set(startDirs);
|
|
3
|
+
const queue = [...found];
|
|
4
|
+
while (queue.length > 0) {
|
|
5
|
+
const dir = queue.shift();
|
|
6
|
+
if (dir === undefined)
|
|
7
|
+
break;
|
|
8
|
+
const packageName = graph.dirToPackageName.get(dir);
|
|
9
|
+
if (packageName === undefined)
|
|
10
|
+
continue;
|
|
11
|
+
const dependents = graph.dependentsOf.get(packageName) ?? [];
|
|
12
|
+
for (const dependent of dependents) {
|
|
13
|
+
if (found.has(dependent.dir))
|
|
14
|
+
continue;
|
|
15
|
+
found.add(dependent.dir);
|
|
16
|
+
queue.push(dependent.dir);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return found;
|
|
20
|
+
}
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Help text for `release-kit prepare`. The module does not have any import-time side effects, so that a test can
|
|
3
3
|
* import it without running the CLI.
|
|
4
4
|
*/
|
|
5
|
-
export declare const prepareHelpText = "\nUsage: release-kit prepare [options]\n\nRun release preparation with automatic workspace discovery.\n\nOptions:\n --config <path> Config file, relative to the current directory\n (default: .config/release-kit.config.ts at the repo root)\n --dry-run Run without modifying any files\n --bump=major|minor|patch Set the level of every release that happens; does not trigger one\n --set-version=X.Y.Z Set an explicit version; bypasses the changelog-derived bump. In monorepo\n mode
|
|
5
|
+
export declare const prepareHelpText = "\nUsage: release-kit prepare [options]\n\nRun release preparation with automatic workspace discovery.\n\nOptions:\n --config <path> Config file, relative to the current directory\n (default: .config/release-kit.config.ts at the repo root)\n --dry-run Run without modifying any files\n --bump=major|minor|patch Set the level of every release that happens; does not trigger one\n --set-version=X.Y.Z Set an explicit version; bypasses the changelog-derived bump. In monorepo\n mode, name each workspace: --set-version=arrays@1.0.0[,strings@2.0.0].\n Rejected when a 'project' block is configured.\n --force Release even when the last tag is not followed by any commits,\n or by any bump-worthy ones. Defaults to patch when --bump is not given;\n use --bump=X for a different level.\n --no-git-checks, -n Skip the clean-working-tree check\n --only=name1,name2 Release only the named workspaces and their dependents (comma-separated,\n monorepo only).\n When a 'project' block is configured, the project release is skipped.\n --with-release-notes Also write per-workspace release-notes previews under <workspacePath>/docs/\n (docs/README.v<version>.md and docs/RELEASE_NOTES.v<version>.md).\n Recommended .gitignore entry: packages/*/docs/*.v*.md (or docs/*.v*.md).\n --help, -h Show this help message\n";
|
|
6
6
|
/** Prints the prepare command's help text. */
|
|
7
7
|
export declare function showPrepareHelp(): void;
|
|
@@ -9,12 +9,14 @@ Options:
|
|
|
9
9
|
--dry-run Run without modifying any files
|
|
10
10
|
--bump=major|minor|patch Set the level of every release that happens; does not trigger one
|
|
11
11
|
--set-version=X.Y.Z Set an explicit version; bypasses the changelog-derived bump. In monorepo
|
|
12
|
-
mode
|
|
12
|
+
mode, name each workspace: --set-version=arrays@1.0.0[,strings@2.0.0].
|
|
13
|
+
Rejected when a 'project' block is configured.
|
|
13
14
|
--force Release even when the last tag is not followed by any commits,
|
|
14
15
|
or by any bump-worthy ones. Defaults to patch when --bump is not given;
|
|
15
16
|
use --bump=X for a different level.
|
|
16
17
|
--no-git-checks, -n Skip the clean-working-tree check
|
|
17
|
-
--only=name1,name2
|
|
18
|
+
--only=name1,name2 Release only the named workspaces and their dependents (comma-separated,
|
|
19
|
+
monorepo only).
|
|
18
20
|
When a 'project' block is configured, the project release is skipped.
|
|
19
21
|
--with-release-notes Also write per-workspace release-notes previews under <workspacePath>/docs/
|
|
20
22
|
(docs/README.v<version>.md and docs/RELEASE_NOTES.v<version>.md).
|
|
@@ -89,7 +89,7 @@ on:
|
|
|
89
89
|
workflow_dispatch:
|
|
90
90
|
inputs:
|
|
91
91
|
only:
|
|
92
|
-
description: 'Workspaces to release (comma-separated, leave empty for all)'
|
|
92
|
+
description: 'Workspaces to release, with their dependents (comma-separated, leave empty for all)'
|
|
93
93
|
required: false
|
|
94
94
|
type: string
|
|
95
95
|
bump:
|
|
@@ -43,6 +43,7 @@ export declare function parseArgs(argv: string[]): {
|
|
|
43
43
|
noGitChecks: boolean;
|
|
44
44
|
bumpOverride: ReleaseType | undefined;
|
|
45
45
|
only: string[] | undefined;
|
|
46
|
+
/** The raw `--set-version` value, whose accepted form depends on the mode. */
|
|
46
47
|
setVersion: string | undefined;
|
|
47
48
|
withReleaseNotes: boolean;
|
|
48
49
|
};
|
|
@@ -2,12 +2,9 @@ import { execSync } from 'node:child_process';
|
|
|
2
2
|
import { formatGlyphLine, parseArgsOrExit, reportError, } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
4
|
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
5
|
-
import { readReleaseHistory } from "./buildChangelogEntries.js";
|
|
6
|
-
import { buildDependencyGraph } from "./buildDependencyGraph.js";
|
|
7
5
|
import { configFlagSchema } from "./configFlagSchema.js";
|
|
8
6
|
import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
9
7
|
import { dim } from "./format.js";
|
|
10
|
-
import { getAllTagPrefixes } from "./generateChangelogs.js";
|
|
11
8
|
import { RELEASE_GLYPHS } from "./glyphs.js";
|
|
12
9
|
import { mergeMonorepoConfig, mergeSinglePackageConfig, readRootPackageVersion } from "./loadConfig.js";
|
|
13
10
|
import { loadValidatedConfig, reportConfigProblem, reportConfigWarnings } from "./loadValidatedConfig.js";
|
|
@@ -17,7 +14,6 @@ import { releasePrepare } from "./releasePrepare.js";
|
|
|
17
14
|
import { releasePrepareMono } from "./releasePrepareMono.js";
|
|
18
15
|
import { reportPrepare } from "./reportPrepare.js";
|
|
19
16
|
import { resolveConfigFlag } from "./resolveConfigFlag.js";
|
|
20
|
-
import { validateOnlyExcludesStrandedDependents } from "./validateOnlyExcludesStrandedDependents.js";
|
|
21
17
|
const VALID_BUMP_TYPES = ['major', 'minor', 'patch'];
|
|
22
18
|
const CANONICAL_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
23
19
|
function isReleaseType(value) {
|
|
@@ -47,26 +43,10 @@ export function parseArgs(argv) {
|
|
|
47
43
|
}
|
|
48
44
|
bumpOverride = flags.bump;
|
|
49
45
|
}
|
|
50
|
-
let setVersion;
|
|
51
|
-
if (flags.setVersion !== undefined) {
|
|
52
|
-
if (!CANONICAL_SEMVER_PATTERN.test(flags.setVersion)) {
|
|
53
|
-
reportError(`Invalid --set-version value "${flags.setVersion}". Must be canonical semver (N.N.N, no pre-release suffix).`);
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
setVersion = flags.setVersion;
|
|
57
|
-
}
|
|
58
46
|
let only;
|
|
59
47
|
if (flags.only !== undefined) {
|
|
60
48
|
only = flags.only.split(',');
|
|
61
49
|
}
|
|
62
|
-
if (setVersion !== undefined && bumpOverride !== undefined) {
|
|
63
|
-
reportError('--set-version cannot be combined with --bump');
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
66
|
-
if (setVersion !== undefined && flags.force) {
|
|
67
|
-
reportError('--set-version cannot be combined with --force');
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
50
|
return {
|
|
71
51
|
configPath: flags.config,
|
|
72
52
|
dryRun: flags.dryRun,
|
|
@@ -74,7 +54,7 @@ export function parseArgs(argv) {
|
|
|
74
54
|
noGitChecks: flags.noGitChecks,
|
|
75
55
|
bumpOverride,
|
|
76
56
|
only,
|
|
77
|
-
setVersion,
|
|
57
|
+
setVersion: flags.setVersion,
|
|
78
58
|
withReleaseNotes: flags.withReleaseNotes,
|
|
79
59
|
};
|
|
80
60
|
}
|
|
@@ -83,7 +63,6 @@ export async function prepareCommand(argv, styles, invocationDir) {
|
|
|
83
63
|
const options = {
|
|
84
64
|
force,
|
|
85
65
|
...(bumpOverride !== undefined && { bumpOverride }),
|
|
86
|
-
...(setVersion !== undefined && { setVersion }),
|
|
87
66
|
...(withReleaseNotes && { withReleaseNotes: true }),
|
|
88
67
|
};
|
|
89
68
|
if (dryRun) {
|
|
@@ -121,19 +100,23 @@ export async function prepareCommand(argv, styles, invocationDir) {
|
|
|
121
100
|
process.exit(1);
|
|
122
101
|
}
|
|
123
102
|
if (workspace.kind === 'single-package') {
|
|
124
|
-
runSinglePackageMode(userConfig, options, only, dryRun, styles.stdout);
|
|
103
|
+
runSinglePackageMode(userConfig, options, only, setVersion, dryRun, styles.stdout);
|
|
125
104
|
}
|
|
126
105
|
else {
|
|
127
106
|
runMonorepoMode(workspace.packageDirs, userConfig, options, only, setVersion, dryRun, styles.stdout);
|
|
128
107
|
}
|
|
129
108
|
}
|
|
130
|
-
function runSinglePackageMode(userConfig, options, only, dryRun, style) {
|
|
109
|
+
function runSinglePackageMode(userConfig, options, only, setVersion, dryRun, style) {
|
|
131
110
|
if (only !== undefined) {
|
|
132
111
|
reportError('--only is only supported for monorepo configurations');
|
|
133
112
|
process.exit(1);
|
|
134
113
|
}
|
|
114
|
+
if (setVersion !== undefined && !CANONICAL_SEMVER_PATTERN.test(setVersion)) {
|
|
115
|
+
reportError(`Invalid --set-version value "${setVersion}". Must be canonical semver (N.N.N, no pre-release suffix).`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
135
118
|
const config = mergeSinglePackageConfig(userConfig);
|
|
136
|
-
runAndReport(() => releasePrepare(config, options), dryRun, style);
|
|
119
|
+
runAndReport(() => releasePrepare(config, { ...options, ...(setVersion !== undefined && { setVersion }) }), dryRun, style);
|
|
137
120
|
}
|
|
138
121
|
function runMonorepoMode(discoveredPaths, userConfig, options, only, setVersion, dryRun, style) {
|
|
139
122
|
let config;
|
|
@@ -147,53 +130,48 @@ function runMonorepoMode(discoveredPaths, userConfig, options, only, setVersion,
|
|
|
147
130
|
}
|
|
148
131
|
if (setVersion !== undefined && config.project !== undefined) {
|
|
149
132
|
reportError('--set-version cannot be combined with a project release. ' +
|
|
150
|
-
|
|
133
|
+
"--set-version sets individual workspaces' versions; a project release rolls up every " +
|
|
151
134
|
'contributing workspace. To use --set-version, run on a config without a `project` block.');
|
|
152
135
|
process.exit(1);
|
|
153
136
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
reportError(`Unknown workspace "${name}". Known workspaces: ${knownNames.join(', ')}`);
|
|
162
|
-
process.exit(1);
|
|
137
|
+
const knownNames = config.workspaces.map((w) => w.dir);
|
|
138
|
+
const onlyNames = only ?? [];
|
|
139
|
+
for (const name of onlyNames) {
|
|
140
|
+
if (knownNames.includes(name)) {
|
|
141
|
+
continue;
|
|
163
142
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
143
|
+
reportError(`Unknown workspace "${name}". Known workspaces: ${knownNames.join(', ')}`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
const monorepoOptions = {
|
|
147
|
+
...options,
|
|
148
|
+
...(only !== undefined && { only }),
|
|
149
|
+
...(setVersion !== undefined && { setVersions: parseSetVersions(setVersion, knownNames) }),
|
|
150
|
+
};
|
|
151
|
+
runAndReport(() => releasePrepareMono(config, monorepoOptions), dryRun, style);
|
|
152
|
+
}
|
|
153
|
+
function parseSetVersions(value, knownNames) {
|
|
154
|
+
const setVersions = new Map();
|
|
155
|
+
for (const entry of value.split(',')) {
|
|
156
|
+
const separatorIndex = entry.lastIndexOf('@');
|
|
157
|
+
const name = entry.slice(0, Math.max(0, separatorIndex));
|
|
158
|
+
const version = entry.slice(separatorIndex + 1);
|
|
159
|
+
if (separatorIndex <= 0 || !CANONICAL_SEMVER_PATTERN.test(version)) {
|
|
160
|
+
reportError(`Invalid --set-version entry "${entry}". In monorepo mode, each entry must be <workspace>@N.N.N ` +
|
|
161
|
+
'(canonical semver, no pre-release suffix), e.g. --set-version=arrays@1.0.0.');
|
|
177
162
|
process.exit(1);
|
|
178
163
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
if (setVersion !== undefined) {
|
|
183
|
-
if (only === undefined) {
|
|
184
|
-
reportError('--set-version requires --only in monorepo mode');
|
|
164
|
+
if (!knownNames.includes(name)) {
|
|
165
|
+
reportError(`Unknown workspace "${name}" in --set-version. Known workspaces: ${knownNames.join(', ')}`);
|
|
185
166
|
process.exit(1);
|
|
186
167
|
}
|
|
187
|
-
if (
|
|
188
|
-
reportError(`--set-version
|
|
168
|
+
if (setVersions.has(name)) {
|
|
169
|
+
reportError(`--set-version names workspace "${name}" more than once`);
|
|
189
170
|
process.exit(1);
|
|
190
171
|
}
|
|
172
|
+
setVersions.set(name, version);
|
|
191
173
|
}
|
|
192
|
-
|
|
193
|
-
...options,
|
|
194
|
-
...(only !== undefined && { only }),
|
|
195
|
-
...(configuredWorkspaceDirs !== undefined && { configuredWorkspaceDirs }),
|
|
196
|
-
}), dryRun, style);
|
|
174
|
+
return setVersions;
|
|
197
175
|
}
|
|
198
176
|
function runAndReport(computePlan, dryRun, style) {
|
|
199
177
|
let plan;
|
|
@@ -10,24 +10,10 @@ export interface ReleasePrepareOptions {
|
|
|
10
10
|
/** Level of the release, in place of the level that the changelog items call for; it doesn't trigger a release. */
|
|
11
11
|
bumpOverride?: ReleaseType;
|
|
12
12
|
/**
|
|
13
|
-
* Explicit target version (canonical `N.N.N`) that bypasses commit-derived bump logic
|
|
14
|
-
*
|
|
15
|
-
* `config.workspaces` to a single workspace before invoking.
|
|
13
|
+
* Explicit target version (canonical `N.N.N`) that bypasses commit-derived bump logic and takes precedence over
|
|
14
|
+
* `bumpOverride` and `force`.
|
|
16
15
|
*/
|
|
17
16
|
setVersion?: string;
|
|
18
|
-
/**
|
|
19
|
-
* Workspace directories to which `--only` narrowed the run (monorepo only), with
|
|
20
|
-
* `config.workspaces` already filtered to match. Present only for a narrowed run, which
|
|
21
|
-
* skips the project release: The project tier rolls up every contributing workspace, and
|
|
22
|
-
* the narrowing has changed which workspaces those are.
|
|
23
|
-
*/
|
|
24
|
-
only?: string[];
|
|
25
|
-
/**
|
|
26
|
-
* Every configured workspace `dir`, taken before `--only` narrowed `config.workspaces`; the unrouted-scope report
|
|
27
|
-
* does not report a scope naming one of them that the run did not read. Defaults to the `dir`s of
|
|
28
|
-
* `config.workspaces`.
|
|
29
|
-
*/
|
|
30
|
-
configuredWorkspaceDirs?: readonly string[];
|
|
31
17
|
/**
|
|
32
18
|
* If true, plan per-workspace release-notes previews under `{workspacePath}/docs/`
|
|
33
19
|
* (`README.v{version}.md` and `RELEASE_NOTES.v{version}.md`) from each workspace's
|
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
import type { ReleasePlan } from './releasePlan.js';
|
|
2
2
|
import type { ReleasePrepareOptions } from './releasePrepare.js';
|
|
3
3
|
import type { MonorepoPrepareConfig } from './types.js';
|
|
4
|
+
/** Options for monorepo release preparation. */
|
|
5
|
+
export interface MonorepoPrepareOptions extends Omit<ReleasePrepareOptions, 'setVersion'> {
|
|
6
|
+
/**
|
|
7
|
+
* Workspace `dir`s from which the release starts; the run plans them and their transitive dependents. Present only
|
|
8
|
+
* for a narrowed run, which skips the project release: The project tier rolls up every contributing workspace, and
|
|
9
|
+
* the narrowing has changed which workspaces those are.
|
|
10
|
+
*/
|
|
11
|
+
only?: readonly string[];
|
|
12
|
+
/**
|
|
13
|
+
* Explicit target versions (canonical `N.N.N`), keyed by workspace `dir`. Each named workspace starts the release
|
|
14
|
+
* and releases at its version, which takes precedence over `bumpOverride` and `force`.
|
|
15
|
+
*/
|
|
16
|
+
setVersions?: ReadonlyMap<string, string>;
|
|
17
|
+
}
|
|
4
18
|
/**
|
|
5
19
|
* Orchestrates release preparation for a monorepo with multiple workspaces.
|
|
6
20
|
*
|
|
21
|
+
* Phase 0: Selects the planned workspaces: those from which the release starts and their transitive dependents.
|
|
7
22
|
* Phase 1: Determines direct bumps from commits for each workspace.
|
|
8
|
-
* Phase 2:
|
|
23
|
+
* Phase 2: Propagates bumps to dependents.
|
|
9
24
|
* Phase 2b: Sorts the full release set topologically.
|
|
10
25
|
* Phase 3: Plans bumps and changelogs in dependency order.
|
|
11
26
|
* Phase 3b: Plans the project release.
|
|
12
27
|
* Phase 4: Renders the format command.
|
|
13
28
|
*/
|
|
14
|
-
export declare function releasePrepareMono(
|
|
29
|
+
export declare function releasePrepareMono(fullConfig: MonorepoPrepareConfig, options: MonorepoPrepareOptions): ReleasePlan;
|
|
@@ -11,6 +11,7 @@ import { applyWorkspaceOverrides, createOverrideContext, formatStaleOverrideKeyW
|
|
|
11
11
|
import { decideRelease } from "./decideRelease.js";
|
|
12
12
|
import { deriveDependenciesHeader, deriveSectionOrder } from "./deriveReleaseNotesConfig.js";
|
|
13
13
|
import { detectUndeclaredTagPrefixes } from "./detectUndeclaredTagPrefixes.js";
|
|
14
|
+
import { findTransitiveDependents } from "./findTransitiveDependents.js";
|
|
14
15
|
import { findUnroutedEntryScopes } from "./findUnroutedEntryScopes.js";
|
|
15
16
|
import { getAllTagPrefixes } from "./generateChangelogs.js";
|
|
16
17
|
import { hasPrettierConfig } from "./hasPrettierConfig.js";
|
|
@@ -21,10 +22,17 @@ import { propagateBumps } from "./propagateBumps.js";
|
|
|
21
22
|
import { planPreservedSections } from "./readChangelogSections.js";
|
|
22
23
|
import { releasePrepareProject } from "./releasePrepareProject.js";
|
|
23
24
|
import { renderChangelogMarkdown } from "./renderChangelogMarkdown.js";
|
|
24
|
-
export function releasePrepareMono(
|
|
25
|
+
export function releasePrepareMono(fullConfig, options) {
|
|
25
26
|
const { only, withReleaseNotes } = options;
|
|
26
27
|
const writes = [];
|
|
27
28
|
const warnings = [];
|
|
29
|
+
const graph = buildDependencyGraph(fullConfig.workspaces);
|
|
30
|
+
const startDirs = selectStartDirs(fullConfig.workspaces, options);
|
|
31
|
+
const plannedDirs = findTransitiveDependents(graph, startDirs);
|
|
32
|
+
const config = {
|
|
33
|
+
...fullConfig,
|
|
34
|
+
workspaces: fullConfig.workspaces.filter((workspace) => plannedDirs.has(workspace.dir)),
|
|
35
|
+
};
|
|
28
36
|
if (withReleaseNotes === true && !config.changelogJson.enabled) {
|
|
29
37
|
warnings.push('--with-release-notes requires changelogJson.enabled; skipping preview generation');
|
|
30
38
|
}
|
|
@@ -32,10 +40,9 @@ export function releasePrepareMono(config, options) {
|
|
|
32
40
|
const sectionOrder = deriveSectionOrder(workTypes);
|
|
33
41
|
const dependenciesHeader = deriveDependenciesHeader(workTypes);
|
|
34
42
|
const overrideContext = createOverrideContext(config.workspaces);
|
|
35
|
-
const { directBumps, directResults, skippedResults, untaggedBaselines } = determineDirectBumps(config, options);
|
|
36
|
-
reportUnroutedEntryScopes([...directResults.values(), ...skippedResults], config,
|
|
43
|
+
const { directBumps, directResults, skippedResults, untaggedBaselines } = determineDirectBumps(config, options, startDirs);
|
|
44
|
+
reportUnroutedEntryScopes([...directResults.values(), ...skippedResults], config, fullConfig.workspaces.map((workspace) => workspace.dir));
|
|
37
45
|
const skippedHistories = new Map(skippedResults.map((skipped) => [skipped.workspace.dir, skipped.history]));
|
|
38
|
-
const graph = buildDependencyGraph(config.workspaces);
|
|
39
46
|
const fullReleaseSet = propagateBumps(directBumps, graph);
|
|
40
47
|
assertTaggedBaseline([...untaggedBaselines]
|
|
41
48
|
.filter(([dir]) => directBumps.has(dir) || !fullReleaseSet.has(dir))
|
|
@@ -107,11 +114,15 @@ export function releasePrepareMono(config, options) {
|
|
|
107
114
|
...(project !== undefined && { project }),
|
|
108
115
|
};
|
|
109
116
|
}
|
|
110
|
-
function
|
|
111
|
-
const {
|
|
112
|
-
if (
|
|
113
|
-
|
|
117
|
+
function selectStartDirs(workspaces, options) {
|
|
118
|
+
const { only, setVersions } = options;
|
|
119
|
+
if (only === undefined) {
|
|
120
|
+
return new Set(workspaces.map((workspace) => workspace.dir));
|
|
114
121
|
}
|
|
122
|
+
return new Set([...only, ...(setVersions?.keys() ?? [])]);
|
|
123
|
+
}
|
|
124
|
+
function determineDirectBumps(config, options, startDirs) {
|
|
125
|
+
const { force, bumpOverride, setVersions } = options;
|
|
115
126
|
const directBumps = new Map();
|
|
116
127
|
const directResults = new Map();
|
|
117
128
|
const skippedResults = [];
|
|
@@ -138,6 +149,7 @@ function determineDirectBumps(config, options) {
|
|
|
138
149
|
if (tag === undefined) {
|
|
139
150
|
maybeEmitBaselineHint(workspace, knownPrefixes, hintState);
|
|
140
151
|
}
|
|
152
|
+
const setVersion = setVersions?.get(workspace.dir);
|
|
141
153
|
if (setVersion !== undefined) {
|
|
142
154
|
directBumps.set(workspace.dir, { releaseType: 'patch', newVersionOverride: setVersion });
|
|
143
155
|
directResults.set(workspace.dir, {
|
|
@@ -152,7 +164,7 @@ function determineDirectBumps(config, options) {
|
|
|
152
164
|
const decision = decideRelease({
|
|
153
165
|
naturalBump: history.unreleased.bump,
|
|
154
166
|
commitCount: history.unreleased.commits.length,
|
|
155
|
-
force,
|
|
167
|
+
force: force === true && startDirs.has(workspace.dir),
|
|
156
168
|
bumpOverride,
|
|
157
169
|
skipReasons: {
|
|
158
170
|
noCommits: `No commits for ${name} ${since}. Pass --force to release at patch. Skipping.`,
|
|
@@ -173,8 +185,8 @@ function determineDirectBumps(config, options) {
|
|
|
173
185
|
}
|
|
174
186
|
return { directBumps, directResults, skippedResults, untaggedBaselines };
|
|
175
187
|
}
|
|
176
|
-
function reportUnroutedEntryScopes(results, config,
|
|
177
|
-
const findings = findUnroutedEntryScopes(results.map(({ workspace, history }) => ({ dir: workspace.dir, commits: history.unreleased.commits })),
|
|
188
|
+
function reportUnroutedEntryScopes(results, config, configuredWorkspaceDirs) {
|
|
189
|
+
const findings = findUnroutedEntryScopes(results.map(({ workspace, history }) => ({ dir: workspace.dir, commits: history.unreleased.commits })), configuredWorkspaceDirs, config.scopeAliases ?? {});
|
|
178
190
|
for (const { workspace, history } of results) {
|
|
179
191
|
history.unreleased.diagnostics.unroutedEntryScopes = findings.get(workspace.dir) ?? [];
|
|
180
192
|
}
|
|
@@ -15,7 +15,12 @@ export function formatLabelsYaml(labels, presetHashes, configFilePath = CONFIG_F
|
|
|
15
15
|
for (const [name, hash] of sortedPresetHashes) {
|
|
16
16
|
headerLines.push(`# ${name} preset hash: ${hash}`);
|
|
17
17
|
}
|
|
18
|
-
const declared = labels.map((
|
|
18
|
+
const declared = labels.map(({ archived, color, description, name }) => ({
|
|
19
|
+
name,
|
|
20
|
+
color,
|
|
21
|
+
description: description ?? '',
|
|
22
|
+
...(archived !== undefined && { archived }),
|
|
23
|
+
}));
|
|
19
24
|
const yamlBody = stringify(declared, { singleQuote: true, lineWidth: 0 });
|
|
20
25
|
return headerLines.join('\n') + '\n' + yamlBody;
|
|
21
26
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** A single GitHub label definition
|
|
1
|
+
/** A single GitHub label definition, as `sync-labels generate` writes it to `.github/labels.yaml`. */
|
|
2
2
|
export interface LabelDefinition {
|
|
3
3
|
/** Display name of the label. */
|
|
4
4
|
name: string;
|
|
@@ -6,4 +6,6 @@ export interface LabelDefinition {
|
|
|
6
6
|
color: string;
|
|
7
7
|
/** Short description shown in the GitHub UI. Absent when the label has none. */
|
|
8
8
|
description?: string;
|
|
9
|
+
/** Marks the label for the sync to archive rather than keep active. Absent for an active label. */
|
|
10
|
+
archived?: true;
|
|
9
11
|
}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -446,11 +446,13 @@ export declare const breakingPolicyValueSchema: z.ZodEnum<{
|
|
|
446
446
|
export declare const labelSpecSchema: z.ZodObject<{
|
|
447
447
|
color: z.ZodString;
|
|
448
448
|
description: z.ZodOptional<z.ZodString>;
|
|
449
|
+
archived: z.ZodOptional<z.ZodLiteral<true>>;
|
|
449
450
|
}, z.core.$strict>;
|
|
450
451
|
/**
|
|
451
|
-
* A label's color and optional
|
|
452
|
-
* An omitted description resolves to empty, which the generated labels file declares explicitly
|
|
453
|
-
* clears whatever description the label has on the repo.
|
|
452
|
+
* A label's color, optional description, and optional archived mark; the name is the record key in
|
|
453
|
+
* `repoLabels.labels`. An omitted description resolves to empty, which the generated labels file declares explicitly
|
|
454
|
+
* so that a sync clears whatever description the label has on the repo. `archived: true` retires the label: The sync
|
|
455
|
+
* archives it, keeping it on the issues and pull requests that have it.
|
|
454
456
|
*/
|
|
455
457
|
export type LabelSpec = z.infer<typeof labelSpecSchema>;
|
|
456
458
|
/**
|
|
@@ -468,6 +470,7 @@ export declare const repoLabelsSchema: z.ZodObject<{
|
|
|
468
470
|
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodObject<{
|
|
469
471
|
color: z.ZodString;
|
|
470
472
|
description: z.ZodOptional<z.ZodString>;
|
|
473
|
+
archived: z.ZodOptional<z.ZodLiteral<true>>;
|
|
471
474
|
}, z.core.$strict>>>>;
|
|
472
475
|
}, z.core.$strict>;
|
|
473
476
|
/** Consumer-facing `repoLabels` block: bundled presets to extend plus per-name adjustments. */
|
|
@@ -496,6 +499,7 @@ export declare const releaseKitConfigSchema: z.ZodObject<{
|
|
|
496
499
|
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodObject<{
|
|
497
500
|
color: z.ZodString;
|
|
498
501
|
description: z.ZodOptional<z.ZodString>;
|
|
502
|
+
archived: z.ZodOptional<z.ZodLiteral<true>>;
|
|
499
503
|
}, z.core.$strict>>>>;
|
|
500
504
|
}, z.core.$strict>>;
|
|
501
505
|
retiredPackages: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
package/dist/esm/types.js
CHANGED
package/docs/labels.md
CHANGED
|
@@ -42,6 +42,20 @@ Names match case-insensitively, as GitHub matches them: `Bug` in the `labels` re
|
|
|
42
42
|
|
|
43
43
|
Overlaps between layers are never errors; order resolves them, and the committed `.github/labels.yaml` diff shows any unexpected change at review. Two misstatements are config errors, because neither is visible in the output diff: a dangling `null`, which removes a name not defined by any preset, and `labels` keys that differ only in case, all but one of which the fold would discard.
|
|
44
44
|
|
|
45
|
+
### Retiring a label
|
|
46
|
+
|
|
47
|
+
Mark a label `archived: true` to retire it without losing its history:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
labels: {
|
|
51
|
+
'scope:basic': { color: '00ff96', archived: true }, // retire the scope of a removed package
|
|
52
|
+
},
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The sync workflow archives a marked label rather than deleting it. An archived label stays on the issues and pull requests that have it, but it cannot be added to new ones and does not appear in the label picker. Removing the mark unarchives the label on the next sync. Removing the entry instead deletes the label, as the sync deletes every label that `.github/labels.yaml` does not declare, and the label disappears from every issue and pull request that had it.
|
|
56
|
+
|
|
57
|
+
The mark is set only in the `labels` record, not in a preset. To retire a preset's label, replace it with an entry that restates its color and adds the mark.
|
|
58
|
+
|
|
45
59
|
### When labels are applied
|
|
46
60
|
|
|
47
61
|
The scaffolded workflow declares three triggers:
|
|
@@ -56,6 +70,8 @@ Applying on merge closes the window in which a regenerated `.github/labels.yaml`
|
|
|
56
70
|
|
|
57
71
|
The `check` job runs the same sync in dry-run. Its **Label diff** log group lists every create, edit, rename, and deletion that the sync would perform, including deletions of labels that the file does not declare: the destructive edits that 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.
|
|
58
72
|
|
|
73
|
+
Its **Archive state** log group lists each label that the sync would archive or unarchive. Archiving runs after the rest of the sync, so a label that the file both adds and marks is created and then archived.
|
|
74
|
+
|
|
59
75
|
The two jobs are split because a job's permissions are fixed when the run is created and cannot vary by trigger. The split keeps a write-scoped token out of pull-request runs.
|
|
60
76
|
|
|
61
77
|
The push trigger filters on the path alone; the `sync` job compares `github.ref_name` against the repository's default branch. The workflow therefore doesn't need a 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.
|
package/docs/project-releases.md
CHANGED
|
@@ -68,7 +68,7 @@ CLI flag interactions:
|
|
|
68
68
|
- `--dry-run` previews project artifacts alongside workspace artifacts; it doesn't write any files.
|
|
69
69
|
- `--bump=major|minor|patch` propagates to the project release as a level chooser. It does not trigger a release on its own when the window doesn't have any commits or any bump-worthy commits.
|
|
70
70
|
- `--force` runs the project release even when the history since the last project tag doesn't have any commits or any bump-worthy commits. Defaults to patch when `--bump` is not given; combine with `--bump=X` to release at a different level.
|
|
71
|
-
- `--only` narrows the run to the named workspaces and skips the project release, which is reported as a warning. The project release rolls up every contributing workspace, and `--only` changes which workspaces those are, so rolling up a narrowed set would release a project version covering work that the run excluded. Run a full `prepare` (without `--only`) to include the project release.
|
|
72
|
-
- `--set-version` is rejected with an error when `project` is configured. `--set-version`
|
|
71
|
+
- `--only` narrows the run to the named workspaces and their dependents and skips the project release, which is reported as a warning. The project release rolls up every contributing workspace, and `--only` changes which workspaces those are, so rolling up a narrowed set would release a project version covering work that the run excluded. Run a full `prepare` (without `--only`) to include the project release.
|
|
72
|
+
- `--set-version` is rejected with an error when `project` is configured. `--set-version` sets individual workspaces' versions, but a project release rolls up every contributing workspace; the two semantics don't compose. To use `--set-version`, run on a config without a `project` block.
|
|
73
73
|
|
|
74
74
|
How `--bump` and `--force` combine for every target is in [Releasing](releasing.md#release-kit-prepare).
|
package/docs/readiness-checks.md
CHANGED
|
@@ -7,12 +7,12 @@ release-kit publishes two [readyup](https://www.npmjs.com/package/readyup) kits
|
|
|
7
7
|
The [README](../README.md#readiness-checks) shows how to name release-kit in the readyup config.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
rdy run --
|
|
10
|
+
rdy run --sources # every kit each listed package publishes
|
|
11
11
|
rdy run --from npm:@williamthorsen/release-kit # the default kit alone
|
|
12
12
|
rdy run --from npm:@williamthorsen/release-kit npm-auto-publish # the npm-auto-publish kit
|
|
13
13
|
rdy list --from npm:@williamthorsen/release-kit # what release-kit publishes
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
`--
|
|
16
|
+
`--sources` is the form that keeps working when release-kit publishes further kits.
|
|
17
17
|
|
|
18
18
|
Because `npm-auto-publish` queries the npm registry for each package's trusted publisher, it needs an npm session elevated by two-factor authentication, and is meant to be invoked deliberately rather than included in an unattended run. A session that cannot answer those queries, whether it is missing a login, missing the elevation, or facing an unreachable registry, is reported once by the `npm session can answer trust queries` gate. The rows that would repeat the failed query are then skipped: the trusted-publisher rows in every case, and `published to npm` when the registry itself is unreachable. Each package's `package.json` checks report throughout, since they don't read a registry.
|
package/docs/releasing.md
CHANGED
|
@@ -29,7 +29,7 @@ The workflows are managed files: release-kit renders them, and a change made by
|
|
|
29
29
|
|
|
30
30
|
Runs release preparation with automatic workspace discovery.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
`--only` names the workspaces from which the release starts, by package directory name (e.g., `arrays`, `release-kit`). The run also releases every workspace that depends on one of them, directly or transitively: A dependent with commits of its own releases at its own bump, and one without releases at a propagated patch bump, so that it publishes against the new version. Workspaces outside that set are left out of the run.
|
|
33
33
|
|
|
34
34
|
`prepare` reads each target's history once and builds the changelog items of its unreleased commits. The bump is the highest level that those items call for, under [`versionPatterns`](configuration.md#versionpatterns), so the bump and the changelog describe the same release: A `feat` entry in a [change-record block](changelogs.md#change-record-blocks) bumps minor under a `docs:` title, and a breaking item bumps major.
|
|
35
35
|
|
|
@@ -82,18 +82,20 @@ docs/*.v*.md
|
|
|
82
82
|
|
|
83
83
|
The `--set-version` flag is a first-class escape hatch for the cases in which the derived bump produces the wrong version, most notably when promoting a pre-1.0 package to 1.0.0. Pre-1.0 packages collapse a `feat!` breaking change to a minor bump (matching semantic-release's `initialMajor: false` and release-please's `bump-minor-pre-major`), so a deliberate promotion to 1.0.0 must be requested explicitly.
|
|
84
84
|
|
|
85
|
+
In a single-package repo, the value is the version: `--set-version 1.0.0`. In a monorepo, it names the workspace with each version, as comma-separated `<workspace>@N.N.N` entries: `--set-version arrays@1.0.0,strings@2.0.0`. Each named workspace releases at its version whether or not it has commits, and starts the release like a workspace named by `--only`.
|
|
86
|
+
|
|
85
87
|
The flag validates that:
|
|
86
88
|
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
89
|
+
- Each version is canonical `N.N.N` semver (pre-release suffixes are rejected).
|
|
90
|
+
- In a monorepo, each entry names a known workspace, and none names it twice.
|
|
91
|
+
- Each version is strictly greater than the workspace's current version (numeric comparison on each component).
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
The rest of the pipeline (changelog generation, tag creation, commit summary, propagation to dependents) runs unchanged, so each dependent receives a propagated patch bump that records the explicit version. Without `--only`, the run is a full run with the explicit versions applied. `--bump` and `--force` combine with `--set-version` and govern every other workspace.
|
|
92
94
|
|
|
93
|
-
Promoting a pre-1.0 package to 1.0.0 in a monorepo:
|
|
95
|
+
Promoting a pre-1.0 package to 1.0.0 in a monorepo, which also releases its dependents:
|
|
94
96
|
|
|
95
97
|
```sh
|
|
96
|
-
release-kit prepare --
|
|
98
|
+
release-kit prepare --set-version arrays@1.0.0 --only arrays
|
|
97
99
|
```
|
|
98
100
|
|
|
99
101
|
An empty changelog section is expected for a bare promotion, because the changelog is generated from commits since the last tag. To include a narrative entry, merge a descriptive release commit (e.g., a `feat!` describing the stable API) before running `prepare`.
|
|
@@ -118,7 +120,7 @@ The `init` command scaffolds a release workflow at `.github/workflows/release.ya
|
|
|
118
120
|
|
|
119
121
|
| Input | Type | Description |
|
|
120
122
|
| ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
121
|
-
| `only` | string | Workspaces to release (comma-separated, leave empty for all)
|
|
123
|
+
| `only` | string | Workspaces to release, with their dependents (comma-separated, leave empty for all) |
|
|
122
124
|
| `bump` | choice | Bump type: `auto` (default, detects it from commits), `patch`, `minor`, or `major` |
|
|
123
125
|
| `force` | boolean | Release even when the window doesn't have any commits or any bump-worthy commits (defaults to patch; combine with `bump` for a different level) |
|
|
124
126
|
|
|
@@ -130,7 +132,7 @@ For repos that need a self-contained workflow instead of the reusable one, the s
|
|
|
130
132
|
# All workspaces
|
|
131
133
|
gh workflow run release.yaml
|
|
132
134
|
|
|
133
|
-
# Specific workspace(s)
|
|
135
|
+
# Specific workspace(s), with their dependents
|
|
134
136
|
gh workflow run release.yaml -f only=arrays
|
|
135
137
|
gh workflow run release.yaml -f only=arrays,strings -f bump=minor
|
|
136
138
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/release-kit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.0.0",
|
|
4
4
|
"description": "Version-bumping and changelog-generation toolkit for release workflows",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"changelog",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"schemas/**"
|
|
51
51
|
],
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@williamthorsen/change-grammar": "0.
|
|
54
|
-
"@williamthorsen/nmr-core": "0.13.
|
|
55
|
-
"@williamthorsen/toolbelt.errors": "0.8.
|
|
53
|
+
"@williamthorsen/change-grammar": "0.2.0",
|
|
54
|
+
"@williamthorsen/nmr-core": "0.13.2",
|
|
55
|
+
"@williamthorsen/toolbelt.errors": "0.8.2",
|
|
56
56
|
"json-stringify-pretty-compact": "4.0.0",
|
|
57
57
|
"semver": "7.8.5",
|
|
58
58
|
"yaml": "2.9.1",
|
|
@@ -60,12 +60,12 @@
|
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/semver": "7.8.0",
|
|
63
|
-
"@williamthorsen/toolbelt.filesystem": "0.9.
|
|
64
|
-
"@williamthorsen/toolbelt.testing": "0.6.
|
|
65
|
-
"@williamthorsen/toolbelt.vitest": "0.10.
|
|
63
|
+
"@williamthorsen/toolbelt.filesystem": "0.9.2",
|
|
64
|
+
"@williamthorsen/toolbelt.testing": "0.6.2",
|
|
65
|
+
"@williamthorsen/toolbelt.vitest": "0.10.2",
|
|
66
66
|
"actionlint": "2.0.6",
|
|
67
67
|
"esbuild": "0.28.2",
|
|
68
|
-
"readyup": "0.
|
|
68
|
+
"readyup": "0.40.0",
|
|
69
69
|
"smol-toml": "1.9.0",
|
|
70
70
|
"vitest": "4.1.11"
|
|
71
71
|
},
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { DependencyGraph } from './buildDependencyGraph.js';
|
|
2
|
-
import type { WorkspaceConfig } from './types.js';
|
|
3
|
-
/** Result of probing a workspace's commits since its last tag. */
|
|
4
|
-
export interface CommitsProbeResult {
|
|
5
|
-
/** Whether the workspace has any commits since its last tag (excluding `release:` commits). */
|
|
6
|
-
has: boolean;
|
|
7
|
-
/**
|
|
8
|
-
* The baseline tag from which the commits were measured; `undefined` when the workspace does not have a prior tag.
|
|
9
|
-
*/
|
|
10
|
-
tag: string | undefined;
|
|
11
|
-
}
|
|
12
|
-
/** A single excluded workspace whose changes would be stranded by the `--only` invocation. */
|
|
13
|
-
export interface StrandedDependentViolation {
|
|
14
|
-
/** The excluded workspace's `dir`. */
|
|
15
|
-
dir: string;
|
|
16
|
-
/** The `dir` of the released (or anticipated-release) dependency whose release republishes this workspace. */
|
|
17
|
-
downstreamOf: string;
|
|
18
|
-
/**
|
|
19
|
-
* The baseline tag from which this workspace's commits were counted; `undefined` if the workspace does not have a
|
|
20
|
-
* prior tag.
|
|
21
|
-
*/
|
|
22
|
-
tag: string | undefined;
|
|
23
|
-
}
|
|
24
|
-
/** Probe callback that returns whether a workspace has commits since its last tag. */
|
|
25
|
-
type CommitsProbe = (workspace: WorkspaceConfig) => CommitsProbeResult;
|
|
26
|
-
/**
|
|
27
|
-
* Detects `--only` invocations that would silently strand changes in excluded internal dependents.
|
|
28
|
-
*
|
|
29
|
-
* First computes R, the `--only` workspaces that will release, then walks the reverse-dependency graph breadth-first
|
|
30
|
-
* from R and records each excluded workspace with its own commits as a violation. The walk continues through a
|
|
31
|
-
* violation, as though the user had added it to `--only`, which reports deeper violations in the same pass. It stops
|
|
32
|
-
* at an excluded workspace without commits, which does not republish, so its dependents are unaffected by R.
|
|
33
|
-
*
|
|
34
|
-
* Returns `undefined` when the walk does not find any violation, or the violations sorted by `dir`.
|
|
35
|
-
*/
|
|
36
|
-
export declare function validateOnlyExcludesStrandedDependents(workspaces: readonly WorkspaceConfig[], only: readonly string[], graph: DependencyGraph, hasCommits: CommitsProbe): StrandedDependentViolation[] | undefined;
|
|
37
|
-
export {};
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
export function validateOnlyExcludesStrandedDependents(workspaces, only, graph, hasCommits) {
|
|
2
|
-
const probeCommits = memoizeCommitsProbe(hasCommits);
|
|
3
|
-
const workspaceByDir = new Map(workspaces.map((w) => [w.dir, w]));
|
|
4
|
-
const released = computeReleasedSet(only, workspaceByDir, graph, probeCommits);
|
|
5
|
-
const violations = collectStrandedViolations(released, new Set(only), graph, probeCommits);
|
|
6
|
-
if (violations.length === 0)
|
|
7
|
-
return undefined;
|
|
8
|
-
violations.sort((a, b) => a.dir.localeCompare(b.dir));
|
|
9
|
-
return violations;
|
|
10
|
-
}
|
|
11
|
-
function memoizeCommitsProbe(probe) {
|
|
12
|
-
const cache = new Map();
|
|
13
|
-
return (workspace) => {
|
|
14
|
-
const cached = cache.get(workspace.dir);
|
|
15
|
-
if (cached !== undefined)
|
|
16
|
-
return cached;
|
|
17
|
-
const result = probe(workspace);
|
|
18
|
-
cache.set(workspace.dir, result);
|
|
19
|
-
return result;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
function computeReleasedSet(only, workspaceByDir, graph, probeCommits) {
|
|
23
|
-
const released = new Set();
|
|
24
|
-
for (const dir of only) {
|
|
25
|
-
const workspace = workspaceByDir.get(dir);
|
|
26
|
-
if (workspace !== undefined && probeCommits(workspace).has) {
|
|
27
|
-
released.add(dir);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
let changed = true;
|
|
31
|
-
while (changed) {
|
|
32
|
-
changed = false;
|
|
33
|
-
for (const dir of only) {
|
|
34
|
-
if (released.has(dir))
|
|
35
|
-
continue;
|
|
36
|
-
if (hasDependencyIn(dir, graph, released)) {
|
|
37
|
-
released.add(dir);
|
|
38
|
-
changed = true;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return released;
|
|
43
|
-
}
|
|
44
|
-
function hasDependencyIn(dir, graph, released) {
|
|
45
|
-
const forwardDeps = graph.dependenciesOf.get(dir);
|
|
46
|
-
if (forwardDeps === undefined)
|
|
47
|
-
return false;
|
|
48
|
-
for (const depPackageName of forwardDeps) {
|
|
49
|
-
const depDir = graph.packageNameToDir.get(depPackageName);
|
|
50
|
-
if (depDir !== undefined && released.has(depDir))
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
function collectStrandedViolations(released, onlySet, graph, probeCommits) {
|
|
56
|
-
const violations = [];
|
|
57
|
-
const violationDirs = new Set();
|
|
58
|
-
const visited = new Set();
|
|
59
|
-
const queue = [];
|
|
60
|
-
for (const dir of released) {
|
|
61
|
-
const packageName = graph.dirToPackageName.get(dir);
|
|
62
|
-
if (packageName !== undefined) {
|
|
63
|
-
queue.push({ packageName, attributionRoot: dir });
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
while (queue.length > 0) {
|
|
67
|
-
const item = queue.shift();
|
|
68
|
-
if (item === undefined)
|
|
69
|
-
break;
|
|
70
|
-
if (visited.has(item.packageName))
|
|
71
|
-
continue;
|
|
72
|
-
visited.add(item.packageName);
|
|
73
|
-
const dependents = graph.dependentsOf.get(item.packageName);
|
|
74
|
-
if (dependents === undefined)
|
|
75
|
-
continue;
|
|
76
|
-
for (const dependent of dependents) {
|
|
77
|
-
visitDependent(dependent, item.attributionRoot, {
|
|
78
|
-
released,
|
|
79
|
-
onlySet,
|
|
80
|
-
graph,
|
|
81
|
-
probeCommits,
|
|
82
|
-
queue,
|
|
83
|
-
violations,
|
|
84
|
-
violationDirs,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return violations;
|
|
89
|
-
}
|
|
90
|
-
function visitDependent(dependent, attributionRoot, ctx) {
|
|
91
|
-
if (ctx.onlySet.has(dependent.dir)) {
|
|
92
|
-
if (ctx.released.has(dependent.dir)) {
|
|
93
|
-
enqueueDependent(dependent, ctx);
|
|
94
|
-
}
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const probe = ctx.probeCommits(dependent);
|
|
98
|
-
if (!probe.has)
|
|
99
|
-
return;
|
|
100
|
-
if (!ctx.violationDirs.has(dependent.dir)) {
|
|
101
|
-
ctx.violationDirs.add(dependent.dir);
|
|
102
|
-
ctx.violations.push({
|
|
103
|
-
dir: dependent.dir,
|
|
104
|
-
downstreamOf: attributionRoot,
|
|
105
|
-
tag: probe.tag,
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
enqueueDependent(dependent, ctx);
|
|
109
|
-
}
|
|
110
|
-
function enqueueDependent(dependent, ctx) {
|
|
111
|
-
const packageName = ctx.graph.dirToPackageName.get(dependent.dir);
|
|
112
|
-
if (packageName !== undefined) {
|
|
113
|
-
ctx.queue.push({ packageName, attributionRoot: dependent.dir });
|
|
114
|
-
}
|
|
115
|
-
}
|