fix(release): sort groups topologically bottom-up and fix typo to allow multi-level group dependencies (#31374)

This commit is contained in:
Miguel 2025-06-06 11:05:13 +02:00 committed by GitHub
parent c43d2f2d62
commit fa9290abf4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 158 additions and 46 deletions

View File

@ -343,6 +343,12 @@ describe('nx release - independent projects', () => {
"scripts": {
"name": "@proj/{project-name}",
- "version": "999.9.9-version-git-operations-test.2",
+ "version": "999.9.9-version-git-operations-test.3",
"scripts": {
"name": "@proj/{project-name}",
- "version": "999.9.9",
+ "version": "999.9.9-version-git-operations-test.3",
@ -354,12 +360,6 @@ describe('nx release - independent projects', () => {
}
"name": "@proj/{project-name}",
- "version": "999.9.9-version-git-operations-test.2",
+ "version": "999.9.9-version-git-operations-test.3",
"scripts": {
Skipped lock file update because {package-manager} workspaces are not enabled.
NX Committing changes with git
@ -906,7 +906,7 @@ describe('nx release - independent projects', () => {
expect(
releaseOutput.match(new RegExp(`New version 1\.4\.1 written`, 'g'))
.length
).toEqual(1);
).toEqual(2);
expect(
releaseOutput.match(new RegExp(`New version 1\.6\.1 written`, 'g'))

View File

@ -186,18 +186,6 @@ describe('nx release multiple release branches', () => {
{project-name} New version 0.1.0 written to manifest: {project-name}/package.json
"name": "@proj/{project-name}",
- "version": "0.0.7",
+ "version": "0.1.0",
"scripts": {
"name": "@proj/{project-name}",
- "version": "0.0.7",
+ "version": "0.1.0",
"scripts": {
"name": "@proj/{project-name}",
- "version": "0.0.7",
+ "version": "0.1.0",
@ -207,6 +195,18 @@ describe('nx release multiple release branches', () => {
+
"name": "@proj/{project-name}",
- "version": "0.0.7",
+ "version": "0.1.0",
"scripts": {
"name": "@proj/{project-name}",
- "version": "0.0.7",
+ "version": "0.1.0",
"scripts": {
NX Committing changes with git
@ -325,18 +325,6 @@ describe('nx release multiple release branches', () => {
{project-name} New version 0.1.0 written to manifest: {project-name}/package.json
"name": "@proj/{project-name}",
- "version": "0.0.0",
+ "version": "0.1.0",
"scripts": {
"name": "@proj/{project-name}",
- "version": "0.0.0",
+ "version": "0.1.0",
"scripts": {
"name": "@proj/{project-name}",
- "version": "0.0.0",
+ "version": "0.1.0",
@ -346,6 +334,18 @@ describe('nx release multiple release branches', () => {
+
"name": "@proj/{project-name}",
- "version": "0.0.0",
+ "version": "0.1.0",
"scripts": {
"name": "@proj/{project-name}",
- "version": "0.0.0",
+ "version": "0.1.0",
"scripts": {
NX Committing changes with git

View File

@ -149,16 +149,16 @@ describe('nx release preserve local dependency protocols', () => {
- "version": "0.0.0",
+ "version": "0.1.0",
"scripts": {
"name": "@proj/{project-name}",
- "version": "0.0.0",
+ "version": "0.1.0",
"scripts": {
"dependencies": {
- "@proj/{project-name}": "workspace:*"
+ "@proj/{project-name}": "0.1.0"
}
}
+
"name": "@proj/{project-name}",
- "version": "0.0.0",
+ "version": "0.1.0",
"scripts": {
NX Updating PM lock file
Would update pnpm-lock.yaml with the following command, but --dry-run was set:
pnpm install --lockfile-only
@ -194,12 +194,12 @@ describe('nx release preserve local dependency protocols', () => {
- "version": "0.0.0",
+ "version": "0.1.0",
"scripts": {
}
+
"name": "@proj/{project-name}",
- "version": "0.0.0",
+ "version": "0.1.0",
"scripts": {
}
+
NX Updating PM lock file
Would update pnpm-lock.yaml with the following command, but --dry-run was set:
pnpm install --lockfile-only

View File

@ -954,7 +954,7 @@ Update packages in both groups with a mix #2
);
expect(versionResult).toContain(
`git add ${pkg5}/package.json ${pkg4}/package.json ${pkg3}/package.json ${pkg2}/package.json ${pkg1}/package.json`
`git add ${pkg1}/package.json ${pkg2}/package.json ${pkg3}/package.json ${pkg4}/package.json ${pkg5}/package.json`
);
expect(readdirSync(versionPlansDir).length).toEqual(2);

View File

@ -434,6 +434,118 @@ describe('Multiple Release Groups', () => {
});
});
describe('Three related groups, all fixed relationship, just JS', () => {
it('should correctly version projects across transitive group boundaries', async () => {
const {
nxReleaseConfig,
projectGraph,
releaseGroups,
releaseGroupToFilteredProjects,
filters,
} = await createNxReleaseConfigAndPopulateWorkspace(
tree,
`
group1 ({ "projectsRelationship": "fixed" }):
- pkg-a@1.0.0 [js]
-> depends on pkg-c
- pkg-b@1.0.0 [js]
group2 ({ "projectsRelationship": "fixed" }):
- pkg-c@2.0.0 [js]
-> depends on pkg-e
- pkg-d@2.0.0 [js]
group3 ({ "projectsRelationship": "fixed" }):
- pkg-e@3.0.0 [js]
- pkg-f@3.0.0 [js]
`,
{
version: {
conventionalCommits: true,
},
},
mockResolveCurrentVersion
);
mockDeriveSpecifierFromConventionalCommits.mockImplementation(
(_, __, ___, ____, { name: projectName }) => {
// pkg-e has a bump, which should cause pkg-f to bump because they are in a fixed group
// pkg-c depends on pkg-e so should also bump, and pkg-d is in a fixed group with pkg-c so should also bump
// pkg-a depends on pkg-c so should also bump, and pkg-b is in a fixed group with pkg-a so should also bump
if (projectName === 'pkg-e') return 'patch';
return 'none';
}
);
const processor = new ReleaseGroupProcessor(
tree,
projectGraph,
nxReleaseConfig,
releaseGroups,
releaseGroupToFilteredProjects,
{
dryRun: false,
verbose: false,
firstRelease: false,
preid: undefined,
filters,
}
);
await processor.init();
await processor.processGroups();
expect(mockResolveVersionActionsForProject).toHaveBeenCalledTimes(6);
expect(tree.read('pkg-a/package.json', 'utf-8')).toMatchInlineSnapshot(`
"{
"name": "pkg-a",
"version": "1.0.1",
"dependencies": {
"pkg-c": "2.0.1"
}
}
"
`);
expect(tree.read('pkg-b/package.json', 'utf-8')).toMatchInlineSnapshot(`
"{
"name": "pkg-b",
"version": "1.0.1"
}
"
`);
expect(tree.read('pkg-c/package.json', 'utf-8')).toMatchInlineSnapshot(`
"{
"name": "pkg-c",
"version": "2.0.1",
"dependencies": {
"pkg-e": "3.0.1"
}
}
"
`);
expect(tree.read('pkg-d/package.json', 'utf-8')).toMatchInlineSnapshot(`
"{
"name": "pkg-d",
"version": "2.0.1"
}
"
`);
expect(tree.read('pkg-e/package.json', 'utf-8')).toMatchInlineSnapshot(`
"{
"name": "pkg-e",
"version": "3.0.1"
}
"
`);
expect(tree.read('pkg-f/package.json', 'utf-8')).toMatchInlineSnapshot(`
"{
"name": "pkg-f",
"version": "3.0.1"
}
"
`);
});
});
describe('Two related groups, both independent relationship, just JS', () => {
const graphDefinition = `
group1 ({ "projectsRelationship": "independent" }):

View File

@ -1504,7 +1504,7 @@ Valid values are: ${validReleaseVersionPrefixes
if (releaseGroup.projectsRelationship === 'fixed') {
// For fixed groups, we only need to check one project
const project = releaseGroupFilteredProjects[0];
const project = releaseGroupFilteredProjects.values().next().value;
const dependencies = this.projectGraph.dependencies[project] || [];
const hasDependencyInChangedGroup = dependencies.some(
(dep) =>

View File

@ -25,10 +25,10 @@ describe('topologicalSort', () => {
const indexC = result.indexOf('C');
const indexD = result.indexOf('D');
expect(indexA).toBeLessThan(indexB); // A before B
expect(indexA).toBeLessThan(indexD); // A before D
expect(indexB).toBeLessThan(indexC); // B before C
expect(indexD).toBeLessThan(indexC); // D before C
expect(indexB).toBeLessThan(indexA); // B before A
expect(indexD).toBeLessThan(indexA); // D before A
expect(indexC).toBeLessThan(indexB); // C before B
expect(indexC).toBeLessThan(indexD); // C before D
});
it('should handle cycles by breaking them', () => {
@ -149,8 +149,8 @@ describe('topologicalSort', () => {
const indexC = result.indexOf('C');
const indexD = result.indexOf('D');
expect(indexA).toBeLessThan(indexB); // A before B
expect(indexC).toBeLessThan(indexD); // C before D
expect(indexB).toBeLessThan(indexA); // B before A
expect(indexD).toBeLessThan(indexC); // D before C
});
it('should handle circular dependencies between two nodes', () => {

View File

@ -44,5 +44,5 @@ export function topologicalSort<T>(
}
}
return result.reverse();
return result;
}