@@ -2023,3 +2023,59 @@ def keywords_mapper(keywords, package):
20232023
20242024 package .keywords = keywords
20252025 return package
2026+ class YarnPnpHandler (BaseNpmHandler ):
2027+ """
2028+ Handle Yarn v2 Plug and Play .pnp.cjs files
2029+ See https://yarnpkg.com/features/pnp
2030+ """
2031+ datasource_id = 'yarn_pnp'
2032+ path_patterns = ('*/.pnp.cjs' ,)
2033+ default_package_type = 'npm'
2034+ default_primary_language = 'JavaScript'
2035+ description = 'Yarn Plug and Play manifest'
2036+ documentation_url = 'https://yarnpkg.com/features/pnp'
2037+
2038+ @classmethod
2039+ def parse (cls , location , package_only = False ):
2040+ with io .open (location , encoding = 'utf-8' ) as f :
2041+ content = f .read ()
2042+
2043+ # Extract the JSON data embedded in the .pnp.cjs file
2044+ match = re .search (r'const RAW_RUNTIME_STATE\s*=\s*(\{.*?\});' , content , re .DOTALL )
2045+ if not match :
2046+ return
2047+
2048+ pnp_data = json .loads (match .group (1 ))
2049+ packages = pnp_data .get ('packageRegistryData' ) or {}
2050+
2051+ dependencies = []
2052+ for name , versions in packages .items ():
2053+ if not name :
2054+ continue
2055+ for version , _data in (versions or {}).items ():
2056+ if not version :
2057+ continue
2058+ ns , _ , pkg_name = name .rpartition ('/' )
2059+ purl = PackageURL (
2060+ type = cls .default_package_type ,
2061+ namespace = ns or None ,
2062+ name = pkg_name ,
2063+ version = version ,
2064+ ).to_string ()
2065+ dep = models .DependentPackage (
2066+ purl = purl ,
2067+ extracted_requirement = version ,
2068+ scope = 'dependencies' ,
2069+ is_runtime = True ,
2070+ is_optional = False ,
2071+ is_pinned = True ,
2072+ )
2073+ dependencies .append (dep .to_dict ())
2074+
2075+ package_data = dict (
2076+ datasource_id = cls .datasource_id ,
2077+ type = cls .default_package_type ,
2078+ primary_language = cls .default_primary_language ,
2079+ dependencies = dependencies ,
2080+ )
2081+ yield models .PackageData .from_data (package_data , package_only )
0 commit comments