@williamthorsen/release-kit 12.0.0 → 13.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 +212 -0
- package/.readyup/kits/default.js +18 -17
- package/.readyup/kits/npm-auto-publish.js +24 -23
- package/.readyup/manifest.json +16 -11
- package/CHANGELOG.md +58 -0
- package/README.md +55 -48
- package/bin/release-kit.js +1 -1
- package/dist/esm/assertCleanWorkingTree.d.ts +1 -5
- package/dist/esm/assertTaggedBaseline.d.ts +29 -0
- package/dist/esm/assertTaggedBaseline.js +41 -0
- package/dist/esm/attachChangelogDiagnostics.d.ts +1 -1
- package/dist/esm/attachChangelogDiagnostics.js +3 -0
- package/dist/esm/bin/release-kit.js +85 -22
- package/dist/esm/buildChangelogEntries.d.ts +23 -13
- package/dist/esm/buildChangelogEntries.js +36 -17
- package/dist/esm/buildDependencyGraph.d.ts +6 -6
- package/dist/esm/buildEmptyReleaseEntry.d.ts +1 -8
- package/dist/esm/buildReleaseSummary.d.ts +2 -8
- package/dist/esm/buildSyntheticChangelogEntry.d.ts +3 -5
- package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
- package/dist/esm/bumpVersion.d.ts +1 -8
- package/dist/esm/changelogJsonFile.d.ts +5 -7
- package/dist/esm/changelogJsonFile.js +2 -17
- package/dist/esm/changelogJsonUtils.d.ts +7 -10
- package/dist/esm/changelogOverrides.d.ts +59 -98
- package/dist/esm/classifyChangelogCommit.d.ts +7 -9
- package/dist/esm/classifyChangelogCommit.js +3 -2
- package/dist/esm/commitCommand.d.ts +2 -4
- package/dist/esm/compareVersions.d.ts +10 -4
- package/dist/esm/compareVersions.js +16 -0
- package/dist/esm/configFlagSchema.d.ts +1 -1
- package/dist/esm/createGithubRelease.d.ts +7 -19
- package/dist/esm/createGithubReleaseCommand.d.ts +5 -5
- package/dist/esm/createGithubReleaseCommand.js +9 -9
- package/dist/esm/createTags.d.ts +2 -3
- package/dist/esm/decideRelease.d.ts +2 -6
- package/dist/esm/defaults.d.ts +5 -24
- package/dist/esm/defaults.js +4 -5
- package/dist/esm/defineConfig.d.ts +1 -1
- package/dist/esm/deleteFileIfExists.d.ts +1 -1
- package/dist/esm/deriveReleaseNotesConfig.d.ts +20 -0
- package/dist/esm/deriveReleaseNotesConfig.js +21 -0
- package/dist/esm/deriveWorkspaceConfig.d.ts +3 -5
- package/dist/esm/detectPackageManager.d.ts +2 -4
- package/dist/esm/detectUndeclaredTagPrefixes.d.ts +4 -6
- package/dist/esm/detectUndeclaredTagPrefixes.js +1 -1
- package/dist/esm/determineBumpType.d.ts +1 -1
- package/dist/esm/discoverWorkspaces.d.ts +14 -15
- package/dist/esm/discoverWorkspaces.js +12 -12
- package/dist/esm/enterRepoRoot.d.ts +12 -0
- package/dist/esm/enterRepoRoot.js +16 -0
- package/dist/esm/enumerateReleaseWindows.d.ts +8 -8
- package/dist/esm/extractMigration.d.ts +6 -8
- package/dist/esm/findUnroutedEntryScopes.d.ts +16 -0
- package/dist/esm/findUnroutedEntryScopes.js +57 -0
- package/dist/esm/formatExcludedSkip.d.ts +3 -0
- package/dist/esm/formatExcludedSkip.js +3 -0
- package/dist/esm/formatPrivateSkip.d.ts +1 -6
- package/dist/esm/generateChangelogs.d.ts +10 -5
- package/dist/esm/glyphs.d.ts +1 -1
- package/dist/esm/help/prepareHelp.d.ts +3 -4
- package/dist/esm/help/prepareHelp.js +4 -3
- package/dist/esm/init/checks.d.ts +3 -3
- package/dist/esm/init/checks.js +4 -1
- package/dist/esm/init/detectRepoType.d.ts +2 -3
- package/dist/esm/init/hasProvenance.d.ts +2 -0
- package/dist/esm/init/hasProvenance.js +3 -0
- package/dist/esm/init/initCommand.d.ts +3 -6
- package/dist/esm/init/initCommand.js +10 -10
- package/dist/esm/init/parseJsonRecord.d.ts +1 -4
- package/dist/esm/init/scaffold.d.ts +1 -1
- package/dist/esm/init/templates.d.ts +9 -11
- package/dist/esm/init/templates.js +12 -11
- package/dist/esm/init/updateTemplatesCommand.d.ts +13 -0
- package/dist/esm/init/updateTemplatesCommand.js +64 -0
- package/dist/esm/injectReleaseNotesIntoReadme.d.ts +12 -17
- package/dist/esm/injectSection.d.ts +3 -3
- package/dist/esm/loadConfig.d.ts +16 -14
- package/dist/esm/loadConfig.js +2 -2
- package/dist/esm/loadValidatedConfig.d.ts +16 -19
- package/dist/esm/loadValidatedConfig.js +6 -1
- package/dist/esm/packsPath.d.ts +9 -9
- package/dist/esm/parseChangeRecordBlock.d.ts +2 -2
- package/dist/esm/parseCommitMessage.d.ts +17 -32
- package/dist/esm/parseCommitMessage.js +28 -37
- package/dist/esm/parseRequestedTags.d.ts +3 -3
- package/dist/esm/planReleaseNotesPreviews.d.ts +6 -6
- package/dist/esm/planVersionBump.d.ts +5 -3
- package/dist/esm/planVersionBump.js +3 -0
- package/dist/esm/prepareCommand.d.ts +4 -7
- package/dist/esm/prepareCommand.js +13 -6
- package/dist/esm/previewTagPrefixes.d.ts +8 -10
- package/dist/esm/propagateBumps.d.ts +6 -7
- package/dist/esm/publish.d.ts +1 -1
- package/dist/esm/publishCommand.d.ts +4 -3
- package/dist/esm/publishCommand.js +8 -8
- package/dist/esm/pushCommand.d.ts +4 -3
- package/dist/esm/pushCommand.js +8 -2
- package/dist/esm/pushRelease.d.ts +2 -2
- package/dist/esm/readChangelogSections.d.ts +33 -0
- package/dist/esm/readChangelogSections.js +80 -0
- package/dist/esm/releaseFiles.d.ts +5 -7
- package/dist/esm/releasePlan.d.ts +11 -13
- package/dist/esm/releasePrepare.d.ts +23 -22
- package/dist/esm/releasePrepare.js +31 -11
- package/dist/esm/releasePrepareMono.d.ts +9 -7
- package/dist/esm/releasePrepareMono.js +85 -33
- package/dist/esm/releasePrepareProject.d.ts +19 -29
- package/dist/esm/releasePrepareProject.js +31 -12
- package/dist/esm/renderChangelogMarkdown.d.ts +10 -4
- package/dist/esm/renderChangelogMarkdown.js +27 -2
- package/dist/esm/renderReleaseNotes.d.ts +3 -11
- package/dist/esm/renderReleaseNotes.js +2 -6
- package/dist/esm/reportPrepare.d.ts +4 -5
- package/dist/esm/reportPrepare.js +33 -19
- package/dist/esm/resolveCommandTags.d.ts +15 -11
- package/dist/esm/resolveCommandTags.js +35 -5
- package/dist/esm/resolveConfigFlag.d.ts +7 -0
- package/dist/esm/resolveConfigFlag.js +4 -0
- package/dist/esm/resolveReleaseTags.d.ts +6 -8
- package/dist/esm/resolveReleaseTags.js +1 -1
- package/dist/esm/resolveStylesOrExit.d.ts +2 -2
- package/dist/esm/showTagPrefixesCommand.d.ts +7 -10
- package/dist/esm/showTagPrefixesCommand.js +4 -4
- package/dist/esm/stripEmojiPrefix.d.ts +1 -0
- package/dist/esm/stripScope.d.ts +2 -2
- package/dist/esm/stripScope.js +3 -3
- package/dist/esm/sync-labels/generateCommand.d.ts +11 -10
- package/dist/esm/sync-labels/generateCommand.js +11 -8
- package/dist/esm/sync-labels/initCommand.d.ts +6 -7
- package/dist/esm/sync-labels/initCommand.js +2 -2
- package/dist/esm/sync-labels/presets.d.ts +5 -4
- package/dist/esm/sync-labels/presets.js +9 -9
- package/dist/esm/sync-labels/resolveLabels.d.ts +7 -7
- package/dist/esm/sync-labels/resolveLabels.js +1 -1
- package/dist/esm/sync-labels/retiredConfig.d.ts +4 -5
- package/dist/esm/sync-labels/syncCommand.d.ts +1 -4
- package/dist/esm/sync-labels/templates.d.ts +3 -3
- package/dist/esm/sync-labels/templates.js +3 -3
- package/dist/esm/sync-labels/typeLabels.d.ts +14 -0
- package/dist/esm/sync-labels/typeLabels.js +38 -0
- package/dist/esm/sync-labels/types.d.ts +1 -1
- package/dist/esm/tagCommand.d.ts +1 -1
- package/dist/esm/typeGuards.d.ts +2 -2
- package/dist/esm/types.d.ts +116 -91
- package/dist/esm/types.js +1 -1
- package/dist/esm/validateConfig.d.ts +2 -11
- package/dist/esm/validateConfig.js +2 -2
- package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -12
- package/dist/esm/validateOverridesCommand.d.ts +17 -20
- package/dist/esm/validateOverridesCommand.js +1 -1
- package/docs/api.md +6 -6
- package/docs/changelogs.md +63 -40
- package/docs/configuration.md +29 -27
- package/docs/editorial-overrides.md +23 -23
- package/docs/labels.md +21 -19
- package/docs/project-releases.md +13 -13
- package/docs/publishing.md +5 -5
- package/docs/readiness-checks.md +3 -3
- package/docs/releasing.md +50 -25
- package/docs/work-types.md +41 -57
- package/package.json +7 -6
- package/presets/labels/common.yaml +1 -41
- package/dist/esm/resolveReleaseNotesConfig.d.ts +0 -26
- package/dist/esm/resolveReleaseNotesConfig.js +0 -29
- package/dist/esm/workTypesData.d.ts +0 -50
- package/dist/esm/workTypesData.js +0 -145
- package/schemas/label-map.json +0 -24
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { compareVersionsDescending } from "./compareVersions.js";
|
|
2
|
+
import { renderReleaseNotesSingle } from "./renderReleaseNotes.js";
|
|
2
3
|
const CHANGELOG_HEADER = `# Changelog
|
|
3
4
|
|
|
4
5
|
All notable changes to this project will be documented in this file.`;
|
|
5
6
|
const CHANGELOG_FOOTER = `<!-- Generated by release-kit. Do not edit this file. Use .meta/changelog-overrides.json to override entries. -->`;
|
|
6
7
|
export function renderChangelogMarkdown(entries, options) {
|
|
7
|
-
const
|
|
8
|
+
const sections = interleavePreservedSections(entries.map((entry) => ({ version: entry.version, text: renderReleaseNotesSingle(entry, options) })), options?.preservedSections ?? []);
|
|
9
|
+
const body = sections
|
|
10
|
+
.map((part) => part.text)
|
|
11
|
+
.filter((text) => text.length > 0)
|
|
12
|
+
.join('\n');
|
|
8
13
|
const trimmedBody = body.trimEnd();
|
|
9
14
|
const parts = [CHANGELOG_HEADER];
|
|
10
15
|
if (trimmedBody.length > 0) {
|
|
@@ -13,3 +18,23 @@ export function renderChangelogMarkdown(entries, options) {
|
|
|
13
18
|
parts.push(CHANGELOG_FOOTER);
|
|
14
19
|
return parts.join('\n\n') + '\n';
|
|
15
20
|
}
|
|
21
|
+
function interleavePreservedSections(rendered, preserved) {
|
|
22
|
+
const sorted = preserved.toSorted((a, b) => compareVersionsDescending(a.version, b.version));
|
|
23
|
+
const pending = sorted.map(withTrailingNewline);
|
|
24
|
+
const result = [];
|
|
25
|
+
let index = 0;
|
|
26
|
+
for (const part of rendered) {
|
|
27
|
+
let section = pending[index];
|
|
28
|
+
while (section !== undefined && compareVersionsDescending(section.version, part.version) < 0) {
|
|
29
|
+
result.push(section);
|
|
30
|
+
index++;
|
|
31
|
+
section = pending[index];
|
|
32
|
+
}
|
|
33
|
+
result.push(part);
|
|
34
|
+
}
|
|
35
|
+
result.push(...pending.slice(index));
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
function withTrailingNewline(section) {
|
|
39
|
+
return { version: section.version, text: `${section.text}\n` };
|
|
40
|
+
}
|
|
@@ -13,22 +13,14 @@ export interface RenderOptions {
|
|
|
13
13
|
sectionOrder?: string[];
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Creates a predicate that matches sections visible to the given audience.
|
|
17
17
|
*
|
|
18
18
|
* `"all"` matches only sections with `audience: "all"` (public-facing).
|
|
19
19
|
* `"dev"` matches all sections (developers see everything).
|
|
20
20
|
*/
|
|
21
21
|
export declare function matchesAudience(audience: ChangelogAudience): (section: ChangelogSection) => boolean;
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* Output format mirrors the existing CHANGELOG.md style: an H2 version heading followed by
|
|
26
|
-
* H3 section headings with bulleted items.
|
|
23
|
+
* Renders a single `ChangelogEntry` to markdown: an H2 version heading followed by H3 section
|
|
24
|
+
* headings with bulleted items.
|
|
27
25
|
*/
|
|
28
26
|
export declare function renderReleaseNotesSingle(entry: ChangelogEntry, options?: RenderOptions): string;
|
|
29
|
-
/**
|
|
30
|
-
* Render multiple `ChangelogEntry` values to a single markdown document.
|
|
31
|
-
*
|
|
32
|
-
* Entries are rendered in the order provided; the caller is responsible for sorting.
|
|
33
|
-
*/
|
|
34
|
-
export declare function renderReleaseNotesMulti(entries: ChangelogEntry[], options?: RenderOptions): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CANONICAL_TAXONOMY } from '@williamthorsen/change-grammar';
|
|
2
2
|
function getBreakingPrefix() {
|
|
3
|
-
const { emoji, label } =
|
|
3
|
+
const { emoji, label } = CANONICAL_TAXONOMY.markers.breaking;
|
|
4
4
|
return `${emoji} **${label}:** `;
|
|
5
5
|
}
|
|
6
6
|
function allSections() {
|
|
@@ -62,7 +62,3 @@ function sortSectionsByOrder(sections, order) {
|
|
|
62
62
|
function indentBodyLines(body) {
|
|
63
63
|
return body.split('\n').map((line) => (line.length === 0 ? '' : ` ${line}`));
|
|
64
64
|
}
|
|
65
|
-
export function renderReleaseNotesMulti(entries, options) {
|
|
66
|
-
const parts = entries.map((entry) => renderReleaseNotesSingle(entry, options)).filter((part) => part.length > 0);
|
|
67
|
-
return parts.join('\n');
|
|
68
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
2
2
|
import type { PrepareResult } from './types.js';
|
|
3
|
-
/** How the plan was carried out, which decides the
|
|
3
|
+
/** How the plan was carried out, which decides the report's tense. */
|
|
4
4
|
export interface ReportPrepareOptions {
|
|
5
5
|
/** True when the plan was written to disk; false for a dry run. */
|
|
6
6
|
applied: boolean;
|
|
@@ -9,14 +9,13 @@ export interface ReportPrepareOptions {
|
|
|
9
9
|
style: OutputStyle;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Formats a release plan into styled terminal output, without writing it.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* Single-workspace mode (no `name` field) renders flat output; multi-workspace mode
|
|
14
|
+
* Single-workspace mode (without a `name` field) renders flat output; multi-workspace mode
|
|
16
15
|
* renders section headers per workspace, an optional project section, and a tag summary
|
|
17
16
|
* at the end.
|
|
18
17
|
*
|
|
19
|
-
* Whether the plan was applied is a property of the render, not of the plan:
|
|
18
|
+
* Whether the plan was applied is a property of the render, not of the plan: The same plan
|
|
20
19
|
* produces the dry-run report and the applied report.
|
|
21
20
|
*/
|
|
22
21
|
export declare function reportPrepare(plan: PrepareResult, options: ReportPrepareOptions): string;
|
|
@@ -51,7 +51,7 @@ function formatSingleWorkspace(result, options) {
|
|
|
51
51
|
formatChangeRecordWarnings(lines, workspace, style);
|
|
52
52
|
formatBumpFiles(lines, workspace, dryRun);
|
|
53
53
|
lines.push(dim('Generating changelogs...'));
|
|
54
|
-
formatChangelogFiles(lines, workspace, dryRun);
|
|
54
|
+
formatChangelogFiles(lines, workspace, dryRun, style);
|
|
55
55
|
formatPreviewFiles(lines, workspace, dryRun);
|
|
56
56
|
formatFormatCommand(lines, result, options);
|
|
57
57
|
formatWarnings(lines, result, style);
|
|
@@ -77,7 +77,7 @@ function formatMultiWorkspace(result, options) {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
else {
|
|
80
|
-
lines.push(`\n${formatStatusLine(style, 'skipped', '
|
|
80
|
+
lines.push(`\n${formatStatusLine(style, 'skipped', 'The run did not find release-worthy changes in any workspace.')}`);
|
|
81
81
|
}
|
|
82
82
|
return lines.join('\n');
|
|
83
83
|
}
|
|
@@ -111,14 +111,7 @@ function formatProjectSection(lines, project, dryRun, style) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
lines.push(dim(' Generating changelogs...'));
|
|
114
|
-
|
|
115
|
-
if (dryRun) {
|
|
116
|
-
lines.push(dim(` [dry-run] Would generate changelog: ${file}`));
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
lines.push(dim(` Generating changelog: ${file}`));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
114
|
+
formatChangelogFiles(lines, project, dryRun, style, ' ');
|
|
122
115
|
formatPreviewFiles(lines, project, dryRun, ' ');
|
|
123
116
|
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(tag))}`);
|
|
124
117
|
}
|
|
@@ -136,7 +129,7 @@ function formatWorkspaceSection(lines, workspace, dryRun, style) {
|
|
|
136
129
|
return;
|
|
137
130
|
}
|
|
138
131
|
const { propagatedFrom } = workspace;
|
|
139
|
-
const isPropagatedOnly =
|
|
132
|
+
const isPropagatedOnly = workspace.propagatedOnly === true;
|
|
140
133
|
formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly);
|
|
141
134
|
formatUnparseableWarning(lines, workspace, style, ' ');
|
|
142
135
|
formatPolicyViolations(lines, workspace.policyViolations, style, ' ');
|
|
@@ -145,14 +138,14 @@ function formatWorkspaceSection(lines, workspace, dryRun, style) {
|
|
|
145
138
|
formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly, style);
|
|
146
139
|
formatBumpFiles(lines, workspace, dryRun, ' ');
|
|
147
140
|
lines.push(dim(' Generating changelogs...'));
|
|
148
|
-
formatChangelogFiles(lines, workspace, dryRun, ' ');
|
|
141
|
+
formatChangelogFiles(lines, workspace, dryRun, style, ' ');
|
|
149
142
|
formatPreviewFiles(lines, workspace, dryRun, ' ');
|
|
150
143
|
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(workspace.tag))}`);
|
|
151
144
|
}
|
|
152
145
|
function formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly) {
|
|
153
146
|
if (isPropagatedOnly && propagatedFrom !== undefined) {
|
|
154
147
|
const depNames = propagatedFrom.map((p) => p.packageName).join(', ');
|
|
155
|
-
lines.push(dim(`
|
|
148
|
+
lines.push(dim(` Bumped via dependency: ${depNames}`));
|
|
156
149
|
}
|
|
157
150
|
else if (workspace.parsedCommitCount !== undefined && workspace.parsedCommitCount > 0) {
|
|
158
151
|
lines.push(dim(` Parsed ${workspace.parsedCommitCount} typed commits`));
|
|
@@ -192,14 +185,26 @@ function formatBumpFiles(lines, workspace, dryRun, indent = '') {
|
|
|
192
185
|
}
|
|
193
186
|
}
|
|
194
187
|
}
|
|
195
|
-
function formatChangelogFiles(lines,
|
|
196
|
-
for (const file of
|
|
188
|
+
function formatChangelogFiles(lines, release, dryRun, style, indent = '') {
|
|
189
|
+
for (const file of release.changelogFiles) {
|
|
197
190
|
if (dryRun) {
|
|
198
191
|
lines.push(dim(`${indent} [dry-run] Would generate changelog: ${file}`));
|
|
199
192
|
}
|
|
200
193
|
else {
|
|
201
194
|
lines.push(dim(`${indent} Generating changelog: ${file}`));
|
|
202
195
|
}
|
|
196
|
+
const preservation = release.changelogPreservation?.find((candidate) => candidate.file === file);
|
|
197
|
+
if (preservation === undefined) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (preservation.preservedVersions.length > 0) {
|
|
201
|
+
lines.push(dim(`${indent} Kept from the existing file: ${preservation.preservedVersions.join(', ')}`));
|
|
202
|
+
}
|
|
203
|
+
const dropped = preservation.droppedUnversionedHeadings;
|
|
204
|
+
if (dropped.length > 0) {
|
|
205
|
+
const message = `Dropped ${dropped.length} section${dropped.length === 1 ? '' : 's'} without a version: ${dropped.join(', ')}`;
|
|
206
|
+
lines.push(`${indent} ${formatStatusLine(style, 'warning', message)}`);
|
|
207
|
+
}
|
|
203
208
|
}
|
|
204
209
|
}
|
|
205
210
|
function formatPreviewFiles(lines, release, dryRun, indent = '') {
|
|
@@ -237,17 +242,17 @@ function formatPolicyViolations(lines, violations, style, indent = '') {
|
|
|
237
242
|
const subject = violation.commitSubject;
|
|
238
243
|
const truncatedSubject = truncateSubject(subject);
|
|
239
244
|
const location = violation.surface === 'entry' ? `entry ${violation.entryPosition ?? '?'}` : `${violation.surface} surface`;
|
|
240
|
-
lines.push(`${indent} · ${shortHash} '${truncatedSubject}'
|
|
245
|
+
lines.push(`${indent} · ${shortHash} '${truncatedSubject}': type '${violation.type}' at ${location}`);
|
|
241
246
|
}
|
|
242
247
|
}
|
|
243
248
|
function formatChangeRecordWarnings(lines, release, style, indent = '') {
|
|
244
|
-
const { malformedBlocks = [], undeclaredEntryTypes = [] } = release;
|
|
249
|
+
const { malformedBlocks = [], undeclaredEntryTypes = [], unroutedEntryScopes = [] } = release;
|
|
245
250
|
if (malformedBlocks.length > 0) {
|
|
246
251
|
const count = malformedBlocks.length;
|
|
247
252
|
const header = `${count} change-record block${count === 1 ? '' : 's'} could not be read (item taken from the title)`;
|
|
248
253
|
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
|
|
249
254
|
for (const block of malformedBlocks) {
|
|
250
|
-
lines.push(`${indent} · ${block.commitHash.slice(0, 7)} '${truncateSubject(block.commitSubject)}'
|
|
255
|
+
lines.push(`${indent} · ${block.commitHash.slice(0, 7)} '${truncateSubject(block.commitSubject)}': ${block.reason}`);
|
|
251
256
|
}
|
|
252
257
|
}
|
|
253
258
|
if (undeclaredEntryTypes.length > 0) {
|
|
@@ -256,7 +261,16 @@ function formatChangeRecordWarnings(lines, release, style, indent = '') {
|
|
|
256
261
|
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
|
|
257
262
|
for (const entry of undeclaredEntryTypes) {
|
|
258
263
|
const shortHash = entry.commitHash.slice(0, 7);
|
|
259
|
-
lines.push(`${indent} · ${shortHash} '${truncateSubject(entry.commitSubject)}'
|
|
264
|
+
lines.push(`${indent} · ${shortHash} '${truncateSubject(entry.commitSubject)}': type '${entry.type}' at entry ${entry.entryPosition}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (unroutedEntryScopes.length > 0) {
|
|
268
|
+
const count = unroutedEntryScopes.length;
|
|
269
|
+
const header = `${count} change-record entry scope${count === 1 ? ' does not match' : 's do not match'} any workspace in the commit's window (no item there)`;
|
|
270
|
+
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
|
|
271
|
+
for (const finding of unroutedEntryScopes) {
|
|
272
|
+
const shortHash = finding.commitHash.slice(0, 7);
|
|
273
|
+
lines.push(`${indent} · ${shortHash} '${truncateSubject(finding.commitSubject)}': scope '${finding.scope}' at entry ${finding.entryPosition}`);
|
|
260
274
|
}
|
|
261
275
|
}
|
|
262
276
|
}
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { type ResolvedTag } from './resolveReleaseTags.js';
|
|
2
|
+
import type { ReleaseKitConfig } from './types.js';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Discovers workspaces, resolves release tags from HEAD, validates `--tags` names against the full resolved tag
|
|
5
|
+
* names (e.g., `nmr-core-v1.3.0`), and returns the filtered tag list. Exits with an error message on any validation
|
|
6
|
+
* failure, including a workspace whose `package.json` does not have a `name` field, a config that
|
|
7
|
+
* `mergeMonorepoConfig` rejects, and a workspace that does not resolve to any package, which is not single-package
|
|
8
|
+
* mode.
|
|
8
9
|
*
|
|
9
|
-
* In
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* In monorepo mode the workspace list is `userConfig` merged over the discovered workspaces, as `prepare` builds it.
|
|
11
|
+
* A tag on HEAD whose workspace the config excludes still counts as a tag on HEAD, so `--tags` may name it and a
|
|
12
|
+
* HEAD whose tags are all excluded returns an empty list rather than failing. Each excluded tag that the run
|
|
13
|
+
* selects, every one without `--tags` or the named ones with it, is dropped with a warning.
|
|
14
|
+
* Single-package mode ignores `userConfig`.
|
|
15
|
+
*
|
|
16
|
+
* In both modes the workspace configs come from `deriveWorkspaceConfig`, so that `WorkspaceConfig.isPublishable`
|
|
17
|
+
* (read from `package.json#private`) propagates onto each `ResolvedTag`.
|
|
14
18
|
*/
|
|
15
|
-
export declare function resolveCommandTags(tags: string[] | undefined): ResolvedTag[];
|
|
19
|
+
export declare function resolveCommandTags(tags: string[] | undefined, userConfig: ReleaseKitConfig | undefined): ResolvedTag[];
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
1
2
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
2
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
4
|
import { deriveWorkspaceConfig } from "./deriveWorkspaceConfig.js";
|
|
4
5
|
import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
6
|
+
import { formatExcludedSkip } from "./formatExcludedSkip.js";
|
|
7
|
+
import { mergeMonorepoConfig, readRootPackageVersion } from "./loadConfig.js";
|
|
5
8
|
import { resolveReleaseTags } from "./resolveReleaseTags.js";
|
|
6
|
-
export function resolveCommandTags(tags) {
|
|
9
|
+
export function resolveCommandTags(tags, userConfig) {
|
|
7
10
|
let workspace;
|
|
8
11
|
try {
|
|
9
12
|
workspace = discoverWorkspaces();
|
|
@@ -18,12 +21,13 @@ export function resolveCommandTags(tags) {
|
|
|
18
21
|
}
|
|
19
22
|
let workspaces;
|
|
20
23
|
let singleWorkspace;
|
|
24
|
+
const excludedDirs = workspace.kind === 'packages' ? collectExcludedDirs(userConfig) : new Set();
|
|
21
25
|
try {
|
|
22
26
|
if (workspace.kind === 'single-package') {
|
|
23
27
|
singleWorkspace = deriveWorkspaceConfig('.');
|
|
24
28
|
}
|
|
25
29
|
else {
|
|
26
|
-
workspaces = workspace.packageDirs
|
|
30
|
+
workspaces = buildMonorepoWorkspaces(workspace.packageDirs, userConfig, excludedDirs);
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
catch (error) {
|
|
@@ -38,10 +42,10 @@ export function resolveCommandTags(tags) {
|
|
|
38
42
|
resolvedTags = resolveReleaseTags({ singleWorkspace });
|
|
39
43
|
}
|
|
40
44
|
else {
|
|
41
|
-
throw new Error('resolveCommandTags: invariant violated
|
|
45
|
+
throw new Error('resolveCommandTags: invariant violated; neither workspaces nor singleWorkspace was derived');
|
|
42
46
|
}
|
|
43
47
|
if (resolvedTags.length === 0) {
|
|
44
|
-
reportError('
|
|
48
|
+
reportError('HEAD does not have any release tags. Create tags with `release-kit tag` first.');
|
|
45
49
|
process.exit(1);
|
|
46
50
|
}
|
|
47
51
|
if (tags !== undefined) {
|
|
@@ -55,5 +59,31 @@ export function resolveCommandTags(tags) {
|
|
|
55
59
|
}
|
|
56
60
|
resolvedTags = resolvedTags.filter((t) => tags.includes(t.tag));
|
|
57
61
|
}
|
|
58
|
-
return resolvedTags;
|
|
62
|
+
return dropExcludedTags(resolvedTags, excludedDirs);
|
|
63
|
+
}
|
|
64
|
+
function buildMonorepoWorkspaces(packageDirs, userConfig, excludedDirs) {
|
|
65
|
+
const { workspaces } = mergeMonorepoConfig(packageDirs, userConfig, readRootPackageVersion());
|
|
66
|
+
if (excludedDirs.size === 0) {
|
|
67
|
+
return workspaces;
|
|
68
|
+
}
|
|
69
|
+
const excluded = packageDirs
|
|
70
|
+
.filter((packageDir) => excludedDirs.has(basename(packageDir)))
|
|
71
|
+
.map((packageDir) => deriveWorkspaceConfig(packageDir));
|
|
72
|
+
return [...workspaces, ...excluded];
|
|
73
|
+
}
|
|
74
|
+
function collectExcludedDirs(userConfig) {
|
|
75
|
+
const overrides = userConfig?.workspaces ?? [];
|
|
76
|
+
return new Set(overrides.filter((override) => override.shouldExclude === true).map((override) => override.dir));
|
|
77
|
+
}
|
|
78
|
+
function dropExcludedTags(resolvedTags, excludedDirs) {
|
|
79
|
+
const retained = [];
|
|
80
|
+
for (const resolvedTag of resolvedTags) {
|
|
81
|
+
if (excludedDirs.has(resolvedTag.dir)) {
|
|
82
|
+
console.warn(formatExcludedSkip(resolvedTag));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
retained.push(resolvedTag);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return retained;
|
|
59
89
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves a `--config` value against the directory from which `release-kit` was invoked, which differs from the
|
|
3
|
+
* working directory once the process has moved to the repo root.
|
|
4
|
+
*
|
|
5
|
+
* Returns `undefined` when the flag is absent, so that `loadConfig` falls back to the default path under the root.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveConfigFlag(config: string | undefined, invocationDir: string): string | undefined;
|
|
@@ -4,10 +4,8 @@ export interface ResolvedTag {
|
|
|
4
4
|
dir: string;
|
|
5
5
|
workspacePath: string;
|
|
6
6
|
/**
|
|
7
|
-
* Whether the workspace this tag belongs
|
|
8
|
-
*
|
|
9
|
-
* workspace config (single-package mode). Consumed by `release-kit publish` to filter the
|
|
10
|
-
* tag set; other commands ignore it.
|
|
7
|
+
* Whether the workspace to which this tag belongs can be published to a registry, copied from the matched
|
|
8
|
+
* workspace's `WorkspaceConfig.isPublishable`.
|
|
11
9
|
*/
|
|
12
10
|
isPublishable: boolean;
|
|
13
11
|
}
|
|
@@ -18,15 +16,15 @@ type ResolveReleaseTagsArgs = {
|
|
|
18
16
|
singleWorkspace: WorkspaceConfig;
|
|
19
17
|
};
|
|
20
18
|
/**
|
|
21
|
-
*
|
|
19
|
+
* Resolves release tags pointing at HEAD into publishable package descriptors.
|
|
22
20
|
*
|
|
23
|
-
* Pass `{ workspaces }` for monorepo mode:
|
|
21
|
+
* Pass `{ workspaces }` for monorepo mode: Each tag is matched against the workspace
|
|
24
22
|
* whose `tagPrefix` it starts with, and that workspace's `isPublishable` propagates onto
|
|
25
23
|
* the `ResolvedTag`. Because `tagPrefix` is derived from `deriveWorkspaceConfig()` (the
|
|
26
24
|
* same source that produced the tag), encoding and decoding stay colocated.
|
|
27
25
|
*
|
|
28
|
-
* Pass `{ singleWorkspace }` for single-package mode:
|
|
29
|
-
* each
|
|
26
|
+
* Pass `{ singleWorkspace }` for single-package mode: Tags like `v1.2.3` are matched and
|
|
27
|
+
* each has the workspace's `isPublishable` bit derived from `./package.json#private`.
|
|
30
28
|
*
|
|
31
29
|
* The no-arg form is a test-isolation convenience for unit tests that exercise tag
|
|
32
30
|
* matching without needing `isPublishable` propagation; resolved tags default to
|
|
@@ -30,7 +30,7 @@ function resolveSinglePackageTags(tags, singleWorkspace) {
|
|
|
30
30
|
return matched.map((tag) => ({ tag, dir: '.', workspacePath: '.', isPublishable }));
|
|
31
31
|
}
|
|
32
32
|
function resolveMonorepoTags(tags, workspaces) {
|
|
33
|
-
const sortedWorkspaces =
|
|
33
|
+
const sortedWorkspaces = workspaces.toSorted((a, b) => b.tagPrefix.length - a.tagPrefix.length);
|
|
34
34
|
const resolved = [];
|
|
35
35
|
for (const tag of tags) {
|
|
36
36
|
const match = findMatchingWorkspace(tag, sortedWorkspaces);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
|
-
/** The environment variable
|
|
2
|
+
/** The environment variable that sets the caller's standing style preference: `auto`, `plain`, or `rich`. */
|
|
3
3
|
export declare const OUTPUT_STYLE_ENV_VAR = "RELEASE_KIT_OUTPUT_STYLE";
|
|
4
4
|
export interface ResolveStylesOptions {
|
|
5
5
|
readonly env: Readonly<Record<string, string | undefined>>;
|
|
6
6
|
readonly stderrIsTty: boolean;
|
|
7
7
|
readonly stdoutIsTty: boolean;
|
|
8
8
|
}
|
|
9
|
-
/** Resolves the style of each output stream, exiting with a usage error when the variable
|
|
9
|
+
/** Resolves the style of each output stream, exiting with a usage error when the variable does not name a setting. */
|
|
10
10
|
export declare function resolveStylesOrExit(options: ResolveStylesOptions): StreamStyles;
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Runs the CLI `show-tag-prefixes` command and returns its exit code.
|
|
4
4
|
*
|
|
5
|
-
* Prints a per-workspace table of derived prefixes, tag counts, and declared legacy
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* understate the declared legacy prefixes. An absent default config previews against derived defaults.
|
|
12
|
-
*
|
|
13
|
-
* @returns The exit code the caller should use.
|
|
5
|
+
* Prints a per-workspace table of derived prefixes, tag counts, and declared legacy entries, followed by an
|
|
6
|
+
* "Undeclared tag prefixes" section when candidate-shaped tags exist outside the known set. Returns `1` on any
|
|
7
|
+
* derivation failure or collision and `0` otherwise; undeclared candidates do not affect the exit code. When a config
|
|
8
|
+
* exists and either fails to load or fails validation, the command reports it to stderr and returns `1` in both repo
|
|
9
|
+
* modes, because a default-config preview would understate the declared legacy prefixes. When the default config is
|
|
10
|
+
* absent, the command previews against derived defaults.
|
|
14
11
|
*/
|
|
15
12
|
export declare function showTagPrefixesCommand(styles: StreamStyles, configPath?: string): Promise<number>;
|
|
@@ -41,7 +41,7 @@ function renderMonorepo(preview, style) {
|
|
|
41
41
|
lines.push('', ...preview.collisions.map((collision) => formatStatusLine(style, 'failed', `tag prefix collision: '${collision.tagPrefix}' used by ${collision.workspacePaths.join(', ')}`)));
|
|
42
42
|
}
|
|
43
43
|
if (preview.undeclaredCandidates.length > 0) {
|
|
44
|
-
lines.push('', 'Undeclared tag prefixes:', '', ...preview.undeclaredCandidates.map((candidate) => ` '${candidate.prefix}'
|
|
44
|
+
lines.push('', 'Undeclared tag prefixes:', '', ...preview.undeclaredCandidates.map((candidate) => ` '${candidate.prefix}': ${candidate.tagCount} tags (e.g., ${candidate.exampleTags.join(', ')})`), '', 'Suggested config snippet (adjust `dir` to match the workspace if the guess is wrong, and replace the `name` placeholder with the legacy npm name):', '', renderSuggestedSnippet(preview.undeclaredCandidates), '', "If the suggested `dir` does not match the workspace, adjust it before pasting. Each legacy identity requires a `name`: Replace the `TODO-fill-in-legacy-npm-name` placeholder with the package's prior npm name.");
|
|
45
45
|
}
|
|
46
46
|
lines.push('');
|
|
47
47
|
return lines.join('\n');
|
|
@@ -50,20 +50,20 @@ function renderWorkspaceRow(row, style) {
|
|
|
50
50
|
const lines = [];
|
|
51
51
|
if (row.derivedPrefix === null) {
|
|
52
52
|
const failure = formatStatusLine(style, 'failed', `derivation failed: ${row.derivationError ?? 'unknown error'}`);
|
|
53
|
-
lines.push(` ${row.workspacePath}
|
|
53
|
+
lines.push(` ${row.workspacePath}: ${failure}`);
|
|
54
54
|
return lines;
|
|
55
55
|
}
|
|
56
56
|
const statusMarker = row.derivedTagCount > 0
|
|
57
57
|
? formatStatusLine(style, 'passed', `${row.derivedTagCount} tags`)
|
|
58
58
|
: formatStatusLine(style, 'warning', 'no existing tags');
|
|
59
|
-
lines.push(` ${row.workspacePath}
|
|
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
62
|
const recognized = `${entry.tagCount} legacy tags with '${entry.prefix}' prefix (recognized)`;
|
|
63
63
|
lines.push(` ${formatStatusLine(style, 'passed', recognized)}`);
|
|
64
64
|
}
|
|
65
65
|
else {
|
|
66
|
-
const tagless = `recorded legacy prefix '${entry.prefix}'
|
|
66
|
+
const tagless = `recorded legacy prefix '${entry.prefix}' does not have any tags`;
|
|
67
67
|
lines.push(` ${formatStatusLine(style, 'warning', tagless)}`);
|
|
68
68
|
}
|
|
69
69
|
}
|
package/dist/esm/stripScope.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Strips scope indicators from a raw commit message.
|
|
3
3
|
*
|
|
4
4
|
* Handles `scope|type: desc`, `type(scope): desc`, and messages with
|
|
5
5
|
* ticket prefixes (e.g., `#72 release-kit|fix: ...`). Returns the
|
|
6
|
-
* message unchanged when
|
|
6
|
+
* message unchanged when it does not detect a scope.
|
|
7
7
|
*/
|
|
8
8
|
export declare function stripScope(message: string): string;
|
package/dist/esm/stripScope.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const PIPE_SCOPE_PATTERN =
|
|
1
|
+
import { TICKET_PREFIX_PATTERNS } from '@williamthorsen/change-grammar';
|
|
2
|
+
const PIPE_SCOPE_PATTERN = /^[^|]+\|(.*)$/;
|
|
3
3
|
export function stripScope(message) {
|
|
4
4
|
let ticketPrefix = '';
|
|
5
5
|
let remainder = message;
|
|
6
|
-
for (const pattern of
|
|
6
|
+
for (const pattern of TICKET_PREFIX_PATTERNS) {
|
|
7
7
|
const match = remainder.match(pattern);
|
|
8
8
|
if (match) {
|
|
9
9
|
ticketPrefix += match[0];
|
|
@@ -4,34 +4,35 @@ import type { LabelDefinition } from './types.js';
|
|
|
4
4
|
/** Output path for the generated labels file. */
|
|
5
5
|
export declare const LABELS_OUTPUT_PATH = ".github/labels.yaml";
|
|
6
6
|
/**
|
|
7
|
-
* Formats resolved labels as YAML
|
|
7
|
+
* Formats resolved labels as YAML under a generated header that includes the preset hashes.
|
|
8
8
|
*
|
|
9
|
-
* `configFilePath`
|
|
10
|
-
* from; it defaults to `CONFIG_FILE_PATH`.
|
|
9
|
+
* The header records `configFilePath` so that a reader of the generated file can find the config from which it came.
|
|
11
10
|
*/
|
|
12
11
|
export declare function formatLabelsYaml(labels: LabelDefinition[], presetHashes: Map<string, string>, configFilePath?: string): string;
|
|
13
12
|
/**
|
|
14
13
|
* Loads the config file and returns its `repoLabels` block.
|
|
15
14
|
*
|
|
16
15
|
* Reports an actionable error and returns `undefined` when the config file is missing,
|
|
17
|
-
* fails to load or validate, or
|
|
18
|
-
* this run read, so that `--config` never points the reader at the default path.
|
|
16
|
+
* fails to load or validate, or lacks a `repoLabels` block. Every message names the file
|
|
17
|
+
* that this run read, so that `--config` never points the reader at the default path.
|
|
19
18
|
*/
|
|
20
19
|
export declare function loadRepoLabelsConfig(stderrStyle: OutputStyle, configPath?: string): Promise<RepoLabelsConfig | undefined>;
|
|
21
20
|
/** Options for the `sync-labels generate` subcommand. */
|
|
22
21
|
interface GenerateOptions {
|
|
23
|
-
/**
|
|
22
|
+
/** Compares the regenerated content against the committed file instead of writing. */
|
|
24
23
|
check?: boolean;
|
|
25
24
|
/** Config file to read, relative to the working directory. Defaults to `CONFIG_FILE_PATH`. */
|
|
26
25
|
configPath?: string;
|
|
27
26
|
styles: StreamStyles;
|
|
28
27
|
}
|
|
29
28
|
/**
|
|
30
|
-
* Runs the `sync-labels generate` command.
|
|
29
|
+
* Runs the `sync-labels generate` command and returns the process exit code.
|
|
31
30
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* Writes `.github/labels.yaml` from the config's `repoLabels` block, or, with `check`, reports whether the committed
|
|
32
|
+
* file is stale and writes nothing.
|
|
33
|
+
*
|
|
34
|
+
* The header names the config by its POSIX path relative to the working directory, the repo root, so that the file
|
|
35
|
+
* does not depend on the invocation directory or on the machine on which the command ran.
|
|
35
36
|
*/
|
|
36
37
|
export declare function generateCommand({ check, configPath, styles }: GenerateOptions): Promise<number>;
|
|
37
38
|
export {};
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
5
5
|
import { stringify } from 'yaml';
|
|
6
6
|
import { CONFIG_FILE_PATH } from "../loadConfig.js";
|
|
7
7
|
import { loadValidatedConfig, reportConfigProblem } from "../loadValidatedConfig.js";
|
|
8
|
-
import {
|
|
8
|
+
import { hashPreset } from "./presets.js";
|
|
9
9
|
import { resolveLabels } from "./resolveLabels.js";
|
|
10
10
|
import { checkRetiredSyncLabelsConfig } from "./retiredConfig.js";
|
|
11
11
|
export const LABELS_OUTPUT_PATH = '.github/labels.yaml';
|
|
12
12
|
export function formatLabelsYaml(labels, presetHashes, configFilePath = CONFIG_FILE_PATH) {
|
|
13
13
|
const headerLines = ['# Generated by release-kit sync-labels — do not edit.', `# Source: ${configFilePath}`];
|
|
14
|
-
const sortedPresetHashes = [...presetHashes].
|
|
14
|
+
const sortedPresetHashes = [...presetHashes].toSorted(([a], [b]) => a.localeCompare(b));
|
|
15
15
|
for (const [name, hash] of sortedPresetHashes) {
|
|
16
16
|
headerLines.push(`# ${name} preset hash: ${hash}`);
|
|
17
17
|
}
|
|
@@ -22,7 +22,7 @@ export function formatLabelsYaml(labels, presetHashes, configFilePath = CONFIG_F
|
|
|
22
22
|
export async function loadRepoLabelsConfig(stderrStyle, configPath) {
|
|
23
23
|
const result = await loadValidatedConfig(configPath);
|
|
24
24
|
if (result.status === 'missing') {
|
|
25
|
-
reportError(`
|
|
25
|
+
reportError(`Config file not found at ${result.configFilePath}. Run \`release-kit sync-labels init\` first.`);
|
|
26
26
|
return undefined;
|
|
27
27
|
}
|
|
28
28
|
if (result.status === 'invalid') {
|
|
@@ -30,7 +30,7 @@ export async function loadRepoLabelsConfig(stderrStyle, configPath) {
|
|
|
30
30
|
return undefined;
|
|
31
31
|
}
|
|
32
32
|
if (result.config.repoLabels === undefined) {
|
|
33
|
-
reportError(`
|
|
33
|
+
reportError(`The \`repoLabels\` block is missing from ${result.configFilePath}. Run \`release-kit sync-labels init\` to seed one.`);
|
|
34
34
|
return undefined;
|
|
35
35
|
}
|
|
36
36
|
return result.config.repoLabels;
|
|
@@ -55,14 +55,14 @@ export async function generateCommand({ check = false, configPath, styles }) {
|
|
|
55
55
|
const presetHashes = new Map();
|
|
56
56
|
const presetNames = repoLabels.extends ?? [];
|
|
57
57
|
for (const presetName of presetNames) {
|
|
58
|
-
presetHashes.set(presetName,
|
|
58
|
+
presetHashes.set(presetName, hashPreset(presetName));
|
|
59
59
|
}
|
|
60
|
-
const content = formatLabelsYaml(labels, presetHashes, configPath);
|
|
60
|
+
const content = formatLabelsYaml(labels, presetHashes, toRootRelativePosixPath(configPath ?? CONFIG_FILE_PATH));
|
|
61
61
|
if (check) {
|
|
62
62
|
return compareAgainstCommittedFile(content);
|
|
63
63
|
}
|
|
64
64
|
try {
|
|
65
|
-
mkdirSync(dirname(LABELS_OUTPUT_PATH), { recursive: true });
|
|
65
|
+
mkdirSync(path.dirname(LABELS_OUTPUT_PATH), { recursive: true });
|
|
66
66
|
writeFileSync(LABELS_OUTPUT_PATH, content, 'utf8');
|
|
67
67
|
}
|
|
68
68
|
catch (error) {
|
|
@@ -89,3 +89,6 @@ function compareAgainstCommittedFile(expected) {
|
|
|
89
89
|
console.info(`${LABELS_OUTPUT_PATH} is up to date.`);
|
|
90
90
|
return 0;
|
|
91
91
|
}
|
|
92
|
+
function toRootRelativePosixPath(filePath) {
|
|
93
|
+
return path.relative(process.cwd(), path.resolve(filePath)).split(path.sep).join('/');
|
|
94
|
+
}
|