@@ -86,6 +86,8 @@ def assemble(cls, package_data, resource, codebase, package_adder):
8686 '.package-lock.json' ,
8787 'npm-shrinkwrap.json' ,
8888 'yarn.lock' ,
89+ 'shrinkwrap.yaml' ,
90+ 'pnpm-lock.yaml'
8991 }
9092
9193 package_resource = None
@@ -723,6 +725,121 @@ def parse(cls, location, package_only=False):
723725 yield models .PackageData .from_data (package_data , package_only )
724726
725727
728+ class BasePnpmLockHandler (BaseNpmHandler ):
729+
730+ @classmethod
731+ def parse (cls , location , package_only = False ):
732+ """
733+ Parses and yields package dependencies for all lockfile versions
734+ present in the spec: https://github.com/pnpm/spec/blob/master/lockfile/
735+ """
736+
737+ with open (location ) as yl :
738+ lock_data = saneyaml .load (yl .read ())
739+
740+ lockfile_version = lock_data .get ("lockfileVersion" )
741+ is_shrinkwrap = False
742+ if not lockfile_version :
743+ lockfile_version = lock_data .get ("shrinkwrapVersion" )
744+ lockfile_minor_version = lock_data .get ("shrinkwrapMinorVersion" )
745+ if lockfile_minor_version :
746+ lockfile_version = f"{ lockfile_version } .{ lockfile_minor_version } "
747+ is_shrinkwrap = True
748+
749+ extra_data = {
750+ "lockfileVersion" : lockfile_version ,
751+ }
752+ major_v , minor_v = lockfile_version .split ("." )
753+
754+ resolved_packages = lock_data .get ("packages" , [])
755+ dependencies_by_purl = {}
756+
757+ for purl_fields , data in resolved_packages .items ():
758+ if major_v == "6" :
759+ clean_purl_fields = purl_fields .split ("(" )[0 ]
760+ elif major_v == "5" or is_shrinkwrap :
761+ clean_purl_fields = purl_fields .split ("_" )[0 ]
762+ else :
763+ clean_purl_fields = purl_fields
764+ raise Exception (lockfile_version , purl_fields )
765+
766+ sections = clean_purl_fields .split ("/" )
767+ name_version = None
768+ if major_v == "6" :
769+ if len (sections ) == 2 :
770+ namespace = None
771+ _ , name_version = sections
772+ elif len (sections ) == 3 :
773+ _ , namespace , name_version = sections
774+
775+ name , version = name_version .split ("@" )
776+ elif major_v == "5" or is_shrinkwrap :
777+ if len (sections ) == 3 :
778+ _ , name , version = sections
779+ elif len (sections ) == 4 :
780+ _ , namespace , name , version = sections
781+
782+ purl = PackageURL (
783+ type = cls .default_package_type ,
784+ name = name ,
785+ namespace = namespace ,
786+ version = version ,
787+ ).to_string ()
788+
789+ # TODO: add resolved_package and dependencies from the following:
790+ # 'peerDependencies', 'optionalDependencies', 'dependencies',
791+ # 'transitivePeerDependencies', 'peerDependenciesMeta'
792+ # add sha512 from 'resolution'
793+ extra_data_fields = ["cpu" , "os" , "engines" , "deprecated" , "hasBin" ]
794+
795+ is_dev = data .get ("dev" , False )
796+ is_runtime = not is_dev
797+ is_optional = data .get ("optional" , False )
798+
799+ extra_data_deps = {}
800+ for key in extra_data_fields :
801+ value = data .get (key , None )
802+ if value is not None :
803+ extra_data_deps [key ] = value
804+
805+ dependency_data = models .DependentPackage (
806+ purl = purl ,
807+ is_optional = is_optional ,
808+ is_runtime = is_runtime ,
809+ is_resolved = True ,
810+ extra_data = extra_data_deps ,
811+ )
812+ dependencies_by_purl [purl ] = dependency_data
813+
814+ dependencies = list (dependencies_by_purl .values ())
815+ root_package_data = dict (
816+ datasource_id = cls .datasource_id ,
817+ type = cls .default_package_type ,
818+ primary_language = cls .default_primary_language ,
819+ dependencies = dependencies ,
820+ extra_data = extra_data ,
821+ )
822+ yield models .PackageData .from_data (root_package_data )
823+
824+
825+ class PnpmShrinkwrapYamlHandler (BasePnpmLockHandler ):
826+ datasource_id = 'pnpm_shrinkwrap_yaml'
827+ path_patterns = ('*/shrinkwrap.yaml' ,)
828+ default_package_type = 'npm'
829+ default_primary_language = 'JavaScript'
830+ description = 'pnpm shrinkwrap.yaml lockfile'
831+ documentation_url = 'https://github.com/pnpm/spec/blob/master/lockfile/4.md'
832+
833+
834+ class PnpmLockYamlHandler (BasePnpmLockHandler ):
835+ datasource_id = 'pnpm_lock_yaml'
836+ path_patterns = ('*/pnpm-lock.yaml' ,)
837+ default_package_type = 'npm'
838+ default_primary_language = 'JavaScript'
839+ description = 'pnpm pnpm-lock.yaml lockfile'
840+ documentation_url = 'https://github.com/pnpm/spec/blob/master/lockfile/6.0.md'
841+
842+
726843def get_checksum_and_url (url ):
727844 """
728845 Return a mapping of {download_url, sha1} where the checksum can be a
0 commit comments