class Importmap::Npm

def find_latest_version(response)

def find_latest_version(response)
  latest_version = response.is_a?(String) ? response : response.dig('dist-tags', 'latest')
  return latest_version if latest_version
  return unless response['versions']
  response['versions'].keys.map { |v| Gem::Version.new(v) rescue nil }.compact.sort.last
end

def get_audit

def get_audit
  uri = self.class.base_uri.dup
  uri.path = "/-/npm/v1/security/advisories/bulk"
  body = packages_with_versions.each.with_object({}) { |(package, version), data|
    data[package] ||= []
    data[package] << version
  }
  return {} if body.empty?
  response = post_json(uri, body)
  JSON.parse(response.body)
end

def get_json(uri)

def get_json(uri)
  request = Net::HTTP::Get.new(uri)
  request["Content-Type"] = "application/json"
  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
    http.request(request)
  }
  response.body
rescue => error
  raise HTTPError, "Unexpected transport error (#{error.class}: #{error.message})"
end

def get_package(package)

def get_package(package)
  uri = self.class.base_uri.dup
  uri.path = "/" + package
  response = get_json(uri)
  JSON.parse(response)
rescue JSON::ParserError
  nil
end

def importmap

def importmap
  @importmap ||= File.read(@importmap_path)
end

def initialize(importmap_path = "config/importmap.rb")

def initialize(importmap_path = "config/importmap.rb")
  @importmap_path = Pathname.new(importmap_path)
end

def outdated?(current_version, latest_version)

def outdated?(current_version, latest_version)
  Gem::Version.new(current_version) < Gem::Version.new(latest_version)
rescue ArgumentError
  current_version.to_s < latest_version.to_s
end

def outdated_packages

def outdated_packages
  packages_with_versions.each.with_object([]) do |(package, current_version), outdated_packages|
    outdated_package = OutdatedPackage.new(name: package,
                                           current_version: current_version)
    if !(response = get_package(package))
      outdated_package.error = 'Response error'
    elsif (error = response['error'])
      outdated_package.error = error
    else
      latest_version = find_latest_version(response)
      next unless outdated?(current_version, latest_version)
      outdated_package.latest_version = latest_version
    end
    outdated_packages << outdated_package
  end.sort_by(&:name)
end

def packages_with_versions

def packages_with_versions
  # We cannot use the name after "pin" because some dependencies are loaded from inside packages
  # Eg. pin "buffer", to: "https://ga.jspm.io/npm:@jspm/core@2.0.0-beta.19/nodelibs/browser/buffer.js"
  importmap.scan(/^pin .*(?<=npm:|npm\/|skypack\.dev\/|unpkg\.com\/)(.*)(?=@\d+\.\d+\.\d+)@(\d+\.\d+\.\d+(?:[^\/\s["']]*)).*$/) |
    importmap.scan(/^pin ["']([^["']]*)["'].* #.*@(\d+\.\d+\.\d+(?:[^\s]*)).*$/)
end

def post_json(uri, body)

def post_json(uri, body)
  Net::HTTP.post(uri, body.to_json, "Content-Type" => "application/json")
rescue => error
  raise HTTPError, "Unexpected transport error (#{error.class}: #{error.message})"
end

def vulnerable_packages

def vulnerable_packages
  get_audit.flat_map do |package, vulnerabilities|
    vulnerabilities.map do |vulnerability|
      VulnerablePackage.new(name: package,
                            severity: vulnerability['severity'],
                            vulnerable_versions: vulnerability['vulnerable_versions'],
                            vulnerability: vulnerability['title'])
    end
  end.sort_by { |p| [p.name, p.severity] }
end