@williamthorsen/release-kit 15.0.3 → 16.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.meta/changelog.json +93 -0
- package/.readyup/kits/default.js +14 -14
- package/.readyup/kits/npm-auto-publish.js +4 -4
- package/.readyup/manifest.json +13 -13
- package/CHANGELOG.md +27 -0
- package/README.md +12 -3
- package/dist/esm/applyChangelogOverrides.js +6 -6
- package/dist/esm/assertReleasesOnNpm.js +11 -11
- package/dist/esm/assertTaggedBaseline.js +5 -5
- package/dist/esm/assertTarballsComplete.d.ts +9 -0
- package/dist/esm/assertTarballsComplete.js +52 -0
- package/dist/esm/bin/release-kit.js +16 -16
- package/dist/esm/buildChangelogEntries.d.ts +19 -19
- package/dist/esm/buildChangelogEntries.js +40 -36
- package/dist/esm/buildChangelogItem.js +24 -24
- package/dist/esm/buildDependencyGraph.js +23 -23
- package/dist/esm/buildReleasedResult.js +21 -19
- package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
- package/dist/esm/bumpVersion.js +8 -8
- package/dist/esm/changelogArchive.d.ts +1 -1
- package/dist/esm/changelogArchive.js +2 -2
- package/dist/esm/changelogJsonFile.js +5 -5
- package/dist/esm/changelogJsonUtils.js +3 -3
- package/dist/esm/changelogOverrides.d.ts +5 -5
- package/dist/esm/changelogOverrides.js +23 -23
- package/dist/esm/classifyChangelogCommit.js +4 -4
- package/dist/esm/commitCommand.js +3 -3
- package/dist/esm/compareVersions.js +13 -13
- package/dist/esm/configSchema.d.ts +3 -2
- package/dist/esm/configSchema.js +2 -1
- package/dist/esm/createGithubRelease.d.ts +4 -4
- package/dist/esm/createGithubRelease.js +10 -10
- package/dist/esm/createGithubReleaseCommand.js +9 -9
- package/dist/esm/createTags.d.ts +2 -2
- package/dist/esm/createTags.js +8 -8
- package/dist/esm/decideDirectReleases.d.ts +5 -5
- package/dist/esm/decideDirectReleases.js +25 -25
- package/dist/esm/decideRelease.d.ts +1 -1
- package/dist/esm/decideRelease.js +2 -2
- package/dist/esm/deriveDirectoryConfig.d.ts +1 -1
- package/dist/esm/deriveDirectoryConfig.js +18 -18
- package/dist/esm/deriveWorkspaceConfig.js +4 -4
- package/dist/esm/detectPackageManager.d.ts +2 -0
- package/dist/esm/detectPackageManager.js +8 -5
- package/dist/esm/detectUndeclaredTagPrefixes.js +6 -6
- package/dist/esm/enumerateReleaseWindows.d.ts +1 -1
- package/dist/esm/enumerateReleaseWindows.js +7 -7
- package/dist/esm/findMissingManifestPaths.d.ts +14 -0
- package/dist/esm/findMissingManifestPaths.js +90 -0
- package/dist/esm/findTransitiveDependents.js +5 -5
- package/dist/esm/findUnroutedEntryScopes.js +11 -11
- package/dist/esm/format.d.ts +1 -1
- package/dist/esm/format.js +1 -1
- package/dist/esm/formatPrepareFiles.d.ts +3 -3
- package/dist/esm/formatPrepareFiles.js +10 -10
- package/dist/esm/formatPrepareWarnings.js +6 -6
- package/dist/esm/hasPrettierConfig.js +2 -2
- package/dist/esm/init/checks.js +5 -3
- package/dist/esm/init/detectRepoType.js +3 -3
- package/dist/esm/init/initCommand.d.ts +4 -4
- package/dist/esm/init/initCommand.js +7 -7
- package/dist/esm/init/parseJsonRecord.d.ts +1 -1
- package/dist/esm/init/parseJsonRecord.js +4 -4
- package/dist/esm/init/scaffold.d.ts +4 -4
- package/dist/esm/init/scaffold.js +15 -15
- package/dist/esm/init/templates.d.ts +10 -10
- package/dist/esm/init/templates.js +5 -5
- package/dist/esm/init/updateTemplatesCommand.d.ts +2 -2
- package/dist/esm/init/updateTemplatesCommand.js +14 -12
- package/dist/esm/injectReleaseNotesIntoReadme.d.ts +1 -1
- package/dist/esm/injectReleaseNotesIntoReadme.js +6 -6
- package/dist/esm/injectSection.js +8 -8
- package/dist/esm/loadChangelogOverrides.d.ts +1 -1
- package/dist/esm/loadChangelogOverrides.js +9 -9
- package/dist/esm/loadConfig.d.ts +3 -3
- package/dist/esm/loadConfig.js +10 -10
- package/dist/esm/loadValidatedConfig.js +4 -4
- package/dist/esm/mergeConfig.d.ts +1 -1
- package/dist/esm/mergeConfig.js +27 -27
- package/dist/esm/monorepoConfigAssertions.js +15 -15
- package/dist/esm/npmRegistry.js +4 -4
- package/dist/esm/packFromCleanCheckout.d.ts +11 -0
- package/dist/esm/packFromCleanCheckout.js +81 -0
- package/dist/esm/packsPath.js +3 -3
- package/dist/esm/parseChangeRecordBlock.js +33 -33
- package/dist/esm/parseCommitMessage.js +5 -5
- package/dist/esm/planChangelogFiles.js +8 -8
- package/dist/esm/planReleaseNotesPreviews.js +10 -10
- package/dist/esm/planVersionBump.js +24 -16
- package/dist/esm/prepareArgs.d.ts +9 -9
- package/dist/esm/prepareArgs.js +11 -11
- package/dist/esm/prepareCommand.js +27 -24
- package/dist/esm/prepareComposite.d.ts +4 -4
- package/dist/esm/prepareComposite.js +16 -16
- package/dist/esm/{releasePrepareTargets.d.ts → prepareReleaseTargets.d.ts} +9 -9
- package/dist/esm/{releasePrepareTargets.js → prepareReleaseTargets.js} +40 -40
- package/dist/esm/prepareRunContext.d.ts +1 -1
- package/dist/esm/prepareRunContext.js +5 -5
- package/dist/esm/prepareTarget.d.ts +3 -3
- package/dist/esm/prepareTarget.js +41 -39
- package/dist/esm/previewTagPrefixes.js +19 -19
- package/dist/esm/propagateBumps.js +8 -8
- package/dist/esm/publish.d.ts +3 -3
- package/dist/esm/publish.js +6 -11
- package/dist/esm/publishCommand.d.ts +3 -3
- package/dist/esm/publishCommand.js +28 -20
- package/dist/esm/pushCommand.js +8 -8
- package/dist/esm/pushRelease.d.ts +1 -1
- package/dist/esm/pushRelease.js +2 -2
- package/dist/esm/readChangelogSections.d.ts +1 -1
- package/dist/esm/readChangelogSections.js +16 -16
- package/dist/esm/releasePlan.js +25 -25
- package/dist/esm/renderChangelogMarkdown.d.ts +4 -4
- package/dist/esm/renderChangelogMarkdown.js +18 -15
- package/dist/esm/renderReleaseNotes.d.ts +1 -1
- package/dist/esm/renderReleaseNotes.js +16 -16
- package/dist/esm/reportPrepare.js +34 -34
- package/dist/esm/resolveCommandTags.js +9 -9
- package/dist/esm/resolveReleaseDeclaration.d.ts +2 -2
- package/dist/esm/resolveReleaseDeclaration.js +22 -22
- package/dist/esm/resolveReleaseTags.js +17 -15
- package/dist/esm/resolveReleaseTargets.js +2 -2
- package/dist/esm/showTagPrefixesCommand.js +4 -4
- package/dist/esm/sync-labels/generateCommand.d.ts +3 -3
- package/dist/esm/sync-labels/generateCommand.js +10 -10
- package/dist/esm/sync-labels/initCommand.d.ts +3 -3
- package/dist/esm/sync-labels/initCommand.js +15 -15
- package/dist/esm/sync-labels/presets.js +3 -3
- package/dist/esm/sync-labels/resolveLabels.js +13 -13
- package/dist/esm/sync-labels/templates.d.ts +2 -2
- package/dist/esm/sync-labels/templates.js +1 -1
- package/dist/esm/tagCommand.js +4 -4
- package/dist/esm/topologicalSort.d.ts +2 -2
- package/dist/esm/topologicalSort.js +7 -7
- package/dist/esm/types.d.ts +3 -3
- package/dist/esm/validateAllChangelogOverrides.js +9 -9
- package/dist/esm/validateConfig.d.ts +1 -1
- package/dist/esm/validateConfig.js +60 -50
- package/dist/esm/validateOverridesCommand.js +2 -2
- package/docs/changelogs.md +5 -5
- package/docs/configuration.md +16 -15
- package/docs/publishing.md +4 -0
- package/docs/releasing.md +6 -0
- package/docs/work-types.md +1 -1
- package/package.json +2 -2
- package/presets/labels/common.yaml +3 -0
|
@@ -6,23 +6,23 @@ export function resolveLabels(config) {
|
|
|
6
6
|
const groupList = caseVariantGroups.map((group) => group.map((name) => `'${name}'`).join(' and ')).join('; ');
|
|
7
7
|
throw new Error(`Keys in 'labels' differ only in case, which GitHub treats as the same label: ${groupList}. Keep one entry per label.`);
|
|
8
8
|
}
|
|
9
|
-
const
|
|
9
|
+
const resolvedLabels = new Map();
|
|
10
10
|
const presetNames = config.extends ?? [];
|
|
11
11
|
for (const presetName of presetNames) {
|
|
12
12
|
for (const label of loadPreset(presetName)) {
|
|
13
|
-
|
|
13
|
+
resolvedLabels.set(getLabelKey(label.name), label);
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
for (const [name, spec] of labelEntries) {
|
|
17
|
-
const key =
|
|
17
|
+
const key = getLabelKey(name);
|
|
18
18
|
if (spec === null) {
|
|
19
|
-
if (!
|
|
19
|
+
if (!resolvedLabels.has(key)) {
|
|
20
20
|
throw new Error(`Label '${name}' is set to null (remove), but the presets in 'extends' don't define it. Fix the name or delete the entry.`);
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
resolvedLabels.delete(key);
|
|
23
23
|
}
|
|
24
24
|
else {
|
|
25
|
-
|
|
25
|
+
resolvedLabels.set(key, {
|
|
26
26
|
name,
|
|
27
27
|
color: spec.color,
|
|
28
28
|
...(spec.description !== undefined && { description: spec.description }),
|
|
@@ -31,7 +31,7 @@ export function resolveLabels(config) {
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
const labels = sortLabels(
|
|
34
|
+
const labels = sortLabels(resolvedLabels.values().toArray());
|
|
35
35
|
const aliasCollisions = findAliasCollisions(labels);
|
|
36
36
|
if (aliasCollisions.length > 0) {
|
|
37
37
|
throw new Error(`Aliases must name labels that are not declared and must not repeat, or the sync merges one label into another and deletes it: ${aliasCollisions.join('; ')}.`);
|
|
@@ -39,13 +39,13 @@ export function resolveLabels(config) {
|
|
|
39
39
|
return labels;
|
|
40
40
|
}
|
|
41
41
|
function findAliasCollisions(labels) {
|
|
42
|
-
const labelNamesByKey = new Map(labels.map((label) => [
|
|
42
|
+
const labelNamesByKey = new Map(labels.map((label) => [getLabelKey(label.name), label.name]));
|
|
43
43
|
const aliasOwnersByKey = new Map();
|
|
44
44
|
const collisions = [];
|
|
45
45
|
for (const label of labels) {
|
|
46
46
|
const aliases = label.aliases ?? [];
|
|
47
47
|
for (const alias of aliases) {
|
|
48
|
-
const key =
|
|
48
|
+
const key = getLabelKey(alias);
|
|
49
49
|
const namedLabel = labelNamesByKey.get(key);
|
|
50
50
|
const earlierOwner = aliasOwnersByKey.get(key);
|
|
51
51
|
if (namedLabel !== undefined) {
|
|
@@ -62,14 +62,14 @@ function findAliasCollisions(labels) {
|
|
|
62
62
|
return collisions;
|
|
63
63
|
}
|
|
64
64
|
function findCaseVariantGroups(names) {
|
|
65
|
-
return Map.groupBy(names,
|
|
65
|
+
return Map.groupBy(names, getLabelKey)
|
|
66
66
|
.values()
|
|
67
67
|
.filter((group) => group.length > 1)
|
|
68
68
|
.toArray();
|
|
69
69
|
}
|
|
70
|
+
function getLabelKey(name) {
|
|
71
|
+
return name.toLowerCase();
|
|
72
|
+
}
|
|
70
73
|
function sortLabels(labels) {
|
|
71
74
|
return labels.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
72
75
|
}
|
|
73
|
-
function toLabelKey(name) {
|
|
74
|
-
return name.toLowerCase();
|
|
75
|
-
}
|
|
@@ -15,5 +15,5 @@ export declare function buildScopeLabels(workspacePaths: string[], retiredNames?
|
|
|
15
15
|
* `.config/release-kit.config.ts`.
|
|
16
16
|
*/
|
|
17
17
|
export declare function renderRepoLabelsBlock(scopeLabels: LabelDefinition[]): string;
|
|
18
|
-
/**
|
|
19
|
-
export declare function
|
|
18
|
+
/** Renders a new `.config/release-kit.config.ts` containing the `repoLabels` block. */
|
|
19
|
+
export declare function renderRepoLabelsConfigScript(scopeLabels: LabelDefinition[]): string;
|
|
@@ -33,7 +33,7 @@ export function renderRepoLabelsBlock(scopeLabels) {
|
|
|
33
33
|
${labelsBlock}
|
|
34
34
|
},`;
|
|
35
35
|
}
|
|
36
|
-
export function
|
|
36
|
+
export function renderRepoLabelsConfigScript(scopeLabels) {
|
|
37
37
|
return `import { defineConfig } from '@williamthorsen/release-kit/config';
|
|
38
38
|
|
|
39
39
|
export default defineConfig({
|
package/dist/esm/tagCommand.js
CHANGED
|
@@ -2,13 +2,13 @@ import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
3
|
import { createTags } from "./createTags.js";
|
|
4
4
|
const tagFlagSchema = {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
isDryRun: { long: '--dry-run', type: 'boolean' },
|
|
6
|
+
shouldSkipGitChecks: { long: '--no-git-checks', type: 'boolean' },
|
|
7
7
|
};
|
|
8
8
|
export function tagCommand(argv, styles) {
|
|
9
|
-
const {
|
|
9
|
+
const { isDryRun, shouldSkipGitChecks } = parseArgsOrExit(argv, tagFlagSchema).flags;
|
|
10
10
|
try {
|
|
11
|
-
createTags({
|
|
11
|
+
createTags({ isDryRun, shouldSkipGitChecks, style: styles.stdout });
|
|
12
12
|
}
|
|
13
13
|
catch (error) {
|
|
14
14
|
reportError(describeError(error));
|
|
@@ -6,7 +6,7 @@ import type { ReleaseEntry } from './propagateBumps.js';
|
|
|
6
6
|
* Uses Kahn's algorithm. Targets not in the release set are excluded. If the graph has
|
|
7
7
|
* cycles, the remaining nodes are appended in arbitrary order and reported via `cyclicIds`.
|
|
8
8
|
*/
|
|
9
|
-
export declare function
|
|
10
|
-
|
|
9
|
+
export declare function sortTopologically(releaseSet: Map<string, ReleaseEntry>, graph: DependencyGraph): {
|
|
10
|
+
sortedIds: string[];
|
|
11
11
|
cyclicIds: string[];
|
|
12
12
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export function
|
|
1
|
+
export function sortTopologically(releaseSet, graph) {
|
|
2
2
|
const releaseIds = new Set(releaseSet.keys());
|
|
3
3
|
if (releaseIds.size === 0) {
|
|
4
|
-
return {
|
|
4
|
+
return { sortedIds: [], cyclicIds: [] };
|
|
5
5
|
}
|
|
6
6
|
const inDegree = new Map();
|
|
7
7
|
const forwardEdges = new Map();
|
|
@@ -31,13 +31,13 @@ export function topologicalSort(releaseSet, graph) {
|
|
|
31
31
|
queue.push(id);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
const
|
|
34
|
+
const sortedIds = [];
|
|
35
35
|
while (queue.length > 0) {
|
|
36
36
|
const id = queue.shift();
|
|
37
37
|
if (id === undefined) {
|
|
38
38
|
break;
|
|
39
39
|
}
|
|
40
|
-
|
|
40
|
+
sortedIds.push(id);
|
|
41
41
|
const dependents = forwardEdges.get(id) ?? [];
|
|
42
42
|
for (const dependent of dependents) {
|
|
43
43
|
const newDegree = (inDegree.get(dependent) ?? 1) - 1;
|
|
@@ -47,14 +47,14 @@ export function topologicalSort(releaseSet, graph) {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
const sortedSet = new Set(
|
|
50
|
+
const sortedSet = new Set(sortedIds);
|
|
51
51
|
const cyclicIds = [];
|
|
52
52
|
for (const id of releaseIds) {
|
|
53
53
|
if (sortedSet.has(id)) {
|
|
54
54
|
continue;
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
sortedIds.push(id);
|
|
57
57
|
cyclicIds.push(id);
|
|
58
58
|
}
|
|
59
|
-
return {
|
|
59
|
+
return { sortedIds, cyclicIds };
|
|
60
60
|
}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -70,7 +70,7 @@ export interface ChangelogMarkdownConfig {
|
|
|
70
70
|
* How many of the newest versioned sections `CHANGELOG.md` keeps; older sections go to per-major archive files
|
|
71
71
|
* under `changelogs/`. When omitted, `CHANGELOG.md` keeps every section.
|
|
72
72
|
*/
|
|
73
|
-
|
|
73
|
+
keptVersionCount?: number;
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
76
76
|
* Editorial override for changelog items, keyed in the override file by `<hash>` (every item of the commit) or
|
|
@@ -245,7 +245,7 @@ export interface ReleasedPackageResult extends PackageResultFields, ReleaseField
|
|
|
245
245
|
/** Present when propagation alone set the bump, the package's own commits having called for none. */
|
|
246
246
|
propagatedOnly?: true;
|
|
247
247
|
/** Present when the package was written via `--set-version`; the explicit version that was applied. */
|
|
248
|
-
|
|
248
|
+
versionOverride?: string;
|
|
249
249
|
}
|
|
250
250
|
/** Result of a package target that skips: the diagnostic fields and a human-readable `skipReason`. */
|
|
251
251
|
export interface SkippedPackageResult extends PackageResultFields {
|
|
@@ -280,7 +280,7 @@ export interface ReleasedDirectoryResult extends DirectoryResultFields, ReleaseF
|
|
|
280
280
|
/** Raw commits of the directory's unreleased window. */
|
|
281
281
|
commits: Commit[];
|
|
282
282
|
/** Present when the directory was written via `--set-version`; the explicit version that was applied. */
|
|
283
|
-
|
|
283
|
+
versionOverride?: string;
|
|
284
284
|
}
|
|
285
285
|
/** Result of a directory target that skips: the diagnostic fields and a human-readable `skipReason`. */
|
|
286
286
|
export interface SkippedDirectoryResult extends DirectoryResultFields {
|
|
@@ -89,8 +89,8 @@ function prefixWithFilePath(filePath, message) {
|
|
|
89
89
|
}
|
|
90
90
|
function processProjectScope(args) {
|
|
91
91
|
const { projectFilePath, projectMap, projectItems, errors, globalMatchedRootKeys } = args;
|
|
92
|
-
const
|
|
93
|
-
for (const message of
|
|
92
|
+
const appliedResult = applyChangelogOverrides(makeValidationEntries(projectItems), projectMap);
|
|
93
|
+
for (const message of appliedResult.errors) {
|
|
94
94
|
errors.push(prefixWithFilePath(projectFilePath, message));
|
|
95
95
|
}
|
|
96
96
|
const positionsByHash = indexItemPositions(projectItems);
|
|
@@ -105,19 +105,19 @@ function processWorkspaceScope(args) {
|
|
|
105
105
|
const { filePath, items, map } = workspace;
|
|
106
106
|
const validationEntries = makeValidationEntries(items);
|
|
107
107
|
const positionsByHash = indexItemPositions(items);
|
|
108
|
-
const
|
|
109
|
-
for (const message of
|
|
108
|
+
const workspaceAppliedResult = applyChangelogOverrides(validationEntries, map);
|
|
109
|
+
for (const message of workspaceAppliedResult.errors) {
|
|
110
110
|
errors.push(prefixWithFilePath(filePath, message));
|
|
111
111
|
}
|
|
112
112
|
if (projectFilePath !== undefined && projectMap.size > 0) {
|
|
113
113
|
const projectMinusShadowed = filterShadowedKeys(projectMap, map);
|
|
114
|
-
const
|
|
115
|
-
for (const message of
|
|
114
|
+
const projectAppliedResult = applyChangelogOverrides(validationEntries, projectMinusShadowed);
|
|
115
|
+
for (const message of projectAppliedResult.errors) {
|
|
116
116
|
errors.push(prefixWithFilePath(projectFilePath, message));
|
|
117
117
|
}
|
|
118
|
-
const perFileErrors = new Set([...
|
|
119
|
-
const
|
|
120
|
-
for (const message of
|
|
118
|
+
const perFileErrors = new Set([...workspaceAppliedResult.errors, ...projectAppliedResult.errors]);
|
|
119
|
+
const composedAppliedResult = applyChangelogOverrides(validationEntries, composeOverrides(projectMap, map));
|
|
120
|
+
for (const message of composedAppliedResult.errors) {
|
|
121
121
|
if (!perFileErrors.has(message)) {
|
|
122
122
|
errors.push(prefixWithFilePath(filePath, message));
|
|
123
123
|
}
|
|
@@ -3,7 +3,7 @@ import { type ReleaseKitConfig } from './configSchema.js';
|
|
|
3
3
|
* Validates a raw config object loaded from `.config/release-kit.config.ts`. Returns `{}` with the errors for a config
|
|
4
4
|
* that fails the schema parse, and runs the cross-field checks on one that passes.
|
|
5
5
|
*/
|
|
6
|
-
export declare function validateConfig(
|
|
6
|
+
export declare function validateConfig(rawConfig: unknown): {
|
|
7
7
|
config: ReleaseKitConfig;
|
|
8
8
|
errors: string[];
|
|
9
9
|
warnings: string[];
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { releaseKitConfigSchema } from "./configSchema.js";
|
|
2
2
|
import { isRecord } from "./typeGuards.js";
|
|
3
|
-
export function validateConfig(
|
|
4
|
-
if (!isRecord(
|
|
3
|
+
export function validateConfig(rawConfig) {
|
|
4
|
+
if (!isRecord(rawConfig)) {
|
|
5
5
|
return { config: {}, errors: ['Config must be an object'], warnings: [] };
|
|
6
6
|
}
|
|
7
|
-
const {
|
|
8
|
-
const parseResult = releaseKitConfigSchema.safeParse(
|
|
7
|
+
const { cleanedInput, deprecationErrors } = preprocessDeprecatedKeys(rawConfig);
|
|
8
|
+
const parseResult = releaseKitConfigSchema.safeParse(cleanedInput);
|
|
9
9
|
if (!parseResult.success) {
|
|
10
10
|
return {
|
|
11
11
|
config: {},
|
|
12
|
-
errors: [
|
|
12
|
+
errors: [
|
|
13
|
+
...deprecationErrors,
|
|
14
|
+
...parseResult.error.issues.flatMap((issue) => formatZodIssue(issue, cleanedInput)),
|
|
15
|
+
],
|
|
13
16
|
warnings: [],
|
|
14
17
|
};
|
|
15
18
|
}
|
|
@@ -26,40 +29,47 @@ export function validateConfig(raw) {
|
|
|
26
29
|
}
|
|
27
30
|
return { config, errors, warnings };
|
|
28
31
|
}
|
|
29
|
-
function preprocessDeprecatedKeys(
|
|
30
|
-
if (!isRecord(
|
|
31
|
-
return {
|
|
32
|
+
function preprocessDeprecatedKeys(rawConfig) {
|
|
33
|
+
if (!isRecord(rawConfig))
|
|
34
|
+
return { cleanedInput: rawConfig, deprecationErrors: [] };
|
|
32
35
|
const errors = [];
|
|
33
|
-
const
|
|
34
|
-
if (Object.hasOwn(
|
|
36
|
+
const cleanedInput = { ...rawConfig };
|
|
37
|
+
if (Object.hasOwn(cleanedInput, 'cliffConfigPath')) {
|
|
35
38
|
errors.push('cliffConfigPath is no longer supported. release-kit reads changelog history from git directly and does not use any external config file. Remove this field from the config, and delete the file that it names.');
|
|
36
|
-
delete
|
|
39
|
+
delete cleanedInput['cliffConfigPath'];
|
|
37
40
|
}
|
|
38
|
-
if (isRecord(
|
|
41
|
+
if (isRecord(cleanedInput['releaseNotes']) &&
|
|
42
|
+
Object.hasOwn(cleanedInput['releaseNotes'], 'shouldCreateGithubRelease')) {
|
|
39
43
|
errors.push('releaseNotes.shouldCreateGithubRelease is no longer supported. Adoption is now signaled by installing the create-github-release workflow, which `release-kit init` scaffolds. Remove this field from the config.');
|
|
40
|
-
const releaseNotesCopy = { ...
|
|
44
|
+
const releaseNotesCopy = { ...cleanedInput['releaseNotes'] };
|
|
41
45
|
delete releaseNotesCopy['shouldCreateGithubRelease'];
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
cleanedInput['releaseNotes'] = releaseNotesCopy;
|
|
47
|
+
}
|
|
48
|
+
if (isRecord(cleanedInput['changelogMarkdown']) && Object.hasOwn(cleanedInput['changelogMarkdown'], 'keepVersions')) {
|
|
49
|
+
errors.push('changelogMarkdown.keepVersions has been renamed. Rename it to changelogMarkdown.keptVersionCount in the config.');
|
|
50
|
+
const changelogMarkdownCopy = { ...cleanedInput['changelogMarkdown'] };
|
|
51
|
+
delete changelogMarkdownCopy['keepVersions'];
|
|
52
|
+
cleanedInput['changelogMarkdown'] = changelogMarkdownCopy;
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(cleanedInput['workspaces'])) {
|
|
55
|
+
cleanedInput['workspaces'] = cleanedInput['workspaces'].map((workspace, index) => {
|
|
56
|
+
if (!isRecord(workspace))
|
|
57
|
+
return workspace;
|
|
58
|
+
const workspaceCopy = { ...workspace };
|
|
59
|
+
if (Object.hasOwn(workspaceCopy, 'tagPrefix')) {
|
|
60
|
+
const dir = typeof workspaceCopy['dir'] === 'string' && workspaceCopy['dir'] !== '' ? workspaceCopy['dir'] : '<dir>';
|
|
61
|
+
errors.push(`workspaces[${index}]: 'tagPrefix' is no longer supported; remove it to use the default '${dir}-v'`);
|
|
62
|
+
delete workspaceCopy['tagPrefix'];
|
|
53
63
|
}
|
|
54
|
-
if (Object.hasOwn(
|
|
55
|
-
errors.push(`workspaces[${
|
|
56
|
-
delete
|
|
64
|
+
if (Object.hasOwn(workspaceCopy, 'legacyTagPrefixes')) {
|
|
65
|
+
errors.push(`workspaces[${index}]: 'legacyTagPrefixes' is no longer supported; use 'legacyIdentities: [{ name, tagPrefix }, ...]' instead`);
|
|
66
|
+
delete workspaceCopy['legacyTagPrefixes'];
|
|
57
67
|
}
|
|
58
|
-
return
|
|
68
|
+
return workspaceCopy;
|
|
59
69
|
});
|
|
60
70
|
}
|
|
61
|
-
if (isRecord(
|
|
62
|
-
const releasesCopy = { ...
|
|
71
|
+
if (isRecord(cleanedInput['releases'])) {
|
|
72
|
+
const releasesCopy = { ...cleanedInput['releases'] };
|
|
63
73
|
for (const [name, entry] of Object.entries(releasesCopy)) {
|
|
64
74
|
if (!isRecord(entry) || !Object.hasOwn(entry, 'paths'))
|
|
65
75
|
continue;
|
|
@@ -68,9 +78,9 @@ function preprocessDeprecatedKeys(raw) {
|
|
|
68
78
|
delete entryCopy['paths'];
|
|
69
79
|
releasesCopy[name] = entryCopy;
|
|
70
80
|
}
|
|
71
|
-
|
|
81
|
+
cleanedInput['releases'] = releasesCopy;
|
|
72
82
|
}
|
|
73
|
-
return {
|
|
83
|
+
return { cleanedInput, deprecationErrors: errors };
|
|
74
84
|
}
|
|
75
85
|
function formatZodIssue(issue, input) {
|
|
76
86
|
if (issue.code === 'invalid_union' && issue.path.length === 2 && issue.path[0] === 'releases') {
|
|
@@ -92,42 +102,42 @@ function customizeMessage(issue) {
|
|
|
92
102
|
}
|
|
93
103
|
const RELEASE_ENTRY_SHAPES = 'must be false, { path, tagPrefix?, legacyIdentities? }, or { includes, tagPrefix? }';
|
|
94
104
|
function renderPath(path) {
|
|
95
|
-
let
|
|
105
|
+
let renderedPath = '';
|
|
96
106
|
for (const segment of path) {
|
|
97
107
|
if (typeof segment === 'number') {
|
|
98
|
-
|
|
108
|
+
renderedPath += `[${segment}]`;
|
|
99
109
|
}
|
|
100
|
-
else if (
|
|
101
|
-
|
|
110
|
+
else if (renderedPath === '') {
|
|
111
|
+
renderedPath += String(segment);
|
|
102
112
|
}
|
|
103
113
|
else {
|
|
104
|
-
|
|
114
|
+
renderedPath += `.${String(segment)}`;
|
|
105
115
|
}
|
|
106
116
|
}
|
|
107
|
-
return
|
|
117
|
+
return renderedPath;
|
|
108
118
|
}
|
|
109
119
|
function detectLegacyIdentityDuplicates(config, errors) {
|
|
110
120
|
for (const { label, legacyIdentities } of listLegacyIdentityDeclarations(config)) {
|
|
111
|
-
const
|
|
121
|
+
const seenKeys = new Set();
|
|
112
122
|
for (const [entryIndex, identity] of legacyIdentities.entries()) {
|
|
113
123
|
const key = `${identity.name}\0${identity.tagPrefix}`;
|
|
114
|
-
if (
|
|
124
|
+
if (seenKeys.has(key)) {
|
|
115
125
|
errors.push(`${label}.legacyIdentities[${entryIndex}]: duplicate identity (name='${identity.name}', tagPrefix='${identity.tagPrefix}')`);
|
|
116
126
|
}
|
|
117
|
-
|
|
127
|
+
seenKeys.add(key);
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
130
|
}
|
|
121
131
|
function detectRetiredPackageDuplicates(config, errors) {
|
|
122
132
|
if (config.retiredPackages === undefined)
|
|
123
133
|
return;
|
|
124
|
-
const
|
|
125
|
-
for (const [index,
|
|
126
|
-
const key = `${
|
|
127
|
-
if (
|
|
128
|
-
errors.push(`retiredPackages[${index}]: duplicate package (name='${
|
|
134
|
+
const seenKeys = new Set();
|
|
135
|
+
for (const [index, retiredPackage] of config.retiredPackages.entries()) {
|
|
136
|
+
const key = `${retiredPackage.name}\0${retiredPackage.tagPrefix}`;
|
|
137
|
+
if (seenKeys.has(key)) {
|
|
138
|
+
errors.push(`retiredPackages[${index}]: duplicate package (name='${retiredPackage.name}', tagPrefix='${retiredPackage.tagPrefix}')`);
|
|
129
139
|
}
|
|
130
|
-
|
|
140
|
+
seenKeys.add(key);
|
|
131
141
|
}
|
|
132
142
|
}
|
|
133
143
|
function detectRetiredVsLegacyCollisions(config, errors) {
|
|
@@ -141,10 +151,10 @@ function detectRetiredVsLegacyCollisions(config, errors) {
|
|
|
141
151
|
}
|
|
142
152
|
}
|
|
143
153
|
}
|
|
144
|
-
for (const [index,
|
|
145
|
-
const collidingOwner = legacyPrefixToOwner.get(
|
|
154
|
+
for (const [index, retiredPackage] of config.retiredPackages.entries()) {
|
|
155
|
+
const collidingOwner = legacyPrefixToOwner.get(retiredPackage.tagPrefix);
|
|
146
156
|
if (collidingOwner !== undefined) {
|
|
147
|
-
errors.push(`retiredPackages[${index}]: tagPrefix '${
|
|
157
|
+
errors.push(`retiredPackages[${index}]: tagPrefix '${retiredPackage.tagPrefix}' collides with a declared legacyIdentities[].tagPrefix on ${collidingOwner}`);
|
|
148
158
|
}
|
|
149
159
|
}
|
|
150
160
|
}
|
|
@@ -11,7 +11,7 @@ const SYNTHETIC_VALIDATE_TAG = 'validate-only';
|
|
|
11
11
|
export async function validateOverridesCommand(styles, configPath, dependencies = {}) {
|
|
12
12
|
const discover = dependencies.discoverWorkspaces ?? discoverWorkspaces;
|
|
13
13
|
const load = dependencies.loadValidatedConfig ?? (() => loadValidatedConfig(configPath));
|
|
14
|
-
const buildEntries = dependencies.buildEntries ??
|
|
14
|
+
const buildEntries = dependencies.buildEntries ?? buildEntriesByDefault;
|
|
15
15
|
const validate = dependencies.validate ?? validateAllChangelogOverrides;
|
|
16
16
|
const configResult = await load();
|
|
17
17
|
if (configResult.status === 'invalid') {
|
|
@@ -66,7 +66,7 @@ function formatSummaryLine(errorCount, warningCount) {
|
|
|
66
66
|
function pluralize(count, noun) {
|
|
67
67
|
return count === 1 ? `${count} ${noun}` : `${count} ${noun}s`;
|
|
68
68
|
}
|
|
69
|
-
function
|
|
69
|
+
function buildEntriesByDefault(config, options) {
|
|
70
70
|
return buildChangelogEntries(config, SYNTHETIC_VALIDATE_TAG, options).entries;
|
|
71
71
|
}
|
|
72
72
|
function flattenEntriesToItems(entries) {
|
package/docs/changelogs.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
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, and, when [`changelogMarkdown.
|
|
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.keptVersionCount`](#archived-versions) is set, one whose `files` field omits `changelogs/`.
|
|
6
6
|
|
|
7
7
|
## `changelog.json` item schema
|
|
8
8
|
|
|
@@ -43,19 +43,19 @@ The label match is exact: `migration:` and `**Migration:**` are not recognized.
|
|
|
43
43
|
|
|
44
44
|
## Archived versions
|
|
45
45
|
|
|
46
|
-
`changelogMarkdown.
|
|
46
|
+
`changelogMarkdown.keptVersionCount` 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
47
|
|
|
48
48
|
```typescript
|
|
49
49
|
export default defineConfig({
|
|
50
|
-
changelogMarkdown: {
|
|
50
|
+
changelogMarkdown: { keptVersionCount: 20 },
|
|
51
51
|
});
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
With the option set, `prepare` writes each changelog as follows:
|
|
55
55
|
|
|
56
|
-
- **`CHANGELOG.md`** contains the newest `
|
|
56
|
+
- **`CHANGELOG.md`** contains as many of the newest sections as `keptVersionCount` names, counting [hand-written sections](#existing-changelogmd-sections), and a paragraph below its header that links to each archive file.
|
|
57
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 `
|
|
58
|
+
- **An archive file that the render no longer produces**, after `keptVersionCount` 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
59
|
|
|
60
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
61
|
|
package/docs/configuration.md
CHANGED
|
@@ -16,21 +16,22 @@ The two paths differ in what an absent file means. An absent default path means
|
|
|
16
16
|
|
|
17
17
|
## `ReleaseKitConfig` reference
|
|
18
18
|
|
|
19
|
-
| Field
|
|
20
|
-
|
|
|
21
|
-
| `releases`
|
|
22
|
-
| `workspaces`
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
19
|
+
| Field | Type | Description |
|
|
20
|
+
| --------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| `releases` | `Record<string, ReleaseEntry>` | Names, excludes, or composes release targets; see [`releases`](#releases). Can't be combined with `workspaces` or `project` |
|
|
22
|
+
| `workspaces` | `WorkspaceOverride[]` | Override or exclude discovered workspaces (matched by `dir`) |
|
|
23
|
+
| `devExportConditions` | `string[]` | Export conditions that resolve only inside the repo, such as one that points at unpublished source; the [tarball check](releasing.md#packages-whose-tarball-would-lack-a-manifest-file) does not require their targets in the tarball. `source` is exempt without being listed |
|
|
24
|
+
| `formatCommand` | `string` | Shell command to run after changelog generation; modified file paths are appended as arguments |
|
|
25
|
+
| `versionPatterns` | `VersionPatterns` | Rules for which commit types trigger major/minor bumps |
|
|
26
|
+
| `scopeAliases` | `Record<string, string>` | Maps shorthand scope names to canonical names in commit titles and in change-record entry `scopes`, which [route items to workspaces](changelogs.md#routing-to-workspaces). A renamed workspace directory or [release target](#releases) needs an entry from its old name to its new one |
|
|
27
|
+
| `workTypes` | `Record<string, WorkTypeConfig>` | Work type definitions, merged with defaults by key |
|
|
28
|
+
| `breakingPolicies` | `Record<string, 'forbidden' \| 'optional'>` | Per-type `!`-policy lookup. Defaults to `DEFAULT_BREAKING_POLICIES`. Replaces the default entirely when provided. Set to `{}` to disable enforcement |
|
|
29
|
+
| `retiredPackages` | `RetiredPackage[]` | Packages that once lived in this repo but have been extracted or removed; suppresses undeclared-tag-prefix warnings |
|
|
30
|
+
| `project` | `ProjectConfig` | Opt-in project-level release block. Declaring `project: {}` (even empty) enables a project-release stage in `prepare` |
|
|
31
|
+
| `repoLabels` | `RepoLabelsConfig` | Declares the repository's label registry for `sync-labels`; see [Label configuration](labels.md#label-configuration) |
|
|
32
|
+
| `changelogJson` | `ChangelogJsonConfig` | Controls `.meta/changelog.json`: `enabled` (default `true`), `outputPath` (default `'.meta/changelog.json'`), and `devOnlySections`; see [Custom work types](work-types.md#custom-work-types) |
|
|
33
|
+
| `changelogMarkdown` | `ChangelogMarkdownConfig` | `keptVersionCount` (an integer, at least 1) bounds `CHANGELOG.md` to that many of the newest versions and moves the rest into per-major archive files; omitted, `CHANGELOG.md` renders every version. See [Archived versions](changelogs.md#archived-versions) |
|
|
34
|
+
| `releaseNotes` | `ReleaseNotesConfig` | `shouldInjectIntoReadme` (default `false`) injects release notes into each package's README while it publishes; see [Release-notes injection](changelogs.md#release-notes-injection) |
|
|
34
35
|
|
|
35
36
|
All fields are optional.
|
|
36
37
|
|
package/docs/publishing.md
CHANGED
|
@@ -23,6 +23,10 @@ Example warning when an explicit tag is unpublishable:
|
|
|
23
23
|
Skipping basic-v1.0.0 (packages/basic): package.json#private is true.
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
### Tarball check
|
|
27
|
+
|
|
28
|
+
Before it publishes any package, and in a dry run as well, `publish` packs every publishable package from a clean checkout of HEAD and refuses when a tarball lacks a file that its manifest names. It is the same check that `prepare` runs; see [Packages whose tarball would lack a manifest file](releasing.md#packages-whose-tarball-would-lack-a-manifest-file).
|
|
29
|
+
|
|
26
30
|
## `release-kit create-github-release`
|
|
27
31
|
|
|
28
32
|
Creates GitHub Releases from `changelog.json` for tags on HEAD. Independent of `npm publish`: Invoking this command creates Releases for publishable packages whether or not they were published to a registry. Private workspaces are skipped, and a project tag gets a Release (see [Publishability filter](#publishability-filter)).
|
package/docs/releasing.md
CHANGED
|
@@ -63,6 +63,12 @@ release-kit prepare --bump=minor
|
|
|
63
63
|
|
|
64
64
|
Before it writes any file, and in a dry run as well, `prepare` asks npm about each publishable package that the release includes, querying the registry that the package's `publishConfig.registry` names when it sets one. OIDC trusted publishing can't publish a package's first version, so `prepare` refuses to release a package that npm doesn't list, and names each one with its fix: Run `npm publish --access public` from the package's directory once, then register the package as a trusted publisher. The `npm-auto-publish` [readiness kit](readiness-checks.md) prints the `npm trust` command. `prepare` also refuses when it can't look a package up, such as when the registry is unreachable, and reports npm's error for that package.
|
|
65
65
|
|
|
66
|
+
### Packages whose tarball would lack a manifest file
|
|
67
|
+
|
|
68
|
+
Before it writes any file, and in a dry run as well, `prepare` packs each publishable package that the release includes as the publish workflow would: It clones HEAD into a temporary directory, runs the package manager's frozen install there, runs the package's `prepublishOnly` script when it declares one, and packs the package. Build output in the working tree therefore does not count, and the check adds the time of one install and one build to every release. `prepare` refuses the release when a tarball lacks a file named by the package's `main`, `types`, `bin`, or `exports`, and names each package with each missing path and the manifest field that names it. The usual fix is to build the files in the package's publish lifecycle, such as in a `prepack` script. A failed install or build in the clean checkout also refuses the release, with the failing step's output.
|
|
69
|
+
|
|
70
|
+
A target under the `source` export condition is exempt, and so is a target under a condition listed in [`devExportConditions`](configuration.md#releasekitconfig-reference), which declares the conditions that resolve only inside the repo. [`publish`](publishing.md#tarball-check) runs the same check before it publishes any package.
|
|
71
|
+
|
|
66
72
|
### Previewing release notes with `--with-release-notes`
|
|
67
73
|
|
|
68
74
|
`--with-release-notes` writes two versioned files per workspace after each workspace's `changelog.json` is produced:
|
package/docs/work-types.md
CHANGED
|
@@ -51,7 +51,7 @@ Each work-type has a `breakingPolicy` value:
|
|
|
51
51
|
|
|
52
52
|
A `BREAKING CHANGE:` body footer on a `forbidden`-policy type triggers the same warning path as the prefix `!` does: A type that cannot be breaking is parsed as non-breaking whichever surface has the signal. The warning is all that a footer triggers; it doesn't raise a bump (see [`🚨 **Breaking:**` bullet marker](#-breaking-bullet-marker)).
|
|
53
53
|
|
|
54
|
-
The release-prepare orchestrator (`
|
|
54
|
+
The release-prepare orchestrator (`prepareReleaseTargets`) applies `DEFAULT_BREAKING_POLICIES` automatically. Violations in the titles and change-record entries of each workspace's or project's unreleased window are collected onto the corresponding result's `policyViolations` field, on a skipped result as on a released one, and rendered under the section in the prepare report:
|
|
55
55
|
|
|
56
56
|
```
|
|
57
57
|
arrays
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/release-kit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "16.1.0",
|
|
4
4
|
"description": "Version-bumping and changelog-generation toolkit for release workflows",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"changelog",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@types/semver": "7.8.0",
|
|
64
64
|
"@williamthorsen/toolbelt.filesystem": "0.10.0",
|
|
65
65
|
"@williamthorsen/toolbelt.testing": "0.8.0",
|
|
66
|
-
"@williamthorsen/toolbelt.vitest": "0.13.
|
|
66
|
+
"@williamthorsen/toolbelt.vitest": "0.13.1",
|
|
67
67
|
"actionlint": "2.0.6",
|
|
68
68
|
"esbuild": "0.28.2",
|
|
69
69
|
"readyup": "0.44.0",
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
- name: bug
|
|
10
10
|
color: d73a4a
|
|
11
11
|
description: "Something isn't working"
|
|
12
|
+
- name: content
|
|
13
|
+
color: 0e8a16
|
|
14
|
+
description: Consumer-facing material, such as copy, images, or displayed data, rather than functionality
|
|
12
15
|
- name: operational
|
|
13
16
|
color: '000000'
|
|
14
17
|
description: Actions outside the codebase
|