@@ -69,6 +69,15 @@ def logger_debug(*args):
69
69
70
70
class BaseNpmHandler (models .DatafileHandler ):
71
71
72
+ lockfile_names = {
73
+ 'package-lock.json' ,
74
+ '.package-lock.json' ,
75
+ 'npm-shrinkwrap.json' ,
76
+ 'yarn.lock' ,
77
+ 'shrinkwrap.yaml' ,
78
+ 'pnpm-lock.yaml'
79
+ }
80
+
72
81
@classmethod
73
82
def assemble (cls , package_data , resource , codebase , package_adder ):
74
83
"""
@@ -85,19 +94,11 @@ def assemble(cls, package_data, resource, codebase, package_adder):
85
94
If there is no package.json, we do not have a package instance. In this
86
95
case, we yield each of the dependencies in each lock file.
87
96
"""
88
- lockfile_names = {
89
- 'package-lock.json' ,
90
- '.package-lock.json' ,
91
- 'npm-shrinkwrap.json' ,
92
- 'yarn.lock' ,
93
- 'shrinkwrap.yaml' ,
94
- 'pnpm-lock.yaml'
95
- }
96
97
97
98
package_resource = None
98
99
if resource .name == 'package.json' :
99
100
package_resource = resource
100
- elif resource .name in lockfile_names :
101
+ elif resource .name in cls . lockfile_names :
101
102
if resource .has_parent ():
102
103
siblings = resource .siblings (codebase )
103
104
package_resource = [r for r in siblings if r .name == 'package.json' ]
@@ -117,10 +118,15 @@ def assemble(cls, package_data, resource, codebase, package_adder):
117
118
pkg_data = package_resource .package_data [0 ]
118
119
pkg_data = models .PackageData .from_dict (pkg_data )
119
120
120
- workspace_root_path = package_resource .parent (codebase ).path
121
+ workspace_root = package_resource .parent (codebase )
122
+ workspace_root_path = None
123
+ if workspace_root :
124
+ workspace_root_path = package_resource .parent (codebase ).path
121
125
workspaces = pkg_data .extra_data .get ('workspaces' ) or []
126
+
122
127
# Also look for pnpm workspaces
123
- if not workspaces :
128
+ pnpm_workspace = None
129
+ if not workspaces and workspace_root :
124
130
pnpm_workspace_path = os .path .join (workspace_root_path , 'pnpm-workspace.yaml' )
125
131
pnpm_workspace = codebase .get_resource (path = pnpm_workspace_path )
126
132
if pnpm_workspace :
@@ -139,7 +145,7 @@ def assemble(cls, package_data, resource, codebase, package_adder):
139
145
cls .update_workspace_members (workspace_members , codebase )
140
146
141
147
# do we have enough to create a package?
142
- if pkg_data .purl :
148
+ if pkg_data .purl and not workspaces :
143
149
package = models .Package .from_package_data (
144
150
package_data = pkg_data ,
145
151
datafile_path = package_resource .path ,
@@ -151,35 +157,128 @@ def assemble(cls, package_data, resource, codebase, package_adder):
151
157
# Always yield the package resource in all cases and first!
152
158
yield package
153
159
154
- root = package_resource .parent (codebase )
155
- if root :
156
- for npm_res in cls .walk_npm (resource = root , codebase = codebase ):
160
+ if workspace_root :
161
+ for npm_res in cls .walk_npm (resource = workspace_root , codebase = codebase ):
157
162
if package_uid and package_uid not in npm_res .for_packages :
158
163
package_adder (package_uid , npm_res , codebase )
159
164
yield npm_res
160
- elif codebase .has_single_resource :
161
- if package_uid and package_uid not in package_resource .for_packages :
162
- package_adder (package_uid , package_resource , codebase )
163
165
yield package_resource
164
166
167
+ elif workspaces :
168
+ yield from cls .create_packages_from_workspaces (
169
+ workspace_members = workspace_members ,
170
+ workspace_root = workspace_root ,
171
+ codebase = codebase ,
172
+ package_adder = package_adder ,
173
+ pnpm = pnpm_workspace and pkg_data .purl ,
174
+ )
175
+
176
+ package_uid = None
177
+ if pnpm_workspace and pkg_data .purl :
178
+ package = models .Package .from_package_data (
179
+ package_data = pkg_data ,
180
+ datafile_path = package_resource .path ,
181
+ )
182
+ package_uid = package .package_uid
183
+
184
+ package .populate_license_fields ()
185
+
186
+ # Always yield the package resource in all cases and first!
187
+ yield package
188
+
189
+ if workspace_root :
190
+ for npm_res in cls .walk_npm (resource = workspace_root , codebase = codebase ):
191
+ if package_uid and not npm_res .for_packages :
192
+ package_adder (package_uid , npm_res , codebase )
193
+ yield npm_res
194
+ yield package_resource
195
+
165
196
else :
166
197
# we have no package, so deps are not for a specific package uid
167
198
package_uid = None
168
199
200
+ yield from cls .yield_npm_dependencies_and_resources (
201
+ package_resource = package_resource ,
202
+ package_data = pkg_data ,
203
+ package_uid = package_uid ,
204
+ codebase = codebase ,
205
+ package_adder = package_adder ,
206
+ )
207
+
208
+ @classmethod
209
+ def yield_npm_dependencies_and_resources (cls , package_resource , package_data , package_uid , codebase , package_adder ):
210
+
169
211
# in all cases yield possible dependencies
170
- yield from yield_dependencies_from_package_data (pkg_data , package_resource .path , package_uid )
212
+ yield from yield_dependencies_from_package_data (package_data , package_resource .path , package_uid )
171
213
172
214
# we yield this as we do not want this further processed
173
215
yield package_resource
174
216
175
217
for lock_file in package_resource .siblings (codebase ):
176
- if lock_file .name in lockfile_names :
218
+ if lock_file .name in cls . lockfile_names :
177
219
yield from yield_dependencies_from_package_resource (lock_file , package_uid )
178
220
179
221
if package_uid and package_uid not in lock_file .for_packages :
180
222
package_adder (package_uid , lock_file , codebase )
181
223
yield lock_file
182
224
225
+ @classmethod
226
+ def create_packages_from_workspaces (
227
+ cls ,
228
+ workspace_members ,
229
+ workspace_root ,
230
+ codebase ,
231
+ package_adder ,
232
+ pnpm = False ,
233
+ ):
234
+
235
+ workspace_package_uids = []
236
+ for workspace_member in workspace_members :
237
+ if not workspace_member .package_data :
238
+ continue
239
+
240
+ pkg_data = workspace_member .package_data [0 ]
241
+ pkg_data = models .PackageData .from_dict (pkg_data )
242
+
243
+ package = models .Package .from_package_data (
244
+ package_data = pkg_data ,
245
+ datafile_path = workspace_member .path ,
246
+ )
247
+ package_uid = package .package_uid
248
+ workspace_package_uids .append (package_uid )
249
+
250
+ package .populate_license_fields ()
251
+
252
+ # Always yield the package resource in all cases and first!
253
+ yield package
254
+
255
+ member_root = workspace_member .parent (codebase )
256
+ package_adder (package_uid , member_root , codebase )
257
+ for npm_res in cls .walk_npm (resource = member_root , codebase = codebase ):
258
+ if package_uid and package_uid not in npm_res .for_packages :
259
+ package_adder (package_uid , npm_res , codebase )
260
+ yield npm_res
261
+
262
+ yield from cls .yield_npm_dependencies_and_resources (
263
+ package_resource = workspace_member ,
264
+ package_data = pkg_data ,
265
+ package_uid = package_uid ,
266
+ codebase = codebase ,
267
+ package_adder = package_adder ,
268
+ )
269
+
270
+ # All resources which are not part of a workspace package exclusively
271
+ # are a part of all packages (this is skipped if we have a root pnpm
272
+ # package)
273
+ if pnpm :
274
+ return
275
+ for npm_res in cls .walk_npm (resource = workspace_root , codebase = codebase ):
276
+ if npm_res .for_packages :
277
+ continue
278
+
279
+ npm_res .for_packages = workspace_package_uids
280
+ npm_res .save (codebase )
281
+
183
282
@classmethod
184
283
def walk_npm (cls , resource , codebase , depth = 0 ):
185
284
"""
@@ -1081,6 +1180,7 @@ def parse(cls, location, package_only=False):
1081
1180
name , version = name_version .split ("@" )
1082
1181
elif major_v == "5" or is_shrinkwrap :
1083
1182
if len (sections ) == 3 :
1183
+ namespace = None
1084
1184
_ , name , version = sections
1085
1185
elif len (sections ) == 4 :
1086
1186
_ , namespace , name , version = sections
0 commit comments