fix(release): allow version plans to have multi-line, arbitrarily formatted messages (#27323)
This commit is contained in:
parent
555182353b
commit
7e2266177d
@ -114,7 +114,7 @@ describe('nx release version plans', () => {
|
||||
await ensureDir(versionPlansDir);
|
||||
|
||||
runCLI(
|
||||
'release plan minor -g fixed-group -m "feat: Update the fixed packages with a minor release." --verbose',
|
||||
'release plan minor -g fixed-group -m "Update the fixed packages with a minor release." --verbose',
|
||||
{
|
||||
silenceError: true,
|
||||
}
|
||||
@ -128,7 +128,9 @@ ${pkg4}: preminor
|
||||
${pkg5}: prerelease
|
||||
---
|
||||
|
||||
feat: Update the independent packages with a patch, preminor, and prerelease.
|
||||
Update the independent packages with a patch, preminor, and prerelease.
|
||||
|
||||
Here is another line in the message.
|
||||
`
|
||||
);
|
||||
|
||||
@ -193,9 +195,11 @@ feat: Update the independent packages with a patch, preminor, and prerelease.
|
||||
+ ## 0.0.1 (YYYY-MM-DD)
|
||||
+
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+ ### 🩹 Fixes
|
||||
+
|
||||
+ - Update the independent packages with a patch, preminor, and prerelease.`
|
||||
+ - **${pkg3}:** Update the independent packages with a patch, preminor, and prerelease.
|
||||
+
|
||||
+ Here is another line in the message.`
|
||||
);
|
||||
|
||||
expect(resultWithoutDate).toContain(
|
||||
@ -207,7 +211,9 @@ feat: Update the independent packages with a patch, preminor, and prerelease.
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+
|
||||
+ - Update the independent packages with a patch, preminor, and prerelease.`
|
||||
+ - **${pkg4}:** Update the independent packages with a patch, preminor, and prerelease.
|
||||
+
|
||||
+ Here is another line in the message.`
|
||||
);
|
||||
|
||||
expect(resultWithoutDate).toContain(
|
||||
@ -217,9 +223,11 @@ feat: Update the independent packages with a patch, preminor, and prerelease.
|
||||
+ ## 0.0.1-0 (YYYY-MM-DD)
|
||||
+
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+ ### 🩹 Fixes
|
||||
+
|
||||
+ - Update the independent packages with a patch, preminor, and prerelease.`
|
||||
+ - **${pkg5}:** Update the independent packages with a patch, preminor, and prerelease.
|
||||
+
|
||||
+ Here is another line in the message.`
|
||||
);
|
||||
|
||||
await writeFile(
|
||||
@ -229,7 +237,7 @@ ${pkg1}: minor
|
||||
${pkg3}: patch
|
||||
---
|
||||
|
||||
fix: Update packages in both groups with a bug fix
|
||||
Update packages in both groups with a mix #1
|
||||
`
|
||||
);
|
||||
await writeFile(
|
||||
@ -240,7 +248,7 @@ ${pkg4}: preminor
|
||||
${pkg5}: patch
|
||||
---
|
||||
|
||||
feat: Update packages in both groups with a feat
|
||||
Update packages in both groups with a mix #2
|
||||
`
|
||||
);
|
||||
|
||||
@ -291,12 +299,12 @@ feat: Update packages in both groups with a feat
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+
|
||||
+ - Update packages in both groups with a feat
|
||||
+ - **${pkg1}:** Update packages in both groups with a mix #1
|
||||
+
|
||||
+
|
||||
+ ### 🩹 Fixes
|
||||
+
|
||||
+ - Update packages in both groups with a bug fix`
|
||||
+ - Update packages in both groups with a mix #2`
|
||||
);
|
||||
expect(result2WithoutDate).toContain(
|
||||
`NX Generating an entry in ${pkg2}/CHANGELOG.md for v0.2.0
|
||||
@ -306,14 +314,9 @@ feat: Update packages in both groups with a feat
|
||||
+ ## 0.2.0 (YYYY-MM-DD)
|
||||
+
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+
|
||||
+ - Update packages in both groups with a feat
|
||||
+
|
||||
+
|
||||
+ ### 🩹 Fixes
|
||||
+
|
||||
+ - Update packages in both groups with a bug fix
|
||||
+ - Update packages in both groups with a mix #2
|
||||
`
|
||||
);
|
||||
expect(result2WithoutDate).toContain(
|
||||
@ -326,7 +329,7 @@ feat: Update packages in both groups with a feat
|
||||
+
|
||||
+ ### 🩹 Fixes
|
||||
+
|
||||
+ - Update packages in both groups with a bug fix`
|
||||
+ - **${pkg3}:** Update packages in both groups with a mix #1`
|
||||
);
|
||||
|
||||
expect(result2WithoutDate).toContain(
|
||||
@ -339,7 +342,7 @@ feat: Update packages in both groups with a feat
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+
|
||||
+ - Update packages in both groups with a feat`
|
||||
+ - **${pkg4}:** Update packages in both groups with a mix #2`
|
||||
);
|
||||
|
||||
expect(result2WithoutDate).toContain(
|
||||
@ -350,9 +353,9 @@ feat: Update packages in both groups with a feat
|
||||
+ ## 0.0.1 (YYYY-MM-DD)
|
||||
+
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+ ### 🩹 Fixes
|
||||
+
|
||||
+ - Update packages in both groups with a feat`
|
||||
+ - **${pkg5}:** Update packages in both groups with a mix #2`
|
||||
);
|
||||
|
||||
expect(exists(join(versionPlansDir, 'bump-mixed1.md'))).toBeFalsy();
|
||||
@ -394,7 +397,7 @@ feat: Update packages in both groups with a feat
|
||||
fixed-group: minor
|
||||
---
|
||||
|
||||
feat: Update the fixed packages with a minor release.
|
||||
Update the fixed packages with a minor release.
|
||||
`
|
||||
);
|
||||
|
||||
@ -406,7 +409,7 @@ ${pkg4}: preminor
|
||||
${pkg5}: prerelease
|
||||
---
|
||||
|
||||
feat: Update the independent packages with a patch, preminor, and prerelease.
|
||||
Update the independent packages with a patch, preminor, and prerelease.
|
||||
`
|
||||
);
|
||||
|
||||
@ -530,9 +533,9 @@ const yargs = require('yargs');
|
||||
+ ## 0.0.1 (YYYY-MM-DD)
|
||||
+
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+ ### 🩹 Fixes
|
||||
+
|
||||
+ - Update the independent packages with a patch, preminor, and prerelease.`
|
||||
+ - **${pkg3}:** Update the independent packages with a patch, preminor, and prerelease.`
|
||||
);
|
||||
|
||||
expect(resultWithoutDate).toContain(
|
||||
@ -544,7 +547,7 @@ const yargs = require('yargs');
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+
|
||||
+ - Update the independent packages with a patch, preminor, and prerelease.`
|
||||
+ - **${pkg4}:** Update the independent packages with a patch, preminor, and prerelease.`
|
||||
);
|
||||
|
||||
expect(resultWithoutDate).toContain(
|
||||
@ -554,9 +557,9 @@ const yargs = require('yargs');
|
||||
+ ## 0.0.1-0 (YYYY-MM-DD)
|
||||
+
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+ ### 🩹 Fixes
|
||||
+
|
||||
+ - Update the independent packages with a patch, preminor, and prerelease.`
|
||||
+ - **${pkg5}:** Update the independent packages with a patch, preminor, and prerelease.`
|
||||
);
|
||||
|
||||
expect(exists(join(versionPlansDir, 'bump-fixed.md'))).toBeFalsy();
|
||||
@ -569,8 +572,8 @@ ${pkg1}: minor
|
||||
${pkg3}: patch
|
||||
---
|
||||
|
||||
fix: Update packages in both groups with a bug fix
|
||||
`
|
||||
Update packages in both groups with a mix #1
|
||||
`
|
||||
);
|
||||
await writeFile(
|
||||
join(versionPlansDir, 'bump-mixed2.md'),
|
||||
@ -580,8 +583,8 @@ ${pkg4}: preminor
|
||||
${pkg5}: patch
|
||||
---
|
||||
|
||||
feat: Update packages in both groups with a feat
|
||||
`
|
||||
Update packages in both groups with a mix #2
|
||||
`
|
||||
);
|
||||
|
||||
await runCommandAsync(`git add ${join(versionPlansDir, 'bump-mixed1.md')}`);
|
||||
@ -631,12 +634,12 @@ feat: Update packages in both groups with a feat
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+
|
||||
+ - Update packages in both groups with a feat
|
||||
+ - **${pkg1}:** Update packages in both groups with a mix #1
|
||||
+
|
||||
+
|
||||
+ ### 🩹 Fixes
|
||||
+
|
||||
+ - Update packages in both groups with a bug fix`
|
||||
+ - Update packages in both groups with a mix #2`
|
||||
);
|
||||
expect(result2WithoutDate).toContain(
|
||||
`NX Generating an entry in ${pkg2}/CHANGELOG.md for v0.2.0
|
||||
@ -646,14 +649,9 @@ feat: Update packages in both groups with a feat
|
||||
+ ## 0.2.0 (YYYY-MM-DD)
|
||||
+
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+
|
||||
+ - Update packages in both groups with a feat
|
||||
+
|
||||
+
|
||||
+ ### 🩹 Fixes
|
||||
+
|
||||
+ - Update packages in both groups with a bug fix
|
||||
+ - Update packages in both groups with a mix #2
|
||||
`
|
||||
);
|
||||
expect(result2WithoutDate).toContain(
|
||||
@ -666,7 +664,7 @@ feat: Update packages in both groups with a feat
|
||||
+
|
||||
+ ### 🩹 Fixes
|
||||
+
|
||||
+ - Update packages in both groups with a bug fix`
|
||||
+ - **${pkg3}:** Update packages in both groups with a mix #1`
|
||||
);
|
||||
|
||||
expect(result2WithoutDate).toContain(
|
||||
@ -679,7 +677,7 @@ feat: Update packages in both groups with a feat
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+
|
||||
+ - Update packages in both groups with a feat`
|
||||
+ - **${pkg4}:** Update packages in both groups with a mix #2`
|
||||
);
|
||||
|
||||
expect(result2WithoutDate).toContain(
|
||||
@ -690,9 +688,9 @@ feat: Update packages in both groups with a feat
|
||||
+ ## 0.0.1 (YYYY-MM-DD)
|
||||
+
|
||||
+
|
||||
+ ### 🚀 Features
|
||||
+ ### 🩹 Fixes
|
||||
+
|
||||
+ - Update packages in both groups with a feat`
|
||||
+ - **${pkg5}:** Update packages in both groups with a mix #2`
|
||||
);
|
||||
|
||||
expect(exists(join(versionPlansDir, 'bump-mixed1.md'))).toBeFalsy();
|
||||
@ -715,7 +713,7 @@ feat: Update packages in both groups with a feat
|
||||
await ensureDir(versionPlansDir);
|
||||
|
||||
runCLI(
|
||||
'release plan minor -m "feat: Update the fixed packages with a minor release." --verbose',
|
||||
'release plan minor -m "Update the fixed packages with a minor release." --verbose',
|
||||
{
|
||||
silenceError: true,
|
||||
}
|
||||
|
||||
@ -404,14 +404,30 @@ function formatChange(
|
||||
changelogRenderOptions: DefaultChangelogRenderOptions,
|
||||
repoSlug?: RepoSlug
|
||||
): string {
|
||||
let description = change.description;
|
||||
let extraLines = [];
|
||||
let extraLinesStr = '';
|
||||
if (description.includes('\n')) {
|
||||
[description, ...extraLines] = description.split('\n');
|
||||
// Align the extra lines with the start of the description for better readability
|
||||
const indentation = ' ';
|
||||
extraLinesStr = extraLines
|
||||
.filter((l) => l.trim().length > 0)
|
||||
.map((l) => `${indentation}${l}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
let changeLine =
|
||||
'- ' +
|
||||
(change.isBreaking ? '⚠️ ' : '') +
|
||||
(change.scope ? `**${change.scope.trim()}:** ` : '') +
|
||||
change.description;
|
||||
description;
|
||||
if (repoSlug && changelogRenderOptions.commitReferences) {
|
||||
changeLine += formatReferences(change.githubReferences, repoSlug);
|
||||
}
|
||||
if (extraLinesStr) {
|
||||
changeLine += '\n\n' + extraLinesStr;
|
||||
}
|
||||
return changeLine;
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import * as chalk from 'chalk';
|
||||
import { prompt } from 'enquirer';
|
||||
import { removeSync } from 'fs-extra';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { valid } from 'semver';
|
||||
import { ReleaseType, valid } from 'semver';
|
||||
import { dirSync } from 'tmp';
|
||||
import type { DependencyBump } from '../../../release/changelog-renderer';
|
||||
import {
|
||||
@ -56,7 +56,6 @@ import {
|
||||
gitPush,
|
||||
gitTag,
|
||||
parseCommits,
|
||||
parseConventionalCommitsMessage,
|
||||
} from './utils/git';
|
||||
import { createOrUpdateGithubRelease, getGitHubRepoSlug } from './utils/github';
|
||||
import { launchEditor } from './utils/launch-editor';
|
||||
@ -281,30 +280,37 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
||||
const releaseGroup = releaseGroups[0];
|
||||
if (releaseGroup.projectsRelationship === 'fixed') {
|
||||
const versionPlans = releaseGroup.versionPlans as GroupVersionPlan[];
|
||||
workspaceChangelogChanges = filterHiddenChanges(
|
||||
versionPlans
|
||||
.map((vp) => {
|
||||
const parsedMessage = parseConventionalCommitsMessage(
|
||||
vp.message
|
||||
);
|
||||
|
||||
// only properly formatted conventional commits messages will be included in the changelog
|
||||
if (!parsedMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <ChangelogChange>{
|
||||
type: parsedMessage.type,
|
||||
scope: parsedMessage.scope,
|
||||
description: parsedMessage.description,
|
||||
body: '',
|
||||
isBreaking: parsedMessage.breaking,
|
||||
githubReferences: [],
|
||||
};
|
||||
})
|
||||
.filter(Boolean),
|
||||
nxReleaseConfig.conventionalCommits
|
||||
);
|
||||
workspaceChangelogChanges = versionPlans
|
||||
.flatMap((vp) => {
|
||||
const releaseType = versionPlanSemverReleaseTypeToChangelogType(
|
||||
vp.groupVersionBump
|
||||
);
|
||||
const changes: ChangelogChange | ChangelogChange[] =
|
||||
!vp.triggeredByProjects
|
||||
? {
|
||||
type: releaseType.type,
|
||||
scope: '',
|
||||
description: vp.message,
|
||||
body: '',
|
||||
isBreaking: releaseType.isBreaking,
|
||||
githubReferences: [],
|
||||
affectedProjects: '*',
|
||||
}
|
||||
: vp.triggeredByProjects.map((project) => {
|
||||
return {
|
||||
type: releaseType.type,
|
||||
scope: project,
|
||||
description: vp.message,
|
||||
body: '',
|
||||
// TODO: what about github references?
|
||||
isBreaking: releaseType.isBreaking,
|
||||
githubReferences: [],
|
||||
affectedProjects: [project],
|
||||
};
|
||||
});
|
||||
return changes;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -485,31 +491,26 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
||||
let commits: GitCommit[];
|
||||
|
||||
if (releaseGroup.versionPlans) {
|
||||
changes = filterHiddenChanges(
|
||||
(releaseGroup.versionPlans as ProjectsVersionPlan[])
|
||||
.map((vp) => {
|
||||
const parsedMessage = parseConventionalCommitsMessage(
|
||||
vp.message
|
||||
);
|
||||
|
||||
// only properly formatted conventional commits messages will be included in the changelog
|
||||
if (!parsedMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
type: parsedMessage.type,
|
||||
scope: parsedMessage.scope,
|
||||
description: parsedMessage.description,
|
||||
body: '',
|
||||
isBreaking: parsedMessage.breaking,
|
||||
affectedProjects: Object.keys(vp.projectVersionBumps),
|
||||
githubReferences: [],
|
||||
};
|
||||
})
|
||||
.filter(Boolean),
|
||||
nxReleaseConfig.conventionalCommits
|
||||
);
|
||||
changes = (releaseGroup.versionPlans as ProjectsVersionPlan[])
|
||||
.map((vp) => {
|
||||
const bumpForProject = vp.projectVersionBumps[project.name];
|
||||
if (!bumpForProject) {
|
||||
return null;
|
||||
}
|
||||
const releaseType =
|
||||
versionPlanSemverReleaseTypeToChangelogType(bumpForProject);
|
||||
return {
|
||||
type: releaseType.type,
|
||||
scope: project.name,
|
||||
description: vp.message,
|
||||
body: '',
|
||||
isBreaking: releaseType.isBreaking,
|
||||
affectedProjects: Object.keys(vp.projectVersionBumps),
|
||||
// TODO: can we include github references when using version plans?
|
||||
githubReferences: [],
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
} else {
|
||||
let fromRef =
|
||||
args.from ||
|
||||
@ -637,31 +638,37 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
||||
// TODO: remove this after the changelog renderer is refactored to remove coupling with git commits
|
||||
let commits: GitCommit[] = [];
|
||||
if (releaseGroup.versionPlans) {
|
||||
changes = filterHiddenChanges(
|
||||
(releaseGroup.versionPlans as GroupVersionPlan[])
|
||||
.map((vp) => {
|
||||
const parsedMessage = parseConventionalCommitsMessage(
|
||||
vp.message
|
||||
);
|
||||
|
||||
// only properly formatted conventional commits messages will be included in the changelog
|
||||
if (!parsedMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <ChangelogChange>{
|
||||
type: parsedMessage.type,
|
||||
scope: parsedMessage.scope,
|
||||
description: parsedMessage.description,
|
||||
body: '',
|
||||
isBreaking: parsedMessage.breaking,
|
||||
githubReferences: [],
|
||||
affectedProjects: '*',
|
||||
};
|
||||
})
|
||||
.filter(Boolean),
|
||||
nxReleaseConfig.conventionalCommits
|
||||
);
|
||||
changes = (releaseGroup.versionPlans as GroupVersionPlan[])
|
||||
.flatMap((vp) => {
|
||||
const releaseType = versionPlanSemverReleaseTypeToChangelogType(
|
||||
vp.groupVersionBump
|
||||
);
|
||||
const changes: ChangelogChange | ChangelogChange[] =
|
||||
!vp.triggeredByProjects
|
||||
? {
|
||||
type: releaseType.type,
|
||||
scope: '',
|
||||
description: vp.message,
|
||||
body: '',
|
||||
isBreaking: releaseType.isBreaking,
|
||||
githubReferences: [],
|
||||
affectedProjects: '*',
|
||||
}
|
||||
: vp.triggeredByProjects.map((project) => {
|
||||
return {
|
||||
type: releaseType.type,
|
||||
scope: project,
|
||||
description: vp.message,
|
||||
body: '',
|
||||
// TODO: what about github references?
|
||||
isBreaking: releaseType.isBreaking,
|
||||
githubReferences: [],
|
||||
affectedProjects: [project],
|
||||
};
|
||||
});
|
||||
return changes;
|
||||
})
|
||||
.filter(Boolean);
|
||||
} else {
|
||||
let fromRef =
|
||||
args.from ||
|
||||
@ -1408,3 +1415,23 @@ function createFileToProjectMap(
|
||||
}
|
||||
return fileToProjectMap;
|
||||
}
|
||||
|
||||
function versionPlanSemverReleaseTypeToChangelogType(bump: ReleaseType): {
|
||||
type: 'fix' | 'feat';
|
||||
isBreaking: boolean;
|
||||
} {
|
||||
switch (bump) {
|
||||
case 'premajor':
|
||||
case 'major':
|
||||
return { type: 'feat', isBreaking: true };
|
||||
case 'preminor':
|
||||
case 'minor':
|
||||
return { type: 'feat', isBreaking: false };
|
||||
case 'prerelease':
|
||||
case 'prepatch':
|
||||
case 'patch':
|
||||
return { type: 'fix', isBreaking: false };
|
||||
default:
|
||||
throw new Error(`Invalid semver bump type: ${bump}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,3 +4,5 @@ pkg4: minor
|
||||
---
|
||||
|
||||
This is a change to packages 3 and 4
|
||||
|
||||
...and it includes multiple lines of text
|
||||
|
||||
@ -75,7 +75,8 @@ describe('version-plans', () => {
|
||||
pkg1: patch,
|
||||
},
|
||||
fileName: plan1.md,
|
||||
message: This is a change to just package 1,
|
||||
message: This is a change to just package 1
|
||||
,
|
||||
relativePath: .nx/version-plans/plan1.md,
|
||||
},
|
||||
{
|
||||
@ -85,7 +86,8 @@ describe('version-plans', () => {
|
||||
pkg2: patch,
|
||||
},
|
||||
fileName: plan2.md,
|
||||
message: This is a change to package 1 and package 2,
|
||||
message: This is a change to package 1 and package 2
|
||||
,
|
||||
relativePath: .nx/version-plans/plan2.md,
|
||||
},
|
||||
{
|
||||
@ -95,7 +97,10 @@ describe('version-plans', () => {
|
||||
pkg4: minor,
|
||||
},
|
||||
fileName: plan3.md,
|
||||
message: This is a change to packages 3 and 4,
|
||||
message: This is a change to packages 3 and 4
|
||||
|
||||
...and it includes multiple lines of text
|
||||
,
|
||||
relativePath: .nx/version-plans/plan3.md,
|
||||
},
|
||||
{
|
||||
@ -107,7 +112,8 @@ describe('version-plans', () => {
|
||||
pkg6: preminor,
|
||||
},
|
||||
fileName: plan4.md,
|
||||
message: This is a change to packages 3, 4, 5, and 6,
|
||||
message: This is a change to packages 3, 4, 5, and 6
|
||||
,
|
||||
relativePath: .nx/version-plans/plan4.md,
|
||||
},
|
||||
{
|
||||
@ -116,7 +122,8 @@ describe('version-plans', () => {
|
||||
fixed-group-1: minor,
|
||||
},
|
||||
fileName: plan5.md,
|
||||
message: This is a change to fixed-group-1,
|
||||
message: This is a change to fixed-group-1
|
||||
,
|
||||
relativePath: .nx/version-plans/plan5.md,
|
||||
},
|
||||
{
|
||||
@ -127,7 +134,8 @@ describe('version-plans', () => {
|
||||
pkg3: major,
|
||||
},
|
||||
fileName: plan6.md,
|
||||
message: This is a major change to fixed-group-1 and pkg3 and a minor change to fixed-group-2,
|
||||
message: This is a major change to fixed-group-1 and pkg3 and a minor change to fixed-group-2
|
||||
,
|
||||
relativePath: .nx/version-plans/plan6.md,
|
||||
},
|
||||
]
|
||||
@ -786,6 +794,11 @@ describe('version-plans', () => {
|
||||
groupVersionBump: patch,
|
||||
message: plan1 message,
|
||||
relativePath: .nx/version-plans/plan1.md,
|
||||
triggeredByProjects: [
|
||||
pkg1,
|
||||
pkg2,
|
||||
pkg3,
|
||||
],
|
||||
},
|
||||
{
|
||||
absolutePath: <workspace-root>/version-plans/plan2.md,
|
||||
@ -794,6 +807,11 @@ describe('version-plans', () => {
|
||||
groupVersionBump: minor,
|
||||
message: plan2 message,
|
||||
relativePath: .nx/version-plans/plan2.md,
|
||||
triggeredByProjects: [
|
||||
pkg1,
|
||||
pkg2,
|
||||
pkg3,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -1005,6 +1023,9 @@ describe('version-plans', () => {
|
||||
groupVersionBump: minor,
|
||||
message: plan2 message,
|
||||
relativePath: .nx/version-plans/plan2.md,
|
||||
triggeredByProjects: [
|
||||
pkg1,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -1026,6 +1047,9 @@ describe('version-plans', () => {
|
||||
groupVersionBump: minor,
|
||||
message: plan2 message,
|
||||
relativePath: .nx/version-plans/plan2.md,
|
||||
triggeredByProjects: [
|
||||
pkg2,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -1047,6 +1071,9 @@ describe('version-plans', () => {
|
||||
groupVersionBump: minor,
|
||||
message: plan2 message,
|
||||
relativePath: .nx/version-plans/plan2.md,
|
||||
triggeredByProjects: [
|
||||
pkg3,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -25,6 +25,11 @@ export interface VersionPlan extends VersionPlanFile {
|
||||
|
||||
export interface GroupVersionPlan extends VersionPlan {
|
||||
groupVersionBump: ReleaseType;
|
||||
/**
|
||||
* Will not be set if the group name was the trigger, otherwise will be a list of
|
||||
* all the individual project names explicitly found in the version plan file.
|
||||
*/
|
||||
triggeredByProjects?: string[];
|
||||
}
|
||||
|
||||
export interface ProjectsVersionPlan extends VersionPlan {
|
||||
@ -54,7 +59,7 @@ export async function readRawVersionPlans(): Promise<RawVersionPlan[]> {
|
||||
relativePath: join(versionPlansDirectory, versionPlanFile),
|
||||
fileName: versionPlanFile,
|
||||
content: parsedContent.attributes,
|
||||
message: getSingleLineMessage(parsedContent.body),
|
||||
message: parsedContent.body,
|
||||
createdOnMs: versionPlanStats.birthtimeMs,
|
||||
});
|
||||
}
|
||||
@ -74,6 +79,12 @@ export function setVersionPlansOnGroups(
|
||||
const isDefaultGroup = isDefault(releaseGroups);
|
||||
|
||||
for (const rawVersionPlan of rawVersionPlans) {
|
||||
if (!rawVersionPlan.message) {
|
||||
throw new Error(
|
||||
`Please add a changelog message to version plan: '${rawVersionPlan.fileName}'`
|
||||
);
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(rawVersionPlan.content)) {
|
||||
if (groupsByName.has(key)) {
|
||||
const group = groupsByName.get(key);
|
||||
@ -232,6 +243,8 @@ export function setVersionPlansOnGroups(
|
||||
`Found a version bump for project '${key}' in '${rawVersionPlan.fileName}' that conflicts with another project's version bump in the same release group '${groupForProject.name}'. When the group is in fixed versioning mode, all projects' version bumps within the same group must match.`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
existingPlan.triggeredByProjects.push(key);
|
||||
}
|
||||
} else {
|
||||
groupForProject.versionPlans.push(<GroupVersionPlan>{
|
||||
@ -241,7 +254,9 @@ export function setVersionPlansOnGroups(
|
||||
createdOnMs: rawVersionPlan.createdOnMs,
|
||||
message: rawVersionPlan.message,
|
||||
// This is a fixed group, so the version bump is for the group, even if a project within it was specified
|
||||
// but we track the projects that triggered the version bump so that we can accurately produce changelog entries.
|
||||
groupVersionBump: value,
|
||||
triggeredByProjects: [key],
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -273,8 +288,3 @@ export function getVersionPlansAbsolutePath() {
|
||||
function isReleaseType(value: string): value is ReleaseType {
|
||||
return RELEASE_TYPES.includes(value as ReleaseType);
|
||||
}
|
||||
|
||||
// changelog messages may only be a single line long, so ignore anything else
|
||||
function getSingleLineMessage(message: string) {
|
||||
return message.trim().split('\n')[0];
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { prompt } from 'enquirer';
|
||||
import { ensureDir, writeFile } from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { ensureDir, readFileSync, writeFile, writeFileSync } from 'fs-extra';
|
||||
import { join } from 'node:path';
|
||||
import { RELEASE_TYPES } from 'semver';
|
||||
import { dirSync } from 'tmp';
|
||||
import { NxReleaseConfiguration, readNxJson } from '../../config/nx-json';
|
||||
import { createProjectFileMapUsingProjectGraph } from '../../project-graph/file-map-utils';
|
||||
import { createProjectGraphAsync } from '../../project-graph/project-graph';
|
||||
@ -13,13 +14,13 @@ import {
|
||||
createNxReleaseConfig,
|
||||
handleNxReleaseConfigError,
|
||||
} from './config/config';
|
||||
import { deepMergeJson } from './config/deep-merge-json';
|
||||
import { filterReleaseGroups } from './config/filter-release-groups';
|
||||
import { getVersionPlansAbsolutePath } from './config/version-plans';
|
||||
import { generateVersionPlanContent } from './utils/generate-version-plan-content';
|
||||
import { parseConventionalCommitsMessage } from './utils/git';
|
||||
import { launchEditor } from './utils/launch-editor';
|
||||
import { printDiff } from './utils/print-changes';
|
||||
import { printConfigAndExit } from './utils/print-config';
|
||||
import { deepMergeJson } from './config/deep-merge-json';
|
||||
|
||||
export const releasePlanCLIHandler = (args: PlanOptions) =>
|
||||
handleErrors(args.verbose, () => createAPI({})(args));
|
||||
@ -79,26 +80,6 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
||||
}
|
||||
};
|
||||
|
||||
if (args.message) {
|
||||
const message = parseConventionalCommitsMessage(args.message);
|
||||
if (!message) {
|
||||
output.error({
|
||||
title: 'Changelog message is not in conventional commits format.',
|
||||
bodyLines: [
|
||||
'Please ensure your message is in the form of:',
|
||||
' type(optional scope): description',
|
||||
'',
|
||||
'For example:',
|
||||
' feat(pkg-b): add new feature',
|
||||
' fix(pkg-a): correct a bug',
|
||||
' chore: update build process',
|
||||
' fix(core)!: breaking change in core package',
|
||||
],
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (releaseGroups[0].name === IMPLICIT_DEFAULT_RELEASE_GROUP) {
|
||||
const group = releaseGroups[0];
|
||||
if (group.projectsRelationship === 'independent') {
|
||||
@ -153,12 +134,14 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const versionPlanMessage = args.message || (await promptForMessage());
|
||||
const versionPlanName = `version-plan-${new Date().getTime()}`;
|
||||
const versionPlanMessage =
|
||||
args.message || (await promptForMessage(versionPlanName));
|
||||
const versionPlanFileContent = generateVersionPlanContent(
|
||||
versionPlanBumps,
|
||||
versionPlanMessage
|
||||
);
|
||||
const versionPlanFileName = `version-plan-${new Date().getTime()}.md`;
|
||||
const versionPlanFileName = `${versionPlanName}.md`;
|
||||
|
||||
if (args.dryRun) {
|
||||
output.logSingleLine(
|
||||
@ -202,47 +185,49 @@ async function promptForVersion(message: string): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
async function promptForMessage(): Promise<string> {
|
||||
async function promptForMessage(versionPlanName: string): Promise<string> {
|
||||
let message: string;
|
||||
do {
|
||||
message = await _promptForMessage();
|
||||
message = await _promptForMessage(versionPlanName);
|
||||
} while (!message);
|
||||
return message;
|
||||
}
|
||||
|
||||
// TODO: support non-conventional commits messages (will require significant changelog renderer changes)
|
||||
async function _promptForMessage(): Promise<string> {
|
||||
async function _promptForMessage(versionPlanName: string): Promise<string> {
|
||||
try {
|
||||
const reply = await prompt<{ message: string }>([
|
||||
{
|
||||
name: 'message',
|
||||
message:
|
||||
'What changelog message would you like associated with this change?',
|
||||
'What changelog message would you like associated with this change? (Leave blank to open an external editor for multi-line messages/easier editing)',
|
||||
type: 'input',
|
||||
},
|
||||
]);
|
||||
|
||||
const conventionalCommitsMessage = parseConventionalCommitsMessage(
|
||||
reply.message
|
||||
);
|
||||
if (!conventionalCommitsMessage) {
|
||||
output.warn({
|
||||
title: 'Changelog message is not in conventional commits format.',
|
||||
bodyLines: [
|
||||
'Please ensure your message is in the form of:',
|
||||
' type(optional scope): description',
|
||||
'',
|
||||
'For example:',
|
||||
' feat(pkg-b): add new feature',
|
||||
' fix(pkg-a): correct a bug',
|
||||
' chore: update build process',
|
||||
' fix(core)!: breaking change in core package',
|
||||
],
|
||||
});
|
||||
return null;
|
||||
let message = reply.message.trim();
|
||||
|
||||
if (!message.length) {
|
||||
const tmpDir = dirSync().name;
|
||||
const messageFilePath = join(
|
||||
tmpDir,
|
||||
`DRAFT_MESSAGE__${versionPlanName}.md`
|
||||
);
|
||||
writeFileSync(messageFilePath, '');
|
||||
await launchEditor(messageFilePath);
|
||||
message = readFileSync(messageFilePath, 'utf-8');
|
||||
}
|
||||
|
||||
return reply.message;
|
||||
message = message.trim();
|
||||
|
||||
if (!message) {
|
||||
output.warn({
|
||||
title:
|
||||
'A changelog message is required in order to create the version plan file',
|
||||
bodyLines: [],
|
||||
});
|
||||
}
|
||||
|
||||
return message;
|
||||
} catch (e) {
|
||||
output.log({
|
||||
title: 'Cancelled version plan creation.',
|
||||
|
||||
@ -29,4 +29,28 @@ describe('generateVersionPlanContent()', () => {
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should work without a message', () => {
|
||||
expect(generateVersionPlanContent({ proj: '1.0.0' }, ''))
|
||||
.toMatchInlineSnapshot(`
|
||||
"---
|
||||
proj: 1.0.0
|
||||
---
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should work with multi-line messages', () => {
|
||||
expect(generateVersionPlanContent({ proj: '1.0.0' }, 'foo\nbar\nbaz'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"---
|
||||
proj: 1.0.0
|
||||
---
|
||||
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@ -2,7 +2,7 @@ export function generateVersionPlanContent(
|
||||
bumps: Record<string, string>,
|
||||
message: string
|
||||
): string {
|
||||
return `---
|
||||
const frontMatter = `---
|
||||
${Object.entries(bumps)
|
||||
.filter(([_, version]) => version !== 'none')
|
||||
.map(([projectOrGroup, version]) => {
|
||||
@ -15,7 +15,7 @@ ${Object.entries(bumps)
|
||||
})
|
||||
.join('\n')}
|
||||
---
|
||||
|
||||
${message}
|
||||
`;
|
||||
|
||||
return `${frontMatter}${message ? `\n${message}\n` : ''}`;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user