Skip to content

fix(release): only error on missing manifestsToUpdate if a project is being directly processed #31004

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 2, 2025
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -889,4 +889,140 @@ describe('Multiple Release Groups', () => {
});
});
});

describe('groups filter', () => {
it('should not error if projects in release groups outside of the filtered groups do not have valid manifestsToUpdate', async () => {
const {
nxReleaseConfig,
projectGraph,
releaseGroups,
releaseGroupToFilteredProjects,
filters,
} = await createNxReleaseConfigAndPopulateWorkspace(
tree,
`
group1 ({ "projectsRelationship": "fixed" }):
- pkg-a@1.0.0 [js]
group2 ({ "projectsRelationship": "fixed" }):
- pkg-c@2.0.0 [js]
`,
{
version: {
conventionalCommits: true,
manifestRootsToUpdate: ['{projectRoot}'],
},
},
mockResolveCurrentVersion,
{
// Only release group1
groups: ['group1'],
}
);

// Delete the package.json for pkg-c which is outside of the filtered groups. This test is asserting that this does NOT throw an error.
tree.delete('pkg-c/package.json');

mockDeriveSpecifierFromConventionalCommits.mockImplementation(
(_, __, ___, ____, { name: projectName }) => {
if (projectName === 'pkg-a') return 'minor';
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();

// Called for each project
expect(mockResolveVersionActionsForProject).toHaveBeenCalledTimes(2);

expect(tree.read('pkg-a/package.json', 'utf-8')).toMatchInlineSnapshot(`
"{
"name": "pkg-a",
"version": "1.1.0"
}
"
`);
});

it('should error when projects in release groups outside of the filtered groups do not have valid manifestsToUpdate IF they are required to be processed because of dependencies to groups included in the filtered groups', 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]
- pkg-d@2.0.0 [js]
`,
{
version: {
conventionalCommits: true,
manifestRootsToUpdate: ['{projectRoot}'],
},
},
mockResolveCurrentVersion,
{
// Only release group2 (but group1 has a dependency on group2)
groups: ['group2'],
}
);

// Delete the package.json for pkg-c which is outside of the filtered groups. This test is asserting that this DOES throw an error.
tree.delete('pkg-c/package.json');

mockDeriveSpecifierFromConventionalCommits.mockImplementation(
(_, __, ___, ____, { name: projectName }) => {
if (projectName === 'pkg-c') return 'patch';
return 'none';
}
);

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

await expect(processor.init()).rejects
.toThrowErrorMatchingInlineSnapshot(`
"The project "pkg-c" does not have a package.json file available in ./pkg-c.

To fix this you will either need to add a package.json file at that location, or configure "release" within your nx.json to exclude "pkg-c" from the current release group, or amend the "release.version.manifestRootsToUpdate" configuration to point to where the relevant manifest should be.

It is also possible that the project is being processed because of a dependency relationship between what you are directly versioning and the project/release group, in which case you will need to amend your filters to include all relevant projects and release groups."
`);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,8 @@ export class ReleaseGroupProcessor {
// Track the projects being directly versioned
let projectsToProcess = new Set<string>();

const resolveVersionActionsForProjectCallbacks = [];

// Precompute all projects in nx release config
for (const [groupName, group] of Object.entries(
this.nxReleaseConfig.groups
Expand Down Expand Up @@ -453,23 +455,34 @@ export class ReleaseGroupProcessor {
);
this.finalConfigsByProject.set(project, finalConfigForProject);

const {
versionActionsPath,
versionActions,
afterAllProjectsVersioned,
} = await resolveVersionActionsForProject(
this.tree,
releaseGroup,
projectGraphNode,
finalConfigForProject
);
if (!this.uniqueAfterAllProjectsVersioned.has(versionActionsPath)) {
this.uniqueAfterAllProjectsVersioned.set(
/**
* For our versionActions validation to accurate, we need to wait until the full allProjectsToProcess
* set is populated so that all dependencies, including those across release groups, are included.
*
* In order to save us fully traversing the graph again to arrive at this project level, schedule a callback
* to resolve the versionActions for the project only once we have all the projects to process.
*/
resolveVersionActionsForProjectCallbacks.push(async () => {
const {
versionActionsPath,
afterAllProjectsVersioned
versionActions,
afterAllProjectsVersioned,
} = await resolveVersionActionsForProject(
this.tree,
releaseGroup,
projectGraphNode,
finalConfigForProject,
// Will be fully populated by the time this callback is executed
this.allProjectsToProcess.has(project)
);
}
this.projectsToVersionActions.set(project, versionActions);
if (!this.uniqueAfterAllProjectsVersioned.has(versionActionsPath)) {
this.uniqueAfterAllProjectsVersioned.set(
versionActionsPath,
afterAllProjectsVersioned
);
}
this.projectsToVersionActions.set(project, versionActions);
});
}
}

Expand All @@ -489,6 +502,11 @@ export class ReleaseGroupProcessor {
}

this.allProjectsToProcess = new Set(projectsToProcess);

// Execute all the callbacks to resolve the version actions for the projects
for (const cb of resolveVersionActionsForProjectCallbacks) {
await cb();
}
}

/**
Expand Down
9 changes: 5 additions & 4 deletions packages/nx/src/command-line/release/version/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,8 @@ export async function mockResolveVersionActionsForProjectImplementation(
tree: Tree,
releaseGroup: any,
projectGraphNode: any,
finalConfigForProject: FinalConfigForProject
finalConfigForProject: FinalConfigForProject,
isInProjectsToProcess: boolean
) {
if (
projectGraphNode.data.release?.versionActions ===
Expand All @@ -597,7 +598,7 @@ export async function mockResolveVersionActionsForProjectImplementation(
finalConfigForProject
);
// Initialize the versionActions with all the required manifest paths etc
await versionActions.init(tree);
await versionActions.init(tree, isInProjectsToProcess);
return {
versionActionsPath: exampleRustVersionActions,
versionActions,
Expand All @@ -615,7 +616,7 @@ export async function mockResolveVersionActionsForProjectImplementation(
finalConfigForProject
);
// Initialize the versionActions with all the required manifest paths etc
await versionActions.init(tree);
await versionActions.init(tree, isInProjectsToProcess);
return {
versionActionsPath: exampleNonSemverVersionActions,
versionActions,
Expand All @@ -632,7 +633,7 @@ export async function mockResolveVersionActionsForProjectImplementation(
finalConfigForProject
);
// Initialize the versionActions with all the required manifest paths etc
await versionActions.init(tree);
await versionActions.init(tree, isInProjectsToProcess);
return {
versionActionsPath,
versionActions: versionActions,
Expand Down
19 changes: 13 additions & 6 deletions packages/nx/src/command-line/release/version/version-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ export async function resolveVersionActionsForProject(
tree: Tree,
releaseGroup: ReleaseGroupWithName,
projectGraphNode: ProjectGraphProjectNode,
finalConfigForProject: FinalConfigForProject
finalConfigForProject: FinalConfigForProject,
isInProjectsToProcess: boolean
): Promise<{
versionActionsPath: string;
versionActions: VersionActions;
Expand Down Expand Up @@ -172,7 +173,7 @@ export async function resolveVersionActionsForProject(
finalConfigForProject
);
// Initialize the version actions with all the required manifest paths etc
await versionActions.init(tree);
await versionActions.init(tree, isInProjectsToProcess);
return {
versionActionsPath,
versionActions,
Expand Down Expand Up @@ -212,7 +213,7 @@ export abstract class VersionActions {
/**
* Asynchronous initialization of the version actions and validation of certain configuration options.
*/
async init(tree: Tree): Promise<void> {
async init(tree: Tree, isInProjectsToProcess: boolean): Promise<void> {
// Default to the first available source manifest root, if applicable, if no custom manifest roots are provided
if (
this.validManifestFilenames?.length &&
Expand Down Expand Up @@ -260,14 +261,20 @@ export abstract class VersionActions {
break;
}
}
if (!hasValidManifest) {
/**
* If projects or groups filters are applied, it is possible that the project is not being actively processed
* and we should not throw an error in this case.
*/
if (!hasValidManifest && isInProjectsToProcess) {
const validManifestFilenames =
this.validManifestFilenames?.join(' or ');

throw new Error(
`The project "${this.projectGraphNode.name}" does not have a ${validManifestFilenames} file available in ./${interpolatedManifestRoot.path}.

To fix this you will either need to add a ${validManifestFilenames} file at that location, or configure "release" within your nx.json to exclude "${this.projectGraphNode.name}" from the current release group, or amend the "release.version.manifestRootsToUpdate" configuration to point to where the relevant manifest should be.`

To fix this you will either need to add a ${validManifestFilenames} file at that location, or configure "release" within your nx.json to exclude "${this.projectGraphNode.name}" from the current release group, or amend the "release.version.manifestRootsToUpdate" configuration to point to where the relevant manifest should be.

It is also possible that the project is being processed because of a dependency relationship between what you are directly versioning and the project/release group, in which case you will need to amend your filters to include all relevant projects and release groups.`
);
}
}
Expand Down
Loading