@@ -69,6 +69,15 @@ def logger_debug(*args):
6969
7070class BaseNpmHandler (models .DatafileHandler ):
7171
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+
7281 @classmethod
7382 def assemble (cls , package_data , resource , codebase , package_adder ):
7483 """
@@ -85,19 +94,11 @@ def assemble(cls, package_data, resource, codebase, package_adder):
8594 If there is no package.json, we do not have a package instance. In this
8695 case, we yield each of the dependencies in each lock file.
8796 """
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- }
9697
9798 package_resource = None
9899 if resource .name == 'package.json' :
99100 package_resource = resource
100- elif resource .name in lockfile_names :
101+ elif resource .name in cls . lockfile_names :
101102 if resource .has_parent ():
102103 siblings = resource .siblings (codebase )
103104 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):
117118 pkg_data = package_resource .package_data [0 ]
118119 pkg_data = models .PackageData .from_dict (pkg_data )
119120
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
121125 workspaces = pkg_data .extra_data .get ('workspaces' ) or []
126+
122127 # Also look for pnpm workspaces
123- if not workspaces :
128+ pnpm_workspace = None
129+ if not workspaces and workspace_root :
124130 pnpm_workspace_path = os .path .join (workspace_root_path , 'pnpm-workspace.yaml' )
125131 pnpm_workspace = codebase .get_resource (path = pnpm_workspace_path )
126132 if pnpm_workspace :
@@ -139,7 +145,7 @@ def assemble(cls, package_data, resource, codebase, package_adder):
139145 cls .update_workspace_members (workspace_members , codebase )
140146
141147 # do we have enough to create a package?
142- if pkg_data .purl :
148+ if pkg_data .purl and not workspaces :
143149 package = models .Package .from_package_data (
144150 package_data = pkg_data ,
145151 datafile_path = package_resource .path ,
@@ -151,35 +157,128 @@ def assemble(cls, package_data, resource, codebase, package_adder):
151157 # Always yield the package resource in all cases and first!
152158 yield package
153159
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 ):
157162 if package_uid and package_uid not in npm_res .for_packages :
158163 package_adder (package_uid , npm_res , codebase )
159164 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 )
163165 yield package_resource
164166
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+
165196 else :
166197 # we have no package, so deps are not for a specific package uid
167198 package_uid = None
168199
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+
169211 # 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 )
171213
172214 # we yield this as we do not want this further processed
173215 yield package_resource
174216
175217 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 :
177219 yield from yield_dependencies_from_package_resource (lock_file , package_uid )
178220
179221 if package_uid and package_uid not in lock_file .for_packages :
180222 package_adder (package_uid , lock_file , codebase )
181223 yield lock_file
182224
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+
183282 @classmethod
184283 def walk_npm (cls , resource , codebase , depth = 0 ):
185284 """
@@ -1081,6 +1180,7 @@ def parse(cls, location, package_only=False):
10811180 name , version = name_version .split ("@" )
10821181 elif major_v == "5" or is_shrinkwrap :
10831182 if len (sections ) == 3 :
1183+ namespace = None
10841184 _ , name , version = sections
10851185 elif len (sections ) == 4 :
10861186 _ , namespace , name , version = sections
0 commit comments