@@ -86,6 +86,8 @@ def assemble(cls, package_data, resource, codebase, package_adder):
86
86
'.package-lock.json' ,
87
87
'npm-shrinkwrap.json' ,
88
88
'yarn.lock' ,
89
+ 'shrinkwrap.yaml' ,
90
+ 'pnpm-lock.yaml'
89
91
}
90
92
91
93
package_resource = None
@@ -723,6 +725,121 @@ def parse(cls, location, package_only=False):
723
725
yield models .PackageData .from_data (package_data , package_only )
724
726
725
727
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
+
726
843
def get_checksum_and_url (url ):
727
844
"""
728
845
Return a mapping of {download_url, sha1} where the checksum can be a
0 commit comments