@williamthorsen/release-kit 10.2.1 → 10.3.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/.readyup/kits/default.js +78 -55
- package/.readyup/kits/npm-auto-publish.js +169 -57
- package/.readyup/manifest.json +6 -6
- package/CHANGELOG.md +54 -0
- package/README.md +18 -6
- package/bin/release-kit.js +1 -1
- package/dist/esm/bin/release-kit.js +4 -4
- package/dist/esm/buildChangelogEntries.js +17 -15
- package/dist/esm/buildDependencyGraph.js +6 -2
- package/dist/esm/buildReleaseSummary.d.ts +1 -1
- package/dist/esm/changelogJsonFile.d.ts +1 -3
- package/dist/esm/changelogJsonFile.js +4 -18
- package/dist/esm/changelogJsonUtils.js +7 -7
- package/dist/esm/changelogOverrides.js +13 -12
- package/dist/esm/checkWorkTypesDrift.js +1 -1
- package/dist/esm/commitCommand.js +3 -13
- package/dist/esm/createTags.js +3 -14
- package/dist/esm/deriveWorkspaceConfig.js +3 -3
- package/dist/esm/detectPackageManager.js +2 -2
- package/dist/esm/detectUndeclaredTagPrefixes.js +1 -1
- package/dist/esm/discoverWorkspaces.js +1 -1
- package/dist/esm/getCommitsSinceTarget.js +2 -2
- package/dist/esm/help/prepareHelp.d.ts +1 -1
- package/dist/esm/help/prepareHelp.js +2 -2
- package/dist/esm/init/checks.js +1 -1
- package/dist/esm/init/detectRepoType.js +1 -1
- package/dist/esm/injectReleaseNotesIntoReadme.d.ts +2 -0
- package/dist/esm/injectReleaseNotesIntoReadme.js +13 -10
- package/dist/esm/loadConfig.js +8 -6
- package/dist/esm/planReleaseNotesPreviews.d.ts +13 -0
- package/dist/esm/planReleaseNotesPreviews.js +43 -0
- package/dist/esm/planVersionBump.d.ts +9 -0
- package/dist/esm/planVersionBump.js +54 -0
- package/dist/esm/prepareCommand.d.ts +0 -4
- package/dist/esm/prepareCommand.js +51 -45
- package/dist/esm/propagateBumps.js +1 -1
- package/dist/esm/releaseFiles.d.ts +4 -0
- package/dist/esm/releaseFiles.js +34 -0
- package/dist/esm/releasePlan.d.ts +10 -0
- package/dist/esm/releasePlan.js +53 -0
- package/dist/esm/releasePrepare.d.ts +3 -3
- package/dist/esm/releasePrepare.js +53 -54
- package/dist/esm/releasePrepareMono.d.ts +3 -2
- package/dist/esm/releasePrepareMono.js +50 -53
- package/dist/esm/releasePrepareProject.d.ts +3 -0
- package/dist/esm/releasePrepareProject.js +33 -37
- package/dist/esm/renderChangelogMarkdown.d.ts +0 -7
- package/dist/esm/renderChangelogMarkdown.js +0 -13
- package/dist/esm/reportPrepare.d.ts +5 -1
- package/dist/esm/reportPrepare.js +37 -17
- package/dist/esm/sync-labels/generateCommand.js +4 -2
- package/dist/esm/sync-labels/presets.js +5 -5
- package/dist/esm/sync-labels/resolveLabels.js +5 -3
- package/dist/esm/syncWorkTypes.js +1 -1
- package/dist/esm/types.d.ts +4 -9
- package/dist/esm/validateConfig.js +11 -11
- package/dist/esm/workTypesData.d.ts +1 -1
- package/dist/esm/workTypesUtils.js +1 -1
- package/package.json +3 -3
- package/dist/esm/bumpAllVersions.d.ts +0 -3
- package/dist/esm/bumpAllVersions.js +0 -61
- package/dist/esm/writeReleaseNotesPreviews.d.ts +0 -18
- package/dist/esm/writeReleaseNotesPreviews.js +0 -63
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PlannedWrite } from './releasePlan.js';
|
|
2
|
+
import type { ChangelogEntry } from './types.js';
|
|
3
|
+
export interface PlanReleaseNotesPreviewsOptions {
|
|
4
|
+
workspacePath: string;
|
|
5
|
+
tag: string;
|
|
6
|
+
entries: readonly ChangelogEntry[];
|
|
7
|
+
sectionOrder: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface ReleaseNotesPreviewsPlan {
|
|
10
|
+
writes: PlannedWrite[];
|
|
11
|
+
warnings: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function planReleaseNotesPreviews(options: PlanReleaseNotesPreviewsOptions): ReleaseNotesPreviewsPlan;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { extractVersion } from "./changelogJsonUtils.js";
|
|
4
|
+
import { renderInjectedReadmeFromEntries } from "./injectReleaseNotesIntoReadme.js";
|
|
5
|
+
export function planReleaseNotesPreviews(options) {
|
|
6
|
+
const { workspacePath, tag, entries, sectionOrder } = options;
|
|
7
|
+
const warnings = [];
|
|
8
|
+
const readmePath = path.join(workspacePath, 'README.md');
|
|
9
|
+
const readme = readWorkspaceReadme(readmePath, warnings);
|
|
10
|
+
const rendered = renderInjectedReadmeFromEntries(readme.content, entries, tag, sectionOrder);
|
|
11
|
+
if (rendered === undefined) {
|
|
12
|
+
return { writes: [], warnings };
|
|
13
|
+
}
|
|
14
|
+
if (!readme.exists && !readme.unreadable) {
|
|
15
|
+
warnings.push(`${readmePath} not found; skipping injected-README preview but still writing standalone release notes`);
|
|
16
|
+
}
|
|
17
|
+
const version = extractVersion(tag);
|
|
18
|
+
const docsDir = path.join(workspacePath, 'docs');
|
|
19
|
+
const writes = [];
|
|
20
|
+
if (readme.exists) {
|
|
21
|
+
writes.push({ path: path.join(docsDir, `README.v${version}.md`), content: rendered.injectedReadme });
|
|
22
|
+
}
|
|
23
|
+
writes.push({
|
|
24
|
+
path: path.join(docsDir, `RELEASE_NOTES.v${version}.md`),
|
|
25
|
+
content: rendered.releaseNotesMarkdown.endsWith('\n')
|
|
26
|
+
? rendered.releaseNotesMarkdown
|
|
27
|
+
: `${rendered.releaseNotesMarkdown}\n`,
|
|
28
|
+
});
|
|
29
|
+
return { writes, warnings };
|
|
30
|
+
}
|
|
31
|
+
function readWorkspaceReadme(readmePath, warnings) {
|
|
32
|
+
if (!existsSync(readmePath)) {
|
|
33
|
+
return { content: '', exists: false, unreadable: false };
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return { content: readFileSync(readmePath, 'utf8'), exists: true, unreadable: false };
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
40
|
+
warnings.push(`failed to read ${readmePath}: ${message}; skipping injected-README preview`);
|
|
41
|
+
return { content: '', exists: false, unreadable: true };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PlannedWrite } from './releasePlan.js';
|
|
2
|
+
import type { ReleaseType } from './types.js';
|
|
3
|
+
export interface VersionBumpPlan {
|
|
4
|
+
currentVersion: string;
|
|
5
|
+
newVersion: string;
|
|
6
|
+
writes: PlannedWrite[];
|
|
7
|
+
}
|
|
8
|
+
export declare function planVersionBump(packageFiles: readonly string[], releaseType: ReleaseType): VersionBumpPlan;
|
|
9
|
+
export declare function planVersionSet(packageFiles: readonly string[], newVersion: string): VersionBumpPlan;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { bumpVersion } from "./bumpVersion.js";
|
|
3
|
+
function isPackageJson(value) {
|
|
4
|
+
return typeof value === 'object' && value !== null && 'version' in value && typeof value.version === 'string';
|
|
5
|
+
}
|
|
6
|
+
export function planVersionBump(packageFiles, releaseType) {
|
|
7
|
+
const { firstFile, firstPkg } = readPrimaryPackage(packageFiles);
|
|
8
|
+
const currentVersion = firstPkg.version;
|
|
9
|
+
const newVersion = bumpVersion(currentVersion, releaseType);
|
|
10
|
+
return { currentVersion, newVersion, writes: renderVersionWrites(packageFiles, firstFile, firstPkg, newVersion) };
|
|
11
|
+
}
|
|
12
|
+
export function planVersionSet(packageFiles, newVersion) {
|
|
13
|
+
const { firstFile, firstPkg } = readPrimaryPackage(packageFiles);
|
|
14
|
+
const currentVersion = firstPkg.version;
|
|
15
|
+
return { currentVersion, newVersion, writes: renderVersionWrites(packageFiles, firstFile, firstPkg, newVersion) };
|
|
16
|
+
}
|
|
17
|
+
function readPrimaryPackage(packageFiles) {
|
|
18
|
+
const firstFile = packageFiles[0];
|
|
19
|
+
if (firstFile === undefined) {
|
|
20
|
+
throw new Error('No package files specified');
|
|
21
|
+
}
|
|
22
|
+
return { firstFile, firstPkg: readPackageJson(firstFile) };
|
|
23
|
+
}
|
|
24
|
+
function renderVersionWrites(packageFiles, firstFile, firstPkg, newVersion) {
|
|
25
|
+
return packageFiles.map((filePath) => {
|
|
26
|
+
const pkg = filePath === firstFile ? firstPkg : readPackageJson(filePath);
|
|
27
|
+
pkg.version = newVersion;
|
|
28
|
+
return { path: filePath, content: JSON.stringify(pkg, null, 2) + '\n' };
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function readPackageJson(filePath) {
|
|
32
|
+
let content;
|
|
33
|
+
try {
|
|
34
|
+
content = readFileSync(filePath, 'utf8');
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new Error(`Failed to read ${filePath}: ${error instanceof Error ? error.message : String(error)}`, {
|
|
38
|
+
cause: error,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
let parsed;
|
|
42
|
+
try {
|
|
43
|
+
parsed = JSON.parse(content);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new Error(`Failed to parse JSON in ${filePath}: ${error instanceof Error ? error.message : String(error)}`, {
|
|
47
|
+
cause: error,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (!isPackageJson(parsed)) {
|
|
51
|
+
throw new Error(`No valid 'version' field found in ${filePath}`);
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import type { WriteResult } from '@williamthorsen/nmr-core';
|
|
2
1
|
import type { ReleaseType } from './types.js';
|
|
3
|
-
export declare const RELEASE_TAGS_FILE = "tmp/.release-tags";
|
|
4
|
-
export declare const RELEASE_SUMMARY_FILE = "tmp/.release-summary";
|
|
5
2
|
export declare const prepareFlagSchema: {
|
|
6
3
|
dryRun: {
|
|
7
4
|
long: string;
|
|
@@ -42,5 +39,4 @@ export declare function parseArgs(argv: string[]): {
|
|
|
42
39
|
setVersion: string | undefined;
|
|
43
40
|
withReleaseNotes: boolean;
|
|
44
41
|
};
|
|
45
|
-
export declare function writeReleaseTags(tags: string[], dryRun: boolean): WriteResult | undefined;
|
|
46
42
|
export declare function prepareCommand(argv: string[]): Promise<void>;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
2
3
|
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
3
4
|
import { buildDependencyGraph } from "./buildDependencyGraph.js";
|
|
4
|
-
import { buildReleaseSummary } from "./buildReleaseSummary.js";
|
|
5
5
|
import { discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
6
6
|
import { dim } from "./format.js";
|
|
7
7
|
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
8
8
|
import { loadConfig, mergeMonorepoConfig, mergeSinglePackageConfig, readRootPackageVersion } from "./loadConfig.js";
|
|
9
|
+
import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
|
|
10
|
+
import { applyReleasePlan } from "./releasePlan.js";
|
|
9
11
|
import { releasePrepare } from "./releasePrepare.js";
|
|
10
12
|
import { releasePrepareMono } from "./releasePrepareMono.js";
|
|
11
13
|
import { reportPrepare } from "./reportPrepare.js";
|
|
12
14
|
import { validateConfig } from "./validateConfig.js";
|
|
13
15
|
import { validateOnlyExcludesStrandedDependents } from "./validateOnlyExcludesStrandedDependents.js";
|
|
14
|
-
export const RELEASE_TAGS_FILE = 'tmp/.release-tags';
|
|
15
|
-
export const RELEASE_SUMMARY_FILE = 'tmp/.release-summary';
|
|
16
16
|
const VALID_BUMP_TYPES = ['major', 'minor', 'patch'];
|
|
17
17
|
const CANONICAL_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
18
18
|
function isReleaseType(value) {
|
|
@@ -71,19 +71,9 @@ export function parseArgs(argv) {
|
|
|
71
71
|
withReleaseNotes: flags.withReleaseNotes,
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
-
export function writeReleaseTags(tags, dryRun) {
|
|
75
|
-
if (tags.length === 0) {
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
return writeFileWithCheck(RELEASE_TAGS_FILE, tags.join('\n'), {
|
|
79
|
-
dryRun,
|
|
80
|
-
overwrite: true,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
74
|
export async function prepareCommand(argv) {
|
|
84
75
|
const { dryRun, force, noGitChecks, bumpOverride, only, setVersion, withReleaseNotes } = parseArgs(argv);
|
|
85
76
|
const options = {
|
|
86
|
-
dryRun,
|
|
87
77
|
force,
|
|
88
78
|
...(bumpOverride !== undefined && { bumpOverride }),
|
|
89
79
|
...(setVersion !== undefined && { setVersion }),
|
|
@@ -219,48 +209,64 @@ async function loadAndValidateConfig() {
|
|
|
219
209
|
}
|
|
220
210
|
return config;
|
|
221
211
|
}
|
|
222
|
-
function runAndReport(
|
|
223
|
-
let
|
|
212
|
+
function runAndReport(computePlan, dryRun) {
|
|
213
|
+
let plan;
|
|
224
214
|
try {
|
|
225
|
-
|
|
215
|
+
plan = computePlan();
|
|
226
216
|
}
|
|
227
217
|
catch (error) {
|
|
228
218
|
reportError(error instanceof Error ? error.message : String(error));
|
|
219
|
+
process.stderr.write('No files were written; the working tree is unchanged.\n');
|
|
229
220
|
process.exit(1);
|
|
230
221
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
222
|
+
if (dryRun) {
|
|
223
|
+
process.stdout.write(reportPrepare(plan, { applied: false }) + '\n');
|
|
224
|
+
reportPlanFiles(plan, true);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
applyReleasePlan(plan);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
235
232
|
process.exit(1);
|
|
236
233
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
234
|
+
const formatError = runFormatCommand(plan.formatCommand);
|
|
235
|
+
process.stdout.write(reportPrepare(plan, { applied: true, ...(formatError !== undefined && { formatError }) }) + '\n');
|
|
236
|
+
reportPlanFiles(plan, false);
|
|
237
|
+
if (formatError !== undefined) {
|
|
238
|
+
reportError(`The format command failed, but the release is prepared and ${RELEASE_TAGS_FILE} is written. ` +
|
|
239
|
+
`Format the release files and run 'release-kit commit'.`);
|
|
240
|
+
process.exit(1);
|
|
245
241
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
242
|
+
}
|
|
243
|
+
function runFormatCommand(formatCommand) {
|
|
244
|
+
if (formatCommand === undefined) {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
execSync(formatCommand.command, { stdio: 'inherit' });
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
return error instanceof Error ? error.message : String(error);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function reportPlanFiles(plan, dryRun) {
|
|
256
|
+
if (plan.tags.length === 0) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (dryRun) {
|
|
260
|
+
console.info(dim(` [dry-run] Would write ${RELEASE_TAGS_FILE}: ${plan.tags.join(' ')}`));
|
|
261
|
+
if (plan.summary.length > 0) {
|
|
257
262
|
console.info(dim(` [dry-run] Would write ${RELEASE_SUMMARY_FILE}`));
|
|
258
263
|
}
|
|
259
|
-
|
|
260
|
-
console.info(dim(` Wrote ${RELEASE_SUMMARY_FILE}`));
|
|
261
|
-
}
|
|
264
|
+
return;
|
|
262
265
|
}
|
|
263
|
-
|
|
264
|
-
|
|
266
|
+
console.info(dim(` Wrote ${RELEASE_TAGS_FILE}: ${plan.tags.join(' ')}`));
|
|
267
|
+
console.info(dim(`\n Release tags file: ${RELEASE_TAGS_FILE}`));
|
|
268
|
+
if (plan.summary.length > 0) {
|
|
269
|
+
console.info(dim(` Wrote ${RELEASE_SUMMARY_FILE}`));
|
|
265
270
|
}
|
|
271
|
+
process.stderr.write(`\nRun 'release-kit commit' to create the release commit.\n`);
|
|
266
272
|
}
|
|
@@ -4,7 +4,7 @@ export function propagateBumps(directBumps, graph, currentVersions) {
|
|
|
4
4
|
for (const [dir, entry] of directBumps) {
|
|
5
5
|
result.set(dir, { ...entry });
|
|
6
6
|
}
|
|
7
|
-
const queue =
|
|
7
|
+
const queue = directBumps.keys().toArray();
|
|
8
8
|
const visited = new Set();
|
|
9
9
|
while (queue.length > 0) {
|
|
10
10
|
const dir = queue.shift();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
export const RELEASE_TAGS_FILE = 'tmp/.release-tags';
|
|
4
|
+
export const RELEASE_SUMMARY_FILE = 'tmp/.release-summary';
|
|
5
|
+
export function resolveReleaseTagsPath() {
|
|
6
|
+
return resolve(RELEASE_TAGS_FILE);
|
|
7
|
+
}
|
|
8
|
+
export function readReleaseTags() {
|
|
9
|
+
let content;
|
|
10
|
+
try {
|
|
11
|
+
content = readFileSync(RELEASE_TAGS_FILE, 'utf8');
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
throw new Error(describeTagsReadFailure(error), { cause: error });
|
|
15
|
+
}
|
|
16
|
+
return content
|
|
17
|
+
.split('\n')
|
|
18
|
+
.map((line) => line.trim())
|
|
19
|
+
.filter((line) => line.length > 0);
|
|
20
|
+
}
|
|
21
|
+
function describeTagsReadFailure(error) {
|
|
22
|
+
const tagsPath = resolveReleaseTagsPath();
|
|
23
|
+
if (getErrnoCode(error) === 'ENOENT') {
|
|
24
|
+
return `No tags file found at ${tagsPath}. Run \`release-kit prepare\` first.`;
|
|
25
|
+
}
|
|
26
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
27
|
+
return `Cannot read the tags file at ${tagsPath}: ${detail}`;
|
|
28
|
+
}
|
|
29
|
+
function getErrnoCode(error) {
|
|
30
|
+
if (error instanceof Error && 'code' in error && typeof error.code === 'string') {
|
|
31
|
+
return error.code;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PrepareResult } from './types.js';
|
|
2
|
+
export interface PlannedWrite {
|
|
3
|
+
path: string;
|
|
4
|
+
content: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ReleasePlan extends PrepareResult {
|
|
7
|
+
writes: readonly PlannedWrite[];
|
|
8
|
+
summary: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function applyReleasePlan(plan: ReleasePlan): string[];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { writeFileWithCheck } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
|
|
3
|
+
const HANDOFF_FILES = new Set([RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE]);
|
|
4
|
+
export function applyReleasePlan(plan) {
|
|
5
|
+
const ordered = orderPlannedWrites(plan);
|
|
6
|
+
const applied = [];
|
|
7
|
+
for (const [index, write] of ordered.entries()) {
|
|
8
|
+
const result = writeFileWithCheck(write.path, write.content, { dryRun: false, overwrite: true });
|
|
9
|
+
if (result.outcome === 'failed') {
|
|
10
|
+
const unwritten = ordered.slice(index).map((pending) => pending.path);
|
|
11
|
+
throw new Error(describeApplyFailure({ failedPath: write.path, error: result.error, applied, unwritten }));
|
|
12
|
+
}
|
|
13
|
+
applied.push({ path: write.path, created: result.outcome === 'created' });
|
|
14
|
+
}
|
|
15
|
+
return applied.map((write) => write.path);
|
|
16
|
+
}
|
|
17
|
+
function orderPlannedWrites(plan) {
|
|
18
|
+
const ordered = [...plan.writes];
|
|
19
|
+
if (plan.summary.length > 0) {
|
|
20
|
+
ordered.push({ path: RELEASE_SUMMARY_FILE, content: plan.summary });
|
|
21
|
+
}
|
|
22
|
+
if (plan.tags.length > 0) {
|
|
23
|
+
ordered.push({ path: RELEASE_TAGS_FILE, content: plan.tags.join('\n') });
|
|
24
|
+
}
|
|
25
|
+
return ordered;
|
|
26
|
+
}
|
|
27
|
+
function describeApplyFailure(args) {
|
|
28
|
+
const { failedPath, error, applied, unwritten } = args;
|
|
29
|
+
const total = applied.length + unwritten.length;
|
|
30
|
+
const lines = [`Failed to write ${failedPath}: ${error ?? 'unknown error'}`, ''];
|
|
31
|
+
if (applied.length === 0) {
|
|
32
|
+
lines.push(`The release was not applied. None of its ${total} files were written.`);
|
|
33
|
+
return lines.join('\n');
|
|
34
|
+
}
|
|
35
|
+
lines.push(`The release was partially applied. ${applied.length} of ${total} files were written:`, ...applied.map((write) => ` ${write.path}`), '', 'Not written:', ...unwritten.map((path) => ` ${path}`), ...describeRecovery(applied));
|
|
36
|
+
return lines.join('\n');
|
|
37
|
+
}
|
|
38
|
+
function describeRecovery(applied) {
|
|
39
|
+
const releaseWrites = applied.filter((write) => !HANDOFF_FILES.has(write.path));
|
|
40
|
+
const replaced = releaseWrites.filter((write) => !write.created).map((write) => write.path);
|
|
41
|
+
const created = releaseWrites.filter((write) => write.created).map((write) => write.path);
|
|
42
|
+
if (replaced.length === 0 && created.length === 0) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
const lines = ['', 'Undo the written release files before re-running `release-kit prepare`:'];
|
|
46
|
+
if (replaced.length > 0) {
|
|
47
|
+
lines.push(` git restore ${replaced.join(' ')}`);
|
|
48
|
+
}
|
|
49
|
+
if (created.length > 0) {
|
|
50
|
+
lines.push(` rm ${created.join(' ')}`);
|
|
51
|
+
}
|
|
52
|
+
return lines;
|
|
53
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ReleasePlan } from './releasePlan.js';
|
|
2
|
+
import type { ReleaseConfig, ReleaseType } from './types.js';
|
|
2
3
|
export interface ReleasePrepareOptions {
|
|
3
|
-
dryRun: boolean;
|
|
4
4
|
force?: boolean;
|
|
5
5
|
bumpOverride?: ReleaseType;
|
|
6
6
|
setVersion?: string;
|
|
7
7
|
withReleaseNotes?: boolean;
|
|
8
8
|
}
|
|
9
|
-
export declare function releasePrepare(config: ReleaseConfig, options: ReleasePrepareOptions):
|
|
9
|
+
export declare function releasePrepare(config: ReleaseConfig, options: ReleasePrepareOptions): ReleasePlan;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { join as joinPath } from 'node:path';
|
|
2
2
|
import { buildChangelogEntries } from "./buildChangelogEntries.js";
|
|
3
3
|
import { buildEmptyReleaseEntry } from "./buildEmptyReleaseEntry.js";
|
|
4
|
-
import {
|
|
5
|
-
import { mergeChangelogEntriesWithDisk,
|
|
4
|
+
import { buildReleaseSummary } from "./buildReleaseSummary.js";
|
|
5
|
+
import { mergeChangelogEntriesWithDisk, renderChangelogJson, resolveChangelogJsonPath } from "./changelogJsonFile.js";
|
|
6
6
|
import { applyChangelogOverrides, formatStaleOverrideKeyWarning, loadOverridesForScopes, } from "./changelogOverrides.js";
|
|
7
7
|
import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
|
|
8
8
|
import { isForwardVersion } from "./compareVersions.js";
|
|
@@ -11,13 +11,15 @@ import { determineBumpFromCommits } from "./determineBumpFromCommits.js";
|
|
|
11
11
|
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
12
12
|
import { hasPrettierConfig } from "./hasPrettierConfig.js";
|
|
13
13
|
import { resolveWorkTypes } from "./loadConfig.js";
|
|
14
|
+
import { planReleaseNotesPreviews } from "./planReleaseNotesPreviews.js";
|
|
15
|
+
import { planVersionBump, planVersionSet } from "./planVersionBump.js";
|
|
14
16
|
import { readCurrentVersion } from "./readCurrentVersion.js";
|
|
15
|
-
import {
|
|
17
|
+
import { renderChangelogMarkdown } from "./renderChangelogMarkdown.js";
|
|
16
18
|
import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
|
|
17
19
|
import { refreshGitCliffCache } from "./runGitCliff.js";
|
|
18
|
-
import { writeReleaseNotesPreviews } from "./writeReleaseNotesPreviews.js";
|
|
19
20
|
export function releasePrepare(config, options) {
|
|
20
|
-
const {
|
|
21
|
+
const { bumpOverride, setVersion, withReleaseNotes } = options;
|
|
22
|
+
const writes = [];
|
|
21
23
|
const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
|
|
22
24
|
const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
|
|
23
25
|
const breakingPolicies = config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES;
|
|
@@ -45,7 +47,7 @@ export function releasePrepare(config, options) {
|
|
|
45
47
|
throw new Error(`--set-version ${setVersion} is not greater than current version ${currentVersion}`);
|
|
46
48
|
}
|
|
47
49
|
refreshGitCliffCache();
|
|
48
|
-
bump =
|
|
50
|
+
bump = planVersionSet(config.packageFiles, setVersion);
|
|
49
51
|
}
|
|
50
52
|
else {
|
|
51
53
|
if (bumpOverride === undefined) {
|
|
@@ -71,46 +73,37 @@ export function releasePrepare(config, options) {
|
|
|
71
73
|
return {
|
|
72
74
|
workspaces: [skipped],
|
|
73
75
|
tags: [],
|
|
74
|
-
|
|
76
|
+
writes: [],
|
|
77
|
+
summary: '',
|
|
78
|
+
formatCommand: undefined,
|
|
75
79
|
};
|
|
76
80
|
}
|
|
77
81
|
refreshGitCliffCache();
|
|
78
|
-
bump =
|
|
82
|
+
bump = planVersionBump(config.packageFiles, releaseType);
|
|
79
83
|
}
|
|
84
|
+
writes.push(...bump.writes);
|
|
80
85
|
const newTag = `${config.tagPrefix}${bump.newVersion}`;
|
|
81
86
|
const overrideWarnings = [];
|
|
82
|
-
const
|
|
87
|
+
const changelogs = planSinglePackageChangelogs({
|
|
83
88
|
config,
|
|
84
89
|
commits,
|
|
85
90
|
newTag,
|
|
86
91
|
newVersion: bump.newVersion,
|
|
87
|
-
dryRun,
|
|
88
92
|
overrides,
|
|
89
93
|
overrideWarnings,
|
|
90
94
|
});
|
|
91
|
-
|
|
95
|
+
const { changelogFiles, changelogJsonFiles } = changelogs;
|
|
96
|
+
const previewWrites = planSinglePackagePreviews(withReleaseNotes === true, config, newTag, changelogs.entries, overrideWarnings);
|
|
97
|
+
writes.push(...changelogs.writes, ...previewWrites);
|
|
92
98
|
const formatCommandStr = config.formatCommand ?? (hasPrettierConfig() ? 'npx prettier --write' : undefined);
|
|
93
99
|
let formatCommand;
|
|
94
100
|
if (formatCommandStr !== undefined) {
|
|
95
101
|
const modifiedFiles = [
|
|
96
102
|
...config.packageFiles,
|
|
97
|
-
...config.changelogPaths.map((
|
|
103
|
+
...config.changelogPaths.map((changelogPath) => joinPath(changelogPath, 'CHANGELOG.md')),
|
|
98
104
|
...changelogJsonFiles,
|
|
99
105
|
];
|
|
100
|
-
|
|
101
|
-
if (dryRun) {
|
|
102
|
-
formatCommand = { command: fullCommand, executed: false, files: modifiedFiles };
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
try {
|
|
106
|
-
execSync(fullCommand, { stdio: 'inherit' });
|
|
107
|
-
}
|
|
108
|
-
catch (error) {
|
|
109
|
-
const baseMessage = error instanceof Error ? error.message : String(error);
|
|
110
|
-
throw new Error(`format stage: ${baseMessage} (command: '${fullCommand}')`, { cause: error });
|
|
111
|
-
}
|
|
112
|
-
formatCommand = { command: fullCommand, executed: true, files: modifiedFiles };
|
|
113
|
-
}
|
|
106
|
+
formatCommand = { command: `${formatCommandStr} ${modifiedFiles.join(' ')}`, files: modifiedFiles };
|
|
114
107
|
}
|
|
115
108
|
const released = buildReleasedSinglePackage({
|
|
116
109
|
commits,
|
|
@@ -123,17 +116,19 @@ export function releasePrepare(config, options) {
|
|
|
123
116
|
unparseableCommits,
|
|
124
117
|
policyViolations: collector.violations.length > 0 ? collector.violations : undefined,
|
|
125
118
|
setVersion,
|
|
119
|
+
previewFiles: previewWrites.map((write) => write.path),
|
|
126
120
|
});
|
|
127
|
-
const
|
|
121
|
+
const plan = {
|
|
128
122
|
workspaces: [released],
|
|
129
123
|
tags: [newTag],
|
|
124
|
+
writes,
|
|
125
|
+
summary: buildReleaseSummary({ workspaces: [released] }),
|
|
130
126
|
formatCommand,
|
|
131
|
-
dryRun,
|
|
132
127
|
};
|
|
133
128
|
if (overrideWarnings.length > 0) {
|
|
134
|
-
|
|
129
|
+
plan.warnings = overrideWarnings;
|
|
135
130
|
}
|
|
136
|
-
return
|
|
131
|
+
return plan;
|
|
137
132
|
}
|
|
138
133
|
function buildSkippedSinglePackage(args) {
|
|
139
134
|
const skipped = {
|
|
@@ -163,7 +158,7 @@ function buildReleasedSinglePackage(args) {
|
|
|
163
158
|
currentVersion: bump.currentVersion,
|
|
164
159
|
newVersion: bump.newVersion,
|
|
165
160
|
tag: newTag,
|
|
166
|
-
bumpedFiles: bump.
|
|
161
|
+
bumpedFiles: bump.writes.map((write) => write.path),
|
|
167
162
|
changelogFiles,
|
|
168
163
|
commits,
|
|
169
164
|
};
|
|
@@ -185,10 +180,13 @@ function buildReleasedSinglePackage(args) {
|
|
|
185
180
|
if (setVersion !== undefined) {
|
|
186
181
|
released.setVersion = setVersion;
|
|
187
182
|
}
|
|
183
|
+
if (args.previewFiles.length > 0) {
|
|
184
|
+
released.previewFiles = args.previewFiles;
|
|
185
|
+
}
|
|
188
186
|
return released;
|
|
189
187
|
}
|
|
190
|
-
function
|
|
191
|
-
const { config, commits, newTag, newVersion,
|
|
188
|
+
function planSinglePackageChangelogs(args) {
|
|
189
|
+
const { config, commits, newTag, newVersion, overrides, overrideWarnings } = args;
|
|
192
190
|
const isEmptyRange = commits.length === 0;
|
|
193
191
|
const today = new Date().toISOString().slice(0, 10);
|
|
194
192
|
const baseEntries = isEmptyRange
|
|
@@ -208,40 +206,41 @@ function writeSinglePackageChangelogs(args) {
|
|
|
208
206
|
const sectionOrder = deriveSectionOrder(resolveWorkTypes(config.workTypes));
|
|
209
207
|
const changelogFiles = [];
|
|
210
208
|
const changelogJsonFiles = [];
|
|
209
|
+
const writes = [];
|
|
210
|
+
let firstMergedEntries = [];
|
|
211
211
|
for (const changelogPath of config.changelogPaths) {
|
|
212
212
|
const jsonPath = resolveChangelogJsonPath(config, changelogPath);
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
const mergedEntries = mergeChangelogEntriesWithDisk(jsonPath, applied.entries);
|
|
214
|
+
if (changelogFiles.length === 0) {
|
|
215
|
+
firstMergedEntries = mergedEntries;
|
|
216
|
+
}
|
|
217
217
|
if (config.changelogJson.enabled) {
|
|
218
|
+
writes.push({ path: jsonPath, content: renderChangelogJson(mergedEntries) });
|
|
218
219
|
changelogJsonFiles.push(jsonPath);
|
|
219
220
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}));
|
|
226
|
-
}
|
|
227
|
-
return { changelogFiles, changelogJsonFiles };
|
|
221
|
+
const changelogFile = joinPath(changelogPath, 'CHANGELOG.md');
|
|
222
|
+
writes.push({ path: changelogFile, content: renderChangelogMarkdown(mergedEntries, { sectionOrder }) });
|
|
223
|
+
changelogFiles.push(changelogFile);
|
|
224
|
+
}
|
|
225
|
+
return { changelogFiles, changelogJsonFiles, entries: firstMergedEntries, writes };
|
|
228
226
|
}
|
|
229
|
-
function
|
|
227
|
+
function planSinglePackagePreviews(withReleaseNotes, config, newTag, entries, warnings) {
|
|
230
228
|
if (!withReleaseNotes) {
|
|
231
|
-
return;
|
|
229
|
+
return [];
|
|
232
230
|
}
|
|
233
231
|
if (!config.changelogJson.enabled) {
|
|
234
232
|
console.warn('Warning: --with-release-notes requires changelogJson.enabled; skipping preview generation');
|
|
235
|
-
return;
|
|
233
|
+
return [];
|
|
236
234
|
}
|
|
237
|
-
if (
|
|
238
|
-
return;
|
|
235
|
+
if (config.changelogPaths.length === 0) {
|
|
236
|
+
return [];
|
|
239
237
|
}
|
|
240
|
-
|
|
238
|
+
const previews = planReleaseNotesPreviews({
|
|
241
239
|
workspacePath: process.cwd(),
|
|
242
240
|
tag: newTag,
|
|
243
|
-
|
|
241
|
+
entries,
|
|
244
242
|
sectionOrder: deriveSectionOrder(resolveWorkTypes(config.workTypes)),
|
|
245
|
-
dryRun,
|
|
246
243
|
});
|
|
244
|
+
warnings.push(...previews.warnings);
|
|
245
|
+
return previews.writes;
|
|
247
246
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReleasePlan } from './releasePlan.js';
|
|
1
2
|
import type { ReleasePrepareOptions } from './releasePrepare.js';
|
|
2
|
-
import type { MonorepoReleaseConfig
|
|
3
|
-
export declare function releasePrepareMono(config: MonorepoReleaseConfig, options: ReleasePrepareOptions):
|
|
3
|
+
import type { MonorepoReleaseConfig } from './types.js';
|
|
4
|
+
export declare function releasePrepareMono(config: MonorepoReleaseConfig, options: ReleasePrepareOptions): ReleasePlan;
|