@@ -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,14 @@ 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+ if not workspaces and workspace_root :
124129 pnpm_workspace_path = os .path .join (workspace_root_path , 'pnpm-workspace.yaml' )
125130 pnpm_workspace = codebase .get_resource (path = pnpm_workspace_path )
126131 if pnpm_workspace :
@@ -139,7 +144,7 @@ def assemble(cls, package_data, resource, codebase, package_adder):
139144 cls .update_workspace_members (workspace_members , codebase )
140145
141146 # do we have enough to create a package?
142- if pkg_data .purl :
147+ if pkg_data .purl and not workspaces :
143148 package = models .Package .from_package_data (
144149 package_data = pkg_data ,
145150 datafile_path = package_resource .path ,
@@ -157,29 +162,92 @@ def assemble(cls, package_data, resource, codebase, package_adder):
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+ )
174+ package_uid = None
175+
165176 else :
166177 # we have no package, so deps are not for a specific package uid
167178 package_uid = None
168179
180+ yield from cls .yield_npm_dependencies_and_resources (
181+ package_resource = package_resource ,
182+ package_data = pkg_data ,
183+ package_uid = package_uid ,
184+ codebase = codebase ,
185+ package_adder = package_adder ,
186+ )
187+
188+ @classmethod
189+ def yield_npm_dependencies_and_resources (cls , package_resource , package_data , package_uid , codebase , package_adder ):
190+
169191 # in all cases yield possible dependencies
170- yield from yield_dependencies_from_package_data (pkg_data , package_resource .path , package_uid )
192+ yield from yield_dependencies_from_package_data (package_data , package_resource .path , package_uid )
171193
172194 # we yield this as we do not want this further processed
173195 yield package_resource
174196
175197 for lock_file in package_resource .siblings (codebase ):
176- if lock_file .name in lockfile_names :
198+ if lock_file .name in cls . lockfile_names :
177199 yield from yield_dependencies_from_package_resource (lock_file , package_uid )
178200
179201 if package_uid and package_uid not in lock_file .for_packages :
180202 package_adder (package_uid , lock_file , codebase )
181203 yield lock_file
182204
205+ @classmethod
206+ def create_packages_from_workspaces (cls , workspace_members , workspace_root , codebase , package_adder ):
207+
208+ workspace_package_uids = []
209+ for workspace_member in workspace_members :
210+ if not workspace_member .package_data :
211+ continue
212+
213+ pkg_data = workspace_member .package_data [0 ]
214+ pkg_data = models .PackageData .from_dict (pkg_data )
215+
216+ package = models .Package .from_package_data (
217+ package_data = pkg_data ,
218+ datafile_path = workspace_member .path ,
219+ )
220+ package_uid = package .package_uid
221+ workspace_package_uids .append (package_uid )
222+
223+ package .populate_license_fields ()
224+
225+ # Always yield the package resource in all cases and first!
226+ yield package
227+
228+ member_root = workspace_member .parent (codebase )
229+ for npm_res in cls .walk_npm (resource = member_root , codebase = codebase ):
230+ if package_uid and package_uid not in npm_res .for_packages :
231+ package_adder (package_uid , npm_res , codebase )
232+ yield npm_res
233+
234+ cls .yield_npm_dependencies_and_resources (
235+ package_resource = workspace_member ,
236+ package_data = pkg_data ,
237+ package_uid = package_uid ,
238+ codebase = codebase ,
239+ package_adder = package_adder ,
240+ )
241+
242+ # All resources which are not part of a workspace package exclusively
243+ # are a part of all packages
244+ for npm_res in cls .walk_npm (resource = workspace_root , codebase = codebase ):
245+ if npm_res .for_packages or npm_res .is_dir :
246+ continue
247+
248+ npm_res .for_packages = workspace_package_uids
249+ npm_res .save (codebase )
250+
183251 @classmethod
184252 def walk_npm (cls , resource , codebase , depth = 0 ):
185253 """
0 commit comments