Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions e2e/release/src/independent-projects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ describe('nx release - independent projects', () => {
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json
{project-name} ❓ Applied semver relative bump "patch", because a dependency was bumped, to get new version 999.9.9
{project-name} ✍️ New version 999.9.9 written to manifest: {project-name}/package.json
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json

NX Running release version for project: {project-name}

Expand Down
7 changes: 6 additions & 1 deletion e2e/release/src/release.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from '@nx/e2e-utils';
import { execSync } from 'node:child_process';
import type { NxReleaseVersionConfiguration } from 'nx/src/config/nx-json';
import { setupWorkspaces } from './utils';

expect.addSnapshotSerializer({
serialize(str: string) {
Expand Down Expand Up @@ -58,7 +59,7 @@ describe('nx release', () => {
let pkg3: string;
let previousPackageManager: string;

beforeAll(() => {
beforeAll(async () => {
previousPackageManager = process.env.SELECTED_PM;
// Ensure consistent package manager usage in all environments for this file
process.env.SELECTED_PM = 'npm';
Expand All @@ -82,6 +83,10 @@ describe('nx release', () => {
json.dependencies[`@proj/${pkg1}`] = '0.0.0';
return json;
});

setupWorkspaces(pkg1, pkg2, pkg3);
const pmc = getPackageManagerCommand();
await runCommandAsync(pmc.install);
});
afterAll(() => {
cleanupProject();
Expand Down
19 changes: 19 additions & 0 deletions e2e/release/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { detectPackageManager, createFile, updateJson } from '@nx/e2e-utils';

export function setupWorkspaces(...packages: string[]) {
const pkgManager = detectPackageManager();
if (pkgManager === 'npm' || pkgManager === 'yarn') {
updateJson('package.json', (packageJson) => {
packageJson.workspaces = packages;
return packageJson;
});
} else if (pkgManager === 'pnpm') {
createFile(
`pnpm-workspace.yaml`,
`
packages:
${packages.map((p) => `- ${p}`).join('\n ')}
`
);
}
}
1 change: 1 addition & 0 deletions e2e/release/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"types": ["jest", "node"]
},
"include": [
"src/utils.ts",
"**/*.test.ts",
"**/*.spec.ts",
"**/*.spec.tsx",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,75 @@ describe('ReleaseGroupProcessor', () => {
* will have already been caught and handled by filterReleaseGroups(), so that is not repeated here.
*/
describe('filters', () => {
it('should bump projects across release groups when one release group is selected but all projects are in nx release config', async () => {
const {
nxReleaseConfig,
projectGraph,
releaseGroups,
releaseGroupToFilteredProjects,
filters,
} = await createNxReleaseConfigAndPopulateWorkspace(
tree,
`
projectJ ({ "projectsRelationship": "independent" }):
- projectJ@1.0.0 [js]
-> depends on projectK
projectK ({ "projectsRelationship": "independent" }):
- projectK@2.0.0 [js]
`,
{
version: {
conventionalCommits: true,
updateDependents: 'auto',
},
},
mockResolveCurrentVersion,
{
// Apply the groups filter to only include projectK in versioning.
groups: ['projectK'],
}
);

const processor = new ReleaseGroupProcessor(
tree,
projectGraph,
nxReleaseConfig,
releaseGroups,
releaseGroupToFilteredProjects,
{
dryRun: false,
verbose: false,
firstRelease: false,
preid: undefined,
filters,
}
);
await processor.init();

mockDeriveSpecifierFromConventionalCommits.mockImplementation(
() => 'minor'
);
await processor.processGroups();

// projectJ should be bumped because updateDependents is set to "auto"
expect(readJson(tree, 'projectJ/package.json')).toMatchInlineSnapshot(`
{
"dependencies": {
"projectK": "2.1.0",
},
"name": "projectJ",
"version": "1.0.1",
}
`);

expect(readJson(tree, 'projectK/package.json')).toMatchInlineSnapshot(`
{
"name": "projectK",
"version": "2.1.0",
}
`);
});

it('should filter out projects with no dependency relationships within a single independent release group based on the provided user filter', async () => {
const {
nxReleaseConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ export class ReleaseGroupProcessor {
private projectToUpdateDependentsSetting: Map<string, 'auto' | 'never'> =
new Map();

private cachedIsDependentUpdate: Map<string, boolean> = new Map();

constructor(
private tree: Tree,
private projectGraph: ProjectGraph,
Expand Down Expand Up @@ -323,6 +325,18 @@ export class ReleaseGroupProcessor {
});
}

for (const [groupName, group] of Object.entries(
this.nxReleaseConfig.groups
)) {
if (!this.groupGraph.has(groupName)) {
this.groupGraph.set(groupName, {
group: { ...group, name: groupName, resolvedVersionPlans: false },
dependencies: new Set(),
dependents: new Set(),
});
}
}

// Process each project within each release group
for (const [, releaseGroupNode] of this.groupGraph) {
for (const projectName of releaseGroupNode.group.projects) {
Expand Down Expand Up @@ -423,6 +437,26 @@ export class ReleaseGroupProcessor {
this.projectToUpdateDependentsSetting.set(project, updateDependents);
}
}

for (const [groupName, group] of Object.entries(
this.nxReleaseConfig.groups
)) {
for (const project of group.projects) {
if (!this.projectToReleaseGroup.has(project)) {
this.projectToReleaseGroup.set(project, {
...group,
name: groupName,
resolvedVersionPlans: false,
});

// Cache updateDependents setting relevant for each project
const updateDependents =
((group.version as NxReleaseVersionConfiguration)
?.updateDependents as 'auto' | 'never') || 'auto';
this.projectToUpdateDependentsSetting.set(project, updateDependents);
}
}
}
}

/**
Expand Down Expand Up @@ -669,7 +703,10 @@ export class ReleaseGroupProcessor {
const processOrder: string[] = [];

// Use the topologically sorted groups instead of getNextGroup
for (const nextGroup of this.sortedReleaseGroups) {
const filteredSortedReleaseGroups = this.sortedReleaseGroups.filter(
(name) => this.releaseGroups.find((group) => group.name === name)
);
for (const nextGroup of filteredSortedReleaseGroups) {
// Skip groups that have already been processed (could happen with circular dependencies)
if (this.processedGroups.has(nextGroup)) {
continue;
Expand Down Expand Up @@ -1064,7 +1101,8 @@ export class ReleaseGroupProcessor {
for (const project of sortedProjects) {
if (
projectsToUpdate.has(project) &&
releaseGroupFilteredProjects.has(project)
(releaseGroupFilteredProjects?.has(project) ||
this.isDependentUpdate(project))
) {
await this.updateDependenciesForProject(project);
}
Expand Down Expand Up @@ -1722,4 +1760,32 @@ Valid values are: ${validReleaseVersionPrefixes
private getProjectDependencies(project: string): Set<string> {
return this.projectToDependencies.get(project) || new Set();
}

private isDependentUpdate(project: string) {
if (this.cachedIsDependentUpdate.has(project)) {
return this.cachedIsDependentUpdate.get(project)!;
}

if (!this.hasAutoUpdateDependents(project)) {
this.cachedIsDependentUpdate.set(project, false);
return false;
}

// Check if the project depends on any project in the filtered release groups
const dependencies = this.projectToDependencies.get(project);
if (dependencies) {
for (const dependency of dependencies) {
if (
this.releaseGroups.some((group) =>
group.projects.includes(dependency)
)
) {
this.cachedIsDependentUpdate.set(project, true);
return true;
}
}
}
this.cachedIsDependentUpdate.set(project, false);
return false;
}
}
Loading