nx 22.0.0-beta.3 → 22.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -11
- package/schemas/nx-schema.json +96 -83
- package/src/command-line/configure-ai-agents/command-object.d.ts +1 -1
- package/src/command-line/configure-ai-agents/command-object.d.ts.map +1 -1
- package/src/command-line/configure-ai-agents/command-object.js +18 -4
- package/src/command-line/configure-ai-agents/configure-ai-agents.d.ts.map +1 -1
- package/src/command-line/configure-ai-agents/configure-ai-agents.js +58 -31
- package/src/command-line/nx-cloud/login/command-object.d.ts.map +1 -1
- package/src/command-line/nx-cloud/login/command-object.js +2 -2
- package/src/command-line/nx-cloud/logout/command-object.js +1 -1
- package/src/command-line/release/changelog.d.ts.map +1 -1
- package/src/command-line/release/changelog.js +40 -29
- package/src/command-line/release/command-object.d.ts +7 -3
- package/src/command-line/release/command-object.d.ts.map +1 -1
- package/src/command-line/release/config/config.d.ts.map +1 -1
- package/src/command-line/release/config/config.js +14 -0
- package/src/command-line/release/config/version-plans.d.ts.map +1 -1
- package/src/command-line/release/config/version-plans.js +4 -1
- package/src/command-line/release/publish.d.ts.map +1 -1
- package/src/command-line/release/publish.js +35 -11
- package/src/command-line/release/release.d.ts.map +1 -1
- package/src/command-line/release/release.js +31 -30
- package/src/command-line/release/utils/release-graph.d.ts +219 -0
- package/src/command-line/release/utils/release-graph.d.ts.map +1 -0
- package/src/command-line/release/utils/release-graph.js +658 -0
- package/src/command-line/release/utils/semver.d.ts +1 -2
- package/src/command-line/release/utils/semver.d.ts.map +1 -1
- package/src/command-line/release/utils/semver.js +3 -5
- package/src/command-line/release/utils/shared.d.ts +1 -1
- package/src/command-line/release/utils/shared.d.ts.map +1 -1
- package/src/command-line/release/utils/shared.js +49 -15
- package/src/command-line/release/version/release-group-processor.d.ts +3 -152
- package/src/command-line/release/version/release-group-processor.d.ts.map +1 -1
- package/src/command-line/release/version/release-group-processor.js +50 -569
- package/src/command-line/release/version/resolve-current-version.d.ts +1 -1
- package/src/command-line/release/version/resolve-current-version.d.ts.map +1 -1
- package/src/command-line/release/version/resolve-current-version.js +1 -1
- package/src/command-line/release/version/test-utils.d.ts +13 -4
- package/src/command-line/release/version/test-utils.d.ts.map +1 -1
- package/src/command-line/release/version/test-utils.js +26 -11
- package/src/command-line/release/version/version-actions.d.ts +12 -5
- package/src/command-line/release/version/version-actions.d.ts.map +1 -1
- package/src/command-line/release/version/version-actions.js +36 -19
- package/src/command-line/release/version.d.ts +6 -1
- package/src/command-line/release/version.d.ts.map +1 -1
- package/src/command-line/release/version.js +57 -28
- package/src/config/nx-json.d.ts +26 -4
- package/src/config/nx-json.d.ts.map +1 -1
- package/src/config/nx-json.js +8 -8
- package/src/tasks-runner/run-command.js +2 -2
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReleaseGraph = exports.validReleaseVersionPrefixes = void 0;
|
|
4
|
+
exports.createReleaseGraph = createReleaseGraph;
|
|
5
|
+
const config_1 = require("../config/config");
|
|
6
|
+
const project_logger_1 = require("../version/project-logger");
|
|
7
|
+
const resolve_current_version_1 = require("../version/resolve-current-version");
|
|
8
|
+
const topological_sort_1 = require("../version/topological-sort");
|
|
9
|
+
const version_actions_1 = require("../version/version-actions");
|
|
10
|
+
const git_1 = require("./git");
|
|
11
|
+
const shared_1 = require("./shared");
|
|
12
|
+
exports.validReleaseVersionPrefixes = ['auto', '', '~', '^', '='];
|
|
13
|
+
/**
|
|
14
|
+
* The complete release graph containing all relationships, caches, and computed data
|
|
15
|
+
* necessary for efficient release operations across versioning, changelog, and publishing.
|
|
16
|
+
*
|
|
17
|
+
* This class encapsulates the complex dependency graph between projects and release groups,
|
|
18
|
+
* providing convenient methods for querying relationships and accessing cached data.
|
|
19
|
+
*/
|
|
20
|
+
class ReleaseGraph {
|
|
21
|
+
constructor(releaseGroups, filters) {
|
|
22
|
+
this.releaseGroups = releaseGroups;
|
|
23
|
+
this.filters = filters;
|
|
24
|
+
this.projectToReleaseGroup = new Map();
|
|
25
|
+
this.projectToDependents = new Map();
|
|
26
|
+
this.projectToDependencies = new Map();
|
|
27
|
+
this.projectToUpdateDependentsSetting = new Map();
|
|
28
|
+
this.groupGraph = new Map();
|
|
29
|
+
this.sortedReleaseGroups = [];
|
|
30
|
+
this.sortedProjects = new Map();
|
|
31
|
+
this.allProjectsConfiguredForNxRelease = new Set();
|
|
32
|
+
this.allProjectsToProcess = new Set();
|
|
33
|
+
this.finalConfigsByProject = new Map();
|
|
34
|
+
this.projectsToVersionActions = new Map();
|
|
35
|
+
this.uniqueAfterAllProjectsVersioned = new Map();
|
|
36
|
+
this.projectLoggers = new Map();
|
|
37
|
+
this.cachedCurrentVersions = new Map();
|
|
38
|
+
this.cachedLatestMatchingGitTag = new Map();
|
|
39
|
+
this.currentVersionsPerFixedReleaseGroup = new Map();
|
|
40
|
+
this.originalDependentProjectsPerProject = new Map();
|
|
41
|
+
this.releaseGroupToFilteredProjects = new Map();
|
|
42
|
+
this.originalFilteredProjects = new Set();
|
|
43
|
+
/**
|
|
44
|
+
* User-friendly log describing what the filter matched.
|
|
45
|
+
* Null if no filters were applied.
|
|
46
|
+
*/
|
|
47
|
+
this.filterLog = null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Initialize the graph by building all relationships and caches
|
|
51
|
+
* @internal - Called by createReleaseGraph(), not meant for external use
|
|
52
|
+
*/
|
|
53
|
+
async init(options) {
|
|
54
|
+
// Step 1: Setup project to release group mapping
|
|
55
|
+
this.setupProjectReleaseGroupMapping();
|
|
56
|
+
// Step 2: Apply initial filtering to determine base set of projects and groups to process
|
|
57
|
+
this.applyInitialFiltering();
|
|
58
|
+
// Step 3: Setup projects to process and resolve version actions
|
|
59
|
+
await this.setupProjectsToProcess(options);
|
|
60
|
+
// Step 4: Precompute dependency relationships
|
|
61
|
+
await this.precomputeDependencyRelationships(options.tree, options.projectGraph);
|
|
62
|
+
// Step 5: Apply dependency-aware filtering based on updateDependents
|
|
63
|
+
this.applyDependencyAwareFiltering();
|
|
64
|
+
// Step 5: Build the group graph structure
|
|
65
|
+
this.buildGroupGraphStructure();
|
|
66
|
+
// Step 6: Resolve current versions for all projects to process (unless explicitly skipped)
|
|
67
|
+
if (!options.skipVersionResolution) {
|
|
68
|
+
await this.resolveCurrentVersionsForProjects(options.tree, options.projectGraph, options.preid ?? '');
|
|
69
|
+
}
|
|
70
|
+
// Step 7: Build dependency relationships between groups
|
|
71
|
+
this.buildGroupDependencyGraph();
|
|
72
|
+
// Step 8: Topologically sort groups and projects
|
|
73
|
+
this.sortedReleaseGroups = this.topologicallySortReleaseGroups();
|
|
74
|
+
for (const group of this.releaseGroups) {
|
|
75
|
+
this.sortedProjects.set(group.name, this.topologicallySortProjects(group));
|
|
76
|
+
}
|
|
77
|
+
// Step 9: Populate dependent projects data
|
|
78
|
+
await this.populateDependentProjectsData(options.tree, options.projectGraph);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Setup mapping from project to release group and cache updateDependents settings
|
|
82
|
+
*/
|
|
83
|
+
setupProjectReleaseGroupMapping() {
|
|
84
|
+
for (const group of this.releaseGroups) {
|
|
85
|
+
for (const project of group.projects) {
|
|
86
|
+
this.projectToReleaseGroup.set(project, group);
|
|
87
|
+
const updateDependents = group.version?.updateDependents ||
|
|
88
|
+
'auto';
|
|
89
|
+
this.projectToUpdateDependentsSetting.set(project, updateDependents);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Apply initial filtering to construct releaseGroupToFilteredProjects based on filters.
|
|
95
|
+
* This determines the base set of projects and groups before considering dependencies.
|
|
96
|
+
*/
|
|
97
|
+
applyInitialFiltering() {
|
|
98
|
+
const matchedReleaseGroups = [];
|
|
99
|
+
for (const releaseGroup of this.releaseGroups) {
|
|
100
|
+
// If group filter is applied and this group doesn't match, skip it entirely
|
|
101
|
+
if (this.filters.groups?.length &&
|
|
102
|
+
!this.filters.groups.includes(releaseGroup.name)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// If filtering by groups (not projects), include ALL projects in the matched group
|
|
106
|
+
if (this.filters.groups?.length && !this.filters.projects?.length) {
|
|
107
|
+
this.releaseGroupToFilteredProjects.set(releaseGroup, new Set(releaseGroup.projects));
|
|
108
|
+
matchedReleaseGroups.push(releaseGroup);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// If filtering by projects, filter down to matching projects
|
|
112
|
+
const filteredProjects = new Set();
|
|
113
|
+
for (const project of releaseGroup.projects) {
|
|
114
|
+
if (this.filters.projects?.length &&
|
|
115
|
+
!this.filters.projects.includes(project)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
filteredProjects.add(project);
|
|
119
|
+
}
|
|
120
|
+
// If no filters applied or group has matching projects, include it
|
|
121
|
+
if (filteredProjects.size > 0 || !this.hasAnyFilters()) {
|
|
122
|
+
const projectsToInclude = filteredProjects.size > 0
|
|
123
|
+
? filteredProjects
|
|
124
|
+
: new Set(releaseGroup.projects);
|
|
125
|
+
this.releaseGroupToFilteredProjects.set(releaseGroup, projectsToInclude);
|
|
126
|
+
matchedReleaseGroups.push(releaseGroup);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Update this.releaseGroups to only include matched groups
|
|
130
|
+
if (this.hasAnyFilters()) {
|
|
131
|
+
this.releaseGroups = matchedReleaseGroups;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Check if any filters are applied
|
|
136
|
+
*/
|
|
137
|
+
hasAnyFilters() {
|
|
138
|
+
return !!(this.filters.projects?.length || this.filters.groups?.length);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Setup projects to process and resolve version actions
|
|
142
|
+
*/
|
|
143
|
+
async setupProjectsToProcess(options) {
|
|
144
|
+
const { tree, projectGraph, nxReleaseConfig, filters, firstRelease, versionActionsOptionsOverrides, } = options;
|
|
145
|
+
let projectsToProcess = new Set();
|
|
146
|
+
const resolveVersionActionsForProjectCallbacks = [];
|
|
147
|
+
// Precompute all projects in nx release config
|
|
148
|
+
for (const [groupName, group] of Object.entries(nxReleaseConfig.groups)) {
|
|
149
|
+
for (const project of group.projects) {
|
|
150
|
+
this.allProjectsConfiguredForNxRelease.add(project);
|
|
151
|
+
this.projectLoggers.set(project, new project_logger_1.ProjectLogger(project));
|
|
152
|
+
if (filters.groups?.includes(groupName)) {
|
|
153
|
+
projectsToProcess.add(project);
|
|
154
|
+
}
|
|
155
|
+
else if (filters.projects?.includes(project)) {
|
|
156
|
+
projectsToProcess.add(project);
|
|
157
|
+
}
|
|
158
|
+
const projectGraphNode = projectGraph.nodes[project];
|
|
159
|
+
const releaseGroup = this.projectToReleaseGroup.get(project);
|
|
160
|
+
const finalConfigForProject = ReleaseGraph.resolveFinalConfigForProject(releaseGroup, projectGraphNode, firstRelease, versionActionsOptionsOverrides);
|
|
161
|
+
this.finalConfigsByProject.set(project, finalConfigForProject);
|
|
162
|
+
resolveVersionActionsForProjectCallbacks.push(async () => {
|
|
163
|
+
const { versionActionsPath, versionActions, afterAllProjectsVersioned, } = await (0, version_actions_1.resolveVersionActionsForProject)(tree, releaseGroup, projectGraphNode, finalConfigForProject);
|
|
164
|
+
if (!this.uniqueAfterAllProjectsVersioned.has(versionActionsPath)) {
|
|
165
|
+
this.uniqueAfterAllProjectsVersioned.set(versionActionsPath, afterAllProjectsVersioned);
|
|
166
|
+
}
|
|
167
|
+
let versionActionsToUse = versionActions;
|
|
168
|
+
const shouldSkip = (0, shared_1.shouldSkipVersionActions)(finalConfigForProject.dockerOptions, project);
|
|
169
|
+
if (shouldSkip) {
|
|
170
|
+
versionActionsToUse = new version_actions_1.NOOP_VERSION_ACTIONS(releaseGroup, projectGraphNode, finalConfigForProject);
|
|
171
|
+
}
|
|
172
|
+
this.projectsToVersionActions.set(project, versionActionsToUse);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!filters.groups?.length && !filters.projects?.length) {
|
|
177
|
+
projectsToProcess = this.allProjectsConfiguredForNxRelease;
|
|
178
|
+
}
|
|
179
|
+
if (projectsToProcess.size === 0) {
|
|
180
|
+
throw new Error('No projects are set to be processed, please report this as a bug on https://github.com/nrwl/nx/issues');
|
|
181
|
+
}
|
|
182
|
+
this.allProjectsToProcess = new Set(projectsToProcess);
|
|
183
|
+
for (const cb of resolveVersionActionsForProjectCallbacks) {
|
|
184
|
+
await cb();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Precompute dependency relationships between all projects
|
|
189
|
+
*/
|
|
190
|
+
async precomputeDependencyRelationships(tree, projectGraph) {
|
|
191
|
+
for (const projectName of this.allProjectsConfiguredForNxRelease) {
|
|
192
|
+
const versionActions = this.projectsToVersionActions.get(projectName);
|
|
193
|
+
if (!this.projectToDependencies.has(projectName)) {
|
|
194
|
+
this.projectToDependencies.set(projectName, new Set());
|
|
195
|
+
}
|
|
196
|
+
const deps = await versionActions.readDependencies(tree, projectGraph);
|
|
197
|
+
for (const dep of deps) {
|
|
198
|
+
if (!this.allProjectsConfiguredForNxRelease.has(dep.target)) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
this.projectToDependencies.get(projectName).add(dep.target);
|
|
202
|
+
if (!this.projectToDependents.has(dep.target)) {
|
|
203
|
+
this.projectToDependents.set(dep.target, new Set());
|
|
204
|
+
}
|
|
205
|
+
this.projectToDependents.get(dep.target).add(projectName);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Apply dependency-aware filtering that considers updateDependents configuration.
|
|
211
|
+
* This includes transitive dependents when updateDependents='auto'.
|
|
212
|
+
*/
|
|
213
|
+
applyDependencyAwareFiltering() {
|
|
214
|
+
// Track the original filtered projects before adding dependents
|
|
215
|
+
this.originalFilteredProjects = new Set(this.allProjectsToProcess);
|
|
216
|
+
if (!this.hasAnyFilters()) {
|
|
217
|
+
// No filtering applied, nothing to do
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// Validate filtering against fixed release groups
|
|
221
|
+
this.validateFilterAgainstFixedGroups();
|
|
222
|
+
// Find all dependents that need to be included based on updateDependents setting
|
|
223
|
+
this.findDependentsToProcess();
|
|
224
|
+
// Generate user-friendly filter log
|
|
225
|
+
this.generateFilterLog();
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Validate that the filter doesn't try to isolate projects in fixed release groups
|
|
229
|
+
*/
|
|
230
|
+
validateFilterAgainstFixedGroups() {
|
|
231
|
+
if (!this.filters.projects?.length) {
|
|
232
|
+
// Group filtering doesn't have this issue
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
for (const releaseGroup of this.releaseGroups) {
|
|
236
|
+
if (releaseGroup.projectsRelationship !== 'fixed') {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const filteredProjectsInGroup = releaseGroup.projects.filter((p) => this.releaseGroupToFilteredProjects.get(releaseGroup)?.has(p));
|
|
240
|
+
if (filteredProjectsInGroup.length > 0 &&
|
|
241
|
+
filteredProjectsInGroup.length < releaseGroup.projects.length) {
|
|
242
|
+
throw new Error(`Cannot filter to a subset of projects within fixed release group "${releaseGroup.name}". ` +
|
|
243
|
+
`Filtered projects: [${filteredProjectsInGroup.join(', ')}], ` +
|
|
244
|
+
`All projects in group: [${releaseGroup.projects.join(', ')}]. ` +
|
|
245
|
+
`Either filter to all projects in the group, use --groups to filter by group, or change the group to "independent".`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Find dependents that should be included in processing based on updateDependents configuration
|
|
251
|
+
*/
|
|
252
|
+
findDependentsToProcess() {
|
|
253
|
+
const projectsToProcess = Array.from(this.allProjectsToProcess);
|
|
254
|
+
const allTrackedDependents = new Set();
|
|
255
|
+
const dependentsToProcess = new Set();
|
|
256
|
+
const additionalGroups = new Map();
|
|
257
|
+
// BFS traversal to find all transitive dependents
|
|
258
|
+
let currentLevel = [...projectsToProcess];
|
|
259
|
+
while (currentLevel.length > 0) {
|
|
260
|
+
const nextLevel = [];
|
|
261
|
+
const dependents = this.getAllNonImplicitDependents(currentLevel);
|
|
262
|
+
for (const dep of dependents) {
|
|
263
|
+
if (allTrackedDependents.has(dep) ||
|
|
264
|
+
this.allProjectsToProcess.has(dep)) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
allTrackedDependents.add(dep);
|
|
268
|
+
// Check if this dependent should be included based on updateDependents settings
|
|
269
|
+
const depUpdateDependentsSetting = this.projectToUpdateDependentsSetting.get(dep);
|
|
270
|
+
// Only include if dependent has 'always' or 'auto' (not 'never')
|
|
271
|
+
if (depUpdateDependentsSetting !== 'never') {
|
|
272
|
+
// Find which project(s) in currentLevel this dependent depends on
|
|
273
|
+
const shouldIncludeDependent = currentLevel.some((proj) => {
|
|
274
|
+
const projUpdateSetting = this.projectToUpdateDependentsSetting.get(proj);
|
|
275
|
+
const projDependents = this.getProjectDependents(proj);
|
|
276
|
+
if (!projDependents.has(dep)) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
// Always include if updateDependents is 'always'
|
|
280
|
+
if (projUpdateSetting === 'always') {
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
// For 'auto', include if in the same release group to match historical behavior
|
|
284
|
+
if (projUpdateSetting === 'auto') {
|
|
285
|
+
const projGroup = this.getReleaseGroupForProject(proj);
|
|
286
|
+
const depGroup = this.getReleaseGroupForProject(dep);
|
|
287
|
+
return projGroup && depGroup && projGroup.name === depGroup.name;
|
|
288
|
+
}
|
|
289
|
+
return false;
|
|
290
|
+
});
|
|
291
|
+
if (shouldIncludeDependent) {
|
|
292
|
+
dependentsToProcess.add(dep);
|
|
293
|
+
// Track the release group of this dependent
|
|
294
|
+
const depGroup = this.getReleaseGroupForProject(dep);
|
|
295
|
+
if (depGroup) {
|
|
296
|
+
// Check if this group is already in our list by name
|
|
297
|
+
const groupAlreadyExists = this.releaseGroups.some((g) => g.name === depGroup.name);
|
|
298
|
+
if (!groupAlreadyExists) {
|
|
299
|
+
additionalGroups.set(depGroup.name, depGroup);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
nextLevel.push(dep);
|
|
305
|
+
}
|
|
306
|
+
currentLevel = nextLevel;
|
|
307
|
+
}
|
|
308
|
+
dependentsToProcess.forEach((dep) => this.allProjectsToProcess.add(dep));
|
|
309
|
+
// Add any additional groups and their filtered projects
|
|
310
|
+
additionalGroups.forEach((group) => {
|
|
311
|
+
// When adding groups due to dependents, clear version plans to avoid duplication
|
|
312
|
+
// Version plans should only be processed for groups that were explicitly included
|
|
313
|
+
const groupForDependents = {
|
|
314
|
+
...group,
|
|
315
|
+
versionPlans: false,
|
|
316
|
+
resolvedVersionPlans: false,
|
|
317
|
+
};
|
|
318
|
+
this.releaseGroups.push(groupForDependents);
|
|
319
|
+
// Add the projects from this group that are actually being processed
|
|
320
|
+
const projectsInGroup = new Set(group.projects.filter((p) => dependentsToProcess.has(p)));
|
|
321
|
+
this.releaseGroupToFilteredProjects.set(groupForDependents, projectsInGroup);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Generate user-friendly log describing what the filter matched
|
|
326
|
+
*/
|
|
327
|
+
generateFilterLog() {
|
|
328
|
+
if (this.filters.projects?.length) {
|
|
329
|
+
// Projects filter - only show the originally filtered projects to match old behavior
|
|
330
|
+
const matchedProjects = Array.from(this.originalFilteredProjects);
|
|
331
|
+
this.filterLog = {
|
|
332
|
+
title: `Your filter "${this.filters.projects.join(',')}" matched the following projects:`,
|
|
333
|
+
bodyLines: matchedProjects.map((p) => {
|
|
334
|
+
const releaseGroupForProject = this.projectToReleaseGroup.get(p);
|
|
335
|
+
if (!releaseGroupForProject ||
|
|
336
|
+
releaseGroupForProject.name === config_1.IMPLICIT_DEFAULT_RELEASE_GROUP) {
|
|
337
|
+
return `- ${p}`;
|
|
338
|
+
}
|
|
339
|
+
return `- ${p} (release group "${releaseGroupForProject.name}")`;
|
|
340
|
+
}),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
// TODO: add groups filter log
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Build the group graph structure
|
|
347
|
+
*/
|
|
348
|
+
buildGroupGraphStructure() {
|
|
349
|
+
for (const group of this.releaseGroups) {
|
|
350
|
+
// Don't overwrite if already exists (may have been added during filtering)
|
|
351
|
+
if (!this.groupGraph.has(group.name)) {
|
|
352
|
+
this.groupGraph.set(group.name, {
|
|
353
|
+
group,
|
|
354
|
+
dependencies: new Set(),
|
|
355
|
+
dependents: new Set(),
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Resolve current versions for all projects that will be processed
|
|
362
|
+
*/
|
|
363
|
+
async resolveCurrentVersionsForProjects(tree, projectGraph, preid) {
|
|
364
|
+
for (const [, releaseGroupNode] of this.groupGraph) {
|
|
365
|
+
for (const projectName of releaseGroupNode.group.projects) {
|
|
366
|
+
const projectGraphNode = projectGraph.nodes[projectName];
|
|
367
|
+
if (!this.allProjectsToProcess.has(projectName)) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const versionActions = this.projectsToVersionActions.get(projectName);
|
|
371
|
+
const finalConfigForProject = this.finalConfigsByProject.get(projectName);
|
|
372
|
+
let latestMatchingGitTag;
|
|
373
|
+
const releaseTagPattern = releaseGroupNode.group.releaseTagPattern;
|
|
374
|
+
if (finalConfigForProject.currentVersionResolver === 'git-tag') {
|
|
375
|
+
latestMatchingGitTag = await (0, git_1.getLatestGitTagForPattern)(releaseTagPattern, {
|
|
376
|
+
projectName: projectGraphNode.name,
|
|
377
|
+
}, {
|
|
378
|
+
checkAllBranchesWhen: releaseGroupNode.group.releaseTagPatternCheckAllBranchesWhen,
|
|
379
|
+
preid: preid,
|
|
380
|
+
releaseTagPatternRequireSemver: releaseGroupNode.group.releaseTagPatternRequireSemver,
|
|
381
|
+
releaseTagPatternStrictPreid: releaseGroupNode.group.releaseTagPatternStrictPreid,
|
|
382
|
+
});
|
|
383
|
+
this.cachedLatestMatchingGitTag.set(projectName, latestMatchingGitTag);
|
|
384
|
+
}
|
|
385
|
+
const currentVersion = await (0, resolve_current_version_1.resolveCurrentVersion)(tree, projectGraphNode, releaseGroupNode.group, versionActions, this.projectLoggers.get(projectName), this.currentVersionsPerFixedReleaseGroup, finalConfigForProject, releaseTagPattern, latestMatchingGitTag);
|
|
386
|
+
this.cachedCurrentVersions.set(projectName, currentVersion);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Build dependency relationships between release groups
|
|
392
|
+
*/
|
|
393
|
+
buildGroupDependencyGraph() {
|
|
394
|
+
for (const [releaseGroupName, releaseGroupNode] of this.groupGraph) {
|
|
395
|
+
for (const projectName of releaseGroupNode.group.projects) {
|
|
396
|
+
const projectDeps = this.getProjectDependencies(projectName);
|
|
397
|
+
for (const dep of projectDeps) {
|
|
398
|
+
const dependencyGroup = this.getReleaseGroupNameForProject(dep);
|
|
399
|
+
if (dependencyGroup && dependencyGroup !== releaseGroupName) {
|
|
400
|
+
releaseGroupNode.dependencies.add(dependencyGroup);
|
|
401
|
+
// Only add to dependents if the dependency group exists in the graph
|
|
402
|
+
// (it may have been filtered out due to user filters)
|
|
403
|
+
const dependencyGroupNode = this.groupGraph.get(dependencyGroup);
|
|
404
|
+
if (dependencyGroupNode) {
|
|
405
|
+
dependencyGroupNode.dependents.add(releaseGroupName);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Topologically sort release groups
|
|
414
|
+
*/
|
|
415
|
+
topologicallySortReleaseGroups() {
|
|
416
|
+
const groupNames = Array.from(this.groupGraph.keys());
|
|
417
|
+
const getGroupDependencies = (groupName) => {
|
|
418
|
+
const groupNode = this.groupGraph.get(groupName);
|
|
419
|
+
if (!groupNode) {
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
422
|
+
return Array.from(groupNode.dependencies);
|
|
423
|
+
};
|
|
424
|
+
return (0, topological_sort_1.topologicalSort)(groupNames, getGroupDependencies);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Topologically sort projects within a release group
|
|
428
|
+
*/
|
|
429
|
+
topologicallySortProjects(releaseGroup) {
|
|
430
|
+
const projects = releaseGroup.projects.filter((p) => this.allProjectsToProcess.has(p));
|
|
431
|
+
const getProjectDependenciesInSameGroup = (project) => {
|
|
432
|
+
const deps = this.getProjectDependencies(project);
|
|
433
|
+
return Array.from(deps).filter((dep) => this.getReleaseGroupNameForProject(dep) === releaseGroup.name &&
|
|
434
|
+
this.allProjectsToProcess.has(dep));
|
|
435
|
+
};
|
|
436
|
+
return (0, topological_sort_1.topologicalSort)(projects, getProjectDependenciesInSameGroup);
|
|
437
|
+
}
|
|
438
|
+
async populateDependentProjectsData(tree, projectGraph) {
|
|
439
|
+
// Populate detailed dependent projects data for all projects being processed
|
|
440
|
+
for (const projectName of this.allProjectsToProcess) {
|
|
441
|
+
const dependentProjectNames = Array.from(this.getProjectDependents(projectName)).filter((dep) => this.allProjectsConfiguredForNxRelease.has(dep));
|
|
442
|
+
const dependentProjectsData = [];
|
|
443
|
+
for (const dependentProjectName of dependentProjectNames) {
|
|
444
|
+
const versionActions = this.projectsToVersionActions.get(dependentProjectName);
|
|
445
|
+
const { currentVersion, dependencyCollection } = await versionActions.readCurrentVersionOfDependency(tree, projectGraph, projectName);
|
|
446
|
+
dependentProjectsData.push({
|
|
447
|
+
source: dependentProjectName,
|
|
448
|
+
target: projectName,
|
|
449
|
+
type: 'static',
|
|
450
|
+
dependencyCollection,
|
|
451
|
+
rawVersionSpec: currentVersion,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
this.originalDependentProjectsPerProject.set(projectName, dependentProjectsData);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Get all non-implicit dependents for a set of projects
|
|
459
|
+
*/
|
|
460
|
+
getAllNonImplicitDependents(projects) {
|
|
461
|
+
return projects
|
|
462
|
+
.flatMap((project) => Array.from(this.getProjectDependents(project)))
|
|
463
|
+
.filter((dep) => !this.allProjectsToProcess.has(dep));
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Resolve final configuration for a project
|
|
467
|
+
*
|
|
468
|
+
* NOTE: We are providing ultimate fallback values via ?? here mainly just to keep TypeScript happy.
|
|
469
|
+
* All default values should have been applied by this point by config.ts but the types can't know
|
|
470
|
+
* that for sure at this point.
|
|
471
|
+
*/
|
|
472
|
+
static resolveFinalConfigForProject(releaseGroup, projectGraphNode, firstRelease, versionActionsOptionsOverrides) {
|
|
473
|
+
const releaseGroupVersionConfig = releaseGroup.version;
|
|
474
|
+
const projectVersionConfig = projectGraphNode.data.release?.version;
|
|
475
|
+
const projectDockerConfig = projectGraphNode.data.release?.docker;
|
|
476
|
+
/**
|
|
477
|
+
* specifierSource
|
|
478
|
+
*
|
|
479
|
+
* If the user has provided a specifier, it always takes precedence,
|
|
480
|
+
* so the effective specifier source is 'prompt', regardless of what
|
|
481
|
+
* the project or release group config says.
|
|
482
|
+
*/
|
|
483
|
+
const specifierSource = projectVersionConfig?.specifierSource ??
|
|
484
|
+
releaseGroupVersionConfig?.specifierSource ??
|
|
485
|
+
'prompt';
|
|
486
|
+
/**
|
|
487
|
+
* versionPrefix, defaults to auto
|
|
488
|
+
*/
|
|
489
|
+
const versionPrefix = projectVersionConfig?.versionPrefix ??
|
|
490
|
+
releaseGroupVersionConfig?.versionPrefix ??
|
|
491
|
+
'auto';
|
|
492
|
+
if (versionPrefix && !exports.validReleaseVersionPrefixes.includes(versionPrefix)) {
|
|
493
|
+
throw new Error(`Invalid value for versionPrefix: "${versionPrefix}"
|
|
494
|
+
|
|
495
|
+
Valid values are: ${exports.validReleaseVersionPrefixes
|
|
496
|
+
.map((s) => `"${s}"`)
|
|
497
|
+
.join(', ')}`);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Merge docker options configured in project with release group config,
|
|
501
|
+
* project level configuration should take precedence
|
|
502
|
+
*/
|
|
503
|
+
const dockerOptions = Object.assign({}, releaseGroup.docker || {}, projectDockerConfig || {});
|
|
504
|
+
/**
|
|
505
|
+
* currentVersionResolver, defaults to disk
|
|
506
|
+
*/
|
|
507
|
+
let currentVersionResolver = projectVersionConfig?.currentVersionResolver ??
|
|
508
|
+
releaseGroupVersionConfig?.currentVersionResolver ??
|
|
509
|
+
'disk';
|
|
510
|
+
// Check if this project should skip version actions based on docker configuration
|
|
511
|
+
const shouldSkip = (0, shared_1.shouldSkipVersionActions)(dockerOptions, projectGraphNode.name);
|
|
512
|
+
if (shouldSkip) {
|
|
513
|
+
// If the project skips version actions, it doesn't need to resolve a current version
|
|
514
|
+
currentVersionResolver = 'none';
|
|
515
|
+
}
|
|
516
|
+
else if (specifierSource === 'conventional-commits' &&
|
|
517
|
+
currentVersionResolver !== 'git-tag') {
|
|
518
|
+
throw new Error(`Invalid currentVersionResolver "${currentVersionResolver}" provided for project "${projectGraphNode.name}". Must be "git-tag" when "specifierSource" is "conventional-commits"`);
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* currentVersionResolverMetadata, defaults to {}
|
|
522
|
+
*/
|
|
523
|
+
const currentVersionResolverMetadata = projectVersionConfig?.currentVersionResolverMetadata ??
|
|
524
|
+
releaseGroupVersionConfig?.currentVersionResolverMetadata ??
|
|
525
|
+
{};
|
|
526
|
+
/**
|
|
527
|
+
* preserveLocalDependencyProtocols
|
|
528
|
+
*
|
|
529
|
+
* This was false by default in legacy versioning, but is true by default now.
|
|
530
|
+
*/
|
|
531
|
+
const preserveLocalDependencyProtocols = projectVersionConfig?.preserveLocalDependencyProtocols ??
|
|
532
|
+
releaseGroupVersionConfig?.preserveLocalDependencyProtocols ??
|
|
533
|
+
true;
|
|
534
|
+
/**
|
|
535
|
+
* preserveMatchingDependencyRanges
|
|
536
|
+
*
|
|
537
|
+
* This was false by default until v22, but is true by default now.
|
|
538
|
+
*/
|
|
539
|
+
const preserveMatchingDependencyRanges = projectVersionConfig?.preserveMatchingDependencyRanges ??
|
|
540
|
+
releaseGroupVersionConfig?.preserveMatchingDependencyRanges ??
|
|
541
|
+
true;
|
|
542
|
+
/**
|
|
543
|
+
* fallbackCurrentVersionResolver, defaults to disk when performing a first release, otherwise undefined
|
|
544
|
+
*/
|
|
545
|
+
const fallbackCurrentVersionResolver = projectVersionConfig?.fallbackCurrentVersionResolver ??
|
|
546
|
+
releaseGroupVersionConfig?.fallbackCurrentVersionResolver ??
|
|
547
|
+
(firstRelease ? 'disk' : undefined);
|
|
548
|
+
/**
|
|
549
|
+
* versionActionsOptions, defaults to {}
|
|
550
|
+
*/
|
|
551
|
+
let versionActionsOptions = projectVersionConfig?.versionActionsOptions ??
|
|
552
|
+
releaseGroupVersionConfig?.versionActionsOptions ??
|
|
553
|
+
{};
|
|
554
|
+
// Apply any optional overrides that may be passed in from the programmatic API
|
|
555
|
+
versionActionsOptions = {
|
|
556
|
+
...versionActionsOptions,
|
|
557
|
+
...(versionActionsOptionsOverrides ?? {}),
|
|
558
|
+
};
|
|
559
|
+
const manifestRootsToUpdate = (projectVersionConfig?.manifestRootsToUpdate ??
|
|
560
|
+
releaseGroupVersionConfig?.manifestRootsToUpdate ??
|
|
561
|
+
[]).map((manifestRoot) => {
|
|
562
|
+
if (typeof manifestRoot === 'string') {
|
|
563
|
+
return {
|
|
564
|
+
path: manifestRoot,
|
|
565
|
+
// Apply the project level preserveLocalDependencyProtocols setting that was already resolved
|
|
566
|
+
preserveLocalDependencyProtocols,
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
return manifestRoot;
|
|
570
|
+
});
|
|
571
|
+
return {
|
|
572
|
+
specifierSource,
|
|
573
|
+
currentVersionResolver,
|
|
574
|
+
currentVersionResolverMetadata,
|
|
575
|
+
fallbackCurrentVersionResolver,
|
|
576
|
+
versionPrefix,
|
|
577
|
+
preserveLocalDependencyProtocols,
|
|
578
|
+
preserveMatchingDependencyRanges,
|
|
579
|
+
versionActionsOptions,
|
|
580
|
+
manifestRootsToUpdate,
|
|
581
|
+
dockerOptions,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Get the release group for a given project
|
|
586
|
+
*/
|
|
587
|
+
getReleaseGroupForProject(projectName) {
|
|
588
|
+
return this.projectToReleaseGroup.get(projectName);
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Get the release group name for a given project
|
|
592
|
+
*/
|
|
593
|
+
getReleaseGroupNameForProject(projectName) {
|
|
594
|
+
const group = this.projectToReleaseGroup.get(projectName);
|
|
595
|
+
return group ? group.name : null;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Get the dependencies of a project
|
|
599
|
+
*/
|
|
600
|
+
getProjectDependencies(projectName) {
|
|
601
|
+
return this.projectToDependencies.get(projectName) || new Set();
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Get the dependents of a project (projects that depend on it)
|
|
605
|
+
*/
|
|
606
|
+
getProjectDependents(projectName) {
|
|
607
|
+
return this.projectToDependents.get(projectName) || new Set();
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Get the version actions for a project
|
|
611
|
+
*/
|
|
612
|
+
getVersionActionsForProject(projectName) {
|
|
613
|
+
return this.projectsToVersionActions.get(projectName);
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Check if a project will be processed
|
|
617
|
+
*/
|
|
618
|
+
isProjectToProcess(projectName) {
|
|
619
|
+
return this.allProjectsToProcess.has(projectName);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Runs validation on resolved VersionActions instances. E.g. check that manifest files exist for all projects that will be processed.
|
|
623
|
+
* This should be called after preVersionCommand has run, as those commands may create manifest files that are needed for versioning.
|
|
624
|
+
*/
|
|
625
|
+
async validate(tree) {
|
|
626
|
+
const validationPromises = [];
|
|
627
|
+
for (const projectName of this.allProjectsToProcess) {
|
|
628
|
+
const versionActions = this.projectsToVersionActions.get(projectName);
|
|
629
|
+
if (versionActions) {
|
|
630
|
+
validationPromises.push(versionActions.validate(tree));
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// Validate in parallel
|
|
634
|
+
await Promise.all(validationPromises);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
exports.ReleaseGraph = ReleaseGraph;
|
|
638
|
+
/**
|
|
639
|
+
* Creates a complete release graph by analyzing all release groups, projects, and their relationships.
|
|
640
|
+
*
|
|
641
|
+
* This function builds the graph structure, applies filtering logic that considers dependencies
|
|
642
|
+
* and updateDependents configuration, and caches all necessary data.
|
|
643
|
+
*
|
|
644
|
+
* The returned graph can be reused across versioning, changelog, and publishing operations.
|
|
645
|
+
*/
|
|
646
|
+
async function createReleaseGraph(options) {
|
|
647
|
+
// Construct ReleaseGroupWithName objects from nxReleaseConfig
|
|
648
|
+
const releaseGroups = Object.entries(options.nxReleaseConfig.groups).map(([name, group]) => {
|
|
649
|
+
return {
|
|
650
|
+
...group,
|
|
651
|
+
name,
|
|
652
|
+
resolvedVersionPlans: group.versionPlans ? [] : false,
|
|
653
|
+
};
|
|
654
|
+
});
|
|
655
|
+
const graph = new ReleaseGraph(releaseGroups, options.filters);
|
|
656
|
+
await graph.init(options);
|
|
657
|
+
return graph;
|
|
658
|
+
}
|
|
@@ -20,7 +20,6 @@ export declare function isValidSemverSpecifier(specifier: string): boolean;
|
|
|
20
20
|
export declare function determineSemverChange(relevantCommits: Map<string, {
|
|
21
21
|
commit: GitCommit;
|
|
22
22
|
isProjectScopedCommit: boolean;
|
|
23
|
-
}[]>,
|
|
24
|
-
config: NxReleaseConfig['conventionalCommits']): Map<string, SemverSpecifier | null>;
|
|
23
|
+
}[]>, config: NxReleaseConfig['conventionalCommits']): Map<string, SemverSpecifier | null>;
|
|
25
24
|
export declare function deriveNewSemverVersion(currentSemverVersion: string, semverSpecifier: string, preid?: string): string;
|
|
26
25
|
//# sourceMappingURL=semver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semver.d.ts","sourceRoot":"","sources":["../../../../../../../packages/nx/src/command-line/release/utils/semver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAiB,WAAW,EAAc,MAAM,QAAQ,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,0BAAkB,eAAe;IAC/B,KAAK,IAAI;IACT,KAAK,IAAI;IACT,KAAK,IAAI;CACV;AAED,eAAO,MAAM,mBAAmB;;;;CAI/B,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,WAAW,CAExE;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAIjE;
|
|
1
|
+
{"version":3,"file":"semver.d.ts","sourceRoot":"","sources":["../../../../../../../packages/nx/src/command-line/release/utils/semver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAiB,WAAW,EAAc,MAAM,QAAQ,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,0BAAkB,eAAe;IAC/B,KAAK,IAAI;IACT,KAAK,IAAI;IACT,KAAK,IAAI;CACV;AAED,eAAO,MAAM,mBAAmB;;;;CAI/B,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,WAAW,CAExE;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAIjE;AAED,wBAAgB,qBAAqB,CACnC,eAAe,EAAE,GAAG,CAClB,MAAM,EACN;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,qBAAqB,EAAE,OAAO,CAAA;CAAE,EAAE,CACxD,EACD,MAAM,EAAE,eAAe,CAAC,qBAAqB,CAAC,GAC7C,GAAG,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC,CA0BrC;AAED,wBAAgB,sBAAsB,CACpC,oBAAoB,EAAE,MAAM,EAC5B,eAAe,EAAE,MAAM,EACvB,KAAK,CAAC,EAAE,MAAM,UA2Bf"}
|