-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Description
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-loggingInternally, 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().
nx/packages/nx/src/command-line/release/changelog.ts
Lines 453 to 564 in 5142079
| 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.
nx/packages/nx/src/command-line/release/changelog.ts
Lines 1092 to 1112 in 5142079
| 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 GBFailure Logs
Package Manager Version
No response
Operating System
- macOS
- Linux
- Windows
- Other (Please specify)
Additional Information
No response