From da467de46ef5ca10d388be9ca32094c23c10bfb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CJamesHenry=E2=80=9D?= Date: Wed, 6 Dec 2023 21:11:46 +0400 Subject: [PATCH 1/2] fix(release): allow interpolating {projectName} in custom commit message when valid --- .../command-line/release/utils/shared.spec.ts | 131 ++++++++++++++++++ .../src/command-line/release/utils/shared.ts | 52 ++++++- 2 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 packages/nx/src/command-line/release/utils/shared.spec.ts diff --git a/packages/nx/src/command-line/release/utils/shared.spec.ts b/packages/nx/src/command-line/release/utils/shared.spec.ts new file mode 100644 index 0000000000000..629e2f7973845 --- /dev/null +++ b/packages/nx/src/command-line/release/utils/shared.spec.ts @@ -0,0 +1,131 @@ +import { ReleaseGroupWithName } from '../config/filter-release-groups'; +import { createCommitMessageValues } from './shared'; + +describe('shared', () => { + describe('createCommitMessageValues()', () => { + describe('userCommitMessage interpolation', () => { + it('should strip {projectName} and {version} from the main commit message if multiple release groups, and instead add all the relevant information at the end of the commit', () => { + const releaseGroups: ReleaseGroupWithName[] = [ + { + name: 'one', + projectsRelationship: 'independent', + projects: ['foo'], // single project, will get flattened in the final commit message + version: { + generator: '@nx/js:version', + generatorOptions: {}, + }, + changelog: false, + releaseTagPattern: '{projectName}-{version}', + }, + { + name: 'two', + projectsRelationship: 'fixed', + projects: ['bar', 'baz'], + version: { + generator: '@nx/js:version', + generatorOptions: {}, + }, + changelog: false, + releaseTagPattern: '{projectName}-{version}', + }, + ]; + const releaseGroupToFilteredProjects = new Map() + .set(releaseGroups[0], new Set(['foo'])) + .set(releaseGroups[1], new Set(['bar', 'baz'])); + const versionData = { + foo: { + currentVersion: '1.0.0', + dependentProjects: [], + newVersion: '1.0.1', + }, + bar: { + currentVersion: '1.0.0', + dependentProjects: [], + newVersion: '1.0.1', + }, + baz: { + currentVersion: '1.0.0', + dependentProjects: [], + newVersion: '1.0.1', + }, + }; + const userCommitMessage = + 'chore(release): publish {projectName} v{version}'; + const result = createCommitMessageValues( + releaseGroups, + releaseGroupToFilteredProjects, + versionData, + userCommitMessage + ); + expect(result).toMatchInlineSnapshot(` + [ + "chore(release): publish", + "- project: foo 1.0.1", + "- release-group: two 1.0.1", + ] + `); + }); + + it('should interpolate the {projectName} and {version} within the main commit message if a single project within a single independent release group is being committed', () => { + const releaseGroups: ReleaseGroupWithName[] = [ + { + projectsRelationship: 'independent', + projects: [ + 'native-federation-typescript', + 'native-federation-tests', + 'storybook-addon', + 'typescript', + 'nextjs-mf', + 'utils', + 'enhanced', + 'core', + 'node', + ], + version: { + generator: '@nx/js:release-version', + generatorOptions: { + specifierSource: 'conventional-commits', + currentVersionResolver: 'git-tag', + }, + }, + changelog: { + createRelease: 'github', + entryWhenNoChanges: + 'This was a version bump only for {projectName} to align it with other projects, there were no code changes.', + file: '{projectRoot}/CHANGELOG.md', + renderer: 'nx/changelog-renderer', + renderOptions: { includeAuthors: true }, + }, + releaseTagPattern: '{projectName}-{version}', + name: '__default__', + }, + ]; + + const result = createCommitMessageValues( + releaseGroups, + new Map().set(releaseGroups[0], new Set(['core'])), + { + core: { + currentVersion: '1.0.0-canary.1', + dependentProjects: [ + { + source: 'react_ts_host', + target: 'core', + type: 'static', + dependencyCollection: 'devDependencies', + }, + ], + newVersion: '1.0.0-canary.2', + }, + }, + 'chore(release): Release {projectName} v{version} [skip ci]' + ); + expect(result).toMatchInlineSnapshot(` + [ + "chore(release): Release core v1.0.0-canary.2 [skip ci]", + ] + `); + }); + }); + }); +}); diff --git a/packages/nx/src/command-line/release/utils/shared.ts b/packages/nx/src/command-line/release/utils/shared.ts index ab3843d87b54a..6b680c332b970 100644 --- a/packages/nx/src/command-line/release/utils/shared.ts +++ b/packages/nx/src/command-line/release/utils/shared.ts @@ -108,14 +108,44 @@ export function createCommitMessageValues( } /** - * At this point we have multiple release groups for a single commit, we will not interpolate an overall {version} because that won't be appropriate - * (for any {version} value within the string, we will replace it with an empty string so that it doesn't end up in the final output). + * There is another special case for interpolation: if, after all filtering, we have a single independent release group with a single project. + * In this case we will directly interpolate both {version} and {projectName} within the commit message. + */ + if ( + releaseGroups.length === 1 && + releaseGroups[0].projectsRelationship === 'independent' + ) { + const releaseGroup = releaseGroups[0]; + const releaseGroupProjectNames = Array.from( + releaseGroupToFilteredProjects.get(releaseGroup) + ); + if (releaseGroupProjectNames.length === 1) { + const projectVersionData = versionData[releaseGroupProjectNames[0]]; + const releaseVersion = new ReleaseVersion({ + version: projectVersionData.newVersion, + releaseTagPattern: releaseGroup.releaseTagPattern, + projectName: releaseGroupProjectNames[0], + }); + commitMessageValues[0] = interpolate(commitMessageValues[0], { + version: releaseVersion.rawVersion, + projectName: releaseGroupProjectNames[0], + }).trim(); + return commitMessageValues; + } + } + + /** + * At this point we have multiple release groups for a single commit, we will not interpolate an overall {version} or {projectName} because that won't be + * appropriate (for any {version} or {projectName} value within the string, we will replace it with an empty string so that it doesn't end up in the final output). * * Instead for fixed groups we will add one bullet point the release group, and for independent groups we will add one bullet point per project. */ - commitMessageValues[0] = commitMessageValues[0] - .replace('{version}', '') - .trim(); + commitMessageValues[0] = stripPlaceholders(commitMessageValues[0], [ + // for cleanest possible final result try and replace the common pattern of a v prefix in front of the version first + 'v{version}', + '{version}', + '{projectName}', + ]); for (const releaseGroup of releaseGroups) { const releaseGroupProjectNames = Array.from( @@ -153,6 +183,18 @@ export function createCommitMessageValues( return commitMessageValues; } +function stripPlaceholders(str: string, placeholders: string[]): string { + for (const placeholder of placeholders) { + // for cleanest possible final result try and replace relevant spacing around placeholders first + str = str + .replace(` ${placeholder}`, '') + .replace(`${placeholder} `, '') + .replace(placeholder, '') + .trim(); + } + return str; +} + export function createGitTagValues( releaseGroups: ReleaseGroupWithName[], releaseGroupToFilteredProjects: Map>, From 0b19daab41d1c5d0879541f85aa85243490b3311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CJamesHenry=E2=80=9D?= Date: Thu, 7 Dec 2023 00:37:57 +0400 Subject: [PATCH 2/2] fix(core): only apply the logic if projectName is used --- packages/nx/src/command-line/release/utils/shared.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/nx/src/command-line/release/utils/shared.ts b/packages/nx/src/command-line/release/utils/shared.ts index 6b680c332b970..be2593286c34c 100644 --- a/packages/nx/src/command-line/release/utils/shared.ts +++ b/packages/nx/src/command-line/release/utils/shared.ts @@ -108,12 +108,14 @@ export function createCommitMessageValues( } /** - * There is another special case for interpolation: if, after all filtering, we have a single independent release group with a single project. + * There is another special case for interpolation: if, after all filtering, we have a single independent release group with a single project, + * and the user has provided {projectName} within the custom message. * In this case we will directly interpolate both {version} and {projectName} within the commit message. */ if ( releaseGroups.length === 1 && - releaseGroups[0].projectsRelationship === 'independent' + releaseGroups[0].projectsRelationship === 'independent' && + userCommitMessage?.includes('{projectName}') ) { const releaseGroup = releaseGroups[0]; const releaseGroupProjectNames = Array.from(