33require "json"
44
55class Importmap ::Npm
6+ PIN_REGEX = /^pin ["']([^["']]*)["'].*/
7+
68 Error = Class . new ( StandardError )
79 HTTPError = Class . new ( Error )
810
911 singleton_class . attr_accessor :base_uri
1012 self . base_uri = URI ( "https://registry.npmjs.org" )
1113
12- def initialize ( importmap_path = "config/importmap.rb" )
14+ def initialize ( importmap_path = "config/importmap.rb" , vendor_path : "vendor/javascript" )
1315 @importmap_path = Pathname . new ( importmap_path )
16+ @vendor_path = Pathname . new ( vendor_path )
1417 end
1518
1619 def outdated_packages
1720 packages_with_versions . each . with_object ( [ ] ) do |( package , current_version ) , outdated_packages |
18- outdated_package = OutdatedPackage . new ( name : package ,
19- current_version : current_version )
21+ outdated_package = OutdatedPackage . new ( name : package , current_version : current_version )
2022
2123 if !( response = get_package ( package ) )
2224 outdated_package . error = 'Response error'
@@ -36,28 +38,33 @@ def outdated_packages
3638 def vulnerable_packages
3739 get_audit . flat_map do |package , vulnerabilities |
3840 vulnerabilities . map do |vulnerability |
39- VulnerablePackage . new ( name : package ,
40- severity : vulnerability [ 'severity' ] ,
41- vulnerable_versions : vulnerability [ 'vulnerable_versions' ] ,
42- vulnerability : vulnerability [ 'title' ] )
41+ VulnerablePackage . new (
42+ name : package ,
43+ severity : vulnerability [ 'severity' ] ,
44+ vulnerable_versions : vulnerability [ 'vulnerable_versions' ] ,
45+ vulnerability : vulnerability [ 'title' ]
46+ )
4347 end
4448 end . sort_by { |p | [ p . name , p . severity ] }
4549 end
4650
4751 def packages_with_versions
4852 # We cannot use the name after "pin" because some dependencies are loaded from inside packages
4953 # Eg. pin "buffer", to: "https://ga.jspm.io/npm:@jspm/[email protected] /nodelibs/browser/buffer.js" 54+ with_versions = importmap . scan ( /^pin .*(?<=npm:|npm\/ |skypack\. dev\/ |unpkg\. com\/ )(.*)(?=@\d +\. \d +\. \d +)@(\d +\. \d +\. \d +(?:[^\/ \s ["']]*)).*$/ ) |
55+ importmap . scan ( /#{ PIN_REGEX } #.*@(\d +\. \d +\. \d +(?:[^\s ]*)).*$/ )
56+
57+ vendored_packages_without_version ( with_versions ) . each do |package , path |
58+ $stdout. puts "Ignoring #{ package } (#{ path } ) since no version is specified in the importmap"
59+ end
5060
51- importmap . scan ( /^pin .*(?<=npm:|npm\/ |skypack\. dev\/ |unpkg\. com\/ )(.*)(?=@\d +\. \d +\. \d +)@(\d +\. \d +\. \d +(?:[^\/ \s ["']]*)).*$/ ) |
52- importmap . scan ( /^pin ["']([^["']]*)["'].* #.*@(\d +\. \d +\. \d +(?:[^\s ]*)).*$/ )
61+ with_versions
5362 end
5463
5564 private
5665 OutdatedPackage = Struct . new ( :name , :current_version , :latest_version , :error , keyword_init : true )
5766 VulnerablePackage = Struct . new ( :name , :severity , :vulnerable_versions , :vulnerability , keyword_init : true )
5867
59-
60-
6168 def importmap
6269 @importmap ||= File . read ( @importmap_path )
6370 end
@@ -130,4 +137,27 @@ def post_json(uri, body)
130137 rescue => error
131138 raise HTTPError , "Unexpected transport error (#{ error . class } : #{ error . message } )"
132139 end
140+
141+ def vendored_packages_without_version ( packages_with_versions )
142+ versioned_packages = packages_with_versions . map ( &:first ) . to_set
143+
144+ importmap
145+ . lines
146+ . filter_map { |line | find_unversioned_vendored_package ( line , versioned_packages ) }
147+ end
148+
149+ def find_unversioned_vendored_package ( line , versioned_packages )
150+ regexp = line . include? ( "to:" ) ? /#{ PIN_REGEX } to: ["']([^["']]*)["'].*/ : PIN_REGEX
151+ match = line . match ( regexp )
152+
153+ return unless match
154+
155+ package , filename = match . captures
156+ filename ||= "#{ package } .js"
157+
158+ return if versioned_packages . include? ( package )
159+
160+ path = File . join ( @vendor_path , filename )
161+ [ package , path ] if File . exist? ( path )
162+ end
133163end
0 commit comments