Skip to content

Commit 01cb80f

Browse files
Improve npm workspace processing
Signed-off-by: Ayan Sinha Mahapatra <ayansmahapatra@gmail.com>
1 parent e26187a commit 01cb80f

11 files changed

Lines changed: 1674 additions & 7399 deletions

File tree

src/packagedcode/npm.py

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ def logger_debug(*args):
6969

7070
class 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

Comments
 (0)