Skip to content

Commit 5fd57ba

Browse files
authored
fix(core): filter out automated release commits in getCommitsRelevantToProjects (#33482)
1 parent 626fb08 commit 5fd57ba

4 files changed

Lines changed: 111 additions & 20 deletions

File tree

packages/nx/src/command-line/release/utils/resolve-semver-specifier.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,21 @@ export async function resolveSemverSpecifierFromConventionalCommits(
1111
from: string,
1212
projectGraph: ProjectGraph,
1313
projectNames: string[],
14-
conventionalCommitsConfig: NxReleaseConfig['conventionalCommits']
14+
releaseConfig: NxReleaseConfig
1515
): // Map of projectName to semver bump type
1616
Promise<Map<string, SemverSpecifier | null>> {
1717
const commits = await getGitDiff(from);
1818
const parsedCommits = parseCommits(commits);
1919
const relevantCommits = await getCommitsRelevantToProjects(
2020
projectGraph,
2121
parsedCommits,
22-
projectNames
22+
projectNames,
23+
releaseConfig
24+
);
25+
return determineSemverChange(
26+
relevantCommits,
27+
releaseConfig.conventionalCommits
2328
);
24-
return determineSemverChange(relevantCommits, conventionalCommitsConfig);
2529
}
2630

2731
export async function resolveSemverSpecifierFromPrompt(

packages/nx/src/command-line/release/utils/shared.spec.ts

Lines changed: 68 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ jest.mock('../../../config/nx-json', () => ({
1313
readNxJson: jest.fn(),
1414
}));
1515
import { createVersionConfig } from './test/test-utils';
16+
import { createNxReleaseConfig, NxReleaseConfig } from '../config/config';
17+
import { createProjectFileMapUsingProjectGraph } from '../../../project-graph/file-map-utils';
1618

1719
describe('shared', () => {
1820
describe('createCommitMessageValues()', () => {
@@ -483,8 +485,9 @@ describe('shared', () => {
483485

484486
describe(`getCommitsRelevantToProjects()`, () => {
485487
let mockProjectGraph: ProjectGraph;
488+
let mockReleaseConfig: NxReleaseConfig | null;
486489

487-
beforeEach(() => {
490+
beforeEach(async () => {
488491
(readNxJson as jest.Mock).mockReturnValue({});
489492

490493
mockProjectGraph = {
@@ -532,6 +535,19 @@ describe('shared', () => {
532535
},
533536
externalNodes: {},
534537
};
538+
539+
({ nxReleaseConfig: mockReleaseConfig } = await createNxReleaseConfig(
540+
mockProjectGraph,
541+
await createProjectFileMapUsingProjectGraph(mockProjectGraph),
542+
{
543+
projects: Object.keys(mockProjectGraph.nodes),
544+
changelog: {
545+
git: {
546+
commitMessage: 'chore(release): publish packages',
547+
},
548+
},
549+
}
550+
));
535551
});
536552

537553
it('should include commits that directly touch target projects', async () => {
@@ -544,7 +560,8 @@ describe('shared', () => {
544560
const result = await getCommitsRelevantToProjects(
545561
mockProjectGraph,
546562
commits,
547-
['lib-a', 'lib-b']
563+
['lib-a', 'lib-b'],
564+
mockReleaseConfig!
548565
);
549566

550567
expect(result.size).toBe(2);
@@ -564,7 +581,8 @@ describe('shared', () => {
564581
const result = await getCommitsRelevantToProjects(
565582
mockProjectGraph,
566583
commits,
567-
['lib-a']
584+
['lib-a'],
585+
mockReleaseConfig!
568586
);
569587

570588
// Both commits should be included - nx.json affects all, and lib-a is directly touched
@@ -584,7 +602,8 @@ describe('shared', () => {
584602
const result = await getCommitsRelevantToProjects(
585603
mockProjectGraph,
586604
commits,
587-
['lib-a']
605+
['lib-a'],
606+
mockReleaseConfig!
588607
);
589608

590609
expect(result.size).toBe(1);
@@ -610,7 +629,8 @@ describe('shared', () => {
610629
const result = await getCommitsRelevantToProjects(
611630
mockProjectGraph,
612631
commits,
613-
['lib-a']
632+
['lib-a'],
633+
mockReleaseConfig!
614634
);
615635

616636
// lib-a depends on lib-c, so commit touching lib-c should affect lib-a
@@ -631,7 +651,8 @@ describe('shared', () => {
631651
const result = await getCommitsRelevantToProjects(
632652
mockProjectGraph,
633653
commits,
634-
['lib-a']
654+
['lib-a'],
655+
mockReleaseConfig!
635656
);
636657

637658
expect(result.size).toBe(1);
@@ -650,7 +671,8 @@ describe('shared', () => {
650671
const result = await getCommitsRelevantToProjects(
651672
mockProjectGraph,
652673
commits,
653-
['lib-a', 'lib-b']
674+
['lib-a', 'lib-b'],
675+
mockReleaseConfig!
654676
);
655677

656678
// Same commit should appear for both projects
@@ -667,7 +689,8 @@ describe('shared', () => {
667689
const result = await getCommitsRelevantToProjects(
668690
mockProjectGraph,
669691
commits,
670-
['lib-a', 'lib-b']
692+
['lib-a', 'lib-b'],
693+
mockReleaseConfig!
671694
);
672695

673696
// Global file should appear for all requested projects
@@ -686,7 +709,8 @@ describe('shared', () => {
686709
const result = await getCommitsRelevantToProjects(
687710
mockProjectGraph,
688711
commits,
689-
['lib-a', 'lib-b']
712+
['lib-a', 'lib-b'],
713+
mockReleaseConfig!
690714
);
691715

692716
expect(result.has('lib-a')).toBe(false);
@@ -698,7 +722,8 @@ describe('shared', () => {
698722
const result = await getCommitsRelevantToProjects(
699723
mockProjectGraph,
700724
[],
701-
['lib-a']
725+
['lib-a'],
726+
mockReleaseConfig!
702727
);
703728

704729
expect(result.size).toBe(0);
@@ -712,7 +737,8 @@ describe('shared', () => {
712737
const result = await getCommitsRelevantToProjects(
713738
mockProjectGraph,
714739
commits,
715-
[]
740+
[],
741+
mockReleaseConfig!
716742
);
717743

718744
expect(result.size).toBe(0);
@@ -727,7 +753,8 @@ describe('shared', () => {
727753
const result = await getCommitsRelevantToProjects(
728754
mockProjectGraph,
729755
commits,
730-
['lib-a']
756+
['lib-a'],
757+
mockReleaseConfig!
731758
);
732759

733760
// Lock file changes typically affect all or many projects
@@ -743,19 +770,45 @@ describe('shared', () => {
743770
const result = await getCommitsRelevantToProjects(
744771
mockProjectGraph,
745772
commits,
746-
['lib-a']
773+
['lib-a'],
774+
mockReleaseConfig!
747775
);
748776

749777
// package.json changes typically affect projects
750778
expect(result.size).toBeGreaterThanOrEqual(0);
751779
});
752780

781+
it('should exclude automated version or changelog commits', async () => {
782+
const commits: GitCommit[] = [
783+
createMockCommit(
784+
'abc123',
785+
['libs/deleted-lib-1/package.json'],
786+
'chore(release): publish 1.0.0' // with version interpolated
787+
),
788+
createMockCommit(
789+
'def456',
790+
['libs/deleted-lib-2/package.json'],
791+
'chore(release): publish packages'
792+
),
793+
];
794+
795+
const result = await getCommitsRelevantToProjects(
796+
mockProjectGraph,
797+
commits,
798+
['lib-a'],
799+
mockReleaseConfig!
800+
);
801+
802+
expect(result.size).toBe(0);
803+
});
804+
753805
function createMockCommit(
754806
shortHash: string,
755-
affectedFiles: string[]
807+
affectedFiles: string[],
808+
message?: string
756809
): GitCommit {
757810
return {
758-
message: `feat: commit ${shortHash}`,
811+
message: message || `feat: commit ${shortHash}`,
759812
body: '',
760813
shortHash,
761814
author: { name: 'Test Author', email: '[email protected]' },

packages/nx/src/command-line/release/utils/shared.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { NxArgs } from '../../../utils/command-line-utils';
1313
import { output } from '../../../utils/output';
1414
import type { ReleaseGroupWithName } from '../config/filter-release-groups';
1515
import { GitCommit, gitAdd, gitCommit } from './git';
16+
import { NxReleaseConfig } from '../config/config';
1617

1718
export const noDiffInChangelogMessage = chalk.yellow(
1819
`NOTE: There was no diff detected for the changelog entry. Maybe you intended to pass alternative git references via --from and --to?`
@@ -387,10 +388,38 @@ export function handleDuplicateGitTags(gitTagValues: string[]): void {
387388
}
388389
}
389390

391+
function isAutomatedReleaseCommit(
392+
message: string,
393+
nxReleaseConfig: NxReleaseConfig
394+
) {
395+
// All possible commit message patterns based on config
396+
const commitMessagePatterns = [
397+
nxReleaseConfig.git.commitMessage,
398+
nxReleaseConfig.version.git.commitMessage,
399+
nxReleaseConfig.changelog.git.commitMessage,
400+
];
401+
// Check if message matches any pattern
402+
for (const pattern of commitMessagePatterns) {
403+
if (!pattern) continue;
404+
// Split on {version}, escape each part for regex, then join with version pattern
405+
const parts = pattern.split('{version}');
406+
const escapedParts = parts.map((part) =>
407+
part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
408+
);
409+
const regexPattern = escapedParts.join('\\S+');
410+
const regex = new RegExp(`^${regexPattern}$`);
411+
if (regex.test(message)) {
412+
return true;
413+
}
414+
}
415+
return false;
416+
}
417+
390418
export async function getCommitsRelevantToProjects(
391419
projectGraph: ProjectGraph,
392420
commits: GitCommit[],
393-
projects: string[]
421+
projects: string[],
422+
nxReleaseConfig: NxReleaseConfig
394423
): // Map of projectName to GitCommit[]
395424
Promise<Map<string, { commit: GitCommit; isProjectScopedCommit: boolean }[]>> {
396425
const projectSet = new Set(projects);
@@ -400,6 +429,11 @@ Promise<Map<string, { commit: GitCommit; isProjectScopedCommit: boolean }[]>> {
400429
> = new Map();
401430

402431
for (const commit of commits) {
432+
// Filter out automated release commits
433+
if (isAutomatedReleaseCommit(commit.message, nxReleaseConfig)) {
434+
continue;
435+
}
436+
403437
// Convert affectedFiles to FileChange[] format with proper diff computation
404438
const touchedFiles = calculateFileChanges(commit.affectedFiles, {
405439
base: `${commit.shortHash}^`,

packages/nx/src/command-line/release/version/derive-specifier-from-conventional-commits.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export async function deriveSpecifierFromConventionalCommits(
5555
previousVersionRef,
5656
projectGraph,
5757
affectedProjects,
58-
nxReleaseConfig.conventionalCommits
58+
nxReleaseConfig
5959
);
6060

6161
const getHighestSemverChange = (

0 commit comments

Comments
 (0)