@williamthorsen/release-kit 15.0.3 → 16.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 +66 -0
- package/.readyup/kits/default.js +13 -13
- package/.readyup/kits/npm-auto-publish.js +4 -4
- package/.readyup/manifest.json +12 -12
- package/CHANGELOG.md +19 -0
- package/README.md +7 -1
- package/dist/esm/applyChangelogOverrides.js +6 -6
- package/dist/esm/assertReleasesOnNpm.js +11 -11
- package/dist/esm/assertTaggedBaseline.js +5 -5
- 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 +2 -2
- package/dist/esm/configSchema.js +1 -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.js +5 -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/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/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 +23 -23
- 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 +2 -2
- package/dist/esm/publish.js +5 -5
- package/dist/esm/publishCommand.js +20 -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 +1 -1
- package/docs/work-types.md +1 -1
- package/package.json +1 -1
|
@@ -22,32 +22,34 @@ export function resolveReleaseTags(args) {
|
|
|
22
22
|
return resolveSinglePackageTags(tags, args.singleWorkspace);
|
|
23
23
|
}
|
|
24
24
|
function resolveSinglePackageTags(tags, singleWorkspace) {
|
|
25
|
-
const
|
|
25
|
+
const matchedTags = tags.filter((tag) => VERSION_PATTERN.test(tag));
|
|
26
26
|
const isPublishable = singleWorkspace?.isPublishable ?? true;
|
|
27
|
-
if (
|
|
28
|
-
console.warn(`Warning: Multiple version tags found on HEAD: ${
|
|
27
|
+
if (matchedTags.length > 1) {
|
|
28
|
+
console.warn(`Warning: Multiple version tags found on HEAD: ${matchedTags.join(', ')}. ` +
|
|
29
29
|
`Publishing the same package multiple times is almost certainly unintended. Using only the first tag.`);
|
|
30
|
-
return
|
|
30
|
+
return matchedTags
|
|
31
|
+
.slice(0, 1)
|
|
32
|
+
.map((tag) => ({ tag, kind: 'package', dir: '.', workspacePath: '.', isPublishable }));
|
|
31
33
|
}
|
|
32
|
-
return
|
|
34
|
+
return matchedTags.map((tag) => ({ tag, kind: 'package', dir: '.', workspacePath: '.', isPublishable }));
|
|
33
35
|
}
|
|
34
36
|
function resolveMonorepoTags(tags, workspaces, directories, project) {
|
|
35
37
|
const sortedWorkspaces = workspaces.toSorted((a, b) => b.tagPrefix.length - a.tagPrefix.length);
|
|
36
|
-
const
|
|
38
|
+
const resolvedTags = [];
|
|
37
39
|
for (const tag of tags) {
|
|
38
|
-
const
|
|
40
|
+
const matchedWorkspace = findMatchingWorkspace(tag, sortedWorkspaces);
|
|
39
41
|
const directory = directories.find((candidate) => hasVersionAfterPrefix(tag, candidate.tagPrefix));
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
+
if (matchedWorkspace !== undefined) {
|
|
43
|
+
resolvedTags.push({
|
|
42
44
|
tag,
|
|
43
45
|
kind: 'package',
|
|
44
|
-
dir: getTargetName(
|
|
45
|
-
workspacePath:
|
|
46
|
-
isPublishable:
|
|
46
|
+
dir: getTargetName(matchedWorkspace),
|
|
47
|
+
workspacePath: matchedWorkspace.workspacePath,
|
|
48
|
+
isPublishable: matchedWorkspace.isPublishable,
|
|
47
49
|
});
|
|
48
50
|
}
|
|
49
51
|
else if (directory !== undefined) {
|
|
50
|
-
|
|
52
|
+
resolvedTags.push({
|
|
51
53
|
tag,
|
|
52
54
|
kind: 'directory',
|
|
53
55
|
dir: directory.name,
|
|
@@ -56,7 +58,7 @@ function resolveMonorepoTags(tags, workspaces, directories, project) {
|
|
|
56
58
|
});
|
|
57
59
|
}
|
|
58
60
|
else if (project !== undefined && hasVersionAfterPrefix(tag, project.tagPrefix)) {
|
|
59
|
-
|
|
61
|
+
resolvedTags.push({
|
|
60
62
|
tag,
|
|
61
63
|
kind: 'project',
|
|
62
64
|
dir: project.name ?? PROJECT_TARGET_ID,
|
|
@@ -65,7 +67,7 @@ function resolveMonorepoTags(tags, workspaces, directories, project) {
|
|
|
65
67
|
});
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
|
-
return
|
|
70
|
+
return resolvedTags;
|
|
69
71
|
}
|
|
70
72
|
function findMatchingWorkspace(tag, sortedWorkspaces) {
|
|
71
73
|
return sortedWorkspaces.find((workspace) => hasVersionAfterPrefix(tag, workspace.tagPrefix));
|
|
@@ -13,8 +13,8 @@ export function resolveReleaseTargets(userConfig, discovery) {
|
|
|
13
13
|
return projectMonorepoConfig(mergeMonorepoConfig(discovery.packageDirs, userConfig, readRootPackageVersion()));
|
|
14
14
|
}
|
|
15
15
|
export function isSinglePackageTargets(targets) {
|
|
16
|
-
const [
|
|
17
|
-
return targets.length === 1 &&
|
|
16
|
+
const [onlyTarget] = targets;
|
|
17
|
+
return targets.length === 1 && onlyTarget?.kind === 'package' && !onlyTarget.isWorkspace;
|
|
18
18
|
}
|
|
19
19
|
export function projectMonorepoConfig(config) {
|
|
20
20
|
const targets = [
|
|
@@ -59,12 +59,12 @@ function renderWorkspaceRow(row, style) {
|
|
|
59
59
|
lines.push(` ${row.workspacePath}: derived prefix '${row.derivedPrefix}', ${statusMarker}`);
|
|
60
60
|
for (const entry of row.legacyEntries) {
|
|
61
61
|
if (entry.tagCount > 0) {
|
|
62
|
-
const
|
|
63
|
-
lines.push(` ${formatStatusLine(style, 'passed',
|
|
62
|
+
const recognizedMessage = `${entry.tagCount} legacy tags with '${entry.prefix}' prefix (recognized)`;
|
|
63
|
+
lines.push(` ${formatStatusLine(style, 'passed', recognizedMessage)}`);
|
|
64
64
|
}
|
|
65
65
|
else {
|
|
66
|
-
const
|
|
67
|
-
lines.push(` ${formatStatusLine(style, 'warning',
|
|
66
|
+
const taglessMessage = `recorded legacy prefix '${entry.prefix}' does not have any tags`;
|
|
67
|
+
lines.push(` ${formatStatusLine(style, 'warning', taglessMessage)}`);
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
return lines;
|
|
@@ -20,7 +20,7 @@ export declare function loadRepoLabelsConfig(stderrStyle: OutputStyle, configPat
|
|
|
20
20
|
/** Options for the `sync-labels generate` subcommand. */
|
|
21
21
|
interface GenerateOptions {
|
|
22
22
|
/** Compares the regenerated content against the committed file instead of writing. */
|
|
23
|
-
|
|
23
|
+
shouldCheck?: boolean;
|
|
24
24
|
/** Config file to read, relative to the working directory. Defaults to `CONFIG_FILE_PATH`. */
|
|
25
25
|
configPath?: string;
|
|
26
26
|
styles: StreamStyles;
|
|
@@ -28,11 +28,11 @@ interface GenerateOptions {
|
|
|
28
28
|
/**
|
|
29
29
|
* Runs the `sync-labels generate` command and returns the process exit code.
|
|
30
30
|
*
|
|
31
|
-
* Writes `.github/labels.yaml` from the config's `repoLabels` block, or, with `
|
|
31
|
+
* Writes `.github/labels.yaml` from the config's `repoLabels` block, or, with `shouldCheck`, reports whether the committed
|
|
32
32
|
* file is stale and writes nothing.
|
|
33
33
|
*
|
|
34
34
|
* The header names the config by its POSIX path relative to the working directory, the repo root, so that the file
|
|
35
35
|
* does not depend on the invocation directory or on the machine on which the command ran.
|
|
36
36
|
*/
|
|
37
|
-
export declare function generateCommand({
|
|
37
|
+
export declare function generateCommand({ shouldCheck, configPath, styles }: GenerateOptions): Promise<number>;
|
|
38
38
|
export {};
|
|
@@ -15,14 +15,14 @@ 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
|
|
18
|
+
const declaredLabels = labels.map(({ aliases, archived, color, description, name }) => ({
|
|
19
19
|
name,
|
|
20
20
|
color,
|
|
21
21
|
description: description ?? '',
|
|
22
22
|
...(aliases !== undefined && { aliases }),
|
|
23
23
|
...(archived !== undefined && { archived }),
|
|
24
24
|
}));
|
|
25
|
-
const yamlBody = stringify(
|
|
25
|
+
const yamlBody = stringify(declaredLabels, { singleQuote: true, lineWidth: 0 });
|
|
26
26
|
return headerLines.join('\n') + '\n' + yamlBody;
|
|
27
27
|
}
|
|
28
28
|
export async function loadRepoLabelsConfig(stderrStyle, configPath) {
|
|
@@ -41,7 +41,7 @@ export async function loadRepoLabelsConfig(stderrStyle, configPath) {
|
|
|
41
41
|
}
|
|
42
42
|
return result.config.repoLabels;
|
|
43
43
|
}
|
|
44
|
-
export async function generateCommand({
|
|
44
|
+
export async function generateCommand({ shouldCheck = false, configPath, styles }) {
|
|
45
45
|
if (checkRetiredSyncLabelsConfig(configPath)) {
|
|
46
46
|
return 1;
|
|
47
47
|
}
|
|
@@ -63,8 +63,8 @@ export async function generateCommand({ check = false, configPath, styles }) {
|
|
|
63
63
|
for (const presetName of presetNames) {
|
|
64
64
|
presetHashes.set(presetName, hashPreset(presetName));
|
|
65
65
|
}
|
|
66
|
-
const content = formatLabelsYaml(labels, presetHashes,
|
|
67
|
-
if (
|
|
66
|
+
const content = formatLabelsYaml(labels, presetHashes, convertToRootRelativePosixPath(configPath ?? CONFIG_FILE_PATH));
|
|
67
|
+
if (shouldCheck) {
|
|
68
68
|
return compareAgainstCommittedFile(content);
|
|
69
69
|
}
|
|
70
70
|
try {
|
|
@@ -79,22 +79,22 @@ export async function generateCommand({ check = false, configPath, styles }) {
|
|
|
79
79
|
console.info(`Generated ${LABELS_OUTPUT_PATH} with ${String(labels.length)} labels.`);
|
|
80
80
|
return 0;
|
|
81
81
|
}
|
|
82
|
-
function compareAgainstCommittedFile(
|
|
83
|
-
let
|
|
82
|
+
function compareAgainstCommittedFile(expectedContent) {
|
|
83
|
+
let existingContent;
|
|
84
84
|
try {
|
|
85
|
-
|
|
85
|
+
existingContent = readFileSync(LABELS_OUTPUT_PATH, 'utf8');
|
|
86
86
|
}
|
|
87
87
|
catch {
|
|
88
88
|
reportError(`${LABELS_OUTPUT_PATH} is missing. Run \`release-kit sync-labels generate\` and commit the result.`);
|
|
89
89
|
return 1;
|
|
90
90
|
}
|
|
91
|
-
if (
|
|
91
|
+
if (existingContent !== expectedContent) {
|
|
92
92
|
reportError(`${LABELS_OUTPUT_PATH} is stale. Run \`release-kit sync-labels generate\` and commit the result.`);
|
|
93
93
|
return 1;
|
|
94
94
|
}
|
|
95
95
|
console.info(`${LABELS_OUTPUT_PATH} is up to date.`);
|
|
96
96
|
return 0;
|
|
97
97
|
}
|
|
98
|
-
function
|
|
98
|
+
function convertToRootRelativePosixPath(filePath) {
|
|
99
99
|
return path.relative(process.cwd(), path.resolve(filePath)).split(path.sep).join('/');
|
|
100
100
|
}
|
|
@@ -3,8 +3,8 @@ import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
|
3
3
|
interface InitOptions {
|
|
4
4
|
/** Config file to read, relative to the working directory. Defaults to `CONFIG_FILE_PATH`. */
|
|
5
5
|
configPath?: string;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
isDryRun: boolean;
|
|
7
|
+
shouldForce: boolean;
|
|
8
8
|
styles: StreamStyles;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
@@ -17,5 +17,5 @@ interface InitOptions {
|
|
|
17
17
|
* `configPath` names the config to read. Because the command fails when the named config does not exist, it
|
|
18
18
|
* reaches the scaffolding branch only under the default path, and that branch always writes to `CONFIG_FILE_PATH`.
|
|
19
19
|
*/
|
|
20
|
-
export declare function syncLabelsInitCommand({ configPath,
|
|
20
|
+
export declare function syncLabelsInitCommand({ configPath, isDryRun, shouldForce, styles, }: InitOptions): Promise<number>;
|
|
21
21
|
export {};
|
|
@@ -6,14 +6,14 @@ import { CONFIG_FILE_PATH } from "../loadConfig.js";
|
|
|
6
6
|
import { loadValidatedConfig, reportConfigProblem } from "../loadValidatedConfig.js";
|
|
7
7
|
import { generateCommand, LABELS_OUTPUT_PATH } from "./generateCommand.js";
|
|
8
8
|
import { checkRetiredSyncLabelsConfig } from "./retiredConfig.js";
|
|
9
|
-
import { buildScopeLabels, readSyncLabelsWorkflowTemplate, renderRepoLabelsBlock,
|
|
9
|
+
import { buildScopeLabels, readSyncLabelsWorkflowTemplate, renderRepoLabelsBlock, renderRepoLabelsConfigScript, } from "./templates.js";
|
|
10
10
|
const WORKFLOW_PATH = '.github/workflows/sync-labels.yaml';
|
|
11
|
-
export async function syncLabelsInitCommand({ configPath,
|
|
11
|
+
export async function syncLabelsInitCommand({ configPath, isDryRun, shouldForce, styles, }) {
|
|
12
12
|
const configFilePath = configPath ?? CONFIG_FILE_PATH;
|
|
13
13
|
if (checkRetiredSyncLabelsConfig(configPath)) {
|
|
14
14
|
return 1;
|
|
15
15
|
}
|
|
16
|
-
if (
|
|
16
|
+
if (isDryRun) {
|
|
17
17
|
console.info('[dry-run mode]');
|
|
18
18
|
}
|
|
19
19
|
console.info('\n> Discovering workspaces');
|
|
@@ -36,8 +36,8 @@ export async function syncLabelsInitCommand({ configPath, dryRun, force, styles
|
|
|
36
36
|
else {
|
|
37
37
|
console.info(` Found ${String(workspace.packageDirs.length)} workspaces`);
|
|
38
38
|
}
|
|
39
|
-
const
|
|
40
|
-
const retiredNames =
|
|
39
|
+
const doesConfigExist = configPath !== undefined || existsSync(CONFIG_FILE_PATH);
|
|
40
|
+
const retiredNames = doesConfigExist ? await loadRetiredPackageNames(styles, configPath) : [];
|
|
41
41
|
if (retiredNames === undefined) {
|
|
42
42
|
return 1;
|
|
43
43
|
}
|
|
@@ -51,9 +51,9 @@ export async function syncLabelsInitCommand({ configPath, dryRun, force, styles
|
|
|
51
51
|
process.stderr.write(` ${describeError(error)}\n`);
|
|
52
52
|
return 1;
|
|
53
53
|
}
|
|
54
|
-
const workflowResult = writeFileWithCheck(WORKFLOW_PATH, workflow, { isDryRun
|
|
55
|
-
reportWriteResult(workflowResult,
|
|
56
|
-
if (
|
|
54
|
+
const workflowResult = writeFileWithCheck(WORKFLOW_PATH, workflow, { isDryRun, shouldOverwrite: shouldForce });
|
|
55
|
+
reportWriteResult(workflowResult, isDryRun, styles);
|
|
56
|
+
if (doesConfigExist) {
|
|
57
57
|
console.info(`\n> ${configFilePath} already exists; add this block to the object passed to defineConfig:\n`);
|
|
58
58
|
console.info(renderRepoLabelsBlock(scopeLabels));
|
|
59
59
|
if (workflowResult.outcome === 'failed') {
|
|
@@ -69,16 +69,16 @@ export async function syncLabelsInitCommand({ configPath, dryRun, force, styles
|
|
|
69
69
|
`);
|
|
70
70
|
return 0;
|
|
71
71
|
}
|
|
72
|
-
const configResult = writeFileWithCheck(CONFIG_FILE_PATH,
|
|
73
|
-
isDryRun
|
|
74
|
-
shouldOverwrite:
|
|
72
|
+
const configResult = writeFileWithCheck(CONFIG_FILE_PATH, renderRepoLabelsConfigScript(scopeLabels), {
|
|
73
|
+
isDryRun,
|
|
74
|
+
shouldOverwrite: shouldForce,
|
|
75
75
|
});
|
|
76
|
-
reportWriteResult(configResult,
|
|
76
|
+
reportWriteResult(configResult, isDryRun, styles);
|
|
77
77
|
if (workflowResult.outcome === 'failed' || configResult.outcome === 'failed') {
|
|
78
78
|
process.stderr.write('Failed to scaffold one or more files.\n');
|
|
79
79
|
return 1;
|
|
80
80
|
}
|
|
81
|
-
if (
|
|
81
|
+
if (isDryRun) {
|
|
82
82
|
console.info(`\n> [dry-run] Would generate ${LABELS_OUTPUT_PATH}`);
|
|
83
83
|
}
|
|
84
84
|
else {
|
|
@@ -109,8 +109,8 @@ async function loadRetiredPackageNames(styles, configPath) {
|
|
|
109
109
|
if (result.status === 'missing') {
|
|
110
110
|
return [];
|
|
111
111
|
}
|
|
112
|
-
return (result.config.retiredPackages ?? []).map((retired) =>
|
|
112
|
+
return (result.config.retiredPackages ?? []).map((retired) => stripNpmScope(retired.name));
|
|
113
113
|
}
|
|
114
|
-
function
|
|
114
|
+
function stripNpmScope(name) {
|
|
115
115
|
return name.startsWith('@') ? name.slice(name.indexOf('/') + 1) : name;
|
|
116
116
|
}
|
|
@@ -25,12 +25,12 @@ export function loadPreset(presetName) {
|
|
|
25
25
|
catch (error) {
|
|
26
26
|
throw chainError(`Failed to read preset "${presetName}"`, error);
|
|
27
27
|
}
|
|
28
|
-
const
|
|
29
|
-
if (!Array.isArray(
|
|
28
|
+
const parsedYaml = parse(content);
|
|
29
|
+
if (!Array.isArray(parsedYaml)) {
|
|
30
30
|
throw new TypeError(`Preset "${presetName}" must be a YAML array of label definitions`);
|
|
31
31
|
}
|
|
32
32
|
const labels = [];
|
|
33
|
-
for (const entry of
|
|
33
|
+
for (const entry of parsedYaml) {
|
|
34
34
|
if (!isRecord(entry)) {
|
|
35
35
|
throw new Error(`Preset "${presetName}" contains an invalid label entry: ${JSON.stringify(entry)}`);
|
|
36
36
|
}
|
|
@@ -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[];
|