@@ -8,53 +8,42 @@ import {
8
8
ProjectConfiguration ,
9
9
TargetConfiguration ,
10
10
} from '../../config/workspace-json-project-json' ;
11
- import { readJsonFile } from '../../utils/fileutils' ;
12
11
import { NX_PREFIX } from '../../utils/logger' ;
13
12
import { NxPluginV2 } from '../../utils/nx-plugin' ;
14
13
import { workspaceRoot } from '../../utils/workspace-root' ;
15
14
16
15
import minimatch = require( 'minimatch' ) ;
17
- export function mergeProjectConfigurationIntoProjectsConfigurations (
18
- // projectName -> ProjectConfiguration
19
- existingProjects : Record < string , ProjectConfiguration > ,
20
- // projectRoot -> projectName
21
- existingProjectRootMap : Map < string , string > ,
16
+
17
+ export function mergeProjectConfigurationIntoRootMap (
18
+ projectRootMap : Map < string , ProjectConfiguration > ,
22
19
project : ProjectConfiguration ,
23
20
// project.json is a special case, so we need to detect it.
24
21
file : string
25
22
) : void {
26
- let matchingProjectName = existingProjectRootMap . get ( project . root ) ;
23
+ const matchingProject = projectRootMap . get ( project . root ) ;
27
24
28
- if ( ! matchingProjectName ) {
29
- existingProjects [ project . name ] = project ;
30
- existingProjectRootMap . set ( project . root , project . name ) ;
25
+ if ( ! matchingProject ) {
26
+ projectRootMap . set ( project . root , project ) ;
31
27
return ;
32
- // There are some special cases for handling project.json - mainly
33
- // that it should override any name the project already has.
34
28
} else if (
35
29
project . name &&
36
- project . name !== matchingProjectName &&
30
+ project . name !== matchingProject . name &&
37
31
basename ( file ) === 'project.json'
38
32
) {
39
- // Copy config to new name
40
- existingProjects [ project . name ] = existingProjects [ matchingProjectName ] ;
41
- // Update name in project config
42
- existingProjects [ project . name ] . name = project . name ;
43
- // Update root map to point to new name
44
- existingProjectRootMap [ project . root ] = project . name ;
45
- // Remove entry for old name
46
- delete existingProjects [ matchingProjectName ] ;
47
- // Update name that config should be merged to
48
- matchingProjectName = project . name ;
33
+ // `name` inside project.json overrides any names from
34
+ // inference plugins
35
+ matchingProject . name = project . name ;
49
36
}
50
37
51
- const matchingProject = existingProjects [ matchingProjectName ] ;
52
-
53
- // This handles top level properties that are overwritten. `srcRoot`, `projectType`, or fields that Nx doesn't know about.
38
+ // This handles top level properties that are overwritten.
39
+ // e.g. `srcRoot`, `projectType`, or other fields that shouldn't be extended
40
+ // Note: `name` is set specifically here to keep it from changing. The name is
41
+ // always determined by the first inference plugin to ID a project, unless it has
42
+ // a project.json in which case it was already updated above.
54
43
const updatedProjectConfiguration = {
55
44
...matchingProject ,
56
45
...project ,
57
- name : matchingProjectName ,
46
+ name : matchingProject . name ,
58
47
} ;
59
48
60
49
// The next blocks handle properties that should be themselves merged (e.g. targets, tags, and implicit dependencies)
@@ -83,11 +72,10 @@ export function mergeProjectConfigurationIntoProjectsConfigurations(
83
72
} ;
84
73
}
85
74
86
- if ( updatedProjectConfiguration . name !== matchingProject . name ) {
87
- delete existingProjects [ matchingProject . name ] ;
88
- }
89
- existingProjects [ updatedProjectConfiguration . name ] =
90
- updatedProjectConfiguration ;
75
+ projectRootMap . set (
76
+ updatedProjectConfiguration . root ,
77
+ updatedProjectConfiguration
78
+ ) ;
91
79
}
92
80
93
81
export function buildProjectsConfigurationsFromProjectPathsAndPlugins (
@@ -99,8 +87,7 @@ export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
99
87
projects : Record < string , ProjectConfiguration > ;
100
88
externalNodes : Record < string , ProjectGraphExternalNode > ;
101
89
} {
102
- const projectRootMap : Map < string , string > = new Map ( ) ;
103
- const projects : Record < string , ProjectConfiguration > = { } ;
90
+ const projectRootMap : Map < string , ProjectConfiguration > = new Map ( ) ;
104
91
const externalNodes : Record < string , ProjectGraphExternalNode > = { } ;
105
92
106
93
// We push the nx core node builder onto the end, s.t. it overwrites any user specified behavior
@@ -119,13 +106,11 @@ export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
119
106
if ( minimatch ( file , pattern ) ) {
120
107
const { projects : projectNodes , externalNodes : pluginExternalNodes } =
121
108
configurationConstructor ( file , {
122
- projectsConfigurations : projects ,
123
109
nxJsonConfiguration : nxJson ,
124
110
workspaceRoot : root ,
125
111
} ) ;
126
112
for ( const node in projectNodes ) {
127
- mergeProjectConfigurationIntoProjectsConfigurations (
128
- projects ,
113
+ mergeProjectConfigurationIntoRootMap (
129
114
projectRootMap ,
130
115
projectNodes [ node ] ,
131
116
file
@@ -136,7 +121,48 @@ export function buildProjectsConfigurationsFromProjectPathsAndPlugins(
136
121
}
137
122
}
138
123
139
- return { projects, externalNodes } ;
124
+ return {
125
+ projects : readProjectConfigurationsFromRootMap ( projectRootMap ) ,
126
+ externalNodes,
127
+ } ;
128
+ }
129
+
130
+ export function readProjectConfigurationsFromRootMap (
131
+ projectRootMap : Map < string , ProjectConfiguration >
132
+ ) {
133
+ const projects : Record < string , ProjectConfiguration > = { } ;
134
+ // If there are projects that have the same name, that is an error.
135
+ // This object tracks name -> (all roots of projects with that name)
136
+ // to provide better error messaging.
137
+ const errors : Map < string , string [ ] > = new Map ( ) ;
138
+
139
+ for ( const [ root , configuration ] of projectRootMap . entries ( ) ) {
140
+ if ( ! configuration . name ) {
141
+ throw new Error ( `Project at ${ root } has no name provided.` ) ;
142
+ } else if ( configuration . name in projects ) {
143
+ let rootErrors = errors . get ( configuration . name ) ?? [
144
+ projects [ configuration . name ] . root ,
145
+ ] ;
146
+ rootErrors . push ( root ) ;
147
+ errors . set ( configuration . name , rootErrors ) ;
148
+ } else {
149
+ projects [ configuration . name ] = configuration ;
150
+ }
151
+ }
152
+
153
+ if ( errors . size > 0 ) {
154
+ throw new Error (
155
+ [
156
+ `The following projects are defined in multiple locations:` ,
157
+ ...Array . from ( errors . entries ( ) ) . map ( ( [ project , roots ] ) =>
158
+ [ `- ${ project } : ` , ...roots . map ( ( r ) => ` - ${ r } ` ) ] . join ( '\n' )
159
+ ) ,
160
+ '' ,
161
+ "To fix this, set a unique name for each project in a project.json inside the project's root. If the project does not currently have a project.json, you can create one that contains only a name." ,
162
+ ] . join ( '\n' )
163
+ ) ;
164
+ }
165
+ return projects ;
140
166
}
141
167
142
168
export function mergeTargetConfigurations (
0 commit comments