@williamthorsen/release-kit 14.1.0 → 14.2.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 +125 -0
- package/.readyup/kits/default.js +63 -6
- package/.readyup/kits/npm-auto-publish.js +47 -35
- package/.readyup/manifest.json +12 -7
- package/CHANGELOG.md +33 -0
- package/README.md +18 -8
- package/dist/esm/assertTaggedBaseline.d.ts +2 -2
- package/dist/esm/attachChangelogDiagnostics.d.ts +2 -2
- package/dist/esm/bin/release-kit.js +5 -4
- package/dist/esm/buildChangelogEntries.d.ts +3 -3
- package/dist/esm/buildChangelogEntries.js +3 -3
- package/dist/esm/buildDependencyGraph.d.ts +11 -11
- package/dist/esm/buildDependencyGraph.js +13 -13
- package/dist/esm/buildReleaseSummary.d.ts +3 -3
- package/dist/esm/buildReleaseSummary.js +4 -12
- package/dist/esm/changelogArchive.d.ts +14 -0
- package/dist/esm/changelogArchive.js +21 -0
- package/dist/esm/changelogJsonFile.d.ts +2 -2
- package/dist/esm/changelogOverrides.d.ts +9 -8
- package/dist/esm/changelogOverrides.js +3 -6
- package/dist/esm/createGithubReleaseCommand.d.ts +2 -1
- package/dist/esm/createGithubReleaseCommand.js +7 -4
- package/dist/esm/deriveDirectoryConfig.d.ts +13 -0
- package/dist/esm/deriveDirectoryConfig.js +59 -0
- package/dist/esm/deriveWorkspaceConfig.js +0 -1
- package/dist/esm/discoverWorkspaces.d.ts +3 -3
- package/dist/esm/findTransitiveDependents.js +3 -3
- package/dist/esm/findUnroutedEntryScopes.d.ts +2 -2
- package/dist/esm/formatDirectoryPublishSkip.d.ts +3 -0
- package/dist/esm/formatDirectoryPublishSkip.js +3 -0
- package/dist/esm/formatExcludedSkip.d.ts +5 -2
- package/dist/esm/formatExcludedSkip.js +2 -2
- package/dist/esm/formatProjectPublishSkip.d.ts +3 -0
- package/dist/esm/formatProjectPublishSkip.js +3 -0
- package/dist/esm/generateChangelogs.d.ts +7 -7
- package/dist/esm/generateChangelogs.js +2 -2
- package/dist/esm/help/prepareHelp.d.ts +1 -1
- package/dist/esm/help/prepareHelp.js +7 -5
- package/dist/esm/init/templates.js +1 -1
- package/dist/esm/loadConfig.d.ts +12 -12
- package/dist/esm/loadConfig.js +79 -30
- package/dist/esm/prepareCommand.d.ts +1 -1
- package/dist/esm/prepareCommand.js +47 -46
- package/dist/esm/prepareTarget.d.ts +103 -0
- package/dist/esm/prepareTarget.js +274 -0
- package/dist/esm/previewTagPrefixes.d.ts +5 -3
- package/dist/esm/previewTagPrefixes.js +58 -13
- package/dist/esm/propagateBumps.js +1 -1
- package/dist/esm/publishCommand.js +13 -1
- package/dist/esm/readChangelogSections.d.ts +6 -4
- package/dist/esm/readChangelogSections.js +18 -4
- package/dist/esm/releasePlan.d.ts +4 -2
- package/dist/esm/releasePlan.js +34 -12
- package/dist/esm/releasePrepareTargets.d.ts +47 -0
- package/dist/esm/releasePrepareTargets.js +335 -0
- package/dist/esm/renderChangelogMarkdown.d.ts +23 -0
- package/dist/esm/renderChangelogMarkdown.js +47 -11
- package/dist/esm/reportPrepare.d.ts +2 -3
- package/dist/esm/reportPrepare.js +24 -12
- package/dist/esm/resolveCommandTags.d.ts +3 -1
- package/dist/esm/resolveCommandTags.js +15 -22
- package/dist/esm/resolveReleaseDeclaration.d.ts +45 -0
- package/dist/esm/resolveReleaseDeclaration.js +153 -0
- package/dist/esm/resolveReleaseTags.d.ts +14 -3
- package/dist/esm/resolveReleaseTags.js +36 -15
- package/dist/esm/resolveReleaseTargets.d.ts +29 -0
- package/dist/esm/resolveReleaseTargets.js +131 -0
- package/dist/esm/sync-labels/generateCommand.js +2 -1
- package/dist/esm/sync-labels/resolveLabels.d.ts +4 -3
- package/dist/esm/sync-labels/resolveLabels.js +30 -1
- package/dist/esm/sync-labels/types.d.ts +2 -0
- package/dist/esm/types.d.ts +251 -167
- package/dist/esm/types.js +22 -0
- package/dist/esm/validateConfig.js +80 -21
- package/dist/esm/validateOverridesCommand.d.ts +1 -1
- package/dist/esm/validateOverridesCommand.js +17 -28
- package/docs/api.md +25 -58
- package/docs/changelogs.md +26 -6
- package/docs/configuration.md +79 -16
- package/docs/labels.md +25 -2
- package/docs/project-releases.md +42 -2
- package/docs/publishing.md +3 -3
- package/docs/releasing.md +4 -4
- package/docs/work-types.md +1 -1
- package/package.json +2 -2
- package/presets/labels/common.yaml +11 -0
- package/dist/esm/releasePrepare.d.ts +0 -36
- package/dist/esm/releasePrepare.js +0 -230
- package/dist/esm/releasePrepareMono.d.ts +0 -29
- package/dist/esm/releasePrepareMono.js +0 -503
- package/dist/esm/releasePrepareProject.d.ts +0 -47
- package/dist/esm/releasePrepareProject.js +0 -164
|
@@ -9,12 +9,13 @@ export function validateConfig(raw) {
|
|
|
9
9
|
if (!parseResult.success) {
|
|
10
10
|
return {
|
|
11
11
|
config: {},
|
|
12
|
-
errors: [...deprecationErrors, ...parseResult.error.issues.
|
|
12
|
+
errors: [...deprecationErrors, ...parseResult.error.issues.flatMap((issue) => formatZodIssue(issue, cleaned))],
|
|
13
13
|
warnings: [],
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
const config = parseResult.data;
|
|
17
17
|
const errors = [...deprecationErrors];
|
|
18
|
+
detectReleasesConflicts(config, errors);
|
|
18
19
|
detectLegacyIdentityDuplicates(config, errors);
|
|
19
20
|
detectRetiredPackageDuplicates(config, errors);
|
|
20
21
|
detectRetiredVsLegacyCollisions(config, errors);
|
|
@@ -57,12 +58,31 @@ function preprocessDeprecatedKeys(raw) {
|
|
|
57
58
|
return wsCopy;
|
|
58
59
|
});
|
|
59
60
|
}
|
|
61
|
+
if (isRecord(cleaned['releases'])) {
|
|
62
|
+
const releasesCopy = { ...cleaned['releases'] };
|
|
63
|
+
for (const [name, entry] of Object.entries(releasesCopy)) {
|
|
64
|
+
if (!isRecord(entry) || !Object.hasOwn(entry, 'paths'))
|
|
65
|
+
continue;
|
|
66
|
+
errors.push(`releases.${name}: 'paths' is not supported; a package's or a directory's commit window is its path, and a composite's is the union of its members' windows`);
|
|
67
|
+
const entryCopy = { ...entry };
|
|
68
|
+
delete entryCopy['paths'];
|
|
69
|
+
releasesCopy[name] = entryCopy;
|
|
70
|
+
}
|
|
71
|
+
cleaned['releases'] = releasesCopy;
|
|
72
|
+
}
|
|
60
73
|
return { cleaned, deprecationErrors: errors };
|
|
61
74
|
}
|
|
62
|
-
function formatZodIssue(issue) {
|
|
75
|
+
function formatZodIssue(issue, input) {
|
|
76
|
+
if (issue.code === 'invalid_union' && issue.path.length === 2 && issue.path[0] === 'releases') {
|
|
77
|
+
const branchIssues = issue.errors[selectReleaseEntryBranch(readAtPath(input, issue.path))];
|
|
78
|
+
if (branchIssues === undefined) {
|
|
79
|
+
return [`${renderPath(issue.path)}: ${RELEASE_ENTRY_SHAPES}`];
|
|
80
|
+
}
|
|
81
|
+
return branchIssues.flatMap((branchIssue) => formatZodIssue({ ...branchIssue, path: [...issue.path, ...branchIssue.path] }, input));
|
|
82
|
+
}
|
|
63
83
|
const path = renderPath(issue.path);
|
|
64
84
|
const message = customizeMessage(issue);
|
|
65
|
-
return path === '' ? message : `${path}: ${message}
|
|
85
|
+
return [path === '' ? message : `${path}: ${message}`];
|
|
66
86
|
}
|
|
67
87
|
function customizeMessage(issue) {
|
|
68
88
|
if (issue.code === 'too_small' && issue.origin === 'string') {
|
|
@@ -70,6 +90,7 @@ function customizeMessage(issue) {
|
|
|
70
90
|
}
|
|
71
91
|
return issue.message;
|
|
72
92
|
}
|
|
93
|
+
const RELEASE_ENTRY_SHAPES = 'must be false, { path, tagPrefix?, legacyIdentities? }, or { includes, tagPrefix? }';
|
|
73
94
|
function renderPath(path) {
|
|
74
95
|
let rendered = '';
|
|
75
96
|
for (const segment of path) {
|
|
@@ -86,16 +107,12 @@ function renderPath(path) {
|
|
|
86
107
|
return rendered;
|
|
87
108
|
}
|
|
88
109
|
function detectLegacyIdentityDuplicates(config, errors) {
|
|
89
|
-
|
|
90
|
-
return;
|
|
91
|
-
for (const [wsIndex, workspace] of config.workspaces.entries()) {
|
|
92
|
-
if (workspace.legacyIdentities === undefined)
|
|
93
|
-
continue;
|
|
110
|
+
for (const { label, legacyIdentities } of listLegacyIdentityDeclarations(config)) {
|
|
94
111
|
const seen = new Set();
|
|
95
|
-
for (const [entryIndex, identity] of
|
|
112
|
+
for (const [entryIndex, identity] of legacyIdentities.entries()) {
|
|
96
113
|
const key = `${identity.name}\0${identity.tagPrefix}`;
|
|
97
114
|
if (seen.has(key)) {
|
|
98
|
-
errors.push(
|
|
115
|
+
errors.push(`${label}.legacyIdentities[${entryIndex}]: duplicate identity (name='${identity.name}', tagPrefix='${identity.tagPrefix}')`);
|
|
99
116
|
}
|
|
100
117
|
seen.add(key);
|
|
101
118
|
}
|
|
@@ -114,22 +131,64 @@ function detectRetiredPackageDuplicates(config, errors) {
|
|
|
114
131
|
}
|
|
115
132
|
}
|
|
116
133
|
function detectRetiredVsLegacyCollisions(config, errors) {
|
|
117
|
-
if (config.retiredPackages === undefined
|
|
134
|
+
if (config.retiredPackages === undefined)
|
|
118
135
|
return;
|
|
119
|
-
const
|
|
120
|
-
for (const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (!legacyPrefixToWorkspace.has(identity.tagPrefix)) {
|
|
125
|
-
legacyPrefixToWorkspace.set(identity.tagPrefix, workspace.dir);
|
|
136
|
+
const legacyPrefixToOwner = new Map();
|
|
137
|
+
for (const { owner, legacyIdentities } of listLegacyIdentityDeclarations(config)) {
|
|
138
|
+
for (const identity of legacyIdentities) {
|
|
139
|
+
if (!legacyPrefixToOwner.has(identity.tagPrefix)) {
|
|
140
|
+
legacyPrefixToOwner.set(identity.tagPrefix, owner);
|
|
126
141
|
}
|
|
127
142
|
}
|
|
128
143
|
}
|
|
129
144
|
for (const [index, retired] of config.retiredPackages.entries()) {
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
132
|
-
errors.push(`retiredPackages[${index}]: tagPrefix '${retired.tagPrefix}' collides with a declared legacyIdentities[].tagPrefix on
|
|
145
|
+
const collidingOwner = legacyPrefixToOwner.get(retired.tagPrefix);
|
|
146
|
+
if (collidingOwner !== undefined) {
|
|
147
|
+
errors.push(`retiredPackages[${index}]: tagPrefix '${retired.tagPrefix}' collides with a declared legacyIdentities[].tagPrefix on ${collidingOwner}`);
|
|
133
148
|
}
|
|
134
149
|
}
|
|
135
150
|
}
|
|
151
|
+
function detectReleasesConflicts(config, errors) {
|
|
152
|
+
if (config.releases === undefined)
|
|
153
|
+
return;
|
|
154
|
+
const legacyBlocks = [
|
|
155
|
+
...(config.workspaces === undefined ? [] : ['workspaces']),
|
|
156
|
+
...(config.project === undefined ? [] : ['project']),
|
|
157
|
+
];
|
|
158
|
+
if (legacyBlocks.length === 0)
|
|
159
|
+
return;
|
|
160
|
+
errors.push(`releases cannot be combined with ${legacyBlocks.join(' or ')}; declare every target under releases, or remove releases`);
|
|
161
|
+
}
|
|
162
|
+
function listLegacyIdentityDeclarations(config) {
|
|
163
|
+
const fromWorkspaces = (config.workspaces ?? []).flatMap((workspace, index) => workspace.legacyIdentities === undefined
|
|
164
|
+
? []
|
|
165
|
+
: [
|
|
166
|
+
{
|
|
167
|
+
label: `workspaces[${index}]`,
|
|
168
|
+
owner: `workspace '${workspace.dir}'`,
|
|
169
|
+
legacyIdentities: workspace.legacyIdentities,
|
|
170
|
+
},
|
|
171
|
+
]);
|
|
172
|
+
const fromReleases = Object.entries(config.releases ?? {}).flatMap(([name, entry]) => entry === false || !('path' in entry) || entry.legacyIdentities === undefined
|
|
173
|
+
? []
|
|
174
|
+
: [{ label: `releases.${name}`, owner: `release '${name}'`, legacyIdentities: entry.legacyIdentities }]);
|
|
175
|
+
return [...fromWorkspaces, ...fromReleases];
|
|
176
|
+
}
|
|
177
|
+
function readAtPath(input, path) {
|
|
178
|
+
let value = input;
|
|
179
|
+
for (const segment of path) {
|
|
180
|
+
if (!isRecord(value) || typeof segment === 'symbol')
|
|
181
|
+
return undefined;
|
|
182
|
+
value = value[String(segment)];
|
|
183
|
+
}
|
|
184
|
+
return value;
|
|
185
|
+
}
|
|
186
|
+
function selectReleaseEntryBranch(entry) {
|
|
187
|
+
if (!isRecord(entry))
|
|
188
|
+
return -1;
|
|
189
|
+
if (Object.hasOwn(entry, 'includes'))
|
|
190
|
+
return 2;
|
|
191
|
+
if (Object.hasOwn(entry, 'path'))
|
|
192
|
+
return 1;
|
|
193
|
+
return -1;
|
|
194
|
+
}
|
|
@@ -2,7 +2,7 @@ import { type OutputStyle, type StreamStyles } from '@williamthorsen/nmr-core';
|
|
|
2
2
|
import { type ReleaseHistoryConfig } from './buildChangelogEntries.js';
|
|
3
3
|
import { type ValidateAllChangelogOverridesInputs, type ValidateAllChangelogOverridesResult } from './changelogOverrides.js';
|
|
4
4
|
import { type WorkspaceDiscovery } from './discoverWorkspaces.js';
|
|
5
|
-
import {
|
|
5
|
+
import type { GenerateChangelogOptions } from './generateChangelogs.js';
|
|
6
6
|
import { type LoadValidatedConfigResult } from './loadValidatedConfig.js';
|
|
7
7
|
import type { ChangelogEntry } from './types.js';
|
|
8
8
|
/**
|
|
@@ -3,9 +3,9 @@ import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
|
3
3
|
import { buildChangelogEntries } from "./buildChangelogEntries.js";
|
|
4
4
|
import { resolveOverridePath, validateAllChangelogOverrides, } from "./changelogOverrides.js";
|
|
5
5
|
import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
6
|
-
import { getAllTagPrefixes } from "./generateChangelogs.js";
|
|
7
|
-
import { mergeMonorepoConfig, mergeSinglePackageConfig, readRootPackageVersion } from "./loadConfig.js";
|
|
8
6
|
import { loadValidatedConfig } from "./loadValidatedConfig.js";
|
|
7
|
+
import { describeHistoryWindow } from "./prepareTarget.js";
|
|
8
|
+
import { isSinglePackageTargets, resolveReleaseTargets } from "./resolveReleaseTargets.js";
|
|
9
9
|
const SYNTHETIC_VALIDATE_TAG = 'validate-only';
|
|
10
10
|
export async function validateOverridesCommand(styles, configPath, dependencies = {}) {
|
|
11
11
|
const discover = dependencies.discoverWorkspaces ?? discoverWorkspaces;
|
|
@@ -32,10 +32,7 @@ export async function validateOverridesCommand(styles, configPath, dependencies
|
|
|
32
32
|
}
|
|
33
33
|
let inputs;
|
|
34
34
|
try {
|
|
35
|
-
inputs =
|
|
36
|
-
workspace.kind === 'single-package'
|
|
37
|
-
? buildSinglePackageInputs(userConfig, buildEntries)
|
|
38
|
-
: buildMonorepoInputs(workspace.packageDirs, userConfig, buildEntries);
|
|
35
|
+
inputs = buildValidationInputs(resolveReleaseTargets(userConfig, workspace), buildEntries);
|
|
39
36
|
}
|
|
40
37
|
catch (error) {
|
|
41
38
|
return { exitCode: 2, message: formatErrorLine(`Failed to resolve overrides scope: ${describeError(error)}`) };
|
|
@@ -89,30 +86,22 @@ function formatConfigProblem(problem) {
|
|
|
89
86
|
? formatErrorLine(`Failed to load config: ${problem.message}`)
|
|
90
87
|
: `Invalid config:\n - ${problem.errors.join('\n - ')}`;
|
|
91
88
|
}
|
|
92
|
-
function
|
|
93
|
-
const config = mergeSinglePackageConfig(userConfig);
|
|
94
|
-
const items = flattenEntriesToItems(buildEntries(config, { tagPrefixes: [config.tagPrefix] }));
|
|
95
|
-
return {
|
|
96
|
-
project: { filePath: resolveOverridePath('.'), items },
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
function buildMonorepoInputs(discoveredPaths, userConfig, buildEntries) {
|
|
100
|
-
const rootPackage = readRootPackageVersion();
|
|
101
|
-
const config = mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage);
|
|
102
|
-
const workspaces = config.workspaces.map((workspace) => {
|
|
103
|
-
const options = { tagPrefixes: getAllTagPrefixes(workspace), paths: workspace.paths, workspaceDir: workspace.dir };
|
|
104
|
-
return {
|
|
105
|
-
filePath: resolveOverridePath(workspace.workspacePath),
|
|
106
|
-
items: flattenEntriesToItems(buildEntries(config, options)),
|
|
107
|
-
};
|
|
108
|
-
});
|
|
109
|
-
const project = config.project;
|
|
89
|
+
function buildValidationInputs({ targets, settings }, buildEntries) {
|
|
110
90
|
const projectScope = {
|
|
111
91
|
filePath: resolveOverridePath('.'),
|
|
112
92
|
};
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
93
|
+
const workspaces = [];
|
|
94
|
+
for (const target of targets) {
|
|
95
|
+
const items = flattenEntriesToItems(buildEntries(settings, describeHistoryWindow(target)));
|
|
96
|
+
if (target.kind === 'package' && target.isWorkspace) {
|
|
97
|
+
workspaces.push({ filePath: resolveOverridePath(target.packagePath), items });
|
|
98
|
+
}
|
|
99
|
+
else if (target.kind === 'directory' && target.directoryPath !== '.') {
|
|
100
|
+
workspaces.push({ filePath: resolveOverridePath(target.directoryPath), items });
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
projectScope.items = items;
|
|
104
|
+
}
|
|
116
105
|
}
|
|
117
|
-
return { project: projectScope, workspaces };
|
|
106
|
+
return isSinglePackageTargets(targets) ? { project: projectScope } : { project: projectScope, workspaces };
|
|
118
107
|
}
|
package/docs/api.md
CHANGED
|
@@ -1,73 +1,40 @@
|
|
|
1
1
|
# Programmatic API
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The package exports two entries: `.`, which validates changelog override files, and `./config`, which types a config file. Every other module is internal, and the CLI is the supported way to release.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## `./config`: typing a config file
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`defineConfig()` returns its argument unchanged, typed as a `ReleaseKitConfig`. The entry also exports the `ReleaseKitConfig`, `RepoLabelsConfig`, and `LabelSpec` types.
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
|
-
|
|
10
|
+
// .config/release-kit.config.ts
|
|
11
|
+
import { defineConfig } from '@williamthorsen/release-kit/config';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
deriveWorkspaceConfig('packages/arrays');
|
|
14
|
-
// => {
|
|
15
|
-
// dir: 'arrays',
|
|
16
|
-
// name: '@scope/arrays',
|
|
17
|
-
// tagPrefix: 'arrays-v',
|
|
18
|
-
// workspacePath: 'packages/arrays',
|
|
19
|
-
// packageFiles: ['packages/arrays/package.json'],
|
|
20
|
-
// changelogPaths: ['packages/arrays'],
|
|
21
|
-
// paths: ['packages/arrays/**'],
|
|
22
|
-
// }
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
`dir` is the basename of the workspace path and is the stable internal identifier used by `--only`, `WorkspaceOverride.dir`, and the dependency graph. `tagPrefix` is derived from the unscoped `package.json` `name` (with any leading `@scope/` stripped), so tags reflect the package identity rather than the directory layout. For example, a workspace at `packages/core` with `"name": "@williamthorsen/nmr-core"` produces `tagPrefix: 'nmr-core-v'`, yielding tags like `nmr-core-v1.3.0`.
|
|
26
|
-
|
|
27
|
-
The workspace's `package.json` must declare a non-empty `name` field; `deriveWorkspaceConfig()` throws otherwise. If two workspaces produce the same `tagPrefix` (because their unscoped names collide), `mergeMonorepoConfig()` throws and names the colliding workspaces so that the developer can rename one.
|
|
28
|
-
|
|
29
|
-
## Legacy script-based approach
|
|
30
|
-
|
|
31
|
-
The CLI-driven approach is recommended for new setups. The script-based approach (using `runReleasePrepare` with a manually maintained config) is still supported for backward compatibility.
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
// .github/scripts/release.config.ts
|
|
35
|
-
import type { MonorepoReleaseConfig } from '@williamthorsen/release-kit';
|
|
36
|
-
import { deriveWorkspaceConfig } from '@williamthorsen/release-kit';
|
|
37
|
-
|
|
38
|
-
export const config: MonorepoReleaseConfig = {
|
|
39
|
-
workspaces: [deriveWorkspaceConfig('packages/arrays'), deriveWorkspaceConfig('packages/strings')],
|
|
13
|
+
export default defineConfig({
|
|
40
14
|
formatCommand: 'npx prettier --write',
|
|
41
|
-
};
|
|
15
|
+
});
|
|
42
16
|
```
|
|
43
17
|
|
|
44
|
-
|
|
45
|
-
// .github/scripts/release-prepare.ts
|
|
46
|
-
import { runReleasePrepare } from '@williamthorsen/release-kit';
|
|
47
|
-
import { config } from './release.config.ts';
|
|
48
|
-
|
|
49
|
-
runReleasePrepare(config);
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
The key difference: The script-based approach requires manually listing every workspace, while the CLI auto-discovers them from `pnpm-workspace.yaml`.
|
|
18
|
+
The entry does not import any value, so a config load does not pull in the rest of the package. [Configuration](configuration.md) describes the fields.
|
|
53
19
|
|
|
54
|
-
##
|
|
20
|
+
## `.`: validating override files
|
|
55
21
|
|
|
56
|
-
|
|
22
|
+
`validateAllChangelogOverrides()` checks override files against the changelog items of their scopes, which the caller supplies, and returns every error and stale-key warning, each prefixed with the path of its file. `formatValidateOverridesResult()` turns that result into the exit code and message that `release-kit overrides validate` prints.
|
|
57
23
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
24
|
+
```typescript
|
|
25
|
+
import { formatValidateOverridesResult, validateAllChangelogOverrides } from '@williamthorsen/release-kit';
|
|
26
|
+
|
|
27
|
+
const result = validateAllChangelogOverrides({
|
|
28
|
+
project: { filePath: '.meta/changelog-overrides.json' },
|
|
29
|
+
workspaces: [
|
|
30
|
+
{
|
|
31
|
+
filePath: 'packages/core/.meta/changelog-overrides.json',
|
|
32
|
+
items: [{ hash: '3f2a9c1e0b7d4a6f8e5c2b1a0d9e8f7a6b5c4d3e' }, { hash: '9b8a7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b', entry: 2 }],
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const { exitCode, message } = formatValidateOverridesResult(result, 'plain');
|
|
71
38
|
```
|
|
72
39
|
|
|
73
|
-
|
|
40
|
+
An item is a commit hash, with `entry` set to the 1-based position of the change-record entry from which the item derives. A relative `filePath` resolves against the working directory. The exported types are `ValidateAllChangelogOverridesInputs`, `ChangelogOverrideScope`, `OverrideTargetItem`, `ValidateAllChangelogOverridesResult`, and `ValidateOverridesCommandResult`. [Editorial overrides](editorial-overrides.md) describes the file format and the tiers.
|
package/docs/changelogs.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Changelogs
|
|
2
2
|
|
|
3
|
-
What a published tarball includes, what each `.meta/changelog.json` item contains, which commits appear in a changelog, how a merge commit's change-record block becomes items, and how release notes are written into a README.
|
|
3
|
+
What a published tarball includes, what each `.meta/changelog.json` item contains, how `CHANGELOG.md` archives older versions, which commits appear in a changelog, how a merge commit's change-record block becomes items, and how release notes are written into a README.
|
|
4
4
|
|
|
5
|
-
npm doesn't include either `CHANGELOG.md` or `.meta/changelog.json` in a published tarball on its own. npm stopped including `CHANGELOG` automatically in npm 7, so a package that declares a `files` field publishes a changelog only when that field names `CHANGELOG.md` and `.meta/changelog.json`. release-kit's readyup kit reports a publishable workspace whose `files` field omits either.
|
|
5
|
+
npm doesn't include either `CHANGELOG.md` or `.meta/changelog.json` in a published tarball on its own. npm stopped including `CHANGELOG` automatically in npm 7, so a package that declares a `files` field publishes a changelog only when that field names `CHANGELOG.md` and `.meta/changelog.json`. release-kit's readyup kit reports a publishable workspace whose `files` field omits either, and, when [`changelogMarkdown.keepVersions`](#archived-versions) is set, one whose `files` field omits `changelogs/`.
|
|
6
6
|
|
|
7
7
|
## `changelog.json` item schema
|
|
8
8
|
|
|
@@ -41,6 +41,26 @@ Three properties are worth knowing:
|
|
|
41
41
|
|
|
42
42
|
The label match is exact: `migration:` and `**Migration:**` are not recognized.
|
|
43
43
|
|
|
44
|
+
## Archived versions
|
|
45
|
+
|
|
46
|
+
`changelogMarkdown.keepVersions` bounds `CHANGELOG.md`, which otherwise grows by one section per release. `.meta/changelog.json` is not affected: It contains every version whatever the option says.
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
export default defineConfig({
|
|
50
|
+
changelogMarkdown: { keepVersions: 20 },
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
With the option set, `prepare` writes each changelog as follows:
|
|
55
|
+
|
|
56
|
+
- **`CHANGELOG.md`** contains the newest `keepVersions` sections, counting [hand-written sections](#existing-changelogmd-sections), and a paragraph below its header that links to each archive file.
|
|
57
|
+
- **`changelogs/CHANGELOG-v{major}.md`**, beside `CHANGELOG.md`, contains every older section of that major version, newest first, under the same header and footer. `0.x` versions share `CHANGELOG-v0.md`, and a prerelease belongs to its major.
|
|
58
|
+
- **An archive file that the render no longer produces**, after `keepVersions` is raised or the option is removed: `prepare` deletes it and renders its sections in `CHANGELOG.md` again. Every section is therefore in exactly one file.
|
|
59
|
+
|
|
60
|
+
`prepare` regenerates the archive files on every release of the target, so an override applies to an archived version as it does to any other. A target that does not release keeps its unbounded `CHANGELOG.md` until its next release. The prepare report lists each archive file that it writes and each that it removes.
|
|
61
|
+
|
|
62
|
+
A package that declares a `files` field publishes the archive files only when the field covers `changelogs/`; without them, the links from `CHANGELOG.md` do not resolve in the published package.
|
|
63
|
+
|
|
44
64
|
## Which commits appear in a changelog
|
|
45
65
|
|
|
46
66
|
release-kit splits the history reachable from `HEAD` into one window per release tag that matches the scope's tag prefixes, restricted to commits that touch the scope's `paths`. The bump reads the same items: Its level is the highest that the unreleased window's items call for, so a commit that bumps is one that the changelog lists.
|
|
@@ -55,7 +75,7 @@ A commit whose last `change-record` block records entries yields its items from
|
|
|
55
75
|
|
|
56
76
|
### Existing `CHANGELOG.md` sections
|
|
57
77
|
|
|
58
|
-
`prepare` also reads the `CHANGELOG.md` that it regenerates, so a version recorded only there, such as history written by hand before release-kit was adopted, survives. The first SemVer token in a `##` heading names the section's version: `## 1.2.0`, `## v1.2.0`, and `## [1.2.0] - 2024-01-01` all name 1.2.0, and so does `## Upgrading from 1.2.0`.
|
|
78
|
+
`prepare` also reads the `CHANGELOG.md` that it regenerates, and its [archive files](#archived-versions), so a version recorded only there, such as history written by hand before release-kit was adopted, survives. The first SemVer token in a `##` heading names the section's version: `## 1.2.0`, `## v1.2.0`, and `## [1.2.0] - 2024-01-01` all name 1.2.0, and so does `## Upgrading from 1.2.0`.
|
|
59
79
|
|
|
60
80
|
- **A version that neither the release windows nor `changelog.json` contain** keeps its section verbatim, placed among the rendered sections in version order.
|
|
61
81
|
- **A version that either contains** is rendered from its entries, and its old text is discarded.
|
|
@@ -67,11 +87,11 @@ The header above the first section and the footer comment are regenerated. The p
|
|
|
67
87
|
|
|
68
88
|
A workspace's window contains every commit that touches the workspace's paths. An item derived from a title is routed to every workspace whose window contains its commit. An item derived from a change-record entry is routed to a workspace in that window only when the entry's `scopes` route it there:
|
|
69
89
|
|
|
70
|
-
- **A scope that names the workspace's `dir
|
|
90
|
+
- **A scope that names the workspace's target name**, which is its `dir` unless a [`releases`](configuration.md#releases) entry renames it, routes the item to that workspace. release-kit resolves each scope through [`scopeAliases`](configuration.md) before it compares the scope with the name.
|
|
71
91
|
- **`*`, or an empty or absent `scopes`,** routes the item to every workspace in the window.
|
|
72
92
|
- **`root`** doesn't name a workspace, so it routes the item nowhere and is not reported. A workspace whose `dir` is `root` is the exception: `root` then names that workspace.
|
|
73
93
|
|
|
74
|
-
A scope narrows the window and never widens it: A scope that names a workspace whose window does not contain the commit doesn't route the item to any workspace, and `prepare` reports it (see [Reported diagnostics](#reported-diagnostics)).
|
|
94
|
+
A scope narrows the window and never widens it: A scope that names a workspace whose window does not contain the commit doesn't route the item to any workspace, and `prepare` reports it (see [Reported diagnostics](#reported-diagnostics)). A composite's changelog, such as the project changelog, and a single-package repo take every entry, whatever its scopes.
|
|
75
95
|
|
|
76
96
|
A workspace's bump reads the items routed to it, so a breaking entry scoped to one workspace does not raise a major bump in another that the same commit touched. A workspace whose window has commits but lacks a routed item doesn't call for a release.
|
|
77
97
|
|
|
@@ -117,7 +137,7 @@ A commit is read from its title, as [Which commits appear in a changelog](#which
|
|
|
117
137
|
- **A malformed block**, with the defect that stopped the read.
|
|
118
138
|
- **An entry whose type is not declared**, with its position. It doesn't yield an item.
|
|
119
139
|
- **An entry that violates its type's breaking policy**, as a policy violation at the entry's position. Its item is not marked breaking.
|
|
120
|
-
- **An entry scope that doesn't name a workspace in its commit's window**, with the commit's hash, the entry's position, and the scope as the entry declares it. release-kit reports it under every workspace whose window contains the commit. A scope that names nothing, or a workspace whose window does not contain the commit, is reported; `*`, `root`, and a scope that names a workspace in the window are not. Under `--only`, a scope that names a configured workspace left out of the run is not reported either. A workspace excluded with `shouldExclude: true` is not a configured workspace, so a scope that names one is reported.
|
|
140
|
+
- **An entry scope that doesn't name a workspace in its commit's window**, with the commit's hash, the entry's position, and the scope as the entry declares it. release-kit reports it under every workspace whose window contains the commit. A scope that names nothing, or a workspace whose window does not contain the commit, is reported; `*`, `root`, and a scope that names a workspace in the window are not. Under `--only`, a scope that names a configured workspace left out of the run is not reported either. A workspace excluded with `shouldExclude: true` or a `releases` entry of `false` is not a configured workspace, so a scope that names one is reported.
|
|
121
141
|
|
|
122
142
|
The result that `prepare` returns includes these as `malformedBlocks`, `undeclaredEntryTypes`, `policyViolations` entries whose `surface` is `'entry'`, and `unroutedEntryScopes`, whether the target releases or is skipped. Because a block whose entries are all undeclared or excluded doesn't yield an item, the commit doesn't raise a bump.
|
|
123
143
|
|
package/docs/configuration.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Configuration
|
|
2
2
|
|
|
3
|
-
The fields of `.config/release-kit.config.ts`, how a workspace declares its prior identities or a retired package, the rule against colliding tag prefixes, the patterns that decide a bump, and the environment variable that sets the output style.
|
|
3
|
+
The fields of `.config/release-kit.config.ts`, how the `releases` block names release targets, how a workspace declares its prior identities or a retired package, the rule against colliding tag prefixes, the patterns that decide a bump, and the environment variable that sets the output style.
|
|
4
4
|
|
|
5
5
|
A minimal config file is in the [README](../README.md#configuration).
|
|
6
6
|
|
|
@@ -16,22 +16,84 @@ The two paths differ in what an absent file means. An absent default path means
|
|
|
16
16
|
|
|
17
17
|
## `ReleaseKitConfig` reference
|
|
18
18
|
|
|
19
|
-
| Field
|
|
20
|
-
|
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
19
|
+
| Field | Type | Description |
|
|
20
|
+
| ------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| `releases` | `Record<string, ReleaseEntry>` | Names, excludes, or composes release targets; see [`releases`](#releases). Can't be combined with `workspaces` or `project` |
|
|
22
|
+
| `workspaces` | `WorkspaceOverride[]` | Override or exclude discovered workspaces (matched by `dir`) |
|
|
23
|
+
| `formatCommand` | `string` | Shell command to run after changelog generation; modified file paths are appended as arguments |
|
|
24
|
+
| `versionPatterns` | `VersionPatterns` | Rules for which commit types trigger major/minor bumps |
|
|
25
|
+
| `scopeAliases` | `Record<string, string>` | Maps shorthand scope names to canonical names in commit titles and in change-record entry `scopes`, which [route items to workspaces](changelogs.md#routing-to-workspaces). A renamed workspace directory or [release target](#releases) needs an entry from its old name to its new one |
|
|
26
|
+
| `workTypes` | `Record<string, WorkTypeConfig>` | Work type definitions, merged with defaults by key |
|
|
27
|
+
| `breakingPolicies` | `Record<string, 'forbidden' \| 'optional'>` | Per-type `!`-policy lookup. Defaults to `DEFAULT_BREAKING_POLICIES`. Replaces the default entirely when provided. Set to `{}` to disable enforcement |
|
|
28
|
+
| `retiredPackages` | `RetiredPackage[]` | Packages that once lived in this repo but have been extracted or removed; suppresses undeclared-tag-prefix warnings |
|
|
29
|
+
| `project` | `ProjectConfig` | Opt-in project-level release block. Declaring `project: {}` (even empty) enables a project-release stage in `prepare` |
|
|
30
|
+
| `repoLabels` | `RepoLabelsConfig` | Declares the repository's label registry for `sync-labels`; see [Label configuration](labels.md#label-configuration) |
|
|
31
|
+
| `changelogJson` | `ChangelogJsonConfig` | Controls `.meta/changelog.json`: `enabled` (default `true`), `outputPath` (default `'.meta/changelog.json'`), and `devOnlySections`; see [Custom work types](work-types.md#custom-work-types) |
|
|
32
|
+
| `changelogMarkdown` | `ChangelogMarkdownConfig` | `keepVersions` (an integer, at least 1) bounds `CHANGELOG.md` to that many of the newest versions and moves the rest into per-major archive files; omitted, `CHANGELOG.md` renders every version. See [Archived versions](changelogs.md#archived-versions) |
|
|
33
|
+
| `releaseNotes` | `ReleaseNotesConfig` | `shouldInjectIntoReadme` (default `false`) injects release notes into each package's README while it publishes; see [Release-notes injection](changelogs.md#release-notes-injection) |
|
|
32
34
|
|
|
33
35
|
All fields are optional.
|
|
34
36
|
|
|
37
|
+
## `releases`
|
|
38
|
+
|
|
39
|
+
`releases` maps each release target's name to its declaration. Every discovered workspace is a target, named by its directory, unless an entry excludes or renames it, so the block lists only what differs from the discovered set, along with any directory that releases without being a workspace.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const config: ReleaseKitConfig = {
|
|
43
|
+
releases: {
|
|
44
|
+
core: { path: 'packages/nmr-core', legacyIdentities: [{ name: '@scope/core', tagPrefix: 'core-v' }] },
|
|
45
|
+
docs: { path: 'docs' },
|
|
46
|
+
scratch: false,
|
|
47
|
+
suite: { includes: ['nmr', 'core', 'docs'], tagPrefix: 'v' },
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
An entry takes one of three shapes:
|
|
53
|
+
|
|
54
|
+
- **`false`** excludes the discovered workspace that the entry's name names by its directory. An excluded workspace is treated as a `shouldExclude` one is: `prepare` never releases it, and `push`, `publish`, and `create-github-release` skip its tags with a warning.
|
|
55
|
+
- **`{ path, tagPrefix?, legacyIdentities? }`** names the discovered workspace at `path`, a repo-relative directory. A `path` that isn't a discovered workspace declares a [directory target](#directory-targets) instead. `legacyIdentities` has the meaning that it has in [`WorkspaceOverride`](#workspaceoverride). `tagPrefix` applies only to a directory target: A workspace's prefix derives from its package name, and an entry that sets one for a workspace is an error.
|
|
56
|
+
- **`{ includes, tagPrefix? }`** declares the composite, a release that rolls up the named package and directory targets. It is versioned by the root `package.json`, writes `./CHANGELOG.md`, and reads the commits of its members' windows. Its tag prefix defaults to `'v'`. A config declares at most one composite, because the root `package.json` holds one version. `includes` names targets by their names in this block, and it names at least one. Like a `project` release, the composite's changelog takes every entry in its window, whatever the entry's scopes.
|
|
57
|
+
|
|
58
|
+
A target's name is its only identity. `--only` and `--set-version` select it by name, and a change-record entry routes to it when a scope names it. A target's name never reaches its tag prefix: A workspace's prefix stays `{unscoped package name}-v` whatever the entry calls it, and a directory's derives from its path. Renaming a workspace in `releases` therefore changes the scope that routes to it; add a [`scopeAliases`](#releasekitconfig-reference) entry from the old name to the new one, so that entries written before the rename still route to the target.
|
|
59
|
+
|
|
60
|
+
`releases` can't be combined with `workspaces` or `project`, which declare the same targets, and a single-package repo rejects it. None of its entries accepts `paths`: A package's or a directory's commit window is its path, and a composite's is its members' windows. [Moving to `releases`](project-releases.md#moving-to-releases) maps each `workspaces` and `project` setting onto the block; both blocks keep working unchanged.
|
|
61
|
+
|
|
62
|
+
### Directory targets
|
|
63
|
+
|
|
64
|
+
A `{ path }` entry whose path isn't a discovered workspace declares a directory target: a Swift app, a Go binary, a container image, or a docs site that has a version, a changelog, and tags but isn't a JS package.
|
|
65
|
+
|
|
66
|
+
- **Version file.** The directory's `package.json` when it has one, otherwise `version.json` in the directory. Either is a JSON file with a string `version` field, which `prepare` bumps. A directory without either is a config error that names the file to create, such as `{ "version": "0.0.0" }`.
|
|
67
|
+
- **Commit window.** The directory. A commit can appear in several targets' changelogs when their windows overlap.
|
|
68
|
+
- **Tag prefix.** The directory's basename followed by `-v`, such as `devopticon-v` for `apps/devopticon`, unless the entry sets `tagPrefix`. A basename that can't form a tag name, such as one containing a space, is an error that asks for `tagPrefix`.
|
|
69
|
+
- **Changelog.** `CHANGELOG.md` in the directory. A change-record entry routes to the target when a scope names it, as it does to a workspace.
|
|
70
|
+
- **Releases.** `--only` and `--set-version` take the target's name. A directory target never takes part in dependency propagation, and `publish` never publishes its tag. `push` pushes the tag, and `create-github-release` creates a GitHub Release for it from the directory's `.meta/changelog.json`.
|
|
71
|
+
|
|
72
|
+
A path outside the repo, or one that isn't a directory, is an error.
|
|
73
|
+
|
|
74
|
+
#### The repo root
|
|
75
|
+
|
|
76
|
+
`{ path: '.' }` declares the repo root as a directory target, which is how a release states whole-repo coverage. Its window is the whole repo, its version file is the root `package.json`, its tag prefix is `v`, and its `CHANGELOG.md` takes every entry in its window, whatever the entry's scopes. A workspace added later is a target of its own and doesn't narrow the root target's window.
|
|
77
|
+
|
|
78
|
+
The root target and a composite would both version the root `package.json`, so declaring both is an error.
|
|
79
|
+
|
|
80
|
+
#### Worked example: an app that is the repo's deliverable
|
|
81
|
+
|
|
82
|
+
`williamthorsen/devopticon` releases a SwiftPM app in `apps/devopticon`, keeps its version in the root `package.json`, and stamps it into the app bundle at build time. Its only workspace is a scaffold under `packages/`. A `project` block covered the whole repo only while the workspace union was empty, so adding a releasable package would have dropped the app and every root-level commit from the changelog. The root target states the coverage:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
export default defineConfig({
|
|
86
|
+
releases: {
|
|
87
|
+
devopticon: { path: '.' },
|
|
88
|
+
scaffold: false,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`prepare` bumps the root `package.json`, writes `./CHANGELOG.md` from every commit since the last `v` tag, and tags `v{version}`. A package added under `packages/` later releases under its own `{name}-v` tags, and the root target still reads the whole repo.
|
|
94
|
+
|
|
95
|
+
To version the app on its own instead, declare `app: { path: 'apps/devopticon' }` with an `apps/devopticon/version.json`; its window is then the app's directory, and its tags are `devopticon-v{version}`.
|
|
96
|
+
|
|
35
97
|
## `WorkspaceOverride`
|
|
36
98
|
|
|
37
99
|
```typescript
|
|
@@ -87,8 +149,9 @@ Validation rules:
|
|
|
87
149
|
Tag prefixes from distinct owners must not be identical or be a strict prefix of one another. An owner is one of:
|
|
88
150
|
|
|
89
151
|
- An active workspace, comprising its derived `tagPrefix` plus any declared `legacyIdentities[].tagPrefix`. Identities of the same workspace are one owner, so their prefixes are allowed to overlap (this represents the same package across renames).
|
|
152
|
+
- A directory target, comprising its `tagPrefix` plus any declared `legacyIdentities[].tagPrefix`, as one owner.
|
|
90
153
|
- A `retiredPackages[]` entry (one owner per entry).
|
|
91
|
-
- The `project` block, when configured.
|
|
154
|
+
- The `project` block or the `releases` composite, when configured.
|
|
92
155
|
|
|
93
156
|
Because release-kit matches a tag to a prefix when the tag name is the prefix followed by a complete SemVer version, a strict-prefix overlap between distinct owners can match the wrong owner's tags. For example, a retired prefix of `pkg-` collides with a workspace prefix of `pkg-1.0.0-v`, since `pkg-1.0.0-v1.2.3` is `pkg-` followed by the pre-release version `1.0.0-v1.2.3`. The rule rejects every strict-prefix overlap, including one such as `v` and `vue-helpers-v`, whose shorter prefix can't match any tag of the longer one.
|
|
94
157
|
|
|
@@ -96,7 +159,7 @@ The rule is enforced at config load; the resulting error identifies both collidi
|
|
|
96
159
|
|
|
97
160
|
## `release-kit show-tag-prefixes`
|
|
98
161
|
|
|
99
|
-
Prints a per-workspace table of derived tag prefixes, tag counts, and declared legacy prefixes. Also reports any release-shaped tag prefix in the repo that is neither a derived prefix nor declared via `legacyIdentities`, along with a copy-pasteable `workspaces: [...]` config snippet. The snippet uses a `TODO-fill-in-legacy-npm-name` placeholder for each identity's `name`; replace it with the package's prior npm name before pasting.
|
|
162
|
+
Prints a per-workspace table of derived tag prefixes, tag counts, and declared legacy prefixes, with a row for each directory target. Also reports any release-shaped tag prefix in the repo that is neither a derived prefix nor declared via `legacyIdentities`, along with a copy-pasteable `workspaces: [...]` config snippet. The snippet uses a `TODO-fill-in-legacy-npm-name` placeholder for each identity's `name`; replace it with the package's prior npm name before pasting.
|
|
100
163
|
|
|
101
164
|
Exits `0` when every workspace derives a prefix and the workspaces' prefixes don't collide; exits `1` on any derivation failure or collision. Undeclared candidates do not affect the exit code; the `legacy tag prefixes are declared` readyup check reports them as a warning.
|
|
102
165
|
|
package/docs/labels.md
CHANGED
|
@@ -36,11 +36,13 @@ The block declares the repository's label registry: the set of labels defined on
|
|
|
36
36
|
|
|
37
37
|
The `common` preset defines a label for every work type in change-grammar's taxonomy, named by the type's `trackerLabel`, in addition to the labels in its bundled YAML file.
|
|
38
38
|
|
|
39
|
+
The bundled file's `confidence:` family grades how advisable a recommendation is, for example one that a triage of spike findings turns into a ticket: `confidence:high` is clearly worth doing, `confidence:medium` is worth doing but arguable, and `confidence:low` is not worth a ticket now.
|
|
40
|
+
|
|
39
41
|
Names match case-insensitively, as GitHub matches them: `Bug` in the `labels` record replaces a preset's `bug`, and `Bug: null` removes it. A label takes the spelling of the last layer to write it.
|
|
40
42
|
|
|
41
|
-
`description` is optional throughout, in a preset as in the `labels` record, and `sync-labels init` generates none for a scope label. The generated file spells an absent description `''` because `github-label-sync` reads an omitted description as "leave the label's current one alone"; the empty form clears it.
|
|
43
|
+
`description` is optional throughout, in a preset as in the `labels` record, and `sync-labels init` generates none for a scope label. The generated file spells an absent description `''` because `github-label-sync` reads an omitted description as "leave the label's current one alone"; the empty form clears it. Because a scope label without a description groups compactly in GitHub's label list, release-kit's readyup kit warns about each `scope:` label in the generated file that has one.
|
|
42
44
|
|
|
43
|
-
Overlaps between layers are never errors; order resolves them, and the committed `.github/labels.yaml` diff shows any unexpected change at review.
|
|
45
|
+
Overlaps between layers are never errors; order resolves them, and the committed `.github/labels.yaml` diff shows any unexpected change at review. Three misstatements are config errors, because none is visible in the output diff: a dangling `null`, which removes a name not defined by any preset; `labels` keys that differ only in case, all but one of which the fold would discard; and an alias that names a resolved label or repeats another alias, which would make the sync merge one label into another and delete it (see [Renaming a label](#renaming-a-label)).
|
|
44
46
|
|
|
45
47
|
### Retiring a label
|
|
46
48
|
|
|
@@ -56,6 +58,27 @@ The sync workflow archives a marked label rather than deleting it. An archived l
|
|
|
56
58
|
|
|
57
59
|
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
60
|
|
|
61
|
+
### Renaming a label
|
|
62
|
+
|
|
63
|
+
List a label's old names in `aliases` to rename it:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
labels: {
|
|
67
|
+
'scope:core': { color: '00ff96', aliases: ['scope:basic'] }, // rename `scope:basic` to `scope:core`
|
|
68
|
+
},
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The sync renames an existing label that an alias names, in place, rather than deleting it and creating a new one, so the label stays on the issues and pull requests that have it. The dry-run **Label diff** lists the rename. When the repository has both the old name and the new one, the sync adds the new label to every issue and pull request that has the old one, then deletes the old label. A label that an alias names is not reported as undeclared.
|
|
72
|
+
|
|
73
|
+
`aliases` is set only in the `labels` record, not in a preset, and it takes at least one name. Names match case-insensitively. An alias must not name a label that the config resolves, including the label that declares it, and must not appear twice: Either would make the sync merge one declared label into another and delete it. To rename a preset's label, remove it with `null` and alias it from the new entry:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
labels: {
|
|
77
|
+
bug: null,
|
|
78
|
+
'type:bug': { color: 'd73a4a', aliases: ['bug'] },
|
|
79
|
+
},
|
|
80
|
+
```
|
|
81
|
+
|
|
59
82
|
### When labels are applied
|
|
60
83
|
|
|
61
84
|
The scaffolded workflow declares three triggers:
|