Skip to content

releaseChangelog() should not attempt to get tags & generate changelogs for dependent projects when projectsRelationship: 'independent' #34438

@w-biggs

Description

@w-biggs

Current Behavior

When running releaseChangelog() (or nx release changelog) on a project whose release is configured with projectsRelationship: 'independent', the code attempts to get git history & the latest release tag for every dependent project, even though those dependents won't actually have changelog entries generated.

Expected Behavior

releaseChangelog() should not attempt to get the latest release tag for dependent projects. That info does not get used, the git operations are heavy, and releaseChangelog() should not error if, say, a dependent project does not have a release yet.

GitHub Repo

https://github.com/w-biggs/nx-changelog-issue/tree/main

Steps to Reproduce

Say I have a project utils with two dependents: icons and components, and I run nx release 1.1.0 -p @my-org/utils to release only utils. You can reproduce this with:

git clone https://github.com/w-biggs/nx-changelog-issue.git
cd nx-changelog-issue
npm install
npm run demo:with-logging

Internally, the following code creates a projectNodes array with all three projects' nodes and iterates through them. For each project, it tries to find the previous git tag release, errors if it can't, then runs generateChangelogForProjects().

const projects = args.projects?.length
? // If the user has passed a list of projects, we need to use the filtered list of projects within the release group, plus any dependents
Array.from(
releaseGraph.releaseGroupToFilteredProjects.get(releaseGroup)
).flatMap((project) => {
return [
project,
...(projectsVersionData[project]?.dependentProjects.map(
(dep) => dep.source
) || []),
];
})
: // Otherwise, we use the full list of projects within the release group
releaseGroup.projects;
const projectNodes = projects.map((name) => projectGraph.nodes[name]);
if (releaseGroup.projectsRelationship === 'independent') {
for (const project of projectNodes) {
let changes: ChangelogChange[] | null = null;
if (releaseGroup.resolvedVersionPlans) {
changes = createChangesFromProjectsVersionPlans(
releaseGroup.resolvedVersionPlans as ProjectsVersionPlan[],
project.name
);
} else {
const projectCacheKey = `${releaseGroup.name}:${project.name}`;
const fromSHA = await getCachedFromSHA(
projectCacheKey,
releaseGroup.releaseTag.pattern,
{
projectName: project.name,
releaseGroupName: releaseGroup.name,
},
projectsPreid[project.name],
releaseGroup.releaseTag.checkAllBranchesWhen,
releaseGroup.releaseTag.requireSemver,
releaseGroup.releaseTag.strictPreid
);
let commits: GitCommit[];
let fromRef = fromSHA;
if (!fromRef && useAutomaticFromRef) {
// For automatic from ref, we already have it cached
fromRef = fromSHACache.get(projectCacheKey);
if (fromRef) {
commits = await filterProjectCommits({
fromSHA: fromRef,
toSHA,
projectPath: project.data.root,
});
fromRef = commits[0]?.shortHash;
if (args.verbose) {
console.log(
`Determined --from ref for ${project.name} from the first commit in which it exists: ${fromRef}`
);
}
}
}
if (!fromRef && !commits) {
throw new Error(
`Unable to determine the previous git tag. If this is the first release of your workspace, use the --first-release option or set the "release.changelog.automaticFromRef" config property in nx.json to generate a changelog from the first commit. Otherwise, be sure to configure the "release.releaseTag.pattern" property in nx.json to match the structure of your repository's git tags.`
);
}
if (!commits) {
commits = await filterProjectCommits({
fromSHA: fromRef,
toSHA,
projectPath: project.data.root,
});
}
const { fileMap } =
await createFileMapUsingProjectGraph(projectGraph);
const fileToProjectMap = createFileToProjectMap(
fileMap.projectFileMap
);
changes = createChangesFromCommits(
commits,
fileMap,
fileToProjectMap,
nxReleaseConfig.conventionalCommits
);
}
const projectChangelogs = await generateChangelogForProjects({
tree,
args,
changes,
projectsVersionData,
releaseGroup,
projects: [project],
nxReleaseConfig,
projectToAdditionalDependencyBumps,
});
if (projectChangelogs) {
for (const [projectName, projectChangelog] of Object.entries(
projectChangelogs
)) {
// Add the post git task (e.g. create a remote release) for the project changelog, if applicable
if (projectChangelog.postGitTask) {
postGitTasks.push(projectChangelog.postGitTask);
}
allProjectChangelogs[projectName] = projectChangelog;
}
}
}

In the demo, you'll see it successfully processes utils (which has a tag @my-org/utils@0.0.1), but then errors on @my-org/components when trying to find its tag - even though generateChangelogForProjects() will never actually do anything on the dependent projects' iterations, because projectsVersionData = utils and projects = [{ name: icons or components }], so projectsVersionData[project.name] is always falsy.

for (const project of projects) {
let interpolatedTreePath = config.file || '';
if (interpolatedTreePath) {
interpolatedTreePath = interpolate(interpolatedTreePath, {
projectName: project.name,
projectRoot: project.data.root,
workspaceRoot: '', // within the tree, workspaceRoot is the root
});
}
/**
* newVersion will be null in the case that no changes were detected (e.g. in conventional commits mode),
* no changelog entry is relevant in that case.
*/
if (
!projectsVersionData[project.name] ||
(projectsVersionData[project.name].newVersion === null &&
!projectsVersionData[project.name].dockerVersion)
) {
continue;
}

You can see a rough workaround working with npm run demo:with-fix, which does not add dependents to the projectNodes array for independent projects.

Nx Report

Node           : 24.13.0
OS             : darwin-arm64
Native Target  : aarch64-macos
npm            : 11.6.2
daemon         : Available

nx             : 22.5.0
@nx/js         : 22.5.0
@nx/workspace  : 22.5.0
@nx/devkit     : 22.5.0
typescript     : 5.9.3
---------------------------------------
Registered Plugins:
@nx/js/typescript
---------------------------------------
Cache Usage: 0.00 B / 46.04 GB

Failure Logs

Package Manager Version

No response

Operating System

  • macOS
  • Linux
  • Windows
  • Other (Please specify)

Additional Information

No response

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions