@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
|
@@ -3,15 +3,15 @@ import { applyChangelogOverrides, formatStaleOverrideKeyWarning } from "./applyC
|
|
|
3
3
|
import { loadChangelogOverrides } from "./loadChangelogOverrides.js";
|
|
4
4
|
const OVERRIDES_FILENAME = '.meta/changelog-overrides.json';
|
|
5
5
|
export function applyWorkspaceOverrides(newEntries, workspacePath, overrideContext) {
|
|
6
|
-
const {
|
|
7
|
-
const workspaceOverrides = workspacePath === undefined ? undefined :
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
if (
|
|
11
|
-
throw new Error(`Changelog override application failed:\n - ${
|
|
6
|
+
const { projectOverrides, overridesByWorkspace, overrideWarnings, globalMatchedRootKeys } = overrideContext;
|
|
7
|
+
const workspaceOverrides = workspacePath === undefined ? undefined : overridesByWorkspace.get(workspacePath);
|
|
8
|
+
const composedOverrides = composeOverrides(projectOverrides, workspaceOverrides);
|
|
9
|
+
const appliedResult = applyChangelogOverrides(newEntries, composedOverrides);
|
|
10
|
+
if (appliedResult.errors.length > 0) {
|
|
11
|
+
throw new Error(`Changelog override application failed:\n - ${appliedResult.errors.join('\n - ')}`);
|
|
12
12
|
}
|
|
13
|
-
overrideWarnings.push(...
|
|
14
|
-
const matchedSet = new Set(
|
|
13
|
+
overrideWarnings.push(...appliedResult.warnings);
|
|
14
|
+
const matchedSet = new Set(appliedResult.matchedKeys);
|
|
15
15
|
if (workspaceOverrides !== undefined) {
|
|
16
16
|
for (const key of workspaceOverrides.keys()) {
|
|
17
17
|
if (!matchedSet.has(key)) {
|
|
@@ -19,45 +19,45 @@ export function applyWorkspaceOverrides(newEntries, workspacePath, overrideConte
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
for (const key of
|
|
22
|
+
for (const key of appliedResult.matchedKeys) {
|
|
23
23
|
if (workspaceOverrides?.has(key))
|
|
24
24
|
continue;
|
|
25
25
|
globalMatchedRootKeys.add(key);
|
|
26
26
|
}
|
|
27
|
-
return
|
|
27
|
+
return appliedResult;
|
|
28
28
|
}
|
|
29
29
|
export function composeOverrides(rootEntries, workspaceEntries) {
|
|
30
|
-
const
|
|
30
|
+
const composedOverrides = new Map(rootEntries);
|
|
31
31
|
if (workspaceEntries !== undefined) {
|
|
32
32
|
for (const [key, value] of workspaceEntries) {
|
|
33
|
-
|
|
33
|
+
composedOverrides.set(key, value);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
return
|
|
36
|
+
return composedOverrides;
|
|
37
37
|
}
|
|
38
38
|
export function createOverrideContext(workspacePaths) {
|
|
39
|
-
const result = loadOverridesForScopes({
|
|
39
|
+
const result = loadOverridesForScopes({ projectPath: '.', workspaces: [...workspacePaths] });
|
|
40
40
|
if (result.errors.length > 0) {
|
|
41
41
|
throw new Error(`Failed to load changelog overrides:\n - ${result.errors.join('\n - ')}`);
|
|
42
42
|
}
|
|
43
43
|
return {
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
projectOverrides: result.projectOverrides,
|
|
45
|
+
overridesByWorkspace: result.overridesByWorkspace,
|
|
46
46
|
overrideWarnings: [],
|
|
47
47
|
globalMatchedRootKeys: new Set(),
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
export function loadOverridesForScopes(scopes) {
|
|
51
51
|
const errors = [];
|
|
52
|
-
let
|
|
53
|
-
const
|
|
54
|
-
if (scopes.
|
|
55
|
-
const result = loadChangelogOverrides(resolveOverridePath(scopes.
|
|
52
|
+
let projectOverrides = new Map();
|
|
53
|
+
const overridesByWorkspace = new Map();
|
|
54
|
+
if (scopes.projectPath !== undefined) {
|
|
55
|
+
const result = loadChangelogOverrides(resolveOverridePath(scopes.projectPath));
|
|
56
56
|
if ('errors' in result) {
|
|
57
57
|
errors.push(...result.errors);
|
|
58
58
|
}
|
|
59
59
|
else {
|
|
60
|
-
|
|
60
|
+
projectOverrides = result.overrides;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
const workspacePaths = scopes.workspaces ?? [];
|
|
@@ -67,10 +67,10 @@ export function loadOverridesForScopes(scopes) {
|
|
|
67
67
|
errors.push(...result.errors);
|
|
68
68
|
}
|
|
69
69
|
else if (result.overrides.size > 0) {
|
|
70
|
-
|
|
70
|
+
overridesByWorkspace.set(workspacePath, result.overrides);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
return {
|
|
73
|
+
return { projectOverrides, overridesByWorkspace, errors };
|
|
74
74
|
}
|
|
75
75
|
export function resolveOverridePath(scopeRoot) {
|
|
76
76
|
return path.posix.join(scopeRoot, OVERRIDES_FILENAME);
|
|
@@ -10,18 +10,18 @@ export function classifyChangelogCommit(commit, workTypes, options) {
|
|
|
10
10
|
if (!hasTicketPrefix(subject)) {
|
|
11
11
|
return { kind: 'unparseable' };
|
|
12
12
|
}
|
|
13
|
-
const
|
|
14
|
-
if (
|
|
13
|
+
const parsedCommit = parseCommitMessage(commit.message, commit.hash, workTypes, undefined, options);
|
|
14
|
+
if (parsedCommit === undefined) {
|
|
15
15
|
return { kind: 'unparseable' };
|
|
16
16
|
}
|
|
17
|
-
const config = workTypes[
|
|
17
|
+
const config = workTypes[parsedCommit.type];
|
|
18
18
|
if (config === undefined) {
|
|
19
19
|
return { kind: 'unparseable' };
|
|
20
20
|
}
|
|
21
21
|
if (config.excludedFromChangelog === true) {
|
|
22
22
|
return { kind: 'excluded' };
|
|
23
23
|
}
|
|
24
|
-
return { kind: 'header', header: config.header, type:
|
|
24
|
+
return { kind: 'header', header: config.header, type: parsedCommit.type, breaking: parsedCommit.breaking };
|
|
25
25
|
}
|
|
26
26
|
export function isNonChangeSubject(subject) {
|
|
27
27
|
return isReleaseSubject(subject) || MERGE_SUBJECT_PATTERN.test(subject);
|
|
@@ -3,10 +3,10 @@ import { readFileSync } from 'node:fs';
|
|
|
3
3
|
import { GIT_OUTPUT_LIMIT_BYTES, hasErrnoCode, parseArgsOrExit } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { readReleaseTags, RELEASE_SUMMARY_FILE, resolveReleaseTagsPath } from "./releaseFiles.js";
|
|
5
5
|
const commitFlagSchema = {
|
|
6
|
-
|
|
6
|
+
isDryRun: { long: '--dry-run', type: 'boolean' },
|
|
7
7
|
};
|
|
8
8
|
export function commitCommand(argv) {
|
|
9
|
-
const
|
|
9
|
+
const isDryRun = parseArgsOrExit(argv, commitFlagSchema).flags.isDryRun;
|
|
10
10
|
const tags = readReleaseTags();
|
|
11
11
|
if (tags.length === 0) {
|
|
12
12
|
throw new Error(`Tags file at ${resolveReleaseTagsPath()} is empty. Run \`release-kit prepare\` first.`);
|
|
@@ -24,7 +24,7 @@ export function commitCommand(argv) {
|
|
|
24
24
|
}
|
|
25
25
|
const title = `release: ${tags.join(' ')}`;
|
|
26
26
|
const message = summary.length > 0 ? `${title}\n\n${summary}` : title;
|
|
27
|
-
if (
|
|
27
|
+
if (isDryRun) {
|
|
28
28
|
console.info('[dry-run] Would create commit with message:\n');
|
|
29
29
|
console.info(message);
|
|
30
30
|
try {
|
|
@@ -13,13 +13,13 @@ function parseCanonicalSemver(version) {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
export function compareVersionsDescending(a, b) {
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
19
|
-
return semver.rcompare(
|
|
20
|
-
if (
|
|
16
|
+
const aValidVersion = semver.valid(a);
|
|
17
|
+
const bValidVersion = semver.valid(b);
|
|
18
|
+
if (aValidVersion && bValidVersion)
|
|
19
|
+
return semver.rcompare(aValidVersion, bValidVersion);
|
|
20
|
+
if (aValidVersion)
|
|
21
21
|
return -1;
|
|
22
|
-
if (
|
|
22
|
+
if (bValidVersion)
|
|
23
23
|
return 1;
|
|
24
24
|
if (a > b)
|
|
25
25
|
return -1;
|
|
@@ -28,13 +28,13 @@ export function compareVersionsDescending(a, b) {
|
|
|
28
28
|
return 0;
|
|
29
29
|
}
|
|
30
30
|
export function isForwardVersion(current, target) {
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
return
|
|
31
|
+
const currentParsedVersion = parseCanonicalSemver(current);
|
|
32
|
+
const targetParsedVersion = parseCanonicalSemver(target);
|
|
33
|
+
if (targetParsedVersion.major !== currentParsedVersion.major) {
|
|
34
|
+
return targetParsedVersion.major > currentParsedVersion.major;
|
|
35
35
|
}
|
|
36
|
-
if (
|
|
37
|
-
return
|
|
36
|
+
if (targetParsedVersion.minor !== currentParsedVersion.minor) {
|
|
37
|
+
return targetParsedVersion.minor > currentParsedVersion.minor;
|
|
38
38
|
}
|
|
39
|
-
return
|
|
39
|
+
return targetParsedVersion.patch > currentParsedVersion.patch;
|
|
40
40
|
}
|
|
@@ -93,7 +93,7 @@ export declare const changelogJsonInputSchema: z.ZodObject<{
|
|
|
93
93
|
}, z.core.$strict>;
|
|
94
94
|
/** Schema for the optional `changelogMarkdown` block (input shape, all fields optional). */
|
|
95
95
|
export declare const changelogMarkdownInputSchema: z.ZodObject<{
|
|
96
|
-
|
|
96
|
+
keptVersionCount: z.ZodOptional<z.ZodNumber>;
|
|
97
97
|
}, z.core.$strict>;
|
|
98
98
|
/** Schema for the optional `releaseNotes` block (input shape, all fields optional). */
|
|
99
99
|
export declare const releaseNotesInputSchema: z.ZodObject<{
|
|
@@ -164,8 +164,9 @@ export declare const releaseKitConfigSchema: z.ZodObject<{
|
|
|
164
164
|
devOnlySections: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
165
165
|
}, z.core.$strict>>;
|
|
166
166
|
changelogMarkdown: z.ZodOptional<z.ZodObject<{
|
|
167
|
-
|
|
167
|
+
keptVersionCount: z.ZodOptional<z.ZodNumber>;
|
|
168
168
|
}, z.core.$strict>>;
|
|
169
|
+
devExportConditions: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
169
170
|
formatCommand: z.ZodOptional<z.ZodString>;
|
|
170
171
|
project: z.ZodOptional<z.ZodObject<{
|
|
171
172
|
paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
package/dist/esm/configSchema.js
CHANGED
|
@@ -48,7 +48,7 @@ export const changelogJsonInputSchema = z
|
|
|
48
48
|
.strict();
|
|
49
49
|
export const changelogMarkdownInputSchema = z
|
|
50
50
|
.object({
|
|
51
|
-
|
|
51
|
+
keptVersionCount: z.number().int().min(1).optional(),
|
|
52
52
|
})
|
|
53
53
|
.strict();
|
|
54
54
|
export const releaseNotesInputSchema = z
|
|
@@ -89,6 +89,7 @@ export const releaseKitConfigSchema = z
|
|
|
89
89
|
breakingPolicies: z.record(z.string(), breakingPolicyValueSchema).optional(),
|
|
90
90
|
changelogJson: changelogJsonInputSchema.optional(),
|
|
91
91
|
changelogMarkdown: changelogMarkdownInputSchema.optional(),
|
|
92
|
+
devExportConditions: z.array(z.string().min(1)).optional(),
|
|
92
93
|
formatCommand: z.string().min(1).optional(),
|
|
93
94
|
project: projectConfigSchema.optional(),
|
|
94
95
|
releaseNotes: releaseNotesInputSchema.optional(),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export interface CreateGithubReleaseOptions {
|
|
3
3
|
tag: string;
|
|
4
4
|
changelogJsonPath: string;
|
|
5
|
-
|
|
5
|
+
isDryRun: boolean;
|
|
6
6
|
/** Section titles in priority order. When omitted, entry order is preserved. */
|
|
7
7
|
sectionOrder?: string[];
|
|
8
8
|
}
|
|
@@ -29,9 +29,9 @@ export declare function createGithubRelease(options: CreateGithubReleaseOptions)
|
|
|
29
29
|
/** Outcome of a `createGithubReleases` invocation. */
|
|
30
30
|
export interface CreateGithubReleasesOutcome {
|
|
31
31
|
/** Tags for which a Release was created (or would be, under `--dry-run`). */
|
|
32
|
-
|
|
32
|
+
createdTags: string[];
|
|
33
33
|
/** Tags that were skipped, paired with the discriminated reason for each skip. */
|
|
34
|
-
|
|
34
|
+
skippedTags: Array<{
|
|
35
35
|
tag: string;
|
|
36
36
|
reason: CreateReleaseSkipReason;
|
|
37
37
|
}>;
|
|
@@ -40,4 +40,4 @@ export interface CreateGithubReleasesOutcome {
|
|
|
40
40
|
export declare function createGithubReleases(tags: Array<{
|
|
41
41
|
tag: string;
|
|
42
42
|
workspacePath: string;
|
|
43
|
-
}>, changelogJsonOutputPath: string,
|
|
43
|
+
}>, changelogJsonOutputPath: string, isDryRun: boolean, sectionOrder?: string[]): CreateGithubReleasesOutcome;
|
|
@@ -4,7 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { extractVersion, readChangelogEntries } from "./changelogJsonUtils.js";
|
|
5
5
|
import { matchesAudience, renderReleaseNotesSingle } from "./renderReleaseNotes.js";
|
|
6
6
|
export function createGithubRelease(options) {
|
|
7
|
-
const { tag, changelogJsonPath,
|
|
7
|
+
const { tag, changelogJsonPath, isDryRun, sectionOrder } = options;
|
|
8
8
|
if (!existsSync(changelogJsonPath)) {
|
|
9
9
|
console.warn(`Warning: ${changelogJsonPath} not found; skipping GitHub Release creation`);
|
|
10
10
|
return { status: 'skipped', reason: 'no-entry' };
|
|
@@ -25,37 +25,37 @@ export function createGithubRelease(options) {
|
|
|
25
25
|
}
|
|
26
26
|
const body = renderReleaseNotesSingle(entry, {
|
|
27
27
|
filter: matchesAudience('all'),
|
|
28
|
-
|
|
28
|
+
shouldIncludeHeading: false,
|
|
29
29
|
...(sectionOrder !== undefined && { sectionOrder }),
|
|
30
30
|
});
|
|
31
31
|
if (body.trim() === '') {
|
|
32
32
|
return { status: 'skipped', reason: 'empty-body' };
|
|
33
33
|
}
|
|
34
34
|
const args = ['release', 'create', tag, '--title', tag, '--notes', body];
|
|
35
|
-
if (
|
|
35
|
+
if (isDryRun) {
|
|
36
36
|
console.info(`[dry-run] Would run: gh ${args.join(' ')}`);
|
|
37
37
|
return { status: 'created' };
|
|
38
38
|
}
|
|
39
39
|
execFileSync('gh', args, { stdio: 'inherit' });
|
|
40
40
|
return { status: 'created' };
|
|
41
41
|
}
|
|
42
|
-
export function createGithubReleases(tags, changelogJsonOutputPath,
|
|
43
|
-
const
|
|
44
|
-
const
|
|
42
|
+
export function createGithubReleases(tags, changelogJsonOutputPath, isDryRun, sectionOrder) {
|
|
43
|
+
const createdTags = [];
|
|
44
|
+
const skippedTags = [];
|
|
45
45
|
for (const { tag, workspacePath } of tags) {
|
|
46
46
|
const changelogJsonPath = join(workspacePath, changelogJsonOutputPath);
|
|
47
47
|
const result = createGithubRelease({
|
|
48
48
|
tag,
|
|
49
49
|
changelogJsonPath,
|
|
50
|
-
|
|
50
|
+
isDryRun,
|
|
51
51
|
...(sectionOrder !== undefined && { sectionOrder }),
|
|
52
52
|
});
|
|
53
53
|
if (result.status === 'created') {
|
|
54
|
-
|
|
54
|
+
createdTags.push(tag);
|
|
55
55
|
}
|
|
56
56
|
else {
|
|
57
|
-
|
|
57
|
+
skippedTags.push({ tag, reason: result.reason });
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
return {
|
|
60
|
+
return { createdTags, skippedTags };
|
|
61
61
|
}
|
|
@@ -10,15 +10,15 @@ import { resolveCommandTags } from "./resolveCommandTags.js";
|
|
|
10
10
|
import { resolveConfigFlag } from "./resolveConfigFlag.js";
|
|
11
11
|
const createGithubReleaseFlagSchema = {
|
|
12
12
|
...configFlagSchema,
|
|
13
|
-
|
|
13
|
+
isDryRun: { long: '--dry-run', type: 'boolean' },
|
|
14
14
|
tags: { long: '--tags', type: 'string' },
|
|
15
15
|
};
|
|
16
16
|
export async function createGithubReleaseCommand(argv, styles, invocationDir) {
|
|
17
|
-
const
|
|
18
|
-
const configPath = resolveConfigFlag(
|
|
19
|
-
const {
|
|
17
|
+
const parsedArgs = parseArgsOrExit(argv, createGithubReleaseFlagSchema);
|
|
18
|
+
const configPath = resolveConfigFlag(parsedArgs.flags.config, invocationDir);
|
|
19
|
+
const { isDryRun } = parsedArgs.flags;
|
|
20
20
|
const userConfig = await loadUsableConfig(styles.stderr, configPath);
|
|
21
|
-
const requestedTags = parseRequestedTags(
|
|
21
|
+
const requestedTags = parseRequestedTags(parsedArgs.flags.tags);
|
|
22
22
|
const resolvedTags = resolveCommandTags(requestedTags, userConfig);
|
|
23
23
|
const releasableTags = resolvedTags.filter(isReleasable);
|
|
24
24
|
for (const resolvedTag of resolvedTags) {
|
|
@@ -32,17 +32,17 @@ export async function createGithubReleaseCommand(argv, styles, invocationDir) {
|
|
|
32
32
|
const { changelogJsonOutputPath, sectionOrder } = deriveReleaseNotesConfig(userConfig);
|
|
33
33
|
let outcome;
|
|
34
34
|
try {
|
|
35
|
-
outcome = createGithubReleases(releasableTags, changelogJsonOutputPath,
|
|
35
|
+
outcome = createGithubReleases(releasableTags, changelogJsonOutputPath, isDryRun, sectionOrder);
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
38
|
reportError(`Failed to create GitHub Releases: ${describeError(error)}`);
|
|
39
39
|
process.exit(1);
|
|
40
40
|
}
|
|
41
|
-
if (outcome.
|
|
41
|
+
if (outcome.skippedTags.length === 0) {
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
const
|
|
45
|
-
console.info(`Skipped ${outcome.
|
|
44
|
+
const formattedSkips = outcome.skippedTags.map((s) => `${s.tag} (${s.reason})`).join(', ');
|
|
45
|
+
console.info(`Skipped ${outcome.skippedTags.length} tag(s) without releasable content: ${formattedSkips}.`);
|
|
46
46
|
}
|
|
47
47
|
function isReleasable(resolvedTag) {
|
|
48
48
|
return resolvedTag.kind !== 'package' || resolvedTag.isPublishable;
|
package/dist/esm/createTags.d.ts
CHANGED
package/dist/esm/createTags.js
CHANGED
|
@@ -4,32 +4,32 @@ import { deleteFileIfExists } from "./deleteFileIfExists.js";
|
|
|
4
4
|
import { RELEASE_GLYPHS } from "./glyphs.js";
|
|
5
5
|
import { readReleaseTags, RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
|
|
6
6
|
export function createTags(options) {
|
|
7
|
-
const {
|
|
7
|
+
const { isDryRun, shouldSkipGitChecks, style } = options;
|
|
8
8
|
const tags = readReleaseTags();
|
|
9
9
|
if (tags.length === 0) {
|
|
10
10
|
return [];
|
|
11
11
|
}
|
|
12
|
-
if (!
|
|
12
|
+
if (!isDryRun && !shouldSkipGitChecks) {
|
|
13
13
|
assertCleanWorkingTree();
|
|
14
14
|
}
|
|
15
|
-
if (
|
|
15
|
+
if (isDryRun) {
|
|
16
16
|
console.info('[dry-run] Would create tags:');
|
|
17
17
|
for (const tag of tags) {
|
|
18
18
|
console.info(formatGlyphLine(RELEASE_GLYPHS, style, 'tag', tag));
|
|
19
19
|
}
|
|
20
20
|
return tags;
|
|
21
21
|
}
|
|
22
|
-
const
|
|
22
|
+
const createdTags = [];
|
|
23
23
|
for (const tag of tags) {
|
|
24
24
|
try {
|
|
25
25
|
execFileSync('git', ['tag', '-a', tag, '-m', tag], { maxBuffer: GIT_OUTPUT_LIMIT_BYTES });
|
|
26
|
-
|
|
26
|
+
createdTags.push(tag);
|
|
27
27
|
}
|
|
28
28
|
catch (error) {
|
|
29
|
-
if (
|
|
29
|
+
if (createdTags.length > 0) {
|
|
30
30
|
console.warn('Tags created before failure:');
|
|
31
|
-
for (const
|
|
32
|
-
console.warn(` ${
|
|
31
|
+
for (const tag of createdTags) {
|
|
32
|
+
console.warn(` ${tag}`);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
throw error;
|
|
@@ -13,13 +13,13 @@ export interface DirectReleases {
|
|
|
13
13
|
untaggedBaselines: Map<string, UntaggedBaseline>;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Reads each planned package and directory target's history and decides its direct release. `
|
|
16
|
+
* Reads each planned package and directory target's history and decides its direct release. `shouldForce` applies only to
|
|
17
17
|
* the targets from which the release starts.
|
|
18
18
|
*/
|
|
19
|
-
export declare function decideDirectReleases(
|
|
20
|
-
|
|
19
|
+
export declare function decideDirectReleases(plannedTargets: readonly MemberTarget[], settings: TargetPrepareSettings, options: {
|
|
20
|
+
shouldForce?: boolean;
|
|
21
21
|
bumpOverride?: ReleaseType;
|
|
22
|
-
|
|
22
|
+
versionOverridesById?: ReadonlyMap<string, string>;
|
|
23
23
|
}, startIds: ReadonlySet<string>): DirectReleases;
|
|
24
24
|
/**
|
|
25
25
|
* Records on each planned workspace's and directory's history the entry scopes that route nowhere in its unreleased
|
|
@@ -27,4 +27,4 @@ export declare function decideDirectReleases(planned: readonly MemberTarget[], s
|
|
|
27
27
|
* targets first, in planned order, which sets the order of each target's findings. The repo-root directory target
|
|
28
28
|
* renders every entry in its window, so it records none, but its window still routes the scope that names it.
|
|
29
29
|
*/
|
|
30
|
-
export declare function reportUnroutedEntryScopes(
|
|
30
|
+
export declare function reportUnroutedEntryScopes(plannedTargets: readonly MemberTarget[], directReleases: DirectReleases, settings: TargetPrepareSettings, memberTargets: readonly MemberTarget[]): void;
|
|
@@ -3,64 +3,64 @@ import { findUnroutedEntryScopes } from "./findUnroutedEntryScopes.js";
|
|
|
3
3
|
import { getAllTagPrefixes } from "./generateChangelogs.js";
|
|
4
4
|
import { describeStage, isRepoRootTarget, readHistory, runStage } from "./prepareRunContext.js";
|
|
5
5
|
import { decideTargetRelease, readTargetHistory, } from "./prepareTarget.js";
|
|
6
|
-
export function decideDirectReleases(
|
|
7
|
-
const {
|
|
8
|
-
const
|
|
6
|
+
export function decideDirectReleases(plannedTargets, settings, options, startIds) {
|
|
7
|
+
const { shouldForce, bumpOverride, versionOverridesById } = options;
|
|
8
|
+
const directReleases = {
|
|
9
9
|
readings: new Map(),
|
|
10
10
|
decisions: new Map(),
|
|
11
11
|
bumps: new Map(),
|
|
12
12
|
untaggedBaselines: new Map(),
|
|
13
13
|
};
|
|
14
14
|
const hintState = { emitted: false };
|
|
15
|
-
const knownPrefixes =
|
|
16
|
-
for (const target of
|
|
17
|
-
const { history,
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
15
|
+
const knownPrefixes = plannedTargets.flatMap(getAllTagPrefixes);
|
|
16
|
+
for (const target of plannedTargets) {
|
|
17
|
+
const { history, untaggedBaseline } = runStage(describeStage(target), () => readTargetHistory(target, settings));
|
|
18
|
+
directReleases.readings.set(target.id, history);
|
|
19
|
+
if (untaggedBaseline !== undefined) {
|
|
20
|
+
directReleases.untaggedBaselines.set(target.id, untaggedBaseline);
|
|
21
21
|
}
|
|
22
22
|
if (history.previousTag === undefined && target.kind === 'package' && target.isWorkspace) {
|
|
23
|
-
|
|
23
|
+
emitBaselineHintOnce(target, knownPrefixes, hintState);
|
|
24
24
|
}
|
|
25
|
-
const
|
|
25
|
+
const versionOverride = versionOverridesById?.get(target.id);
|
|
26
26
|
const decision = decideTargetRelease(target, history, {
|
|
27
|
-
|
|
27
|
+
shouldForce: shouldForce === true && startIds.has(target.id),
|
|
28
28
|
bumpOverride,
|
|
29
|
-
setVersion,
|
|
29
|
+
setVersion: versionOverride,
|
|
30
30
|
});
|
|
31
|
-
|
|
31
|
+
directReleases.decisions.set(target.id, decision);
|
|
32
32
|
if (decision.outcome === 'skip') {
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
directReleases.bumps.set(target.id, decision.setVersion === undefined
|
|
36
36
|
? { releaseType: decision.releaseType }
|
|
37
37
|
: { releaseType: 'patch', newVersionOverride: decision.setVersion });
|
|
38
38
|
}
|
|
39
|
-
return
|
|
39
|
+
return directReleases;
|
|
40
40
|
}
|
|
41
|
-
export function reportUnroutedEntryScopes(
|
|
42
|
-
const
|
|
43
|
-
const windows = [...
|
|
41
|
+
export function reportUnroutedEntryScopes(plannedTargets, directReleases, settings, memberTargets) {
|
|
42
|
+
const releasingTargets = plannedTargets.filter((target) => directReleases.decisions.get(target.id)?.outcome === 'release');
|
|
43
|
+
const windows = [...releasingTargets, ...plannedTargets.filter((target) => !releasingTargets.includes(target))].map((target) => ({
|
|
44
44
|
target,
|
|
45
|
-
history: readHistory(
|
|
45
|
+
history: readHistory(directReleases.readings, target),
|
|
46
46
|
}));
|
|
47
|
-
const findings = findUnroutedEntryScopes(windows.map(({ target, history }) => ({ dir: target.id, commits: history.
|
|
47
|
+
const findings = findUnroutedEntryScopes(windows.map(({ target, history }) => ({ dir: target.id, commits: history.unreleasedReading.commits })), memberTargets.map((target) => target.id), settings.scopeAliases ?? {});
|
|
48
48
|
for (const { target, history } of windows) {
|
|
49
|
-
history.
|
|
49
|
+
history.unreleasedReading.diagnostics.unroutedEntryScopes = isRepoRootTarget(target)
|
|
50
50
|
? []
|
|
51
51
|
: (findings.get(target.id) ?? []);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
function
|
|
54
|
+
function emitBaselineHintOnce(target, knownPrefixes, state) {
|
|
55
55
|
if (state.emitted || (target.legacyIdentities?.length ?? 0) > 0)
|
|
56
56
|
return;
|
|
57
57
|
const candidates = detectUndeclaredTagPrefixes(knownPrefixes);
|
|
58
58
|
if (candidates.length === 0)
|
|
59
59
|
return;
|
|
60
60
|
const totalTags = candidates.reduce((sum, candidate) => sum + candidate.tagCount, 0);
|
|
61
|
-
const
|
|
61
|
+
const exampleTag = candidates[0]?.exampleTags[0] ?? `${candidates[0]?.prefix ?? ''}?`;
|
|
62
62
|
process.stderr.write(`Hint: no baseline tag found for ${target.id} under '${target.tagPrefix}', but ` +
|
|
63
|
-
`${totalTags} candidate-shaped tags exist (e.g., ${
|
|
63
|
+
`${totalTags} candidate-shaped tags exist (e.g., ${exampleTag}). ` +
|
|
64
64
|
"Run 'release-kit show-tag-prefixes' to check for undeclared legacy prefixes.\n");
|
|
65
65
|
state.emitted = true;
|
|
66
66
|
}
|
|
@@ -6,7 +6,7 @@ export interface DecideReleaseArgs {
|
|
|
6
6
|
/** The number of commits in the window, which selects the skip reason. */
|
|
7
7
|
commitCount: number;
|
|
8
8
|
/** True when `--force` was passed; treats absence of a natural bump as patch. Defaults to false. */
|
|
9
|
-
|
|
9
|
+
shouldForce?: boolean | undefined;
|
|
10
10
|
/** Explicit `--bump=X` override. When set, the level is X regardless of the natural bump. */
|
|
11
11
|
bumpOverride: ReleaseType | undefined;
|
|
12
12
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export function decideRelease(args) {
|
|
2
|
-
const { naturalBump, commitCount,
|
|
3
|
-
if (naturalBump === undefined && !
|
|
2
|
+
const { naturalBump, commitCount, shouldForce = false, bumpOverride, skipReasons } = args;
|
|
3
|
+
if (naturalBump === undefined && !shouldForce) {
|
|
4
4
|
const skipReason = commitCount === 0 ? skipReasons.noCommits : skipReasons.noBumpWorthy;
|
|
5
5
|
return { outcome: 'skip', skipReason };
|
|
6
6
|
}
|
|
@@ -10,4 +10,4 @@ export declare const DIRECTORY_VERSION_FILE = "version.json";
|
|
|
10
10
|
* repo root. Throws when the path isn't a directory, when the version file is missing or lacks a `version`, and when
|
|
11
11
|
* the basename would not make a valid tag prefix.
|
|
12
12
|
*/
|
|
13
|
-
export declare function deriveDirectoryConfig(
|
|
13
|
+
export declare function deriveDirectoryConfig(declaredDirectory: DeclaredDirectory, root?: string): DirectoryConfig;
|
|
@@ -4,24 +4,24 @@ import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
|
4
4
|
import { isRecord } from "./typeGuards.js";
|
|
5
5
|
export const DIRECTORY_VERSION_FILE = 'version.json';
|
|
6
6
|
const TAG_PREFIX_PATTERN = /^[\w.-]+$/;
|
|
7
|
-
export function deriveDirectoryConfig(
|
|
8
|
-
const label = `releases.${
|
|
9
|
-
if (statSync(path.resolve(root,
|
|
10
|
-
throw new Error(`${label}: path '${
|
|
7
|
+
export function deriveDirectoryConfig(declaredDirectory, root = process.cwd()) {
|
|
8
|
+
const label = `releases.${declaredDirectory.name}`;
|
|
9
|
+
if (statSync(path.resolve(root, declaredDirectory.path), { throwIfNoEntry: false })?.isDirectory() !== true) {
|
|
10
|
+
throw new Error(`${label}: path '${declaredDirectory.path}' is not a directory; declare a directory that exists in the repo`);
|
|
11
11
|
}
|
|
12
|
-
const versionFile = resolveVersionFile(
|
|
12
|
+
const versionFile = resolveVersionFile(declaredDirectory.path, root);
|
|
13
13
|
assertVersionField(label, versionFile, root);
|
|
14
14
|
const config = {
|
|
15
|
-
name:
|
|
16
|
-
path:
|
|
15
|
+
name: declaredDirectory.name,
|
|
16
|
+
path: declaredDirectory.path,
|
|
17
17
|
versionFile,
|
|
18
|
-
tagPrefix:
|
|
18
|
+
tagPrefix: declaredDirectory.tagPrefix ?? deriveTagPrefix(label, declaredDirectory.path),
|
|
19
19
|
};
|
|
20
|
-
if (
|
|
21
|
-
config.paths = [...
|
|
20
|
+
if (declaredDirectory.paths !== undefined) {
|
|
21
|
+
config.paths = [...declaredDirectory.paths];
|
|
22
22
|
}
|
|
23
|
-
if (
|
|
24
|
-
config.legacyIdentities =
|
|
23
|
+
if (declaredDirectory.legacyIdentities !== undefined) {
|
|
24
|
+
config.legacyIdentities = declaredDirectory.legacyIdentities;
|
|
25
25
|
}
|
|
26
26
|
return config;
|
|
27
27
|
}
|
|
@@ -30,14 +30,14 @@ function assertVersionField(label, versionFile, root) {
|
|
|
30
30
|
if (!existsSync(absolutePath)) {
|
|
31
31
|
throw new Error(`${label}: ${versionFile} not found; create it with the directory's current version, such as { "version": "0.0.0" }`);
|
|
32
32
|
}
|
|
33
|
-
let
|
|
33
|
+
let parsedJson;
|
|
34
34
|
try {
|
|
35
|
-
|
|
35
|
+
parsedJson = JSON.parse(readFileSync(absolutePath, 'utf8'));
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
38
|
throw chainError(`${label}: failed to read ${versionFile}`, error);
|
|
39
39
|
}
|
|
40
|
-
if (!isRecord(
|
|
40
|
+
if (!isRecord(parsedJson) || typeof parsedJson['version'] !== 'string') {
|
|
41
41
|
throw new Error(`${label}: ${versionFile} does not contain a string 'version' field; add one`);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -52,8 +52,8 @@ function deriveTagPrefix(label, directoryPath) {
|
|
|
52
52
|
return `${basename}-v`;
|
|
53
53
|
}
|
|
54
54
|
function resolveVersionFile(directoryPath, root) {
|
|
55
|
-
const
|
|
56
|
-
return existsSync(path.resolve(root,
|
|
57
|
-
?
|
|
55
|
+
const packageJsonPath = path.posix.join(directoryPath, 'package.json');
|
|
56
|
+
return existsSync(path.resolve(root, packageJsonPath))
|
|
57
|
+
? packageJsonPath
|
|
58
58
|
: path.posix.join(directoryPath, DIRECTORY_VERSION_FILE);
|
|
59
59
|
}
|
|
@@ -6,19 +6,19 @@ import { isRecord } from "./typeGuards.js";
|
|
|
6
6
|
export function deriveWorkspaceConfig(workspacePath) {
|
|
7
7
|
const dir = basename(workspacePath);
|
|
8
8
|
const packageJsonPath = `${workspacePath}/package.json`;
|
|
9
|
-
let
|
|
9
|
+
let parsedJson;
|
|
10
10
|
try {
|
|
11
|
-
|
|
11
|
+
parsedJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
12
12
|
}
|
|
13
13
|
catch (error) {
|
|
14
14
|
throw chainError(`Failed to read ${packageJsonPath}`, error);
|
|
15
15
|
}
|
|
16
|
-
const name = isRecord(
|
|
16
|
+
const name = isRecord(parsedJson) ? parsedJson['name'] : undefined;
|
|
17
17
|
if (typeof name !== 'string' || name.length === 0) {
|
|
18
18
|
throw new Error(`${packageJsonPath} is missing a 'name' field (required for tag derivation).`);
|
|
19
19
|
}
|
|
20
20
|
const unscopedName = stripNpmScope(name);
|
|
21
|
-
const isPublishable = isPublishableManifest(
|
|
21
|
+
const isPublishable = isPublishableManifest(parsedJson);
|
|
22
22
|
return {
|
|
23
23
|
dir,
|
|
24
24
|
name,
|